Skip to content

Use this endpoint when a CRM or automation platform needs one simple webhook that sends one SMS and can safely retry after a timeout.

POST /api/v1/integrations/sms/send · Permission: sms.send

Send a strict JSON object with three fields and one stable idempotency key:

Terminal window
curl -X POST https://restlink23telecom.com/api/v1/integrations/sms/send \
-H "X-API-Key: $API_KEY" \
-H "Idempotency-Key: crm-event-20260718-001" \
-H "Content-Type: application/json" \
-d '{
"to": "+447700900123",
"message": "Your booking is confirmed",
"sender_id": "YourBrand"
}'
Value Rule
X-API-Key Required restricted key with sms.send. JWT is not accepted
Idempotency Send exactly one source: Idempotency-Key (generic clients, 1–128 characters using letters, numbers, _, -, ., :) or Customer.io’s automatic X-CIO-Idempotency-Key (1–1024 visible ASCII bytes). Surrounding whitespace is trimmed from Idempotency-Key
to One phone number of 6–15 digits with an optional leading +, for example +447700900123; a number without + is treated as the same number with +
message Non-empty, maximum 4096 UTF-8 bytes and 10 SMS segments
sender_id 3–11 ASCII letters/numbers; cannot start with a number

The full request body must be at most 32 KiB. Unknown fields, arrays and a second JSON value are rejected. Do not send X-Workspace-ID: the API key is already bound to the correct workspace.

202 Accepted
{
"status": "accepted",
"message_id": "api_7_1784394012896569000_7c6d09d704f37e4b",
"mode": "live"
}

Sandbox keys return the same shape with mode: "sandbox" and a test_... message ID. Sandbox acceptance is a simulation and never creates a live SMS.

accepted means 23 Telecom durably recorded the live request for delivery. It does not mean the handset received it. Use the returned message_id with delivery webhooks or message status.

Create one Idempotency-Key for one logical SMS. If the request times out or returns 5xx, retry the exact same body with the same key.

Customer.io supplies X-CIO-Idempotency-Key automatically instead. Do not add either idempotency header manually in Customer.io, and never send both sources. They use separate internal namespaces, so one source cannot accidentally replay the other.

  • A completed retry returns the same 202 body and message ID with Idempotent-Replayed: true.
  • The replay record is retained for 24 hours.
  • A key is scoped to the account, the workspace, this endpoint and the key type (live or sk_test_). The two headers are separate namespaces: the same value sent as Idempotency-Key and as X-CIO-Idempotency-Key is two different keys.
  • Requests are matched by a fingerprint of to, message and sender_id after leading and trailing whitespace is trimmed; a to without a leading + matches the same number with one. JSON formatting and field order do not change the fingerprint.
  • A different fingerprint with a key that is running or completed returns 409 IDEMPOTENCY_KEY_REUSED. With a live key, some failures after reservation normally keep it bound to the first fingerprint for 24 hours when the retryable state is saved (for example 403 NO_SMS_ACCESS, 422 DESTINATION_BLOCKED, 503 QUEUE_UNAVAILABLE, 500 DB_ERROR, and a 403 WORKSPACE_NOT_AVAILABLE returned while the SMS is being stored); retry those only with the same body. If saving that state fails, the original in-progress claim expires after 15 minutes. Authentication and request-validation errors occur before a key is reserved. Any 429, 403 INTEGRATION_*, 503 INTEGRATION_GATE_UNAVAILABLE and 403 WORKSPACE_NOT_AVAILABLE returned when access is checked normally leave the key unused. If releasing a reserved claim fails, it may remain in progress for up to 15 minutes.
  • A retry with the same fingerprint while the first request is still running returns 409 IDEMPOTENCY_REQUEST_IN_PROGRESS with Retry-After: 1; wait and retry.
  • The legacy X-Idempotency-Key is not accepted on this endpoint.

Every error body is {"status": false, "error_code": "...", "description": "..."}. For 5xx the description is generic and carries a reference; branch on error_code. No error response is a 202.

Status Code Meaning
400 INVALID_BODY The body could not be read, is not one JSON object with to, message and sender_id, has an unknown field, or is followed by a second JSON value
400 INVALID_TO to is missing, has characters other than digits and one leading +, or does not have 6–15 digits
400 INVALID_MESSAGE message is empty, over 4096 bytes or over 10 SMS segments
400 INVALID_SENDER_ID sender_id is not 3–11 ASCII letters or numbers, or starts with a number
400 IDEMPOTENCY_KEY_REQUIRED Neither idempotency header was sent, or Idempotency-Key is empty
400 MULTIPLE_IDEMPOTENCY_KEYS Both idempotency headers were sent, or one was repeated
400 INVALID_IDEMPOTENCY_KEY Idempotency-Key is over 128 characters or has other characters, or X-CIO-Idempotency-Key is not 1–1024 visible ASCII bytes
400 INVALID_IDEMPOTENCY_HEADER The legacy X-Idempotency-Key header was sent
401 API_KEY_REQUIRED No X-API-Key header; a portal session or JWT is not accepted
401 INVALID_API_KEY The API key is unknown, its stored permissions cannot be read, or its account could not be resolved
403 INTEGRATION_KEY_NOT_ELIGIBLE The key lacks the sms.send permission, or a live key is not a production key. A sandbox sk_test_ key with sms.send is not refused; it runs in sandbox mode
403 INTEGRATION_NOT_ENABLED Customer.io sending is not enabled for the account. Message: “Customer.io sending is not enabled yet for this account. Please ask 23 Telecom support to enable it.”
403 INTEGRATION_DISABLED_BY_SUPPORT Support has blocked Customer.io sending for this workspace. Message: “Customer.io sending is disabled for this workspace by 23 Telecom support.”
403 WORKSPACE_NOT_AVAILABLE The key’s workspace is not available for SMS sending; no SMS was accepted. If it is returned when access is checked, the key stays unused; if it is returned while the SMS is being stored, the key normally stays bound to this body for 24 hours if its retryable state is saved
403 NO_SMS_ACCESS The workspace’s SMS credentials could not be read when the SMS was prepared; no SMS was accepted. Retry with the same key and body; contact support if it persists
403 CONFIG_ERROR The workspace’s SMS credentials became incomplete after the access check; no SMS was accepted. Contact support
409 IDEMPOTENCY_KEY_REUSED The key was already used for a different send (see Safe retries). Use a new key for a new SMS
409 IDEMPOTENCY_REQUEST_IN_PROGRESS The first request with this key is still running. Wait Retry-After (1 second) and retry
413 REQUEST_TOO_LARGE The body is over 32 KiB
415 UNSUPPORTED_MEDIA_TYPE Content-Type is not application/json (only a charset=utf-8 parameter is allowed)
422 DESTINATION_BLOCKED The destination is blocked for this account; no SMS was accepted
429 RATE_LIMIT_EXCEEDED Live keys: the workspace integration limit (at most 100 per second, burst 1,000) was exceeded. Wait Retry-After seconds and retry with the same key and body
500 IDEMPOTENCY_ERROR The idempotency record could not be read or reserved; no SMS was accepted. Retry with the same key and body
500 DB_ERROR Storing the SMS failed or its commit could not be confirmed, so the SMS may or may not have been accepted. Retry with the same key and body: if it was accepted, the retry returns the original 202 with Idempotent-Replayed: true
500 INTERNAL_ERROR An unexpected error, including an accepted SMS whose 202 could not be returned, so the SMS may or may not have been accepted. Retry with the same key and body: if it was accepted, the retry returns the original 202
503 INTEGRATION_GATE_UNAVAILABLE Access, workspace credentials or the integration rate limit could not be verified; no SMS was accepted and the key stays unused. Retry later
503 IDEMPOTENCY_ERROR A refused attempt’s key could not be released; no SMS was accepted. For up to 15 minutes a retry with this key may return 409 IDEMPOTENCY_REQUEST_IN_PROGRESS
503 QUEUE_UNAVAILABLE The SMS queue is unavailable; no SMS was accepted. Retry later with the same key and body
503 SANDBOX_UNAVAILABLE Sandbox keys only: the sandbox could not process the request. Retry later

These come from layers shared with other API-key routes; see also Errors and Rate limits.

Status Code Meaning
400 AMBIGUOUS_AUTHENTICATION The request also carried a portal session cookie; send only X-API-Key
401 API_KEY_INACTIVE The key has been disabled
401 API_KEY_EXPIRED The key has expired
401 API_KEY_OWNER_INACTIVE The account that owns the key is inactive
401 CROSS_TENANT_WORKSPACE The key’s workspace has been deleted
401 SIGNATURE_REQUIRED The key requires HMAC signing and X-Signature or X-Timestamp is missing
401 INVALID_SIGNATURE Only one of X-Signature and X-Timestamp was sent, or the signature did not verify
403 API_KEY_CREATOR_INACTIVE The team member who created the key is no longer active in the account
403 SECTION_ACCESS_DENIED The team member who created the key may no longer send SMS; the body also has section
403 CORS_ORIGIN_NOT_ALLOWED A browser preflight (OPTIONS) came from an origin that is not allowed; server-side clients do not send preflights
405 METHOD_NOT_ALLOWED Any method other than POST or an OPTIONS preflight; the Allow header is POST
429 TOO_MANY_ATTEMPTS Too many unknown-key attempts from this IP address; wait Retry-After seconds
429 RATE_LIMIT_EXCEEDED The per-IP limit or the account-wide limit was exceeded; wait Retry-After seconds (X-RateLimit-Limit and X-RateLimit-Burst are set). A suspended account gets Retry-After: 60 and limit 0; contact support
500 INTERNAL_SERVER_ERROR An unexpected server fault; the SMS may or may not have been accepted. Retry later with the same key and body
503 API_KEY_VALIDATION_UNAVAILABLE The key could not be checked; retry after Retry-After: 1
503 AUTH_PROTECTION_UNAVAILABLE Protection against key guessing is unavailable, so an unrecognised key was refused; retry after Retry-After: 1
503 REPLAY_PROTECTION_UNAVAILABLE A signed request could not be checked for replay; retry after Retry-After: 1
503 SECTION_ACCESS_UNAVAILABLE The access of the team member who created the key could not be checked; retry later
503 RATE_LIMIT_UNAVAILABLE The account limiter is unavailable; retry later
  1. Complete the sandbox test with a dedicated sk_test_... key.
  2. Replace it with an active workspace-bound sk_prod_... key that has SMS send permission. An existing SMS-capable production key is eligible too.
  3. Keep the same URL and request body, then publish after the first 202.

If 23 Telecom has not yet enabled Customer.io sending for your account you receive INTEGRATION_NOT_ENABLED; if support has blocked the workspace you receive INTEGRATION_DISABLED_BY_SUPPORT; a key without SMS send permission, or a live key that is not a production key, receives INTEGRATION_KEY_NOT_ELIGIBLE. A sandbox sk_test_ key with SMS send permission is not refused: it runs in sandbox mode. None of these sends an SMS or charges your balance.