Splashify RCS

Docs

Open panel
Panel

Contact lists

Team-member access: operators can create + delete lists and add or remove members; viewers can list + get but get 403 not_operator on any mutation. Lists aren't scoped per bot, every member sees every list on the account. See the Team members reference.

A contact list is a named bag of recipient phone numbers. Every broadcast picks one list and an audience: rcs (the default) leaves out the numbers the list's check found unreachable, not_rcs sends only to those, and all sends to every number. Numbers that opted out are never sent. See Audience.

A check is optional. Number checks work only with an active bot that supports them. Not every bot does. Through the API, a check needs your own active bot that supports number checks. The answers of a check count for broadcasts from every bot on your account, including bots that cannot check. A list that was never checked goes to every number except the people who opted out; numbers that cannot get RCS fail and are not charged.

http
POST   /api/v1/rcs/contact-lists
GET    /api/v1/rcs/contact-lists
DELETE /api/v1/rcs/contact-lists/{id}

POST   /api/v1/rcs/contact-lists/{id}/members
GET    /api/v1/rcs/contact-lists/{id}/members
DELETE /api/v1/rcs/contact-lists/{id}/members/{phone}

POST   /api/v1/rcs/contact-lists/{id}/import      # CSV import

POST   /api/v1/rcs/contact-lists/{id}/check-reachability
GET    /api/v1/rcs/contact-lists/{id}/audience?bot_id={bot_id}
DELETE /api/v1/rcs/contact-lists/{id}/unreachable
GET    /api/v1/rcs/contact-lists/{id}/reachable.csv
POST   /api/v1/rcs/contact-lists/{id}/create-reachable-list

Create a list

http
POST /api/v1/rcs/contact-lists
{ "name": "May launch, opted in" }

Response:

json
{
  "success": true,
  "list_id": "a1b2…",
  "name":    "May launch, opted in",
  "count":   0,
  "created_at": "2026-05-27T09:14:00Z"
}

Add members (bulk)

CSV-style uploads are passed as JSON. Phones follow the same rule as Import CSV below; a phone holding several numbers adds each one.

http
POST /api/v1/rcs/contact-lists/{id}/members
{
  "members": [
    { "phone": "+919876543210", "name": "Asha",  "extra": { "city": "Mumbai" } },
    { "phone": "+919876543211", "name": "Rohan", "extra": { "city": "Pune"   } }
  ]
}

Up to 100,000 members per request. Members already in the list overwrite (idempotent, safe to retry on partial network failure).

Response:

json
{
  "success": true,
  "added":   2,
  "skipped": 0,
  "count":   2
}

skipped counts numbers that are not a valid mobile, numbers Excel changed into a short form (9.87654E+09), and rows that could not be saved.

List members

http
GET /api/v1/rcs/contact-lists/{id}/members?size=200

size defaults to 200, max 1000. For huge lists prefer slicing by phone prefix using the extra map at upload time.

A member carries reachable (true or false) only when the list's answers that count stand behind it, the same answers the audience counts and a broadcast uses. A member with no answer that counts has no reachable. Answers from a check made with a bot that does not support number checks are not shown: those members read as not checked.

Delete a member

http
DELETE /api/v1/rcs/contact-lists/{id}/members/{phone}

{phone} is the number with its + written as %2B. A number without a + is read the same way as in Import CSV, so 9876543210 removes +919876543210.

Delete a list

http
DELETE /api/v1/rcs/contact-lists/{id}

Removes the list plus every member. Broadcasts that already snapshotted this list at create time keep running. They don't read the list table after the snapshot.

Deleting a list also stops an import or a reachability check that is running on it.

Import CSV

Bulk-import a CSV of phones (and optional name / custom fields) into an existing list. Streams the upload, the file is never loaded fully in memory.

http
POST /api/v1/rcs/contact-lists/{id}/import
Content-Type: multipart/form-data

Form fields:

Mapping schema

json
{
  "phone_col": 0,
  "name_col": 1,
  "custom_cols": [
    { "index": 2, "name": "city" },
    { "index": 3, "name": "plan" }
  ]
}

Rules:

  • phone_col is required and must appear exactly once. Any other role picking the same column returns a 400.
  • name_col is optional; at most one column can be name.
  • custom_cols[] accepts up to 10 entries. Each name becomes a key in the member's extra map, so the operator can address the fields at broadcast render time. Custom column names are trimmed to 40 chars.

If the first row of the CSV has no digits in any cell it is treated as a header row and skipped from the insert.

Without a +, only an Indian mobile is accepted: 10 digits starting 6 to 9, optionally with 0, 91 or 0091 in front, saved as +91 and the 10 digits. With a +, any country is kept as written, but +91 must be followed by a 10-digit mobile. Other rows are reported in malformed_rows and skipped.

Sample request

bash
curl -X POST https://api.splashifypro.com/api/v1/rcs/contact-lists/<list_id>/import \
     -H "Authorization: Bearer <panel_jwt>" \
     -F "[email protected]" \
     -F "dedup=true" \
     -F 'mapping={"phone_col":0,"name_col":1,"custom_cols":[{"index":2,"name":"city"}]}'

Sample CSV

csv
phone,name,city
+919876543210,Asha,Mumbai
9876543211,Rohan,Pune
918293834982,Kiran,Bengaluru
9812345678,Priya,Chennai

Response

json
{
  "success": true,
  "inserted": 4890,
  "duplicates_skipped": 33,
  "malformed_rows": [
    { "row": 42,  "phone": "abc",  "reason": "not a valid Indian mobile number" },
    { "row": 118, "phone": "98765432",  "reason": "not a valid Indian mobile number" }
  ],
  "elapsed_ms": 4218
}

When dry_run=true, inserted is 0 and an additional would_insert field carries the count that WOULD have been written.

Dedup

Access

Operators + owners can import. Viewers get 403 not_operator.

Check reachability

http
POST /api/v1/rcs/contact-lists/{id}/check-reachability?bot_id={bot_id}

Starts a background check of the list's numbers and answers 202 at once. The check runs with one bot, and its answers count for broadcasts from every bot on your account. Pass the bot as ?bot_id=, the X-Bot-Id header, or {"bot_id": "…"} in the body.

Through the API, only your own active bot that supports number checks can run one. Without a bot, your first active bot that supports them is used. To know whether a given bot is one, read check_bot_ok from the audience endpoint, or check the bots on your account.

In the panel, an account with no bot that supports number checks can still check its lists: we run the check for you, and the list's check shows no bot name. This applies only in the panel. An API call from such an account is refused as before: 400 no_check_bot, or 400 bot_cannot_check when it names a bot.

Which numbers a check covers:

There is no way to check a reachable answer again. To send to those numbers anyway, create the broadcast with audience: "all".

You never have to wait for a check to send: a broadcast created or started while a check runs uses the answers saved when its recipients are prepared. While a check runs, and after one fails or stops part way, the answers from the check before it still count, together with the answers the new check has saved. On a list with no answers that count before the check (has_answers was false), there is no check before it: the new answers count once the check finishes, and until then every number reads as not checked.

A check of 5,000 numbers or more runs in the background on the network's side and can take up to an hour; poll the list as for any check. Numbers the network does not answer for stay unchecked, and the check finishes with a note saying how many. When the bot is not live on the network yet, the check stops at once and reachability_check_error says Your bot is not live on the network yet, so its numbers could not be checked. Check again after it goes live. No answer from that check is used.

An account runs at most two checks of 5,000 numbers or more at a time. Another one stops at once, with nothing checked, and reachability_check_error says Too many number checks of big lists are running on your account. Run this one when one of them finishes. Start it again when one finishes.

While a check runs, a second call gets 409 check_in_progress. The Check RCS capability page has the 202 body and how to follow progress.

A bot that cannot check is refused before anything starts:

json
{
  "success": false,
  "error":   "bot_cannot_check",
  "code":    "bot_cannot_check",
  "message": "This bot cannot check numbers. Pick another bot."
}

Audience: who a broadcast reaches

http
GET /api/v1/rcs/contact-lists/{id}/audience?bot_id={bot_id}

Counts who a broadcast of this list from this bot would reach, with the same rule the send uses, for each audience at once (choices). Call it before you create a broadcast, to show the numbers or to catch a send nobody would get.

The rcs rule, per number (sendable, choices.rcs):

  • On your opt-out list, for the account or for this bot: not sent.
  • Found unreachable by the list's check, made with any of your bots: not sent.
  • Failed as not on RCS in a broadcast from this bot in the last 30 days: not sent, unless the list's check, made after that failure, found it reachable. A failure on another of your bots does not count. See Reachability filter.
  • Everything else is sent. That includes numbers never checked.

choices.not_rcs counts only the numbers found unreachable, and choices.all every number; neither has the 30-day skip, and opted-out numbers are never counted in any of them.

Only a bot that supports number checks can make that check, but its answers count for a broadcast from any of your bots, including one that cannot check. A check made earlier with a bot that does not support number checks, or with a bot you have since deleted, is ignored: the list counts as never checked.

bot_id can also come as the X-Bot-Id header. Without either, your default bot is used. Viewers can call it.

Response:

json
{
  "total":            1200,
  "reachable":        950,
  "unreachable":      180,
  "unchecked":        70,
  "checked_bot_id":   "9c1b7f4e-2a11-4c8e-8e0e-3f5d1b2c9e01",
  "checked_bot_name": "Acme Boutique",
  "checked_at":       "2026-09-26T10:42:00Z",
  "same_bot":         true,
  "check_running":    false,
  "check_available":  true,
  "check_bot_ok":     true,
  "opted_out":        12,
  "not_on_rcs":       8,
  "sendable":         1000,
  "reason":           "",
  "has_answers":      true,
  "choices":          { "rcs": 1000, "not_rcs": 180, "all": 1188 },
  "check_unfinished": false
}

When reason is set, creating a broadcast from this list and bot with audience: "rcs" is refused with 400 no_reachable_recipients. When choices.not_rcs is 0, not_rcs is refused the same way, with the reason none_cannot_get; when choices.all is 0, every audience is refused.

A count is reused for up to a minute while the list and its check stay the same.

Remove, export or copy by reachability

http
DELETE /api/v1/rcs/contact-lists/{id}/unreachable
GET    /api/v1/rcs/contact-lists/{id}/reachable.csv
POST   /api/v1/rcs/contact-lists/{id}/create-reachable-list

These three act on the answers of the list's latest finished check, whatever bot made it, as long as that bot supports number checks. A check made with a bot that does not support them does not count: the list is treated as never checked. After a check that finished with gaps, only the answers that check saved count.

  • DELETE …/unreachable removes the numbers the check found unreachable. Numbers never checked stay. Response: { "success": true, "deleted": 180, "remaining": 1020 }. When there is nothing to remove, deleted is 0 and message says so.
  • GET …/reachable.csv downloads the numbers the check found reachable, as a CSV you can import again: phone, name, then your extra columns.
  • POST …/create-reachable-list copies those numbers into a new list. The body { "name": "Acme Boutique VIP. Reachable" } is optional; the default name is the list's name followed by . Reachable. It answers 201 with list_id, name and count.

They are refused with HTTP 409 instead of acting on answers that may be half written or older than the latest check:

json
{
  "success": false,
  "code":    "no_finished_check",
  "message": "This list needs a finished reachability check first. Run a check, then try again."
}

create-reachable-list is refused before the new list is made, so a refusal never leaves an empty list behind.