Create and activate sub-account profiles via the API
This guide shows you how to provision a sub-account Sender Profile entirely through the API: create the profile, run the asynchronous completion step, handle the webhook callback, and confirm the profile is ready to operate. It is written for platforms and resellers that onboard tenants programmatically; for the dashboard flow, see Creating a Sender Profile, for the underlying concepts, see Sender Profiles, and for choosing between shared and per-tenant architectures, see Multi-Tenant Architectures.
Before you start, you need:
- An organization account. Call
GET /v3/me: the responsetypemust beorganization. - An organization API key whose user has the
adminrole in the organization. See roles and permissions for how roles are granted. - A WhatsApp Business Account: either your organization has completed WhatsApp Embedded Signup, or you have direct credentials (
waba_id,phone_number_id,access_token) from a Meta Business Manager System User withwhatsapp_business_messagingandwhatsapp_business_managementpermissions.
All requests authenticate with the x-api-key header.
Provision and activate the profile
Choose inheritance, sharing, and billing settings
Decide these before creating the profile: they determine which resources the sub-account shares with your organization and who pays for its messaging.
Inheritance and sharing flags:
| Field | Default | Effect |
|---|---|---|
inherit_contacts | true | Profile reads the organization's contacts |
inherit_templates | true | Profile reads the organization's templates |
inherit_tcr_brand | true | Profile uses the organization's TCR brand registration |
inherit_tcr_campaign | true | Profile uses the organization's TCR campaign |
allow_contact_sharing | false | Profile's own contacts become visible to other profiles |
allow_template_sharing | false | Profile's own templates become visible to other profiles |
If your tenants must not see each other's data, set inherit_contacts and inherit_templates to false and leave the sharing flags off. If your tenants are brands that need their own US A2P registration, set inherit_tcr_brand and inherit_tcr_campaign to false and supply a brand object in the create request (next step). To run all tenants under your own registration, keep the defaults.
Billing model (billing_model, default profile):
| Value | Who is billed | Requirements |
|---|---|---|
organization | The organization's billing details are used | No profile-level billing info |
profile | The profile is billed independently | billing_contact required |
profile_and_organization | Profile billed first, organization as fallback | billing_contact required |
Use organization when your platform absorbs messaging costs and invoices tenants itself. Use profile when each tenant pays Sent directly. Use profile_and_organization when tenants pay directly but you guarantee their usage. With profile or profile_and_organization you may also pass payment_details (card number, MM/YY expiry, CVC, ZIP code); card details are never stored on Sent's servers and are forwarded directly to the payment processor. Passing payment_details with billing_model: "organization" is rejected.
Create the profile
Send POST /v3/profiles. Only name is required to create, but set short_name (3–11 characters: letters, numbers, and spaces, with at least one letter) and description now: the completion step in this guide rejects profiles that lack them.
For a tenant with its own TCR brand and direct billing:
curl -X POST "https://api.sent.dm/v3/profiles" \
-H "x-api-key: $ORG_API_KEY" \
-H "Idempotency-Key: create-profile-tenant-42" \
-H "Content-Type: application/json" \
-d '{
"name": "Tenant 42 Coffee Co",
"short_name": "T42COFFEE",
"description": "Sender profile for tenant 42",
"inherit_contacts": false,
"inherit_templates": false,
"inherit_tcr_campaign": false,
"billing_model": "profile",
"billing_contact": {
"name": "Tenant 42 Coffee Co",
"email": "billing@tenant42.example.com",
"phone": "+12025551234",
"address": "123 Main Street, New York, NY 10001, US"
},
"brand": {
"contact": {
"name": "Jane Doe",
"businessName": "Tenant 42 Coffee Co",
"email": "jane@tenant42.example.com"
},
"business": {
"legalName": "Tenant 42 Coffee Company LLC",
"country": "US"
},
"compliance": {
"vertical": "PROFESSIONAL",
"brandRelationship": "SMALL_ACCOUNT",
"isTcrApplication": true
}
}
}'Providing brand implicitly sets inherit_tcr_brand to false; it cannot be combined with inherit_tcr_brand: true. If you keep the organization's registration instead, omit brand and the inheritance flags, but read the callout in the next step first.
If the profile needs its own WhatsApp Business Account, add direct credentials to the request body:
"whatsapp_business_account": {
"waba_id": "123456789012345",
"phone_number_id": "987654321098765",
"access_token": "EAAxxxxxxxxxxxxxxx"
}Omit the field to inherit the organization's WhatsApp Business Account instead. If you omit it and the organization has not completed WhatsApp Embedded Signup, the request fails with HTTP 422.
A successful request returns 201 with the profile in data, including its id (every later call needs it) and "status": "incomplete". The Idempotency-Key header makes retries safe; responses are cached for 24 hours per key. Refer to the create profile reference for the full field list.
Start completion with a webhook URL
Completion validates the profile, connects it to the SMS and WhatsApp providers, and sets its final status. It runs in the background, so the request requires a webHookUrl that Sent calls when processing finishes, on success or failure:
curl -X POST "https://api.sent.dm/v3/profiles/$PROFILE_ID/complete" \
-H "x-api-key: $ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webHookUrl": "https://your-app.example.com/webhooks/profile-complete"
}'A 202 response ("Profile completion in progress") means validation passed and processing started. A 200 means the profile was already completed; the body contains its current status. A 400 means a prerequisite failed: the profile needs name, short_name, and description set, its own KYC submission, an available TCR brand (its own or the organization's), and, for TCR applications, at least one campaign, either inherited or created with POST /v3/profiles/{profileId}/campaigns.
Completion requires the profile itself to have brand (KYC) information on file, even when inherit_tcr_brand is true. When provisioning entirely through the API, include the brand object in the create request. A profile that inherits the TCR brand cannot receive a brand object through the API, so its KYC information must be submitted from the dashboard before completion succeeds.
Handle the completion callback
When the background process finishes, Sent sends a POST request with a JSON body to your webHookUrl:
{
"profileId": "770e8400-e29b-41d4-a716-446655440002",
"success": true,
"status": "COMPLETED",
"timestamp": "2026-07-25T14:03:11.402Z"
}| Field | Meaning |
|---|---|
profileId | UUID of the profile that finished processing |
success | true if provisioning finished, false if it failed |
status | COMPLETED or SUBMITTED on success; failed on failure |
timestamp | When the callback was generated (UTC) |
COMPLETED means the profile is fully activated. SUBMITTED means processing finished but the profile is not yet sendable. The profile lands there when either the SMS or WhatsApp channel configuration is missing, when a dedicated (non-inherited) TCR brand or campaign has not yet been submitted to TCR, or when a non-TCR profile declares a main destination country, which requires more information before activation.
A minimal receiver:
app.post("/webhooks/profile-complete", (req, res) => {
res.sendStatus(200);
const { profileId, success, status } = req.body;
if (success) {
// status is "COMPLETED" (ready) or "SUBMITTED" (pending registration/config)
markTenantProvisioned(profileId, status);
} else {
// status is "failed", retry the complete call or investigate
flagTenantProvisioningFailure(profileId);
}
});Sent calls the webhook once and does not retry. If your endpoint misses the callback, poll the profile status (next step) instead.
Verify the profile
Confirm the outcome with GET /v3/profiles/{profileId}:
curl "https://api.sent.dm/v3/profiles/$PROFILE_ID" \
-H "x-api-key: $ORG_API_KEY"The status field reports the public setup state: approved corresponds to the COMPLETED webhook status, submitted to SUBMITTED, processing means completion is still running, and failed means it did not finish. To see the profile in context, call GET /v3/me with your organization key: the profiles array lists every child profile with its status and the calling user's role in it.
Operate on the child profile
To call any /v3 endpoint as the new profile, add the x-profile-id header to a request made with your organization API key:
curl -X POST "https://api.sent.dm/v3/messages" \
-H "x-api-key: $ORG_API_KEY" \
-H "x-profile-id: $PROFILE_ID" \
-H "Content-Type: application/json" \
-d '{
"to": ["+15551234567"],
"template": {"id": "tmpl_123"}
}'The request executes as the profile (its templates, contacts, numbers, and settings apply) and the response echoes the scope in an X-Profile-Id header. Only organization API keys can use x-profile-id; profile-scoped keys are rejected with 403, and a profile ID outside your organization returns 404. If you prefer to hand tenants their own credentials instead of proxying through your organization key, each profile also has its own API key. See API credentials and isolation.
Reuse sender numbers across profiles
If a new profile should send from a number already provisioned on another profile, reference that profile instead of provisioning a new number. Update the profile with PATCH /v3/profiles/{profileId}:
sending_phone_number_profile_id: use another profile's SMS number and provider configuration. Completion then copies that configuration instead of provisioning a new SMS setup.sending_whatsapp_number_profile_id: use another profile's WhatsApp number configuration.
curl -X PATCH "https://api.sent.dm/v3/profiles/$PROFILE_ID" \
-H "x-api-key: $ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sending_phone_number_profile_id": "660e8400-e29b-41d4-a716-446655440000"
}'When a profile inherits the organization's WhatsApp Business Account at creation, sending_whatsapp_number_profile_id is set to the organization automatically. Set the referenced profile IDs before calling the completion endpoint so the copied configuration counts toward the channel checks that decide COMPLETED versus SUBMITTED. Refer to the update profile reference for all updatable fields, including allow_number_change_during_onboarding.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
403 "You do not have admin access to this organization" on create | Your API key's user lacks the admin role | Ask the organization owner to grant the admin role (see roles and permissions) |
422 "Organization does not have a WhatsApp Business Account configured" | whatsapp_business_account omitted and no Embedded Signup completed | Complete Embedded Signup for the organization, or pass direct WABA credentials |
400 "Profile ShortName is required" or "Profile Description is required" on complete | Profile was created with only name | PATCH the missing fields, then retry |
400 "KYC form submission is required before completing profile" | The profile has no brand (KYC) submission of its own | Include brand in the create request, or submit KYC from the dashboard for inheriting profiles |
400 "TCR applications must have at least one campaign before completing profile" | Dedicated TCR registration without a campaign | Create a campaign for the profile's brand, or set inherit_tcr_campaign: true |
400 "Missing required compliance documents for the following countries: …" | A main destination country requires documents | Upload the listed documents in the dashboard, then retry |
403 "Profile API keys cannot use x-profile-id" | Scoping header sent with a profile-scoped key | Use an organization API key, or drop the header |
Webhook arrives with success: false | Background provisioning failed | Retry the complete call; if it keeps failing, contact support with the X-Request-Id response header |
Related pages
- Sender Profiles - the inheritance model
- Creating a Sender Profile - the dashboard flow
- Multi-Tenant Architectures - choosing shared vs. per-tenant setups
- Refer to the create, complete, and update profile references for the full list of fields and responses
- 10DLC Registration - registering campaigns for US SMS
Multi-tenant architectures on Sent: profiles vs. accounts
Choose a tenant model for your messaging platform: Sender Profiles with per-tenant provisioning, one shared Sent account, or separate accounts per tenant.
Help & Reference Overview
Quick answers when you need help with Sent: frequently asked questions, problem-solution troubleshooting guides, error code references, and support channels.