Managing channels

A channel is where a sender can actually send: a market you send SMS in, your WhatsApp account, your RCS agent. This guide covers reading them, adding them, and clearing what their compliance still owes. For why Sent routes three channels behind one send call, see Channels.

All requests authenticate with the x-api-key header. Send x-profile-id with an organization key to read or change one of your Sender Profiles instead of the calling account. Only organization API keys may send x-profile-id; a profile-scoped key is rejected with 403, and a profile outside your organization returns 404. A profile key is already scoped to its own profile, so it reads and changes that profile's channels with no header at all.

Status lives on the channel

An identity has no lifecycle. What has one is the registration of a sender in a market and the approval of a channel, so a sender live in one country and mid-registration in another reports exactly that, rather than one flattened answer.

StatusMeaning
PROVISIONINGSent is arranging the sender; nothing is required from you
ACTION_NEEDEDSomething is owed: read compliance.requirements for the keys
PENDING_REVIEWSubmitted and awaiting an external answer, such as a carrier or registry verdict
ACTIVEThe channel can send

Read every channel at once

GET /v3/channels returns every market you send SMS in, your WhatsApp account, and your RCS agent, each with its status:

curl "https://api.sent.dm/v3/channels" \
  -H "x-api-key: $SENT_API_KEY"

sms is a list with one entry per market. Each entry carries a compliance object naming the country and sender type it answers for. An empty requirements means that market is satisfied; anything listed is what to supply.

The entries under sms are the same ones GET /v3/channels/sms returns, in the same shape a single-market read returns, so a list, a member read, and the all-channels read cannot describe a market differently.

SMS markets

A market is a country plus a sender type. The pair identifies it everywhere: in the list, in the path, and in the compliance it owes.

Sender types are LOCAL, MOBILE, TEN_DLC, TOLL_FREE, SHORT_CODE, and ALPHANUMERIC. 10DLC is accepted as a synonym of TEN_DLC, and availability varies by country. In a path the type is matched ignoring case, so /v3/channels/sms/us/ten_dlc and /v3/channels/sms/US/TEN_DLC are the same market.

Ask what the market wants

Call GET /v3/compliance/requirements before you add a market. It returns what that market demands, and setup[] — the calls that satisfy it, each with a body you can copy and send as it stands:

curl "https://api.sent.dm/v3/compliance/requirements?channel=sms&country=US&type=TEN_DLC" \
  -H "x-api-key: $SENT_API_KEY"
FieldWhat it carries
requiredWhether this market demands anything at all
sender_id_patternThe values the market accepts as a sender id, where you choose it
requirements[]One entry per value the market asks for, keyed as you write it — brand.legal_name, campaign.message_flow — with its type, format, options, minimum length, and item shape
setup[]The ordered calls that satisfy the requirements, with a body template and any attachments

There is always exactly one setup step, POST /v3/channels/sms, because the identity travels in that call's body rather than needing a separate call. Anything that cannot be a JSON value, such as an uploaded document, is listed under attachments instead of in the body template.

Requirements vary enormously. US TEN_DLC registers a brand and a campaign with The Campaign Registry and asks for a substantial set of values; an alphanumeric sender pre-registers with supporting documents in one country and needs nothing at all in another. This is the same declaration that validates the calls themselves, so it cannot drift from what the API accepts.

channel defaults to sms, which is the only channel answered today. whatsapp and rcs return 501 until their requirements are declared. They are refused rather than answered with an empty requirement set, because empty would say the channel demands nothing — and both demand plenty: WhatsApp a business verification and a display-name review, RCS a carrier approval.

Add the market

POST /v3/channels/sms gives the sender a market — the first one it sends in, and the fifth:

curl -X POST "https://api.sent.dm/v3/channels/sms" \
  -H "x-api-key: $ORG_API_KEY" \
  -H "x-profile-id: $PROFILE_ID" \
  -H "Idempotency-Key: add-us-tendlc-tenant-42" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "US",
    "number_type": "TEN_DLC",
    "compliance": {
      "brand": {
        "legal_name": "Tenant 42 Coffee Company LLC",
        "business_name": "Tenant 42 Coffee Co",
        "tax_id": "12-3456789",
        "tax_id_type": "us_ein",
        "entity_type": "PRIVATE_PROFIT",
        "street": "123 Main Street",
        "city": "New York",
        "state": "NY",
        "postal_code": "10001",
        "country": "US",
        "contact_name": "Jane Doe",
        "contact_email": "jane@tenant42.example.com",
        "contact_phone": "+12125550123"
      },
      "campaign": {
        "description": "Order and delivery updates for coffee subscriptions",
        "message_flow": "Customers opt in at checkout on tenant42.example.com by ticking a box that is unchecked by default. Consent is not a condition of purchase.",
        "use_cases": [
          {
            "use_case": "DELIVERY_NOTIFICATION",
            "sample_messages": ["Tenant 42 Coffee: your order #1234 ships today. Reply STOP to opt out."]
          }
        ],
        "opt_in_keywords": "START,YES",
        "opt_out_keywords": "STOP",
        "help_keywords": "HELP",
        "opt_in_message": "Tenant 42 Coffee: you are subscribed to order updates. Reply STOP to opt out, HELP for help.",
        "opt_out_message": "Tenant 42 Coffee: you will receive no further messages.",
        "help_message": "Tenant 42 Coffee: for help, email support@tenant42.example.com. Reply STOP to opt out.",
        "privacy_policy_link": "https://tenant42.example.com/privacy",
        "terms_and_conditions_link": "https://tenant42.example.com/terms"
      }
    }
  }'

Everything the regime wants arrives in compliance, as two declared objects: brand, the identity the registration is filed against, and campaign, with the use case, the consent narrative, the volume, the sample messages, and the keywords. There is nothing to set up first, and nothing is submitted by this call — the rows are what a submission is later assembled from, and filing happens externally.

Send { "inherit": true } in place of either object to run on your organization's:

{
  "country": "US",
  "number_type": "TEN_DLC",
  "compliance": {
    "brand": { "inherit": true },
    "campaign": { "inherit": true }
  }
}

A TEN_DLC market needs an identity, and this call takes one. A market with neither — none stored and none sent — is a 400, because accepting it would write a channel that could never register and leave it provisioning for ever with nothing saying why.

A few field rules worth knowing before you fill the template in:

  • campaign.message_flow is the consent narrative in prose. Under 40 characters is refused, and carriers reject vague descriptions — say where opt-in happens, that the box is unchecked by default, and that consent is not a condition of purchase.
  • campaign.use_cases takes one to five entries, each with one to five sample messages. MIXED and LOW_VOLUME are refused as use cases: campaign.volume decides those. Omitting volume registers the campaign at the higher-fee standard tier.
  • sender_value is only for the types where you choose the sender rather than the carrier assigning it — an alphanumeric sender id, a short code, or a number you already own. Omit it for carrier-assigned types and one is provisioned.

Read one market

GET /v3/channels/sms/{country}/{type} reads one market — its sender, where that sender stands, and everything its compliance holds:

curl "https://api.sent.dm/v3/channels/sms/us/ten_dlc" \
  -H "x-api-key: $ORG_API_KEY" \
  -H "x-profile-id: $PROFILE_ID"

A market that registers with a compliance regime reports brand and campaign, each carrying inherit to say whether it is yours or your organization's.

A market that registers with nobody has neither, so both keys are absent rather than null. Absent says this market does not ask; null would say it asks and you supplied nothing. It reports documents instead.

requirements lists what is still owed, keyed as you write it. GET /v3/compliance/requirements says what a market demands of anybody; this says what you gave it.

Correct what it holds

PATCH /v3/channels/sms/{country}/{type} takes the compliance object from the read straight back:

curl -X PATCH "https://api.sent.dm/v3/channels/sms/us/ten_dlc" \
  -H "x-api-key: $ORG_API_KEY" \
  -H "x-profile-id: $PROFILE_ID" \
  -H "Content-Type: application/json" \
  -d '{ "compliance": { "brand": { "contact_phone": "+12125550123" } } }'

Partial at every level. An omitted key is left alone; an explicit null clears it. Those are different requests, so {} changes nothing and is refused rather than silently accepted, and correcting one field does not blank the others. A requirements key is the path into the body that clears it.

BodyEffect
"brand": { … }Supplies or corrects a dedicated identity for this sender
"brand": { "inherit": true }Runs on your organization's identity, giving up the dedicated one
"brand": nullThe same thing, said the short way
"campaign": { … }Supplies or corrects a dedicated campaign for this sender
"campaign": { "inherit": true }Runs on your organization's campaign
Key omittedLeft exactly as it is

Nothing here creates a second anything — one identity and one campaign per market — so a write is an upsert and a replayed request lands on the row it already wrote. There is no sub-resource for either half: both are singletons of the market, so both are written here.

The response is the market as stored, in the same shape GET returns, so what comes back can be sent back.

WhatsApp

POST /v3/channels/whatsapp connects a WhatsApp Business Account that your organization already holds through a Multi-Partner Solution. It takes two fields, waba_id and an optional phone_number_id; the solution, the owning business portfolio, and the access token are all resolved from your organization rather than accepted from you.

See Connect a WhatsApp Business Account for that call in full, including why the token is not a field.

RCS

POST /v3/channels/rcs asks for an RCS agent and records the order form the carriers vet it against. It always returns 202: an agent is approved by the carriers, not by this call, and nothing here can make that happen sooner.

Three fields are required, because US carriers refuse a submission without them:

FieldWhy
brand_nameThe brand this agent presents. Not taken from compliance.brand: a business may present a different brand on RCS than the one its 10DLC registration is filed against, and the carriers vet this one.
privacy_policy_urlFetched over the public internet at review time, so it must be an absolute http(s) URL. One behind a login passes this call and fails the review.
terms_and_conditions_urlThe same.

Everything else is optional here and can follow as the submission is assembled: display_name, description, agent_use_case, brand_color, the contact details, official_address, the consent narrative, and the start, help, and stop messages.

Two fields cannot be changed casually. hosting_region is us or eu, defaults to us, and cannot be changed once the agent is approved. billing_category is CONVERSATIONAL, SINGLE_MESSAGE, or BASIC_MESSAGE, and defaults to CONVERSATIONAL.

Troubleshooting

SymptomLikely causeFix
400 "compliance.brand is required…" on adding a TEN_DLC marketNo identity stored and none sentSend compliance.brand as {"inherit": true} or with the identity's fields
400 on PATCH with an empty body{} is refused rather than treated as a no-opSend the field you meant to change, or null to clear it
400 on campaign.message_flowUnder 40 charactersDescribe where opt-in happens, the unchecked box, and that consent is not a condition of purchase
400 on campaign.use_casesMIXED or LOW_VOLUME used as a use caseRemove them and set campaign.volume, which decides how the campaign registers
404 on a market you believe existsThe country and type pair does not name a market this sender hasList with GET /v3/channels/sms first
409 on add or patchA replay of a request already in flight, or a conflicting concurrent writeReuse the same Idempotency-Key and read the market back
501 from the requirements endpointchannel=whatsapp or channel=rcsOnly sms is answered today
Market stuck at PROVISIONINGSent is arranging the senderNothing is owed by you; ACTION_NEEDED is the status that asks for something

On this page