Registration API
Automate 10DLC brand, campaign, and number registration with the /v1/sms/10dlc endpoints.
Everything in the registration walkthrough is available programmatically. Base URL https://api.speko.dev, authenticated with your API key. All endpoints return 403 with code SMS_10DLC_DISABLED_FOR_ORG unless SMS is enabled for your workspace.
Overview
curl https://api.speko.dev/v1/sms/10dlc \
-H "Authorization: Bearer $SPEKO_API_KEY"Returns the full registration state:
{
"ownBrand": { "status": "verified", "identityStatus": "VERIFIED", "profile": { "..." : "..." }, "reviewErrors": [], "submittedAt": "2026-07-01T22:04:11.000Z" },
"ownCampaign": { "status": "active", "useCase": "ACCOUNT_NOTIFICATION", "reviewErrors": [], "submittedAt": "2026-07-02T18:30:00.000Z" },
"sharedCampaign": { "available": false, "label": null, "campaignId": null, "status": null },
"numbers": [
{ "id": "pn_...", "e164": "+15551234567", "smsEligible": true, "smsAssignmentStatus": "ASSIGNED", "assignedTo": "own", "...": "..." }
],
"smsReady": true,
"activeCampaignSource": "own"
}smsReady is true once at least one number is assigned to a currently approved campaign. Statuses are stored snapshots — they refresh when you resubmit the brand (safe) or perform a new action, not on their own.
Brand
Save the business profile, then submit it to the carriers:
curl -X PUT https://api.speko.dev/v1/sms/10dlc/brand \
-H "Authorization: Bearer $SPEKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"legalName": "MoonAI Inc",
"displayName": "MoonAI",
"entityType": "PRIVATE_PROFIT",
"vertical": "TECHNOLOGY",
"ein": "365142965",
"website": "https://example.com/",
"country": "US",
"street": "131 Continental Dr Suite 305",
"city": "Newark",
"state": "DE",
"postalCode": "19713",
"supportEmail": "support@example.com",
"supportPhone": "+15551234567",
"authorizedContact": {
"firstName": "Askhat", "lastName": "Adkhamov",
"email": "founder@example.com", "phone": "+15551234567"
}
}'
curl -X POST https://api.speko.dev/v1/sms/10dlc/brand/submit \
-H "Authorization: Bearer $SPEKO_API_KEY"All fields are required except ein (required in practice for every entity type except sole proprietors) and country (defaults to US). Phones are E.164. legalName must match the IRS record for the EIN.
Re-POSTing /brand/submit is the status refresh: it re-syncs your existing brand with the carriers and returns the current status / identityStatus. Brand is ready when status is "verified" (carrier status OK with identity VERIFIED or VETTED_VERIFIED).
Optional enhanced vetting after the brand is submitted:
curl -X POST https://api.speko.dev/v1/sms/10dlc/brand/vetting \
-H "Authorization: Bearer $SPEKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "vettingClass": "STANDARD" }'Campaign
Save the campaign profile, then submit — once:
curl -X PUT https://api.speko.dev/v1/sms/10dlc/campaign \
-H "Authorization: Bearer $SPEKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"useCase": "ACCOUNT_NOTIFICATION",
"description": "Appointment confirmations and reminders sent by our AI receptionist.",
"messageFlow": "Customers book an appointment by phone and verbally agree to receive a confirmation text and reminder texts for their visit.",
"optInKeywords": "START",
"optInMessage": "MoonAI: you are subscribed to appointment updates. Reply HELP for help, STOP to opt out.",
"helpKeywords": "HELP",
"helpMessage": "MoonAI: for help contact support@example.com. Reply STOP to opt out.",
"optOutKeywords": "STOP",
"optOutMessage": "MoonAI: you are unsubscribed and will receive no more messages.",
"sampleMessages": [
"MoonAI: your visit is confirmed for Jul 3 at 5:00 PM at 123 Main St. Reply STOP to opt out."
],
"embeddedLink": false,
"embeddedPhone": false
}'
curl -X POST https://api.speko.dev/v1/sms/10dlc/campaign/submit \
-H "Authorization: Bearer $SPEKO_API_KEY"sampleMessages accepts 1-5 entries. Campaign submit requires a verified brand (400 code BRAND_NOT_READY otherwise). Carrier review takes 1-7 business days; the campaign is ready when status is "active".
Do not re-POST /campaign/submit to poll status — unlike the brand endpoint, each campaign submission registers a new campaign with the carriers, with its own recurring carrier fees. Read status from GET /v1/sms/10dlc instead, and only resubmit after fixing an actual rejection.
Numbers
curl -X POST https://api.speko.dev/v1/sms/10dlc/numbers/{phoneNumberId}/assign \
-H "Authorization: Bearer $SPEKO_API_KEY"Assigns the number to your approved campaign (pass { "target": "shared" } to use Speko's shared campaign where available; with no body it prefers your own campaign, then shared). Phone number IDs come from GET /v1/phone-numbers or the numbers array in the overview. SIP-trunk imported numbers return 400 with code SMS_ASSIGNMENT_UNSUPPORTED_NUMBER.
smsAssignmentStatus values: PENDING_ASSIGNMENT, ASSIGNED, FAILED_ASSIGNMENT, PENDING_UNASSIGNMENT, FAILED_UNASSIGNMENT.
Error codes
| Code | Meaning |
|---|---|
SMS_10DLC_DISABLED_FOR_ORG | SMS is not enabled for the workspace — ask the Speko team |
PROFILE_REQUIRED | Save the brand/campaign draft before submitting |
BRAND_REQUIRED | Vetting requested before the brand was submitted |
BRAND_NOT_READY | Campaign submitted before the brand is verified |
PHONE_NUMBER_NOT_FOUND | The phone number id doesn't belong to this workspace |
OWN_CAMPAIGN_NOT_READY / SHARED_CAMPAIGN_UNAVAILABLE / NO_ELIGIBLE_CAMPAIGN | No approved campaign to assign the number to |
SMS_ASSIGNMENT_UNSUPPORTED_NUMBER | SIP-trunk numbers can't join a Speko 10DLC campaign |
TELNYX_BRAND_SUBMIT_FAILED / TELNYX_CAMPAIGN_SUBMIT_FAILED / TELNYX_NUMBER_ASSIGNMENT_FAILED / TELNYX_VETTING_FAILED | Carrier-side rejection or outage — check the details and retry |
VALIDATION_ERROR | Field validation failed — see the issues array |