Create a sender profile
Creates a sender profile under your organization. A profile is a customer of yours: it sends under its own or your identity, and your customers never see Sent.
The minimum
{ "name": "Example Retail", "short_name": "Example" }. short_name is the SMS sender id and is required. Identity, inbox, opt-out list and billing come from your organization, so the profile is usable immediately with no compliance work.
A sender is not one of them. The brand and the campaign can be inherited; a number cannot. A profile either has a sender of its own — channels.sms — or it has none and terminates through the shared routes for that country.
Saying what a profile owns
Every capability is one of three things, and there is no flag per state:
- Its own — supply the capability's fields. Data is what owning looks like.
- The organization's —
{ "inherit": true }, with nothing else. - None — omit the block.
There is no "inherit": false that also carries no data. A second way to say the same thing would let a request contradict itself.
Inheriting needs no id. A customer has exactly one brand, so brand resolves without one.
The campaign has no block of its own. It is only ever a registration, and every field of one is something a specific market demands — so it lives in compliance, alongside the rest of what that market asks for, and it says whose it is there too: "compliance": { "inherit": true } runs the profile on your organization's campaign and stops this call asking for any campaign field. Omitted, or false, gives the profile its own; inheriting takes all of your organization's, because inheritance resolves an owner rather than a single row.
A profile with a brand of its own cannot inherit the campaign, and asking is refused with 400: a campaign is filed against a brand registration, and your organization's campaign is filed against your organization's brand rather than this profile's. Inherit both, or own both.
channels.sms is the market the profile starts in — one country and number_type. It decides what else this call asks for: the compliance catalog is keyed on that pair, and it determines which fields brand accepts and what compliance answers for.
A profile is a tenant, not a market, so it can go on to send in several — but through POST /v3/channels/sms with the x-profile-id header, one market and its compliance at a time. Reads return every market as channels.sms[], so the read shape is a list even where this one is not.
brand is required when number_type is TEN_DLC — inherited or its own. A 10DLC registration has nothing to attach to without one, and no later call can supply it, so omitting it is refused with 400 rather than reported as outstanding.
Use cases and volume
A TCR campaign declares one to five use_cases, each with its own sample_messages — TCR attributes samples to the use case they illustrate, so they travel together.
How that set registers is derived from volume and never declared. Below the low-volume threshold the campaign registers as LOW_VOLUME with every entry as a sub-use-case; above it, one entry registers as that use case outright and several register as MIXED with sub-use-cases. MIXED and LOW_VOLUME are therefore refused as use_case values: they describe the shape of a set, which is ours to work out.
Leaving volume out is accepted and costs more. An absent value registers the campaign at the standard tier, which carries the higher fee — so say so when a campaign is low volume. Sole proprietor brands are the exception: their fee is fixed by entity type, and volume only decides the use case.
Compliance
brand is captured identically in every country. Everything a specific market demands on top of it — including every field of the campaign — goes in compliance, as key/value entries whose keys depend on the sender's country and type. US TEN_DLC registers with The Campaign Registry and asks for a substantial set; an alphanumeric sender may pre-register with documents, or need nothing at all.
You do not have to know any of it. Omit compliance and the response lists exactly what is outstanding, with types and accepted values — or call GET /v3/compliance/requirements first. Anything Sent supplies on your behalf is never asked for, and is rejected if sent.
Requirements can be conditional, and the condition is published as required_when so you can render a form from it rather than discover the rule by being rejected. Every US TEN_DLC campaign field carries compliance.inherit != true — send "inherit": true and none of them is asked for or accepted, because the profile is running on your organization's campaign.
Uploading documents
Send application/json for everything except a market that pre-registers a sender with supporting documents. For those, send multipart/form-data instead: put this entire document — unchanged — in a single field named profile, and attach each file under a field named after the compliance key it satisfies. Individual form fields per property are not read; the payload is always the one profile field.
A pre-registration market has no brand and no campaign — those are asked for only where a regime registers one — so its compliance carries nothing at all, and the documents are the request:
curl -X POST https://api.sent.dm/v3/sender-profiles \
-H 'x-api-key: YOUR_SECRET_TOKEN' \
-F 'profile={
"name": "Example Retail",
"short_name": "EXAMPLE",
"channels": {
"sms": {
"country": "XK",
"number_type": "ALPHANUMERIC",
"sender_value": "EXAMPLE"
}
}
}' \
-F 'business_registration=@registration.pdf' \
-F 'letter_of_authorization=@loa.pdf'
business_registration and letter_of_authorization are illustrative. The real field names are whatever that market configures, so read them from GET /v3/compliance/requirements, which lists documents separately from the values — under attachments, each with the field_name to put the file under.
A document has no JSON form, so naming one in compliance is refused with 400 rather than ignored: accepting it would report the requirement as satisfied while no file was ever stored.
WhatsApp and RCS
Neither can be inherited, so neither takes an inherit: the profile's WhatsApp account and RCS agent are its own or it has none. Sharing your organization's would share its inbox and its throughput, which makes the two indistinguishable to a recipient.
channels.whatsapp names one of the accounts shared into your organization, and takes the two identifiers nothing else can supply: waba_id, because your organization holds one account per client, and phone_number_id, because an account may hold several numbers and choosing for you would decide which of your customer's numbers their traffic leaves from.
Everything else is resolved from your organization — the Multi-Partner Solution the account arrived through, the client business portfolio that owns it, the access token and that token's expiry. None of them is accepted here: the first two are Meta's answer about provenance, so a value you sent could assert a partnership Meta disagrees with, and the token is minted per client business and rotates roughly every sixty days.
phone_number_id is optional. Omit it to connect the account now and settle the number later — the channel reads as provisioning and the profile keeps sending from your organization's account until a number is named. POST /v3/channels/whatsapp connects an account to an existing profile and resolves the same fields the same way.
An account of its own needs a sender of its own, so channels.whatsapp requires channels.sms. A profile sending SMS on your organization's sender shares that inbox and opt-out list, and is not a separate tenant to give an account to — asking is refused with 400, before your organization is asked to resolve anything.
channels.rcs records the agent. A carrier approval no API call can trigger is arranged offline, so the capability reports awaiting_submission until it is. rcs.brand_id is Google's identifier and is unrelated to the top-level brand.
Responses
201 when the profile is complete on return — the fully-inherited case, where nothing needs registering. 202 when something is still provisioning, with per-capability status and outstanding requirements. 409 when the WhatsApp account supplied is already recorded. 400 covers a wrong value as well as a missing one: a missing brand under TEN_DLC, an unknown compliance key, a bad type, a sender id the market does not allow, or a waba_id that is not one of the accounts shared into your organization.
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v3/sender-profiles" \ -H "Idempotency-Key: req_abc123_retry1" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Retail", "short_name": "Example", "description": null, "billing": null, "channels": { "sms": { "country": "US", "number_type": "TEN_DLC", "sender_value": null }, "whatsapp": null, "rcs": null }, "compliance": { "brand": { "legal_name": "Example Retail Inc", "business_name": "Example Retail", "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", "website": "https://example.com", "contact_name": "John Smith", "contact_email": "john@example.com", "contact_phone": "+12125550123" }, "campaign": { "description": "Order and delivery updates for customers who have bought from Example Retail, plus occasional offers.", "message_flow": "Customers opt in at checkout on https://example.com by ticking a box that is unchecked by default, and again in the account settings page. Consent is not a condition of purchase.", "use_cases": [ { "use_case": "DELIVERY_NOTIFICATION", "sample_messages": [ "Example Retail: order 10432 is out for delivery and arrives today. Track it at https://example.com/t/10432. Reply STOP to opt out.", "Example Retail: order 10432 was delivered. Reply STOP to opt out." ] }, { "use_case": "MARKETING", "sample_messages": [ "Example Retail: 20% off everything this weekend with code SAVE20 at https://example.com/sale. Reply STOP to opt out." ] } ], "opt_in_message": "Example Retail: you are subscribed to order updates and offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.", "opt_out_message": "Example Retail: you are unsubscribed and will receive no further messages. Reply START to resubscribe.", "help_message": "Example Retail: email support@example.com or call +1 212 555 0123. Msg & data rates may apply. Reply STOP to unsubscribe.", "opt_in_keywords": "START,YES", "opt_out_keywords": "STOP,END,CANCEL,UNSUBSCRIBE,QUIT", "help_keywords": "HELP,INFO", "privacy_policy_link": "https://example.com/privacy", "terms_and_conditions_link": "https://example.com/terms", "volume": "1500" } }, "sandbox": false }'{
"success": true,
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"name": "string",
"short_name": "string",
"description": "string",
"api_key": "string",
"billing": {
"inherit": true,
"fallback": true
},
"channels": {
"customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
"sms": [
{
"country": "string",
"number_type": "string",
"sender_value": "string",
"status": "PROVISIONING",
"compliance": {
"brand": {
"property1": null,
"property2": null
},
"campaign": {
"property1": null,
"property2": null
},
"documents": [
{
"key": "string",
"file_name": "string",
"document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6"
}
]
}
}
],
"whatsapp": {
"waba_id": "string",
"phone_number_id": "string",
"solution_id": "string",
"owner_business_id": "string",
"status": "PROVISIONING"
},
"rcs": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "PROVISIONING",
"phone_number": "string",
"display_name": "string",
"description": "string",
"agent_use_case": "string",
"brand_name": "string",
"privacy_policy_url": "string",
"terms_and_conditions_url": "string",
"website_url": "string",
"brand_color": "string",
"brand_phone_number": "string",
"customer_support_phone_number": "string",
"brand_email": "string",
"customer_support_email": "string",
"contact_name_and_title": "string",
"company_ein": "string",
"entity_type": "string",
"official_address": {
"street": "string",
"city": "string",
"state": "string",
"postal_code": "string",
"country": "string"
},
"brief_company_description": "string",
"opt_in_process_description": "string",
"start_message": "string",
"help_message": "string",
"stop_message": "string",
"sample_messages": [
"string"
],
"created_at": "2019-08-24T14:15:22Z"
}
},
"compliance": {
"brand": {
"property1": null,
"property2": null
},
"campaign": {
"property1": null,
"property2": null
},
"documents": [
{
"key": "string",
"file_name": "string",
"document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6"
}
]
},
"created_at": "2019-08-24T14:15:22Z"
},
"error": {
"code": "string",
"message": "string",
"details": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"doc_url": "string"
},
"meta": {
"request_id": "string",
"timestamp": "2019-08-24T14:15:22Z",
"version": "string"
}
}{
"success": true,
"data": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Example Retail",
"short_name": null,
"description": null,
"api_key": "sntdm_live_xxxxxxxxxxxx",
"billing": null,
"channels": {
"customer_id": null,
"sms": [
{
"country": "US",
"number_type": "TEN_DLC",
"sender_value": null,
"status": "PROVISIONING",
"compliance": null
}
],
"whatsapp": null,
"rcs": null
},
"compliance": {
"brand": {
"inherit": false,
"legal_name": "Example Retail Inc",
"business_name": "Example Retail",
"tax_id": "12-3456789",
"tax_id_type": "us_ein",
"ein_issuing_country": "US",
"entity_type": "PRIVATE_PROFIT",
"street": "123 Main Street",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"website": "https://example.com",
"contact_name": "John Smith",
"contact_email": "john@example.com",
"contact_phone": "+12125550123"
},
"campaign": {
"inherit": false,
"description": "Order and delivery updates for customers who have bought from Example Retail, plus occasional offers.",
"message_flow": "Customers opt in at checkout on https://example.com by ticking a box that is unchecked by default, and again in the account settings page. Consent is not a condition of purchase.",
"use_cases": [
{
"use_case": "DELIVERY_NOTIFICATION",
"sample_messages": [
"Example Retail: order 10432 is out for delivery and arrives today. Track it at https://example.com/t/10432. Reply STOP to opt out.",
"Example Retail: order 10432 was delivered. Reply STOP to opt out."
]
},
{
"use_case": "MARKETING",
"sample_messages": [
"Example Retail: 20% off everything this weekend with code SAVE20 at https://example.com/sale. Reply STOP to opt out."
]
}
],
"opt_in_message": "Example Retail: you are subscribed to order updates and offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
"opt_out_message": "Example Retail: you are unsubscribed and will receive no further messages. Reply START to resubscribe.",
"help_message": "Example Retail: email support@example.com or call +1 212 555 0123. Msg & data rates may apply. Reply STOP to unsubscribe.",
"opt_in_keywords": "START,YES",
"opt_out_keywords": "STOP,END,CANCEL,UNSUBSCRIBE,QUIT",
"help_keywords": "HELP,INFO",
"privacy_policy_link": "https://example.com/privacy",
"terms_and_conditions_link": "https://example.com/terms",
"volume": "1500"
},
"documents": []
},
"created_at": "2026-09-04T16:45:29.0294284+00:00"
},
"error": null,
"meta": {
"request_id": "req_7X9zKp2jDw",
"timestamp": "2026-09-04T16:45:29.0294713+00:00",
"version": "v3"
}
}{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_001",
"message": "One or more compliance values were rejected",
"details": {
"compliance.phone": [
"'phone' must be a valid e164."
],
"compliance.mock": [
"'mock' is set by Sent and cannot be supplied."
]
},
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
},
"meta": {
"request_id": "req_7X9zKp2jDw",
"timestamp": "2026-09-04T16:45:29.0294878+00:00",
"version": "v3"
}
}{
"success": true,
"error": {
"code": "string",
"message": "string",
"details": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"doc_url": "string"
},
"meta": {
"request_id": "string",
"timestamp": "2019-08-24T14:15:22Z",
"version": "string"
}
}{
"success": false,
"data": null,
"error": {
"code": "RESOURCE_007",
"message": "This WhatsApp account is already recorded",
"details": {
"channels.whatsapp": [
"Meta registers a number to exactly one business account, so this one cannot be added twice. Omit \"channels.whatsapp\" to send from the account your organization already has."
]
},
"doc_url": "https://docs.sent.dm/reference/api/error-catalog"
},
"meta": {
"request_id": "req_7X9zKp2jDw",
"timestamp": "2026-09-04T16:45:29.0294862+00:00",
"version": "v3"
}
}{
"success": true,
"error": {
"code": "string",
"message": "string",
"details": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"doc_url": "string"
},
"meta": {
"request_id": "string",
"timestamp": "2019-08-24T14:15:22Z",
"version": "string"
}
}{
"success": true,
"error": {
"code": "string",
"message": "string",
"details": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"doc_url": "string"
},
"meta": {
"request_id": "string",
"timestamp": "2019-08-24T14:15:22Z",
"version": "string"
}
}Get a sender profile GET
Reads one of your sender profiles. The document mirrors `POST /v3/sender-profiles`, with three differences that follow from this being a read rather than a request. `channels.sms` is a **list**. Create takes the one market a profile starts in; a profile can go on to send in several, and this reports all of them, each with its own `status` and its own outstanding `compliance`. `brand` comes back **in full, with `inherit` saying whose it is**. On a request `{ "inherit": true }` carries no data because there is nothing to send; here it carries the data, because the point of a read is to say what the profile actually sends under. **There is no `campaigns` array.** A campaign is only ever a registration, so what a read can usefully say about one is whether anything is still owed for it — and that is reported per market, in `channels.sms[].compliance.requirements`. A profile running on your organization's campaign is asked for none of the campaign fields, so an empty `requirements` is how an inherited campaign reads. `api_key` is **never** returned here. It is shown once, by create. A profile belonging to another organization reads as `404`, not `403`: saying which of the two it is would confirm the id exists.
Update a sender profile PATCH
Changes a profile's own attributes: `name`, `short_name` and `description`. **Only those.** A profile's identity, its markets and its compliance each have a sub-resource of their own, because each has its own validation and its own external consequences — a brand change may need re-registering, a market change provisions a sender. Folding them in here would give those fields two writers and no way to say which one wins. Omitting a field leaves it alone; sending `null` clears it. Those are different requests, so `{}` changes nothing and is refused rather than silently accepted — a caller who misspelt every field would otherwise get a `200` and an unchanged profile. `name` and `short_name` cannot be cleared: `short_name` is the SMS sender id, so a supplied value must be 3–11 characters of letters, numbers and spaces with at least one letter. Returns the profile as `GET /v3/sender-profiles/{id}` would, so the result of the change needs no second call to see.