---
title: "Add an SMS market — a country and sender type"
method: POST
path: "/v3/channels/sms"
tags: ["Channels"]
---

# Add an SMS market — a country and sender type

`POST /v3/channels/sms`

## Request

- Base URL: `https://api.sent.dm`
- URL: `https://api.sent.dm/v3/channels/sms`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Gives the customer a sender in one country — the first market it sends in, and the fifth.

## What to have in place first

**Call `GET /v3/compliance/requirements?country=US&type=TEN_DLC` first.** It returns this request's body ready to fill in, plus the brand and campaign calls to make before it, in order. You never have to track forty countries' rules yourself.

**A market that registers with a compliance regime needs an identity, and this call takes one.** US `TEN_DLC` registers with The Campaign Registry and files that registration against a business identity. Send it in `compliance.brand` and it is recorded here, before the registration that attaches to it — so a customer with no identity reaches a first 10DLC market in one call.

`{ "inherit": true }` runs on your organization's instead. A market with neither — none stored, none sent — is `400`, because accepting it would write a channel that could never register and leave it provisioning for ever with nothing saying why.

**Everything the regime wants arrives in this request's `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. Nothing is submitted here either: the rows are what a submission is later assembled from, and filing is externally rejectable so it happens on its own path.

**A half you run yourself has to be complete here.** Whichever of `brand` and `campaign` is your own rather than your organization's must carry every field the regime asks of it, counting what you already hold — send the rest in this request. A missing one is `400` naming each field, rather than a `202` for a market that could never be filed. A half you inherit is asked for nothing.

A market that registers with nobody — an alphanumeric sender in most countries — needs neither, and may need documents instead.

## Sending it

Takes the same fields `channels.sms` takes on `POST /v3/sender-profiles`, because it is the same work, and both run one provisioning path. `compliance` is top-level and answers for the market in this request, carrying `brand` and `campaign` where the market registers.

**Accepts multipart as well as JSON**, with each file named after the compliance key it satisfies — a market that pre-registers its sender needs its documents at the same moment it needs the sender. Every required document must be attached to this request; a missing or invalid file is rejected before the market is added.

**`area_codes` puts the sender somewhere specific**, in your order of preference, for a `LOCAL` sender in any country or a US `TEN_DLC` sender — a 10DLC number is an ordinary local long code bound to a registered campaign, so both take the same codes out of the same inventory. It is honoured exactly: you get a number in one of the codes you named or you get none, never one in a code you left out.

## Several numbers

**`numbers` lists the numbers you want, one entry per area code**, e.g. `[{"area_code": "212", "quantity": 2}, {"quantity": 1}]` for two numbers in 212 and one in any area code. `quantity` defaults to 1, and an entry without `area_code` means any area code. Omit `numbers` for one number. It is for `LOCAL` and `TEN_DLC` only and cannot be combined with `area_codes`. A market holds at most 49 numbers, counting the ones it already has.

**On a `LOCAL` market you already hold, `numbers` adds those numbers to it** instead of `409`. US `TEN_DLC` numbers belong to the market's campaign and are assigned when the campaign is approved, not at request time. Until then they read as `PROVISIONING` in the response's `numbers`, each with the area code it was asked for. A profile inheriting your organization's campaign cannot request `numbers` (`400`). `numbers` on a `TEN_DLC` market you already hold is `409`: its numbers belong to its one campaign and are set when the market is created.

**A market you already send in is `409`**: adding what is already there is a mistake worth reporting rather than a silent no-op. A market is a `(country, sender_type)` pair, so a **second sender type in a country you already send in is fine** — `US TOLL_FREE` alongside `US TEN_DLC` is the ordinary case. Two of the same type is not.

**A market you hold that has no sender yet is not a duplicate**, and re-sending it re-asks for a sender rather than being refused. That is the way out of an area-code request nothing could fill: send the call again to name different codes, or no codes at all to take a number anywhere in the country. It answers `201` with the sender when the pool has one, otherwise `202` with the codes recorded — and changes nothing else; compliance on a market you hold is `PATCH /v3/channels/sms/{country}/{type}`.

One exception: while a market's sender type is still unassigned, adding any type to that country is `409` once it has a sender, because the market you have may be about to become the one you asked for.

Returns **the market**, in the shape `GET` and `PATCH` on `/v3/channels/sms/{country}/{type}` use — so what this answers can be read back and written back without reshaping. Not every channel you have: adding a market cannot change an RCS agent or a WhatsApp account, and `GET /v3/channels` is the whole picture. `201` when the market is ready; `202` while a sender or a registration is still outstanding.

Send `x-profile-id` with an organization key to add a market to one of your profiles.

## Headers

- `Idempotency-Key` string
- `x-profile-id` string, uuid

## Request body

- SentDmServicesEndpointsCustomerAPIv3ChannelsRequestsAddSmsChannelRequest: Adds an SMS market — a country and a sender type — to a customer. The same fields channels.sms takes on POST /v3/sender-profiles, because it is the same work: create is "make the profile, then add its first market", and both run one provisioning path. compliance is top-level and answers for the market this request concerns, exactly as it does there.
  - `sandbox` boolean: Sandbox flag - when true, the operation is simulated without side effects Useful for testing integrations without actual execution
  - `country` string, required: ISO 3166-1 alpha-2 country the sender operates in.
  - `number_type` 'LOCAL' | 'TOLL_FREE' | 'SHORT_CODE' | 'ALPHANUMERIC' | 'TEN_DLC' | 'MOBILE' | '10DLC', required: Sender type. Decides the provisioning flow and what compliance is asked for. [Required] so the published document says so. The validator has always demanded it, but requiredness reached the schema by matching a rule to a property name — which works for country, whose wire name is its camel-cased C# name, and silently does not for number_type. A client generating a request from our OpenAPI therefore sent what the document called sufficient and was answered 400.
  - `sender_value` string, nullable: The sender's identity where the caller chooses it. Required when number_type is ALPHANUMERIC; optional for carrier-assigned types, which are procured afterwards.
  - `area_codes` string[], nullable: The area codes to put this sender in, in your own order of preference, e.g. ["415", "628"] for the US or ["20"] for London. Omit it and a number is claimed anywhere in the country. Ordered, and the order is the request. The claim tries each code in turn and stops at the first that yields a number. Nothing sorts the list and a code listed twice is refused. One to fifty entries, each the area code as digits: 415, with the trunk prefix as 020, or with the country's dialing prefix as +1415 (all are stored as the bare code). A dialing prefix from a different country than country is refused. Each code is checked against the country's numbering plan: it must start real local or mobile numbers there, so toll-free and premium ranges, service codes and another country's codes are refused with a reason. LOCAL senders in any country, and US TEN_DLC senders. No other sender type has a geographic area code, and the list is refused with one rather than accepted and dropped. Honoured exactly or not at all. Ask for 415 and you get a 415 number or you get none, never a neighbouring code you did not name. A miss leaves the market with no sender yet and the codes recorded on it, so it is filled when stock appears rather than filled now from somewhere else. Many countries outside North America require address or business documents before a local number can be bought, which can delay or prevent the fill.
  - `numbers` SentDmServicesCommonSenderProfilesContractsSmsNumberRequest[], nullable: The numbers to assign, one entry per area code, e.g. [{"area_code": "212", "quantity": 2}, {"area_code": "415"}, {"quantity": 1}]: two numbers in 212, one in 415 and one in any area code. quantity defaults to 1, and an entry without area_code means any area code. Omit numbers for one number. Only with LOCAL or TEN_DLC, and not together with area_codes. Each area code is validated against country the same way area_codes is and may appear once, and at most one entry may leave area_code out. On a LOCAL market you already hold, this adds the numbers to it. US TEN_DLC numbers are assigned when the campaign is approved, not at request time. Not available when the profile inherits the organization's campaign.
    - `area_code` string, nullable: The area code, e.g. 212, validated against the request's country. Omit it for numbers in any area code.
    - `quantity` integer, nullable: How many numbers. At least 1; omitted means 1.
  - `compliance` MarketCompliance: The compliance values a market registers with. `brand` and `campaign` apply to US `TEN_DLC`; other markets fill this map with the document keys their own requirements name.
    - `brand` object: Business identity. The identity the registration is filed against. Omit to have none; { "inherit": true } to register under your organization's.
      - `inherit` boolean: Run on the organization's brand. True to register under the parent organization's identity rather than one of this profile's own; every field below is then neither asked for nor accepted. Omitted, the brand is the organization's unless this block supplies identity fields, which says plainly enough that it wants one of its own.
      - `legal_name` string: Registered legal name. TCR companyName — the name on the incorporation documents, which is often not the name recipients would recognise. Required when `compliance.brand.inherit != true`.
      - `business_name` string: Public-facing name. TCR displayName — the name recipients would recognise. Named for what it holds rather than for TCR's field, which is why it is not `display_name`. Required when `compliance.brand.inherit != true`.
      - `tax_id` string: Tax identifier. TCR ein. TcrBrandSubmissionValidator refuses a submission without one, so it is wanted here rather than discovered at filing time. Required when `compliance.brand.inherit != true`.
      - `tax_id_type` string: Kind of tax identifier. Which kind of identifier `tax_id` is, e.g. us_ein. Derived onto the brand form, and not itself sent to TCR. Required when `compliance.brand.inherit != true`.
      - `ein_issuing_country` string, iso3166_alpha2: Country that issued the tax id. TCR einIssuingCountry — where the tax id was issued, which is not always where the business trades. Defaults to `country` when omitted, which is right for most. Takes the code or the country spelled out, as `country` does.
      - `entity_type` 'PRIVATE_PROFIT' | 'PUBLIC_PROFIT' | 'NON_PROFIT' | 'SOLE_PROPRIETOR' | 'GOVERNMENT': Entity type. TCR entityType. Decides which vetting TCR applies and, for SOLE_PROPRIETOR, fixes the campaign fee regardless of volume. Also refused at submission if absent. Required when `compliance.brand.inherit != true`.
      - `street` string: Street address. Required when `compliance.brand.inherit != true`.
      - `city` string: City. Required when `compliance.brand.inherit != true`.
      - `state` string: State or region. TCR state. Two letters for a US registration. Required when `compliance.brand.inherit != true`.
      - `postal_code` string: Postal code. Required when `compliance.brand.inherit != true`.
      - `country` string, iso3166_alpha2: Country of registration. Two letters — US. The country spelled out in full is accepted too and stored as its code, which is what a read answers with and what is filed. Required when `compliance.brand.inherit != true`.
      - `website` string, url: Website. TCR website. Optional, and worth sending: carriers weigh it when vetting, so a brand that has one and omits it is vetted on less than it could be.
      - `contact_name` string: Responsible contact. Split into TCR's firstName/lastName on submission. BrandService refuses a blank one, so a missing name is reported here rather than thrown after the profile was written. Required when `compliance.brand.inherit != true`.
      - `contact_email` string, email: Contact email. TCR email. Required when `compliance.brand.inherit != true`.
      - `contact_phone` string, e164: Contact phone. TCR sends it as both phone and mobilePhone, so it is asked for once. Named for the contact it belongs to rather than as a bare `phone` on a block that also carries an address. Required when `compliance.brand.inherit != true`.
    - `campaign` object: Messaging campaign. What the programme sends and how recipients consented. Omit to run on your organization's; supplying it takes one of your own. { "inherit": true } says so outright. Required when `compliance.campaign.inherit != true`.
      - `inherit` boolean: Run on the organization's campaign. True to register under the parent organization's programme rather than one of this profile's own; every field below is then neither asked for nor accepted. Owning the identity and inheriting the campaign is refused — a campaign is filed against an identity registration, and chk_tcr_inheritance forbids that pairing outright.
      - `description` string: What this programme sends. TCR description: what recipients will get and why, in a sentence. The only one of these TCR takes verbatim as a description of the programme, which is why it is asked for rather than derived from the customer's name. Required when `compliance.campaign.inherit != true`.
      - `message_flow` string: Consent narrative. TCR messageFlow: how recipients opt in, 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. Required when `compliance.campaign.inherit != true`.
      - `use_cases` object[]: Use cases. What the campaign sends, and an example of each. One to five entries; how they register is decided by volume, never declared. Required when `compliance.campaign.inherit != true`.
        - `use_case` 'MARKETING' | 'ACCOUNT_NOTIFICATION' | 'CUSTOMER_CARE' | 'FRAUD_ALERT' | '2FA' | 'DELIVERY_NOTIFICATION' | 'SECURITY_ALERT' | 'M2M' | 'HIGHER_EDUCATION' | 'POLLING_VOTING' | 'PUBLIC_SERVICE_ANNOUNCEMENT', required: Use case. What this entry sends. MIXED and LOW_VOLUME are refused — volume decides those.
        - `sample_messages` unknown[], required: Sample messages. TCR sample1..sample5 for this use case. One to five, each carrying the brand name and an opt-out instruction.
          - unknown
      - `opt_in_message` string: Opt-in confirmation. TCR optinMessage. Should state message frequency and that rates may apply. Its presence is what sets subscriberOptin. Required when `compliance.campaign.inherit != true`.
      - `opt_out_message` string: Opt-out confirmation. TCR optoutMessage. Its presence is what sets subscriberOptout. Required when `compliance.campaign.inherit != true`.
      - `help_message` string: Help reply. TCR helpMessage. Its presence is what sets subscriberHelp. Required when `compliance.campaign.inherit != true`.
      - `opt_in_keywords` string, csv_keywords: Opt-in keywords. TCR optinKeywords, comma separated, e.g. START,YES. Required when `compliance.campaign.inherit != true`.
      - `opt_out_keywords` string, csv_keywords: Opt-out keywords. TCR optoutKeywords, comma separated. STOP, END, CANCEL, UNSUBSCRIBE and QUIT are honoured regardless of what is declared. Required when `compliance.campaign.inherit != true`.
      - `help_keywords` string, csv_keywords: Help keywords. TCR helpKeywords, comma separated, e.g. HELP,INFO. Required when `compliance.campaign.inherit != true`.
      - `privacy_policy_link` string, url: Privacy policy URL. TCR privacyPolicyLink. Required when `compliance.campaign.inherit != true`.
      - `terms_and_conditions_link` string, url: Terms and conditions URL. TCR termsAndConditionsLink. Required when `compliance.campaign.inherit != true`.
      - `volume` string: Expected monthly volume. Decides how the campaign registers and which fee tier applies: below the low-volume threshold it registers as LOW_VOLUME with sub-use-cases, above it as a single use case or MIXED. Omitting it registers the campaign at the higher-fee standard tier.

## Response `201`

The market is ready

- SentDmServicesEndpointsCustomerAPIv3SharedApiResponseOfSmsMarketState: Standard API response envelope for all v3 endpoints
  - `success` boolean: Indicates whether the request was successful
  - `data` SentDmServicesCommonChannelsContractsSmsMarketState: One SMS market the profile sends in — its sender, where that sender stands, and what the market wants. A profile may send in several: it is a tenant, not a market. Each carries its own status, because a profile can be live in one country and mid-registration in another — and each carries what it still owes, rather than that being reported in a separate array a caller has to join on.
    - `country` string, nullable
    - `number_type` string, nullable
    - `sender_value` string, nullable: The sender itself. Null until a carrier-assigned number is procured. For a market holding several numbers, the first active one (the first assigned while none is active) — every number is in numbers.
    - `numbers` SentDmServicesCommonChannelsContractsSmsNumberState[]: Every number this market holds or is still owed, each with its area code and its own status (ACTIVE once it can send, PROVISIONING while it is still to be assigned, INACTIVE once it could send and no longer can). One entry for a market holding one number. US 10DLC numbers are assigned when the campaign is approved, so they read as owed until then. Read-only.
      - `sender_value` string, nullable: The number, or null while it is still to be assigned.
      - `area_code` string, nullable: The number's area code, e.g. 212. While the number is still to be assigned, the area code it was asked for, or null when it was asked for in any area code.
      - `status` string, nullable: ACTIVE once the number can send; PROVISIONING while it is still to be assigned; INACTIVE once it could send and no longer can.
    - `status` 'PROVISIONING' | 'ACTION_NEEDED' | 'PENDING_REVIEW' | 'ACTIVE' | 'INACTIVE', nullable: Where this market's sender stands, in terms of who is holding it up — PROVISIONING, ACTION_NEEDED, PENDING_REVIEW or ACTIVE. Read-only. Resolved rather than reported raw: a live sender reads ACTIVE whatever it still owes, a market with outstanding requirements or an open resubmission request reads ACTION_NEEDED, and a market that registers with a regime reads PENDING_REVIEW once its brand exists — filing to The Campaign Registry is ours to do and need not have happened yet, so a dedicated brand created through this API reads PENDING_REVIEW from the moment it is stored. Describes whether the market can send, not each number: a market holding several numbers reads ACTIVE as soon as one of them is, whatever the others are doing. Each number's own status is in numbers.
    - `reason_code` string, nullable: Why the market is not ACTIVE, as a stable code: an ErrorCodes CHANNEL_xxx value such as CHANNEL_001 (something you owe) or CHANNEL_009 (under review). Switch on this rather than on reason. Omitted while ACTIVE. Read-only; when nothing more specific is known it is the generic reason for the status.
    - `reason` string, nullable: A sentence for reason_code: this market's own explanation, naming what is outstanding where that is you. Omitted while ACTIVE.
    - `compliance` SentDmServicesCommonChannelsContractsSuppliedComplianceState: What a market has been given: the identity it registers under, its programme, and any documents attached. What it does not carry is what the market asks for. That is the subject of GET /v3/compliance/requirements, and it is the same answer for every caller — a description of what a compliance regime wants, not a record of one customer's progress through it. It was reported here as well for a while, which put the same array in six response shapes and left a caller deciding which of two sources to believe. Present on a list read for markets that register (carrying brand and campaign), but with documents absent — documents are not fetched for a list, because a catalog lookup and a document read per market would multiply across a page. Absent documents is distinct from an empty list: absent says they were not fetched; empty says the market has been given none. The parent object is null only when the market registers with nobody and compliance was not computed — nothing to show at all.
      - `brand` object, nullable: The identity this market registers under, with inherit saying whose it is. Reported here rather than on the profile because it belongs to the registration this market files, and only one market files one. It was a top-level block for a while, which put a per-registration value beside a list of markets and left a caller to work out which market it belonged to. Absent for a market that registers with nobody — such a market asks for no identity, so there is none to report. Absent and null mean different things: absent says this market does not ask, null would say it asks and nothing was supplied. Untyped, like the request side, because its members are declared by the market's own schema rather than by a C# class. A typed pair here would be a second definition of what a market wants, free to drift from the one that validates.
      - `campaign` object, nullable: The programme this market registers, with inherit saying whose it is. One, not a list. TcrCampaigns permits several and an account built on the admin side may hold them, but this surface offers one — which is what lets the market's PATCH be an upsert rather than a collection with an addressable create behind it. An account holding several is reported as its first and refused on write, rather than half-edited. Carries no id. Nothing addresses a campaign, and an undeclared key would be refused if the caller sent this object back — which it is meant to be able to do.
      - `documents` SentDmServicesCommonChannelsContractsSuppliedDocument[], nullable: What has been supplied for this market. Files, not values — the declared halves above carry the values. A document cannot be a JSON value, so it is sent as multipart on the channel call and reported here as a reference. Absent on a list read, which fetches identity but does not compute compliance documents per market. Absent and empty mean different things: absent says the documents were not fetched; empty says the market has been given none.
        - `key` string: The catalog's name for this document, matching the requirement it satisfies.
        - `file_name` string, nullable
        - `document_id` string, uuid, nullable: Identifier of the upload, for fetching it back through the documents endpoints.
  - `error` SentDmServicesEndpointsCustomerAPIv3SharedApiError: Error information
    - `code` string: Machine-readable error code (e.g., "RESOURCE_001")
    - `message` string: Human-readable error message
    - `details` object, nullable: Additional validation error details (field-level errors)
    - `doc_url` string, nullable: URL to documentation about this error
  - `meta` SentDmServicesEndpointsCustomerAPIv3SharedApiMeta: Request and response metadata
    - `request_id` string: Unique identifier for this request (for tracing and support)
    - `timestamp` string, date-time: Server timestamp when the response was generated
    - `version` string: API version used for this request

## Other responses

- `202`: Added, or re-asked, with a sender or a registration still outstanding
- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: The customer could not be read
- `409`: The customer already sends in that market, or in that country under a sender type that is not assigned yet, or the campaign is registered with TCR and the volume would change its tier
- `422`: A required pre-registration document is missing or an uploaded file is invalid
- `500`: Server Error

## Changes

- **2026-10-16** `6c829543a93e`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/sentdm/apis/sent-dm-api-v3/changes/v3/channels/sms/post.md)

---

[API](https://skmtc.dev/sentdm/apis/sent-dm-api-v3.md) - [All operations](https://skmtc.dev/sentdm/apis/sent-dm-api-v3/llms.txt) - [OpenAPI document](https://skmtc.dev/sentdm/apis/sent-dm-api-v3/revisions/bdcca958cac7?raw)
