{"openapi":"3.1.1","info":{"title":"SMS21 API","version":"1.0.0","summary":"OTP, SMS and WhatsApp messaging API","description":"The SMS21 API sends one-time passwords, SMS and WhatsApp messages for your applications and reports their delivery. Your apps call SMS21; SMS21 handles providers, failover, DLT compliance and billing.\n\n## Authentication\n\nEvery request needs a project API key, sent **from your server only** — never from a browser, mobile app or public repository:\n\n    X-API-Key: sms21_live_…\n    Authorization: Bearer sms21_live_…    (equivalent)\n\nA key belongs to one project and holds only the scopes chosen when it was created (`otp:send`, `otp:verify`, `sms:send`, `whatsapp:send`, `reports:read`). Keys can be limited to IP addresses and can expire. The secret is shown once; SMS21 stores only a hash.\n\n## Test mode\n\nKeys starting with `sms21_test_` use the sandbox. Nothing reaches a handset or WhatsApp, nothing is charged, DLT templates are not required, and every OTP code is `123456`. Sandbox messages are reported delivered unless the number ends in:\n\n| Ending | Sandbox result |\n|---|---|\n| `0001` | rejected |\n| `0002` | provider temporarily unavailable |\n| `0003` | provider timeout (outcome uncertain, never resent) |\n| `0004` | submitted but not delivered |\n\nKeys starting with `sms21_live_` send real messages and are charged to your wallet. Use separate environment variables for live and test keys.\n\n## Idempotency and retries\n\nSend endpoints accept an `Idempotency-Key` header. Repeating a request with the same key and body returns the original result with `Idempotent-Replayed: true` — nothing is sent or charged twice. Retry network errors, `5xx` and `429 RATE_LIMITED` **with the same key**, honouring `Retry-After`. Do not retry other `4xx` responses unchanged. Never retry `POST /otp/verify` automatically.\n\n## Errors\n\n    { \"success\": false,\n      \"error\": { \"code\": \"VALIDATION_FAILED\", \"message\": \"Request validation failed\",\n                 \"details\": [{ \"field\": \"mobile\", \"message\": \"…\" }] },\n      \"requestId\": \"…\" }\n\nBranch on `error.code`, not on `message`. Unknown request fields are rejected. Every response has an `X-Request-Id` header.\n\n## Rate limits\n\nLimits apply per API key (600 requests per minute by default), per project and per client IP. Responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; `429` responses carry `Retry-After`. OTPs also have per-number, per-IP and per-project limits and a resend cooldown.\n\n## Pagination\n\nLists return `{ success, data, pagination }`. Pass `pagination.nextCursor` as `cursor` until `hasMore` is false. `limit` is 1–100 (default 20).\n\n## Versioning\n\nBreaking changes get a new path prefix (`/v2`). Within `/v1`, SMS21 may add endpoints, optional request fields, response fields, enum values, event types and error codes: ignore what you do not recognise.","contact":{"name":"SMS21 support","url":"https://sms21.in/docs"}},"externalDocs":{"description":"Guides, SDKs and examples","url":"https://sms21.in/docs"},"servers":[{"url":"https://sms21.in/v1","description":"Live and test traffic share this URL; the API key decides."}],"tags":[{"name":"OTP","description":"Send and verify one-time passwords."},{"name":"SMS","description":"Transactional SMS with DLT templates."},{"name":"Bulk","description":"Bulk campaigns and the opt-out list."},{"name":"Contacts","description":"Saved audiences a campaign can be sent to."},{"name":"Inbound","description":"Replies to your numbers, and opt-out keywords."},{"name":"WhatsApp","description":"WhatsApp Business Platform messages."},{"name":"Messages","description":"Delivery status and message reports."},{"name":"Account","description":"API key and wallet."},{"name":"Webhooks","description":"Signed events SMS21 sends to your endpoints."}],"paths":{"/otp/send":{"post":{"operationId":"sendOtp","tags":["OTP"],"summary":"Send an OTP","description":"Generates a one-time password and queues its delivery by SMS (default) or WhatsApp. The code is **never returned** and never stored in plain text.\n\n- A new OTP to the same number and purpose supersedes the previous one, and is subject to a resend cooldown (`resendAfter`) and per-number, per-IP and per-project limits.\n- `channel: \"whatsapp\"` uses the project WhatsApp authentication template; add `fallbackChannel: \"sms\"` to send by SMS when WhatsApp definitely fails.\n- Live OTPs to India need a DLT-approved SMS template with an `{{otp}}` variable (`templateId`, or the project default OTP template).\n- **Test keys:** nothing is sent and the code is always `123456`.","security":[{"ApiKeyHeader":["otp:send"]},{"BearerAuth":["otp:send"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendOtpRequest"},"examples":{"sms":{"summary":"SMS","value":{"mobile":"919876543210","purpose":"login"}},"whatsapp":{"summary":"WhatsApp, SMS fallback","value":{"mobile":"919876543210","purpose":"login","channel":"whatsapp","fallbackChannel":"sms","ip":"203.0.113.24"}}}}}},"responses":{"202":{"description":"Accepted: the code was generated and its delivery queued.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpSendResponse"},"example":{"success":true,"requestId":"otp_01k55q8y4m3d9v7x2c6b0n1r8t","status":"pending","channel":"sms","purpose":"login","expiresIn":300,"expiresAt":"2026-09-15T10:35:00.000Z","resendAfter":30,"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t"}}}},"400":{"description":"Invalid request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Request validation failed","details":[{"field":"purpose","message":"purpose must be one of the following values: login, registration, password_reset, transaction, mobile_verification"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The recipient is not a valid mobile number","details":[{"field":"mobile","message":"mobile must be a mobile number in international format, e.g. 919876543210"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"country":{"summary":"DESTINATION_NOT_ALLOWED","value":{"success":false,"error":{"code":"DESTINATION_NOT_ALLOWED","message":"OTPs cannot be sent to this country","details":[{"field":"mobile","message":"destination country is not enabled for OTP"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"template":{"summary":"TEMPLATE_REQUIRED","value":{"success":false,"error":{"code":"TEMPLATE_REQUIRED","message":"Live OTP to IN must use an approved DLT template","details":[{"field":"templateId","message":"is required for this destination"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The template does not exist in this company.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"template":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Template not found","details":[{"field":"templateId","message":"no such template"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"409":{"description":"Conflicting state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency":{"summary":"IDEMPOTENCY_KEY_REUSED","value":{"success":false,"error":{"code":"IDEMPOTENCY_KEY_REUSED","message":"This Idempotency-Key was already used with a different request"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"notApproved":{"summary":"TEMPLATE_NOT_APPROVED","value":{"success":false,"error":{"code":"TEMPLATE_NOT_APPROVED","message":"The template is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"pricing":{"summary":"PRICING_NOT_CONFIGURED","value":{"success":false,"error":{"code":"PRICING_NOT_CONFIGURED","message":"No price is configured for this message"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"description":"Too many OTPs or requests. Honour `Retry-After`; do not loop.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"cooldown":{"summary":"OTP_RESEND_COOLDOWN","value":{"success":false,"error":{"code":"OTP_RESEND_COOLDOWN","message":"Please wait 24 seconds before requesting another OTP"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"rateLimited":{"summary":"RATE_LIMITED","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Too many OTPs for this number"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"daily":{"summary":"DAILY_LIMIT_EXCEEDED","value":{"success":false,"error":{"code":"DAILY_LIMIT_EXCEEDED","message":"The daily live message limit is reached"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/otp/verify":{"post":{"operationId":"verifyOtp","tags":["OTP"],"summary":"Verify an OTP","description":"Checks the code a user entered. Succeeds **exactly once** per OTP request.\n\n- The request is locked after too many wrong codes (5 by default), expires after `expiresIn`, and is superseded by a newer OTP to the same number and purpose.\n- Pass `mobile` to bind verification to the number you expect; a mismatch counts as a wrong code.\n- Do not retry this call automatically after a timeout: a second call after a success returns `409 OTP_ALREADY_VERIFIED`, and each wrong attempt counts. Ask the user to request a new code instead.\n- OTPs sent with a test key can only be verified with a test key.","security":[{"ApiKeyHeader":["otp:verify"]},{"BearerAuth":["otp:verify"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyOtpRequest"},"examples":{"verify":{"summary":"Verify","value":{"requestId":"otp_01k55q8y4m3d9v7x2c6b0n1r8t","otp":"123456","mobile":"919876543210"}}}}}},"responses":{"200":{"description":"Verified.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpVerificationResponse"},"example":{"success":true,"requestId":"otp_01k55q8y4m3d9v7x2c6b0n1r8t","verified":true,"mobile":"919876543210","purpose":"login","verifiedAt":"2026-09-15T10:30:00.000Z"}}}},"400":{"description":"Wrong code, or invalid request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"wrongCode":{"summary":"OTP_INVALID","value":{"success":false,"error":{"code":"OTP_INVALID","message":"The OTP is incorrect","details":[{"field":"otp","message":"4 attempts remaining"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"validation":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Request validation failed","details":[{"field":"otp","message":"otp must be 4-10 digits"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No such OTP request for this project and environment.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"notFound":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"OTP request not found"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"409":{"description":"Already verified.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"verified":{"summary":"OTP_ALREADY_VERIFIED","value":{"success":false,"error":{"code":"OTP_ALREADY_VERIFIED","message":"This OTP has already been verified"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"410":{"description":"Expired, or superseded by a newer OTP.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"expired":{"summary":"OTP_EXPIRED","value":{"success":false,"error":{"code":"OTP_EXPIRED","message":"The OTP has expired"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"superseded":{"summary":"OTP_EXPIRED","value":{"success":false,"error":{"code":"OTP_EXPIRED","message":"A newer OTP was sent to this number; use the latest code"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"description":"Locked after too many wrong codes (final), or rate limited.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"locked":{"summary":"OTP_ATTEMPTS_EXCEEDED","value":{"success":false,"error":{"code":"OTP_ATTEMPTS_EXCEEDED","message":"Too many incorrect attempts"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"rateLimited":{"summary":"RATE_LIMITED","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded for this API key"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/sms/send":{"post":{"operationId":"sendSms","tags":["SMS"],"summary":"Send an SMS","description":"Queues one SMS (202 Accepted). Follow its status with webhooks or `GET /messages/{messageId}`.\n\nSend exactly one of:\n- `templateId` + `variables`: an approved DLT template. **Required for live SMS to DLT countries (India).** The sender ID comes from the template.\n- `text` (optionally `senderId`): free text, for test keys and destinations without DLT rules.\n\nA single SMS holds 160 GSM-7 or 70 Unicode (UCS-2) characters; longer texts are split into segments (153 / 67 characters each) and billed per segment, up to 10 segments.","security":[{"ApiKeyHeader":["sms:send"]},{"BearerAuth":["sms:send"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSmsRequest"},"examples":{"template":{"summary":"DLT template","value":{"to":"919876543210","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","variables":{"order":"1042"}}},"text":{"summary":"Free text (test key or non-DLT country)","value":{"to":"447911123456","text":"Your order 1042 has shipped.","senderId":"ACMESHOP"}}}}}},"responses":{"202":{"description":"Accepted and queued.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"success":true,"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"queued","channel":"sms","type":"transactional","environment":"live","to":"919876543210","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","whatsappTemplateId":null,"segments":1,"encoding":"gsm7","failure":null,"queuedAt":"2026-09-15T10:30:00.000Z","submittedAt":null,"sentAt":null,"deliveredAt":null,"failedAt":null}}}},"400":{"description":"Invalid request or content.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The recipient is not a valid mobile number","details":[{"field":"to","message":"to must be a mobile number in international format, e.g. 919876543210"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"templateRequired":{"summary":"TEMPLATE_REQUIRED","value":{"success":false,"error":{"code":"TEMPLATE_REQUIRED","message":"Live SMS to IN must use an approved DLT template","details":[{"field":"templateId","message":"is required for this destination; free text is not allowed"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"variables":{"summary":"TEMPLATE_VARIABLES_INVALID","value":{"success":false,"error":{"code":"TEMPLATE_VARIABLES_INVALID","message":"Template variables are invalid","details":[{"field":"variables.order","message":"is required"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"tooLong":{"summary":"MESSAGE_TOO_LONG","value":{"success":false,"error":{"code":"MESSAGE_TOO_LONG","message":"The message needs 11 SMS segments; the maximum is 10","details":[{"field":"text","message":"exceeds 10 segments (GSM7 encoding)"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The template does not exist in this company.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"template":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Template not found","details":[{"field":"templateId","message":"no such template"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"409":{"description":"Conflicting state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency":{"summary":"IDEMPOTENCY_KEY_REUSED","value":{"success":false,"error":{"code":"IDEMPOTENCY_KEY_REUSED","message":"This Idempotency-Key was already used with a different request"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"template":{"summary":"TEMPLATE_NOT_APPROVED","value":{"success":false,"error":{"code":"TEMPLATE_NOT_APPROVED","message":"The template is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"entity":{"summary":"DLT_ENTITY_NOT_APPROVED","value":{"success":false,"error":{"code":"DLT_ENTITY_NOT_APPROVED","message":"The DLT entity is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"sender":{"summary":"SENDER_ID_NOT_APPROVED","value":{"success":false,"error":{"code":"SENDER_ID_NOT_APPROVED","message":"The sender ID is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/whatsapp/send":{"post":{"operationId":"sendWhatsAppText","tags":["WhatsApp"],"summary":"Send a WhatsApp text","description":"Free-form (session) text through the WhatsApp Business Platform. WhatsApp only delivers it within 24 hours of the recipient's last message to you; outside that window the message is rejected — use a template.","security":[{"ApiKeyHeader":["whatsapp:send"]},{"BearerAuth":["whatsapp:send"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendWhatsAppTextRequest"},"examples":{"text":{"summary":"Session text","value":{"to":"919876543210","text":"Your table is ready."}}}}}},"responses":{"202":{"description":"Accepted and queued.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"success":true,"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"queued","channel":"whatsapp","type":"transactional","environment":"live","to":"919876543210","templateId":null,"whatsappTemplateId":null,"segments":1,"encoding":"gsm7","failure":null,"queuedAt":"2026-09-15T10:30:00.000Z","submittedAt":null,"sentAt":null,"deliveredAt":null,"failedAt":null}}}},"400":{"description":"Invalid request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The recipient is not a valid mobile number","details":[{"field":"to","message":"to must be a mobile number in international format, e.g. 919876543210"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Conflicting state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency":{"summary":"IDEMPOTENCY_KEY_REUSED","value":{"success":false,"error":{"code":"IDEMPOTENCY_KEY_REUSED","message":"This Idempotency-Key was already used with a different request"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/whatsapp/template":{"post":{"operationId":"sendWhatsAppTemplate","tags":["WhatsApp"],"summary":"Send a WhatsApp template","description":"Approved utility or marketing template, registered in SMS21 exactly as Meta approved it. OTPs use `POST /otp/send` with `channel: \"whatsapp\"`.","security":[{"ApiKeyHeader":["whatsapp:send"]},{"BearerAuth":["whatsapp:send"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendWhatsAppTemplateRequest"},"examples":{"template":{"summary":"Utility template","value":{"to":"919876543210","templateId":"wtpl_01k55q8y4m3d9v7x2c6b0n1r8t","variables":{"name":"Asha","order":"1042"}}}}}}},"responses":{"202":{"description":"Accepted and queued.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"success":true,"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"queued","channel":"whatsapp","type":"transactional","environment":"live","to":"919876543210","templateId":null,"whatsappTemplateId":"wtpl_01k55q8y4m3d9v7x2c6b0n1r8t","segments":1,"encoding":"gsm7","failure":null,"queuedAt":"2026-09-15T10:30:00.000Z","submittedAt":null,"sentAt":null,"deliveredAt":null,"failedAt":null}}}},"400":{"description":"Invalid request.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The recipient is not a valid mobile number","details":[{"field":"to","message":"to must be a mobile number in international format, e.g. 919876543210"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"authentication":{"summary":"WHATSAPP_TEMPLATE_INVALID","value":{"success":false,"error":{"code":"WHATSAPP_TEMPLATE_INVALID","message":"Authentication templates are sent with POST /v1/otp/send","details":[{"field":"templateId","message":"use the OTP API for one-time passwords"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The template does not exist in this company.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"template":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Template not found","details":[{"field":"templateId","message":"no such template"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"409":{"description":"Conflicting state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency":{"summary":"IDEMPOTENCY_KEY_REUSED","value":{"success":false,"error":{"code":"IDEMPOTENCY_KEY_REUSED","message":"This Idempotency-Key was already used with a different request"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"template":{"summary":"TEMPLATE_NOT_APPROVED","value":{"success":false,"error":{"code":"TEMPLATE_NOT_APPROVED","message":"The template is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/messages":{"get":{"operationId":"listMessages","tags":["Messages"],"summary":"List messages","description":"Messages of the API key project, newest first. Filters combine with AND. Unknown query parameters are rejected.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"status","in":"query","required":false,"description":"Only this status.","schema":{"type":"string","enum":["queued","processing","submitted","sent","delivered","failed","rejected","expired"]}},{"name":"type","in":"query","required":false,"description":"Only this type.","schema":{"type":"string","enum":["otp","transactional","promotional"]}},{"name":"channel","in":"query","required":false,"description":"Only this channel.","schema":{"type":"string","enum":["sms","whatsapp"]}},{"name":"mobile","in":"query","required":false,"description":"Recipient in international format.","schema":{"type":"string","minLength":8,"maxLength":16}},{"name":"templateId","in":"query","required":false,"description":"SMS template (`tmpl_…`).","schema":{"type":"string","minLength":4,"maxLength":40}},{"name":"createdFrom","in":"query","required":false,"description":"Created at or after (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"name":"createdTo","in":"query","required":false,"description":"Created at or before (ISO 8601).","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"A page of messages.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageListResponse"},"example":{"success":true,"data":[{"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"delivered","channel":"sms","type":"transactional","environment":"live","to":"919876543210","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","whatsappTemplateId":null,"segments":1,"encoding":"gsm7","failure":null,"queuedAt":"2026-09-15T10:30:00.000Z","submittedAt":"2026-09-15T10:30:00.000Z","sentAt":"2026-09-15T10:30:00.000Z","deliveredAt":"2026-09-15T10:30:04.000Z","failedAt":null}],"pagination":{"limit":20,"hasMore":true,"nextCursor":"eyJpZCI6IjAxOTk0OTFjIn0"}}}}},"400":{"description":"Invalid filter.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"filter":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Invalid filter","details":[{"field":"createdFrom","message":"must not be after createdTo"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/messages/{messageId}":{"get":{"operationId":"getMessage","tags":["Messages"],"summary":"Get a message","description":"Current status of one message of this project. Prefer webhooks; if you poll, back off (for example 5 s, 30 s, 2 min) and stop at a final status (`delivered`, `failed`, `rejected`, `expired`).","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"$ref":"#/components/parameters/MessageId"}],"responses":{"200":{"description":"The message.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"success":true,"messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"failed","channel":"sms","type":"transactional","environment":"live","to":"919876543210","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","whatsappTemplateId":null,"segments":1,"encoding":"gsm7","failure":{"code":"DELIVERY_FAILED"},"queuedAt":"2026-09-15T10:30:00.000Z","submittedAt":"2026-09-15T10:30:00.000Z","sentAt":null,"deliveredAt":null,"failedAt":"2026-09-15T10:31:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/api-keys/current":{"get":{"operationId":"getCurrentApiKey","tags":["Account"],"summary":"Inspect the API key","description":"Returns the environment, scopes and project of the key making the request. Needs no scope; useful as a startup check.","security":[{"ApiKeyHeader":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"The key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyResponse"},"example":{"success":true,"id":"key_01k55q8y4m3d9v7x2c6b0n1r8t","environment":"test","scopes":["otp:send","otp:verify","reports:read"],"projectId":"prj_01k55q8y4m3d9v7x2c6b0n1r8t","companyId":"cmp_01k55q8y4m3d9v7x2c6b0n1r8t"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/wallet":{"get":{"operationId":"getWallet","tags":["Account"],"summary":"Get the wallet","description":"Balance of the company wallet that live messages are charged to.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"responses":{"200":{"description":"The wallet.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletResponse"},"example":{"success":true,"currency":"INR","status":"active","balance":"1250.0000","reserved":"3.5000","available":"1246.5000","creditLimit":"0.0000","spendable":"1246.5000","lowBalanceThreshold":"500.0000","lowBalance":false,"updatedAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The company has no wallet.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"notFound":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Wallet not found"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts/lists":{"post":{"operationId":"createContactList","tags":["Contacts"],"summary":"Create a contact list","description":"A saved audience. Send a campaign to it with `listId` instead of listing recipients.","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactListRequest"},"examples":{"list":{"summary":"A new list","value":{"name":"October offers"}}}}}},"responses":{"201":{"description":"The list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"},"example":{"success":true,"id":"lst_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","description":null,"contactCount":2,"createdAt":"2026-09-15T10:30:00.000Z","updatedAt":"2026-09-15T10:30:00.000Z"}}}},"400":{"description":"The request is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"name":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Request validation failed","details":[{"field":"name","message":"must be 1 to 120 characters"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The name is taken, or the company has too many lists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"duplicate":{"summary":"CONFLICT","value":{"success":false,"error":{"code":"CONFLICT","message":"A contact list with this name already exists"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"limit":{"summary":"CONTACTS_LIMIT_REACHED","value":{"success":false,"error":{"code":"CONTACTS_LIMIT_REACHED","message":"A company may keep at most 200 contact lists"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"operationId":"listContactLists","tags":["Contacts"],"summary":"List contact lists","description":"Lists of the key’s company, newest first.","security":[{"ApiKeyHeader":["contacts:read"]},{"BearerAuth":["contacts:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Contact lists.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListsResponse"},"example":{"success":true,"data":[{"id":"lst_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","description":null,"contactCount":2,"createdAt":"2026-09-15T10:30:00.000Z","updatedAt":"2026-09-15T10:30:00.000Z"}],"pagination":{"limit":25,"hasMore":false,"nextCursor":null}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/contacts/lists/{listId}":{"get":{"operationId":"getContactList","tags":["Contacts"],"summary":"Get a contact list","security":[{"ApiKeyHeader":["contacts:read"]},{"BearerAuth":["contacts:read"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"},"example":{"success":true,"id":"lst_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","description":null,"contactCount":2,"createdAt":"2026-09-15T10:30:00.000Z","updatedAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateContactList","tags":["Contacts"],"summary":"Rename a contact list","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactListRequest"},"examples":{"rename":{"summary":"Rename","value":{"name":"October offers (India)"}}}}}},"responses":{"200":{"description":"The list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"},"example":{"success":true,"id":"lst_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","description":null,"contactCount":2,"createdAt":"2026-09-15T10:30:00.000Z","updatedAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The name is taken.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"duplicate":{"summary":"CONFLICT","value":{"success":false,"error":{"code":"CONFLICT","message":"A contact list with this name already exists"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}}}},"delete":{"operationId":"deleteContactList","tags":["Contacts"],"summary":"Delete a contact list","description":"Deletes the list, its contacts and its import history. Campaigns already created are untouched.","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/lists/{listId}/import":{"post":{"operationId":"importContacts","tags":["Contacts"],"summary":"Import contacts from CSV","description":"Uploads the file as the request body with `Content-Type: text/csv`.\n\nThe first row names the columns. One of them must be the number (`to`, `mobile`, `phone`, `number` or `msisdn`); every other column becomes a template variable available as `{{name}}`.\n\nRows that cannot be used are reported with their line numbers and the rest are imported, so a file with three bad numbers still loads the other 4,997. Duplicate numbers within the file are collapsed, keeping the first.\n\nRe-importing a number updates its attributes but never its status: someone who unsubscribed stays unsubscribed.","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"merge (default) updates and adds; replace empties the list first.","schema":{"type":"string","enum":["merge","replace"]}},{"name":"fileName","in":"query","required":false,"description":"Recorded with the import so uploads can be told apart.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"text/csv":{"schema":{"type":"string"},"example":"mobile,name,city\n919876543210,Asha,Mumbai\n919812345678,Ravi,Pune\n"}}},"responses":{"201":{"description":"What the upload did.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactImportResponse"},"example":{"success":true,"id":"imp_01k55q8y4m3d9v7x2c6b0n1r8t","fileName":"october.csv","totalRows":4,"imported":2,"updated":0,"invalid":1,"duplicates":1,"errors":[{"line":3,"message":"\"not-a-number\" is not a mobile number in international format"}],"createdAt":"2026-09-15T10:30:00.000Z"}}}},"400":{"description":"The file cannot be read, or carries too many rows.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"format":{"summary":"CSV_INVALID","value":{"success":false,"error":{"code":"CSV_INVALID","message":"No number column found; name one of them to, mobile, phone, number, msisdn"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"tooMany":{"summary":"CONTACTS_LIMIT_REACHED","value":{"success":false,"error":{"code":"CONTACTS_LIMIT_REACHED","message":"One upload may carry at most 10000 rows; split the file"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The list would grow beyond its limit.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"limit":{"summary":"CONTACTS_LIMIT_REACHED","value":{"success":false,"error":{"code":"CONTACTS_LIMIT_REACHED","message":"A list may hold at most 50000 contacts"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}}}}},"/contacts/lists/{listId}/imports":{"get":{"operationId":"listContactImports","tags":["Contacts"],"summary":"List imports","description":"What each upload did, newest first.","security":[{"ApiKeyHeader":["contacts:read"]},{"BearerAuth":["contacts:read"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Imports.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactImportsResponse"},"example":{"success":true,"data":[],"pagination":{"limit":25,"hasMore":false,"nextCursor":null}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/lists/{listId}/contacts":{"get":{"operationId":"listContacts","tags":["Contacts"],"summary":"List contacts","security":[{"ApiKeyHeader":["contacts:read"]},{"BearerAuth":["contacts:read"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Only contacts in this state.","schema":{"type":"string","enum":["active","unsubscribed"]}},{"name":"mobile","in":"query","required":false,"description":"Exact number lookup.","schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Contacts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactsResponse"},"example":{"success":true,"data":[],"pagination":{"limit":25,"hasMore":false,"nextCursor":null}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/lists/{listId}/contacts/{contactId}":{"delete":{"operationId":"deleteContact","tags":["Contacts"],"summary":"Remove a contact","description":"Deletes the row. To keep the record but stop sending, use unsubscribe.","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}},{"name":"contactId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/lists/{listId}/contacts/{contactId}/unsubscribe":{"post":{"operationId":"unsubscribeContact","tags":["Contacts"],"summary":"Unsubscribe a contact","description":"Takes the contact out of campaigns built from this list, keeping the record. Re-importing the number does not undo it.","security":[{"ApiKeyHeader":["contacts:manage"]},{"BearerAuth":["contacts:manage"]}],"parameters":[{"name":"listId","in":"path","required":true,"schema":{"type":"string"}},{"name":"contactId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The contact.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"},"example":{"success":true,"id":"cnt_01k55q8y4m3d9v7x2c6b0n1r8t","to":"919876543210","attributes":{"name":"Asha"},"status":"unsubscribed","createdAt":"2026-09-15T10:30:00.000Z","updatedAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/sms/bulk":{"post":{"operationId":"createBatch","tags":["Bulk"],"summary":"Create a bulk campaign","description":"Queues a campaign (202 Accepted). SMS21 creates the individual messages in the background, so the response carries counts, not messages.\n\nSend exactly one of:\n- `templateId` + per-recipient `variables`: an approved template. **Required for live SMS to DLT countries (India).** Promotional templates are allowed here (and only here).\n- `text`: the same free text for every recipient (test keys and destinations without DLT rules).\n\nDuplicate numbers are collapsed. Numbers on the suppression list or blocked for your account are accepted and reported as `skipped` rather than failing the campaign.\n\nMoney is held per message as the campaign is expanded, not for the whole campaign up front: if the wallet runs out, the campaign stops with `failure.code = INSUFFICIENT_BALANCE` and the remaining recipients are cancelled.","security":[{"ApiKeyHeader":["sms:bulk"]},{"BearerAuth":["sms:bulk"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBatchRequest"},"examples":{"template":{"summary":"Template campaign","value":{"name":"October offers","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","recipients":[{"to":"919876543210","variables":{"name":"Asha"}},{"to":"919812345678","variables":{"name":"Ravi"}}]}},"scheduled":{"summary":"Scheduled free text (test key)","value":{"name":"Diwali sale","text":"Our Diwali sale starts today.","senderId":"ACMESHOP","scheduledAt":"2026-09-15T10:30:00.000Z","recipients":[{"to":"447911123456"}]}}}}}},"responses":{"202":{"description":"Campaign accepted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"},"example":{"success":true,"batchId":"bat_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","status":"queued","environment":"live","channel":"sms","type":"promotional","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","senderId":"ACMESHOP","scheduledAt":null,"counts":{"recipients":2500,"expanded":0,"skipped":12,"pending":2488},"failure":null,"createdAt":"2026-09-15T10:30:00.000Z","startedAt":null,"completedAt":null,"cancelledAt":null}}}},"400":{"description":"Invalid request, recipients or content.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The campaign contains numbers that are not valid mobile numbers","details":[{"field":"recipients[3].to","message":"must be a mobile number in international format"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"tooMany":{"summary":"BULK_TOO_MANY_RECIPIENTS","value":{"success":false,"error":{"code":"BULK_TOO_MANY_RECIPIENTS","message":"A campaign may hold at most 5000 recipients; split larger lists","details":[{"field":"recipients","message":"has 12000 entries"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"templateRequired":{"summary":"TEMPLATE_REQUIRED","value":{"success":false,"error":{"code":"TEMPLATE_REQUIRED","message":"Live SMS to IN must use an approved DLT template","details":[{"field":"templateId","message":"is required for this destination; free text is not allowed"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The template does not exist in this company.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"template":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Template not found"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"409":{"description":"Conflicting state.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency":{"summary":"IDEMPOTENCY_KEY_REUSED","value":{"success":false,"error":{"code":"IDEMPOTENCY_KEY_REUSED","message":"This Idempotency-Key was already used with a different request"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"template":{"summary":"TEMPLATE_NOT_APPROVED","value":{"success":false,"error":{"code":"TEMPLATE_NOT_APPROVED","message":"The template is not approved"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"get":{"operationId":"listBatches","tags":["Bulk"],"summary":"List campaigns","description":"Campaigns of the key’s project, newest first.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Only campaigns in this state.","schema":{"$ref":"#/components/schemas/BatchStatus"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of campaigns.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchListResponse"},"example":{"success":true,"data":[{"batchId":"bat_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","status":"queued","environment":"live","channel":"sms","type":"promotional","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","senderId":"ACMESHOP","scheduledAt":null,"counts":{"recipients":2500,"expanded":0,"skipped":12,"pending":2488},"failure":null,"createdAt":"2026-09-15T10:30:00.000Z","startedAt":null,"completedAt":null,"cancelledAt":null}],"pagination":{"limit":20,"hasMore":false,"nextCursor":null}}}}},"400":{"description":"Invalid filter or cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"cursor":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Invalid cursor","details":[{"field":"cursor","message":"cursor is invalid or expired"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/sms/bulk/{batchId}":{"get":{"operationId":"getBatch","tags":["Bulk"],"summary":"Campaign progress","description":"Counts move from `pending` to `expanded` while the campaign is sent. Delivery of each message is reported by webhooks or `GET /messages`.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"name":"batchId","in":"path","required":true,"description":"Campaign ID (bat_…).","schema":{"type":"string","minLength":4,"maxLength":40}}],"responses":{"200":{"description":"The campaign.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"},"example":{"success":true,"batchId":"bat_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","status":"queued","environment":"live","channel":"sms","type":"promotional","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","senderId":"ACMESHOP","scheduledAt":null,"counts":{"recipients":2500,"expanded":0,"skipped":12,"pending":2488},"failure":null,"createdAt":"2026-09-15T10:30:00.000Z","startedAt":null,"completedAt":null,"cancelledAt":null}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/sms/bulk/{batchId}/recipients":{"get":{"operationId":"listBatchRecipients","tags":["Bulk"],"summary":"Per-recipient outcome","description":"The message created for each recipient, or why the number was skipped (opted out, blocked destination).","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"name":"batchId","in":"path","required":true,"description":"Campaign ID (bat_…).","schema":{"type":"string","minLength":4,"maxLength":40}},{"name":"status","in":"query","required":false,"description":"Only recipients in this state.","schema":{"type":"string","enum":["pending","expanded","skipped","cancelled"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of recipients.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecipientListResponse"},"example":{"success":true,"data":[{"to":"919876543210","status":"expanded","messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","reason":null},{"to":"919812345678","status":"skipped","messageId":null,"reason":"Number is on this company’s suppression list"}],"pagination":{"limit":20,"hasMore":false,"nextCursor":null}}}}},"400":{"description":"Invalid filter or cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"cursor":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Invalid cursor","details":[{"field":"cursor","message":"cursor is invalid or expired"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/sms/bulk/{batchId}/cancel":{"post":{"operationId":"cancelBatch","tags":["Bulk"],"summary":"Cancel a campaign","description":"Cancels recipients that have not been expanded yet. Messages already handed to a provider are never recalled — SMS21 does not claim to stop what is already sent.","security":[{"ApiKeyHeader":["sms:bulk"]},{"BearerAuth":["sms:bulk"]}],"parameters":[{"name":"batchId","in":"path","required":true,"description":"Campaign ID (bat_…).","schema":{"type":"string","minLength":4,"maxLength":40}}],"responses":{"200":{"description":"The cancelled campaign.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"},"example":{"success":true,"batchId":"bat_01k55q8y4m3d9v7x2c6b0n1r8t","name":"October offers","status":"cancelled","environment":"live","channel":"sms","type":"promotional","templateId":"tmpl_01k55q8y4m3d9v7x2c6b0n1r8t","senderId":"ACMESHOP","scheduledAt":null,"counts":{"recipients":2500,"expanded":0,"skipped":12,"pending":2488},"failure":null,"createdAt":"2026-09-15T10:30:00.000Z","startedAt":null,"completedAt":null,"cancelledAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The campaign has already finished.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"finished":{"summary":"BATCH_NOT_CANCELLABLE","value":{"success":false,"error":{"code":"BATCH_NOT_CANCELLABLE","message":"This campaign is already completed"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/inbound":{"get":{"operationId":"listInbound","tags":["Inbound"],"summary":"List replies","description":"Replies people sent to your numbers, newest first.\n\nSMS21 acts on opt-out keywords itself: a reply starting with STOP (or UNSUBSCRIBE, CANCEL, …) adds the sender to your opt-out list, and START removes them. `action` says what happened.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Sender, international format.","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"One of your numbers, international format.","schema":{"type":"string"}},{"name":"action","in":"query","required":false,"description":"Only replies SMS21 acted on (or did not).","schema":{"type":"string","enum":["none","opt_out","opt_in"]}},{"name":"receivedFrom","in":"query","required":false,"description":"Inclusive lower bound on arrival time.","schema":{"type":"string","format":"date-time"}},{"name":"receivedTo","in":"query","required":false,"description":"Inclusive upper bound on arrival time.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of replies.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundListResponse"},"example":{"success":true,"data":[{"inboundId":"inb_01k55q8y4m3d9v7x2c6b0n1r8t","from":"919876543210","to":"919812345678","numberId":"num_01k55q8y4m3d9v7x2c6b0n1r8t","text":"STOP","segments":1,"keyword":"STOP","action":"opt_out","receivedAt":"2026-09-15T10:30:00.000Z"}],"pagination":{"limit":20,"hasMore":false,"nextCursor":null}}}}},"400":{"description":"Invalid filter or cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"filter":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The number is not a valid mobile number","details":[{"field":"from","message":"must be in international format"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/inbound/simulate":{"post":{"operationId":"simulateInbound","tags":["Inbound"],"summary":"Simulate a reply (test keys)","description":"Delivers a reply through the sandbox as if a handset had sent it, so you can build an integration before a number is live: keyword handling, the opt-out list and your webhooks all run.\n\nTest keys only, and only to a number assigned to the same project.","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateInboundRequest"},"examples":{"stop":{"summary":"Opt-out reply","value":{"from":"919876543210","to":"919812345678","text":"STOP"}},"reply":{"summary":"Ordinary reply","value":{"from":"919876543210","to":"919812345678","text":"Yes, tomorrow works"}}}}}},"responses":{"202":{"description":"Queued for delivery through the sandbox.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateInboundResponse"},"example":{"success":true,"accepted":true}}}},"400":{"$ref":"#/components/responses/Unauthorized"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Live keys cannot simulate inbound messages.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"live":{"summary":"FORBIDDEN","value":{"success":false,"error":{"code":"FORBIDDEN","message":"Inbound messages can only be simulated with a test API key"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"404":{"description":"The number is not assigned to this project.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"number":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"No active inbound number of this project has that number"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/inbound/{inboundId}":{"get":{"operationId":"getInbound","tags":["Inbound"],"summary":"Fetch one reply","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"name":"inboundId","in":"path","required":true,"description":"Inbound message ID (inb_…).","schema":{"type":"string","minLength":4,"maxLength":40}}],"responses":{"200":{"description":"The reply.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundResponse"},"example":{"success":true,"inboundId":"inb_01k55q8y4m3d9v7x2c6b0n1r8t","from":"919876543210","to":"919812345678","numberId":"num_01k55q8y4m3d9v7x2c6b0n1r8t","text":"STOP","segments":1,"keyword":"STOP","action":"opt_out","receivedAt":"2026-09-15T10:30:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/suppressions":{"post":{"operationId":"addSuppression","tags":["Bulk"],"summary":"Add a number to the opt-out list","description":"Campaign recipients on this list are never sent to. Adding a number twice is not an error. Single sends through `POST /sms/send` are transactional and are not filtered.","security":[{"ApiKeyHeader":["sms:bulk"]},{"BearerAuth":["sms:bulk"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSuppressionRequest"},"examples":{"stop":{"summary":"Customer replied STOP","value":{"mobile":"919876543210","reason":"replied STOP"}}}}}},"responses":{"201":{"description":"The entry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionResponse"},"example":{"success":true,"to":"919876543210","reason":"replied STOP","source":"api","createdAt":"2026-09-15T10:30:00.000Z"}}}},"400":{"description":"Invalid filter or cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"cursor":{"summary":"VALIDATION_FAILED","value":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"Invalid cursor","details":[{"field":"cursor","message":"cursor is invalid or expired"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"get":{"operationId":"listSuppressions","tags":["Bulk"],"summary":"List opt-outs","security":[{"ApiKeyHeader":["reports:read"]},{"BearerAuth":["reports:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of entries.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionListResponse"},"example":{"success":true,"data":[{"to":"919876543210","reason":"replied STOP","source":"api","createdAt":"2026-09-15T10:30:00.000Z"}],"pagination":{"limit":20,"hasMore":false,"nextCursor":null}}}}},"400":{"description":"Invalid number.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"recipient":{"summary":"INVALID_RECIPIENT","value":{"success":false,"error":{"code":"INVALID_RECIPIENT","message":"The number is not a valid mobile number","details":[{"field":"mobile","message":"must be a mobile number in international format"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/suppressions/{mobile}":{"delete":{"operationId":"removeSuppression","tags":["Bulk"],"summary":"Remove a number from the opt-out list","security":[{"ApiKeyHeader":["sms:bulk"]},{"BearerAuth":["sms:bulk"]}],"parameters":[{"name":"mobile","in":"path","required":true,"description":"Number in E.164 digits without \"+\".","schema":{"type":"string","minLength":8,"maxLength":16}}],"responses":{"204":{"description":"Removed; campaigns may send to this number again."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The number is not on the list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"notFound":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"This number is not on the suppression list"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}}},"webhooks":{"messageReceived":{"post":{"operationId":"messageReceivedWebhook","tags":["Webhooks"],"summary":"Reply received","description":"Configure endpoints (HTTPS only) in the dashboard under **Developers → Webhooks**.\n\n1. Verify `SMS21-Signature` against the **raw** body before parsing JSON.\n2. Reject timestamps older than 5 minutes (replay protection).\n3. Ignore event IDs you have already processed: deliveries are at-least-once and may arrive out of order.\n\nEvent: `message.received`. Unlike `message.*` status events, this payload carries the sender's number and the text — that is the message being delivered to you.","parameters":[{"name":"SMS21-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256>` over `\"<t>.<raw body>\"` with the endpoint signing secret (`whsec_…`). Several `v1` values can appear while a secret is rotated; accept the request if any matches. Reject timestamps more than 5 minutes from your clock.","schema":{"type":"string","pattern":"^t=[0-9]+(,v1=[0-9a-f]{64})+$"}},{"name":"SMS21-Timestamp","in":"header","required":true,"description":"Same value as `t` in the signature.","schema":{"type":"string","pattern":"^[0-9]+$"}},{"name":"SMS21-Event-Id","in":"header","required":true,"description":"Event ID (same as `id` in the body): deduplicate on it.","schema":{"type":"string","pattern":"^evt_"}},{"name":"SMS21-Event-Type","in":"header","required":true,"description":"Same as `event` in the body.","schema":{"type":"string","pattern":"^[a-z]+\\.[a-z]+$"}},{"name":"SMS21-Delivery-Id","in":"header","required":true,"description":"This delivery (differs per endpoint).","schema":{"type":"string","pattern":"^whd_"}},{"name":"SMS21-Attempt","in":"header","required":true,"description":"Attempt number, starting at 1.","schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundEvent"},"example":{"id":"evt_01k55q8y4m3d9v7x2c6b0n1r8t","event":"message.received","timestamp":"2026-09-15T10:30:00.000Z","inboundId":"inb_01k55q8y4m3d9v7x2c6b0n1r8t","from":"919876543210","to":"919812345678","text":"STOP","keyword":"STOP","action":"opt_out"}}}},"responses":{"2XX":{"description":"Acknowledged. Respond within 10 seconds and process the event asynchronously. Any other status or a timeout is retried with backoff (by default up to 8 attempts over about 11 hours); an endpoint that keeps failing is disabled automatically."}}}},"messageStatus":{"post":{"operationId":"messageStatusWebhook","tags":["Webhooks"],"summary":"Message status changed","description":"Configure endpoints (HTTPS only) in the dashboard under **Developers → Webhooks**.\n\n1. Verify `SMS21-Signature` against the **raw** body before parsing JSON.\n2. Reject timestamps older than 5 minutes (replay protection).\n3. Ignore event IDs you have already processed: deliveries are at-least-once and may arrive out of order.\n\nEvents: `message.submitted`, `message.sent`, `message.delivered`, `message.failed`, `message.rejected`, `message.expired`, `message.received`.","parameters":[{"name":"SMS21-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256>` over `\"<t>.<raw body>\"` with the endpoint signing secret (`whsec_…`). Several `v1` values can appear while a secret is rotated; accept the request if any matches. Reject timestamps more than 5 minutes from your clock.","schema":{"type":"string","pattern":"^t=[0-9]+(,v1=[0-9a-f]{64})+$"}},{"name":"SMS21-Timestamp","in":"header","required":true,"description":"Same value as `t` in the signature.","schema":{"type":"string","pattern":"^[0-9]+$"}},{"name":"SMS21-Event-Id","in":"header","required":true,"description":"Event ID (same as `id` in the body): deduplicate on it.","schema":{"type":"string","pattern":"^evt_"}},{"name":"SMS21-Event-Type","in":"header","required":true,"description":"Same as `event` in the body.","schema":{"type":"string","pattern":"^[a-z]+\\.[a-z]+$"}},{"name":"SMS21-Delivery-Id","in":"header","required":true,"description":"This delivery (differs per endpoint).","schema":{"type":"string","pattern":"^whd_"}},{"name":"SMS21-Attempt","in":"header","required":true,"description":"Attempt number, starting at 1.","schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageEvent"},"example":{"id":"evt_01k55q8y4m3d9v7x2c6b0n1r8t","event":"message.delivered","timestamp":"2026-09-15T10:30:00.000Z","messageId":"msg_01k55q8y4m3d9v7x2c6b0n1r8t","status":"delivered","channel":"sms","type":"otp","environment":"live","failure":null}}}},"responses":{"2XX":{"description":"Acknowledged. Respond within 10 seconds and process the event asynchronously. Any other status or a timeout is retried with backoff (by default up to 8 attempts over about 11 hours); an endpoint that keeps failing is disabled automatically."}}}},"webhookTest":{"post":{"operationId":"webhookTestWebhook","tags":["Webhooks"],"summary":"Test event","description":"Configure endpoints (HTTPS only) in the dashboard under **Developers → Webhooks**.\n\n1. Verify `SMS21-Signature` against the **raw** body before parsing JSON.\n2. Reject timestamps older than 5 minutes (replay protection).\n3. Ignore event IDs you have already processed: deliveries are at-least-once and may arrive out of order.\n\nSent when you click **Send test event**; signed like every other event.","parameters":[{"name":"SMS21-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256>` over `\"<t>.<raw body>\"` with the endpoint signing secret (`whsec_…`). Several `v1` values can appear while a secret is rotated; accept the request if any matches. Reject timestamps more than 5 minutes from your clock.","schema":{"type":"string","pattern":"^t=[0-9]+(,v1=[0-9a-f]{64})+$"}},{"name":"SMS21-Timestamp","in":"header","required":true,"description":"Same value as `t` in the signature.","schema":{"type":"string","pattern":"^[0-9]+$"}},{"name":"SMS21-Event-Id","in":"header","required":true,"description":"Event ID (same as `id` in the body): deduplicate on it.","schema":{"type":"string","pattern":"^evt_"}},{"name":"SMS21-Event-Type","in":"header","required":true,"description":"Same as `event` in the body.","schema":{"type":"string","pattern":"^[a-z]+\\.[a-z]+$"}},{"name":"SMS21-Delivery-Id","in":"header","required":true,"description":"This delivery (differs per endpoint).","schema":{"type":"string","pattern":"^whd_"}},{"name":"SMS21-Attempt","in":"header","required":true,"description":"Attempt number, starting at 1.","schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestEvent"},"example":{"id":"evt_01k55q8y4m3d9v7x2c6b0n1r8t","event":"webhook.test","timestamp":"2026-09-15T10:30:00.000Z","endpointId":"whk_01k55q8y4m3d9v7x2c6b0n1r8t","projectId":"prj_01k55q8y4m3d9v7x2c6b0n1r8t"}}}},"responses":{"2XX":{"description":"Acknowledged. Respond within 10 seconds and process the event asynchronously. Any other status or a timeout is retried with backoff (by default up to 8 attempts over about 11 hours); an endpoint that keeps failing is disabled automatically."}}}}},"components":{"schemas":{"ErrorCode":{"type":"string","enum":["BAD_REQUEST","VALIDATION_FAILED","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","METHOD_NOT_ALLOWED","CONFLICT","PAYLOAD_TOO_LARGE","UNSUPPORTED_MEDIA_TYPE","RATE_LIMITED","INTERNAL_ERROR","SERVICE_UNAVAILABLE","AUTHENTICATION_REQUIRED","ACCESS_TOKEN_INVALID","ACCESS_TOKEN_EXPIRED","REFRESH_TOKEN_INVALID","INVALID_CREDENTIALS","ACCOUNT_SUSPENDED","REGISTRATION_DISABLED","MFA_CHALLENGE_REQUIRED","MFA_REQUIRED","MFA_CODE_INVALID","MFA_ALREADY_ENROLLED","MFA_NOT_ENROLLED","STEP_UP_REQUIRED","PASSWORD_RESET_TOKEN_INVALID","PASSWORD_RESET_UNAVAILABLE","EMAIL_ALREADY_REGISTERED","API_KEY_INVALID","API_KEY_REVOKED","API_KEY_EXPIRED","INSUFFICIENT_SCOPE","IP_NOT_ALLOWED","PROJECT_INACTIVE","API_KEY_LIMIT_REACHED","API_KEY_SUSPENDED","PROJECT_SUSPENDED","INVALID_RECIPIENT","MESSAGE_TOO_LONG","IDEMPOTENCY_KEY_INVALID","IDEMPOTENCY_KEY_REUSED","BULK_TOO_MANY_RECIPIENTS","CONTACTS_LIMIT_REACHED","CONTACT_LIST_EMPTY","CSV_INVALID","BATCH_NOT_CANCELLABLE","RECIPIENT_SUPPRESSED","TEMPLATE_REQUIRED","TEMPLATE_NOT_APPROVED","TEMPLATE_VARIABLES_INVALID","TEMPLATE_CATEGORY_NOT_ALLOWED","INVALID_TEMPLATE_CONTENT","DLT_ENTITY_NOT_APPROVED","SENDER_ID_NOT_APPROVED","REVIEW_PREREQUISITES_NOT_MET","OTP_INVALID","OTP_EXPIRED","OTP_ALREADY_VERIFIED","OTP_ATTEMPTS_EXCEEDED","OTP_RESEND_COOLDOWN","OTP_TEMPLATE_INVALID","CHANNEL_NOT_AVAILABLE","DESTINATION_NOT_ALLOWED","WHATSAPP_TEMPLATE_INVALID","WEBHOOK_URL_NOT_ALLOWED","WEBHOOK_LIMIT_REACHED","WEBHOOK_DELIVERY_IN_PROGRESS","PAYMENT_NOT_COMPLETED","PAYMENT_ALREADY_SETTLED","PAYMENT_VERIFICATION_FAILED","PAYMENTS_NOT_CONFIGURED","BILLING_PROFILE_INCOMPLETE","INSUFFICIENT_BALANCE","WALLET_SUSPENDED","PRICING_NOT_CONFIGURED","PRICING_RULE_OVERLAP","REFUND_NOT_ALLOWED","SENDING_PAUSED","DESTINATION_BLOCKED","DAILY_LIMIT_EXCEEDED","CREDENTIALS_KEY_NOT_CONFIGURED","PERMISSION_DENIED","COMPANY_SUSPENDED","ROLE_ASSIGNMENT_FORBIDDEN","LAST_OWNER_REQUIRED","SLUG_TAKEN","USER_NOT_FOUND","ALREADY_A_MEMBER","MOBILE_ALREADY_REGISTERED","MOBILE_REQUIRED","NOT_A_RESELLER","SUBACCOUNT_NOT_FOUND","PRICE_BELOW_COST"],"description":"Stable machine-readable error code. Codes are never renamed or reused; new codes may be added, so treat unknown codes by HTTP status."},"ErrorDetail":{"type":"object","required":["message"],"properties":{"field":{"type":"string","description":"Dot-path of the input field, e.g. \"variables.name\"."},"message":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["success","error","requestId"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable; may change. Do not parse."},"details":{"type":"array","items":{"$ref":"#/components/schemas/ErrorDetail"}}}},"requestId":{"type":"string","description":"Same as the X-Request-Id header. Quote it when contacting support."}}},"Pagination":{"type":"object","required":["limit","hasMore","nextCursor"],"properties":{"limit":{"type":"integer","minimum":1,"maximum":100},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to get the next page; null on the last page."}}},"OtpPurpose":{"type":"string","enum":["login","registration","password_reset","transaction","mobile_verification"],"description":"Why the OTP is requested; used in reports and abuse limits."},"OtpChannel":{"type":"string","enum":["sms","whatsapp"],"description":"Delivery channel of the first attempt."},"OtpStatus":{"type":"string","enum":["pending","verified","expired","attempts_exceeded","superseded"],"description":"`pending` until verified; `superseded` when a newer OTP was sent to the same number and purpose."},"MessageStatus":{"type":"string","enum":["queued","processing","submitted","sent","delivered","failed","rejected","expired"],"description":"Unified status across providers:\n\n| Status | Meaning |\n|---|---|\n| `queued` | Accepted, waiting to be processed. |\n| `processing` | Being routed to a provider. |\n| `submitted` | Handed to a provider. Also used when a hand-over timed out: SMS21 never resends an uncertain submission. |\n| `sent` | The provider confirmed it left their platform. |\n| `delivered` | Delivered to the handset (final). |\n| `failed` | Could not be delivered (final). |\n| `rejected` | Refused: invalid number, policy or DLT mismatch; not retried (final). |\n| `expired` | No final report within the validity period (final). |"},"MessageChannel":{"type":"string","enum":["sms","whatsapp"],"description":"Channel."},"MessageType":{"type":"string","enum":["otp","transactional","promotional"],"description":"Message type."},"Environment":{"type":"string","enum":["live","test"],"description":"`test`: sandbox traffic from a test key (never delivered, never charged)."},"SendOtpRequest":{"type":"object","additionalProperties":false,"required":["mobile","purpose"],"properties":{"mobile":{"type":"string","minLength":8,"maxLength":16,"description":"Recipient in international format with country code: \"919876543210\" or \"+919876543210\".","examples":["919876543210"]},"purpose":{"$ref":"#/components/schemas/OtpPurpose"},"channel":{"$ref":"#/components/schemas/OtpChannel","default":"sms"},"fallbackChannel":{"type":"string","enum":["sms"],"description":"`channel: \"whatsapp\"` only. If WhatsApp definitely fails (for example the number is not on WhatsApp), the code is sent by SMS. Nothing is resent while the WhatsApp outcome is uncertain."},"templateId":{"type":"string","minLength":4,"maxLength":40,"description":"SMS template with an `{{otp}}` variable. Defaults to the project OTP template. Live OTPs to DLT countries (India) need one of the two.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t"]},"variables":{"type":"object","additionalProperties":true,"description":"Other template variables. `otp` and `minutes` are filled in by SMS21 and must not be sent.","examples":[{"name":"Asha","order":"1042"}]},"expiresIn":{"type":"integer","minimum":60,"maximum":3600,"description":"Validity in seconds. Default 300; values above the platform maximum (900 unless configured otherwise) are refused."},"ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"description":"The END USER's IP address, when known, for per-IP abuse limits. Never send your server's address.","examples":["203.0.113.24"]}}},"VerifyOtpRequest":{"type":"object","additionalProperties":false,"required":["requestId","otp"],"properties":{"requestId":{"type":"string","minLength":4,"maxLength":40,"description":"`requestId` returned by `POST /otp/send`.","examples":["otp_01k55q8y4m3d9v7x2c6b0n1r8t"]},"otp":{"type":"string","pattern":"^[0-9]{4,10}$","description":"The code the user entered."},"mobile":{"type":"string","minLength":8,"maxLength":16,"description":"Optional binding: verification only succeeds if the OTP was sent to this number (a mismatch counts as a wrong code).","examples":["919876543210"]}}},"SendSmsRequest":{"type":"object","additionalProperties":false,"required":["to"],"description":"Send exactly one of `templateId` (with `variables`) or `text`.","oneOf":[{"title":"DLT template","required":["templateId"]},{"title":"Free text","required":["text"]}],"properties":{"to":{"type":"string","minLength":8,"maxLength":16,"description":"Recipient in international format: \"919876543210\" or \"+919876543210\".","examples":["919876543210"]},"templateId":{"type":"string","minLength":4,"maxLength":40,"description":"Approved SMS template of the key's company. Required for live SMS to DLT countries (India); the sender ID comes from the template.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t"]},"variables":{"type":"object","additionalProperties":true,"description":"Values for the template `{{variables}}`. Only with `templateId`.","examples":[{"name":"Asha","order":"1042"}]},"text":{"type":"string","minLength":1,"maxLength":1600,"description":"Free text: test keys and destinations without DLT rules only. At most 10 SMS segments."},"senderId":{"type":"string","pattern":"^(?:[A-Za-z0-9]{3,11}|\\+?[1-9][0-9]{7,14})$","description":"Free text only: alphanumeric sender (3-11 characters) or a phone number."}}},"SendWhatsAppTextRequest":{"type":"object","additionalProperties":false,"required":["to","text"],"properties":{"to":{"type":"string","minLength":8,"maxLength":16,"description":"Recipient in international format.","examples":["919876543210"]},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"Delivered only within 24 hours of the recipient's last message to you; otherwise WhatsApp rejects it."}}},"SendWhatsAppTemplateRequest":{"type":"object","additionalProperties":false,"required":["to","templateId"],"properties":{"to":{"type":"string","minLength":8,"maxLength":16,"description":"Recipient in international format.","examples":["919876543210"]},"templateId":{"type":"string","minLength":4,"maxLength":40,"description":"Approved utility or marketing template. Authentication templates are sent with `POST /otp/send`.","examples":["wtpl_01k55q8y4m3d9v7x2c6b0n1r8t"]},"variables":{"type":"object","additionalProperties":true,"description":"Values for the template variables, by name.","examples":[{"name":"Asha","order":"1042"}]}}},"OtpSend":{"type":"object","required":["requestId","status","channel","purpose","expiresIn","expiresAt","resendAfter","messageId"],"properties":{"requestId":{"type":"string","pattern":"^otp_[0-9a-hjkmnp-tv-z]{26}$","description":"Identifies this OTP request. Pass it to `POST /otp/verify`.","examples":["otp_01k55q8y4m3d9v7x2c6b0n1r8t"]},"status":{"$ref":"#/components/schemas/OtpStatus"},"channel":{"$ref":"#/components/schemas/OtpChannel"},"purpose":{"$ref":"#/components/schemas/OtpPurpose"},"expiresIn":{"type":"integer","minimum":0,"description":"Seconds until the code stops working."},"expiresAt":{"type":"string","format":"date-time","description":"When the code stops working.","examples":["2026-09-15T10:30:00.000Z"]},"resendAfter":{"type":"integer","minimum":0,"description":"Seconds until another OTP may be sent to this number for this purpose."},"messageId":{"type":["string","null"],"pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"The first delivery attempt. Follow it with `GET /messages/{messageId}` or webhooks. The message body is stored masked; the code never appears in reports.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t",null]}}},"OtpVerification":{"type":"object","required":["requestId","verified","mobile","purpose","verifiedAt"],"properties":{"requestId":{"type":"string","pattern":"^otp_[0-9a-hjkmnp-tv-z]{26}$","description":"The verified OTP request.","examples":["otp_01k55q8y4m3d9v7x2c6b0n1r8t"]},"verified":{"type":"boolean","const":true},"mobile":{"type":"string","pattern":"^[0-9]{8,15}$","description":"The number the OTP was sent to (E.164 digits, no \"+\"). Bind the result to the user you expected.","examples":["919876543210"]},"purpose":{"$ref":"#/components/schemas/OtpPurpose"},"verifiedAt":{"type":"string","format":"date-time","description":"When the code was accepted.","examples":["2026-09-15T10:30:00.000Z"]}}},"Message":{"type":"object","required":["messageId","status","channel","type","environment","to","templateId","whatsappTemplateId","segments","encoding","failure","queuedAt","submittedAt","sentAt","deliveredAt","failedAt"],"properties":{"messageId":{"type":"string","pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"Message ID.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t"]},"status":{"$ref":"#/components/schemas/MessageStatus"},"channel":{"$ref":"#/components/schemas/MessageChannel"},"type":{"$ref":"#/components/schemas/MessageType"},"environment":{"$ref":"#/components/schemas/Environment"},"to":{"type":"string","pattern":"^([0-9]{8,15}|[0-9]{2}\\*{2,9}[0-9]{4})$","description":"Recipient in E.164 digits without \"+\". Masked (e.g. \"91******3210\") once the data retention period for message content has passed.","examples":["919876543210"]},"templateId":{"type":["string","null"],"pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"SMS template the message was rendered from.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"whatsappTemplateId":{"type":["string","null"],"pattern":"^wtpl_[0-9a-hjkmnp-tv-z]{26}$","description":"WhatsApp template of a template message.","examples":["wtpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"segments":{"type":"integer","minimum":0,"description":"Billable SMS segments (1 for WhatsApp messages)."},"encoding":{"type":"string","enum":["gsm7","ucs2"],"description":"SMS encoding: GSM-7 (160 characters per single SMS) or UCS-2 (70)."},"failure":{"type":["object","null"],"description":"Why the message failed. Provider names and vendor error codes are never exposed.","required":["code"],"properties":{"code":{"type":"string","enum":["MESSAGE_REJECTED","DELIVERY_FAILED"]}}},"queuedAt":{"type":"string","format":"date-time","description":"Accepted by the API.","examples":["2026-09-15T10:30:00.000Z"]},"submittedAt":{"type":["string","null"],"format":"date-time","description":"Handed to a provider."},"sentAt":{"type":["string","null"],"format":"date-time","description":"The provider confirmed it left their platform."},"deliveredAt":{"type":["string","null"],"format":"date-time","description":"Handset delivery confirmed."},"failedAt":{"type":["string","null"],"format":"date-time","description":"Failed, rejected or expired."}}},"ApiKey":{"type":"object","required":["id","environment","scopes","projectId","companyId"],"properties":{"id":{"type":"string","pattern":"^key_[0-9a-hjkmnp-tv-z]{26}$","description":"API key ID (not the secret key).","examples":["key_01k55q8y4m3d9v7x2c6b0n1r8t"]},"environment":{"$ref":"#/components/schemas/Environment"},"scopes":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["otp:send","otp:verify","sms:send","sms:bulk","whatsapp:send","contacts:read","contacts:manage","reports:read"],"description":"Scope granted to the key."}},"projectId":{"type":"string","pattern":"^prj_[0-9a-hjkmnp-tv-z]{26}$","description":"Project the key belongs to.","examples":["prj_01k55q8y4m3d9v7x2c6b0n1r8t"]},"companyId":{"type":"string","pattern":"^cmp_[0-9a-hjkmnp-tv-z]{26}$","description":"Company the project belongs to.","examples":["cmp_01k55q8y4m3d9v7x2c6b0n1r8t"]}}},"Wallet":{"type":"object","required":["currency","status","balance","reserved","available","creditLimit","spendable","lowBalanceThreshold","lowBalance","updatedAt"],"properties":{"currency":{"type":"string","pattern":"^[A-Z]{3}$","examples":["INR"]},"status":{"type":"string","enum":["active","suspended"],"description":"`suspended`: live sending is refused; credits are still accepted."},"balance":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Settled funds. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"reserved":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Held for accepted messages that are not submitted yet. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"available":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"balance − reserved. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"creditLimit":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Credit granted by SMS21. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"spendable":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"available + creditLimit: what new messages may still reserve. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"lowBalanceThreshold":{"type":["string","null"],"pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Low-balance alert threshold."},"lowBalance":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time","description":"Last change.","examples":["2026-09-15T10:30:00.000Z"]}}},"OtpSendResponse":{"type":"object","description":"OTP accepted for delivery.","required":["success","requestId","status","channel","purpose","expiresIn","expiresAt","resendAfter","messageId"],"properties":{"success":{"type":"boolean","const":true},"requestId":{"type":"string","pattern":"^otp_[0-9a-hjkmnp-tv-z]{26}$","description":"Identifies this OTP request. Pass it to `POST /otp/verify`.","examples":["otp_01k55q8y4m3d9v7x2c6b0n1r8t"]},"status":{"$ref":"#/components/schemas/OtpStatus"},"channel":{"$ref":"#/components/schemas/OtpChannel"},"purpose":{"$ref":"#/components/schemas/OtpPurpose"},"expiresIn":{"type":"integer","minimum":0,"description":"Seconds until the code stops working."},"expiresAt":{"type":"string","format":"date-time","description":"When the code stops working.","examples":["2026-09-15T10:30:00.000Z"]},"resendAfter":{"type":"integer","minimum":0,"description":"Seconds until another OTP may be sent to this number for this purpose."},"messageId":{"type":["string","null"],"pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"The first delivery attempt. Follow it with `GET /messages/{messageId}` or webhooks. The message body is stored masked; the code never appears in reports.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t",null]}}},"OtpVerificationResponse":{"type":"object","description":"OTP verified.","required":["success","requestId","verified","mobile","purpose","verifiedAt"],"properties":{"success":{"type":"boolean","const":true},"requestId":{"type":"string","pattern":"^otp_[0-9a-hjkmnp-tv-z]{26}$","description":"The verified OTP request.","examples":["otp_01k55q8y4m3d9v7x2c6b0n1r8t"]},"verified":{"type":"boolean","const":true},"mobile":{"type":"string","pattern":"^[0-9]{8,15}$","description":"The number the OTP was sent to (E.164 digits, no \"+\"). Bind the result to the user you expected.","examples":["919876543210"]},"purpose":{"$ref":"#/components/schemas/OtpPurpose"},"verifiedAt":{"type":"string","format":"date-time","description":"When the code was accepted.","examples":["2026-09-15T10:30:00.000Z"]}}},"MessageResponse":{"type":"object","description":"A message.","required":["success","messageId","status","channel","type","environment","to","templateId","whatsappTemplateId","segments","encoding","failure","queuedAt","submittedAt","sentAt","deliveredAt","failedAt"],"properties":{"success":{"type":"boolean","const":true},"messageId":{"type":"string","pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"Message ID.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t"]},"status":{"$ref":"#/components/schemas/MessageStatus"},"channel":{"$ref":"#/components/schemas/MessageChannel"},"type":{"$ref":"#/components/schemas/MessageType"},"environment":{"$ref":"#/components/schemas/Environment"},"to":{"type":"string","pattern":"^([0-9]{8,15}|[0-9]{2}\\*{2,9}[0-9]{4})$","description":"Recipient in E.164 digits without \"+\". Masked (e.g. \"91******3210\") once the data retention period for message content has passed.","examples":["919876543210"]},"templateId":{"type":["string","null"],"pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"SMS template the message was rendered from.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"whatsappTemplateId":{"type":["string","null"],"pattern":"^wtpl_[0-9a-hjkmnp-tv-z]{26}$","description":"WhatsApp template of a template message.","examples":["wtpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"segments":{"type":"integer","minimum":0,"description":"Billable SMS segments (1 for WhatsApp messages)."},"encoding":{"type":"string","enum":["gsm7","ucs2"],"description":"SMS encoding: GSM-7 (160 characters per single SMS) or UCS-2 (70)."},"failure":{"type":["object","null"],"description":"Why the message failed. Provider names and vendor error codes are never exposed.","required":["code"],"properties":{"code":{"type":"string","enum":["MESSAGE_REJECTED","DELIVERY_FAILED"]}}},"queuedAt":{"type":"string","format":"date-time","description":"Accepted by the API.","examples":["2026-09-15T10:30:00.000Z"]},"submittedAt":{"type":["string","null"],"format":"date-time","description":"Handed to a provider."},"sentAt":{"type":["string","null"],"format":"date-time","description":"The provider confirmed it left their platform."},"deliveredAt":{"type":["string","null"],"format":"date-time","description":"Handset delivery confirmed."},"failedAt":{"type":["string","null"],"format":"date-time","description":"Failed, rejected or expired."}}},"MessageListResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"ApiKeyResponse":{"type":"object","description":"The authenticated API key.","required":["success","id","environment","scopes","projectId","companyId"],"properties":{"success":{"type":"boolean","const":true},"id":{"type":"string","pattern":"^key_[0-9a-hjkmnp-tv-z]{26}$","description":"API key ID (not the secret key).","examples":["key_01k55q8y4m3d9v7x2c6b0n1r8t"]},"environment":{"$ref":"#/components/schemas/Environment"},"scopes":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["otp:send","otp:verify","sms:send","sms:bulk","whatsapp:send","contacts:read","contacts:manage","reports:read"],"description":"Scope granted to the key."}},"projectId":{"type":"string","pattern":"^prj_[0-9a-hjkmnp-tv-z]{26}$","description":"Project the key belongs to.","examples":["prj_01k55q8y4m3d9v7x2c6b0n1r8t"]},"companyId":{"type":"string","pattern":"^cmp_[0-9a-hjkmnp-tv-z]{26}$","description":"Company the project belongs to.","examples":["cmp_01k55q8y4m3d9v7x2c6b0n1r8t"]}}},"WalletResponse":{"type":"object","description":"The company wallet.","required":["success","currency","status","balance","reserved","available","creditLimit","spendable","lowBalanceThreshold","lowBalance","updatedAt"],"properties":{"success":{"type":"boolean","const":true},"currency":{"type":"string","pattern":"^[A-Z]{3}$","examples":["INR"]},"status":{"type":"string","enum":["active","suspended"],"description":"`suspended`: live sending is refused; credits are still accepted."},"balance":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Settled funds. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"reserved":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Held for accepted messages that are not submitted yet. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"available":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"balance − reserved. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"creditLimit":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Credit granted by SMS21. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"spendable":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"available + creditLimit: what new messages may still reserve. Decimal string with up to 4 decimal places; never use floating point for arithmetic.","examples":["250.0000"]},"lowBalanceThreshold":{"type":["string","null"],"pattern":"^-?[0-9]+(\\.[0-9]{1,4})?$","description":"Low-balance alert threshold."},"lowBalance":{"type":"boolean"},"updatedAt":{"type":"string","format":"date-time","description":"Last change.","examples":["2026-09-15T10:30:00.000Z"]}}},"BatchStatus":{"type":"string","enum":["scheduled","queued","expanding","sending","completed","cancelled","failed"],"description":"Campaign lifecycle: scheduled → queued → expanding → sending → completed (or cancelled / failed)."},"ContactList":{"type":"object","required":["id","name","description","contactCount","createdAt","updatedAt"],"properties":{"id":{"type":"string","pattern":"^lst_[0-9a-hjkmnp-tv-z]{26}$","example":"lst_01k55q8y4m3d9v7x2c6b0n1r8t"},"name":{"type":"string","example":"October offers"},"description":{"type":["string","null"],"example":null},"contactCount":{"type":"integer","example":2},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"Contact":{"type":"object","required":["id","to","attributes","status","createdAt","updatedAt"],"properties":{"id":{"type":"string","pattern":"^cnt_[0-9a-hjkmnp-tv-z]{26}$","example":"cnt_01k55q8y4m3d9v7x2c6b0n1r8t"},"to":{"type":"string","example":"919876543210"},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Column values from the import, used as template variables.","example":{"name":"Asha","city":"Mumbai"}},"status":{"type":"string","enum":["active","unsubscribed"],"example":"active"},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"ContactImport":{"type":"object","required":["id","fileName","totalRows","imported","updated","invalid","duplicates","errors","createdAt"],"properties":{"id":{"type":"string","pattern":"^imp_[0-9a-hjkmnp-tv-z]{26}$","example":"imp_01k55q8y4m3d9v7x2c6b0n1r8t"},"fileName":{"type":["string","null"],"example":"october.csv"},"totalRows":{"type":"integer","example":4},"imported":{"type":"integer","example":2},"updated":{"type":"integer","example":0},"invalid":{"type":"integer","example":1},"duplicates":{"type":"integer","example":1},"errors":{"type":"array","description":"The first rows that failed, with the line numbers from your file.","items":{"type":"object","required":["line","message"],"properties":{"line":{"type":"integer"},"message":{"type":"string"}}},"example":[{"line":3,"message":"\"not-a-number\" is not a mobile number in international format"}]},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"CreateContactListRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120,"example":"October offers"},"description":{"type":"string","maxLength":500}}},"ContactListResponse":{"type":"object","description":"A contact list.","required":["success","id","name","description","contactCount","createdAt","updatedAt"],"properties":{"success":{"type":"boolean","const":true},"id":{"type":"string","pattern":"^lst_[0-9a-hjkmnp-tv-z]{26}$","example":"lst_01k55q8y4m3d9v7x2c6b0n1r8t"},"name":{"type":"string","example":"October offers"},"description":{"type":["string","null"],"example":null},"contactCount":{"type":"integer","example":2},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"ContactResponse":{"type":"object","description":"A contact.","required":["success","id","to","attributes","status","createdAt","updatedAt"],"properties":{"success":{"type":"boolean","const":true},"id":{"type":"string","pattern":"^cnt_[0-9a-hjkmnp-tv-z]{26}$","example":"cnt_01k55q8y4m3d9v7x2c6b0n1r8t"},"to":{"type":"string","example":"919876543210"},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Column values from the import, used as template variables.","example":{"name":"Asha","city":"Mumbai"}},"status":{"type":"string","enum":["active","unsubscribed"],"example":"active"},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"ContactImportResponse":{"type":"object","description":"What one upload did.","required":["success","id","fileName","totalRows","imported","updated","invalid","duplicates","errors","createdAt"],"properties":{"success":{"type":"boolean","const":true},"id":{"type":"string","pattern":"^imp_[0-9a-hjkmnp-tv-z]{26}$","example":"imp_01k55q8y4m3d9v7x2c6b0n1r8t"},"fileName":{"type":["string","null"],"example":"october.csv"},"totalRows":{"type":"integer","example":4},"imported":{"type":"integer","example":2},"updated":{"type":"integer","example":0},"invalid":{"type":"integer","example":1},"duplicates":{"type":"integer","example":1},"errors":{"type":"array","description":"The first rows that failed, with the line numbers from your file.","items":{"type":"object","required":["line","message"],"properties":{"line":{"type":"integer"},"message":{"type":"string"}}},"example":[{"line":3,"message":"\"not-a-number\" is not a mobile number in international format"}]},"createdAt":{"type":"string","format":"date-time","example":"2026-09-15T10:30:00.000Z"}}},"ContactListsResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactList"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"ContactsResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"ContactImportsResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactImport"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"CreateBatchRequest":{"type":"object","description":"Send exactly one of `templateId` (with per-recipient `variables`) or `text`. Live SMS to DLT countries (India) must use an approved template.","required":["recipients"],"additionalProperties":false,"properties":{"listId":{"type":"string","pattern":"^lst_[0-9a-hjkmnp-tv-z]{26}$","description":"lst_… — send to a saved contact list instead of listing recipients. Mutually exclusive with `recipients`.","example":"lst_01k55q8y4m3d9v7x2c6b0n1r8t"},"name":{"type":"string","maxLength":120,"description":"Label shown in reports."},"templateId":{"type":"string","minLength":4,"maxLength":40,"description":"Approved template (transactional or promotional).","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t"]},"text":{"type":"string","maxLength":1600,"description":"The same text for every recipient."},"senderId":{"type":"string","maxLength":16,"description":"Free text only; template campaigns use the registered header."},"scheduledAt":{"type":"string","format":"date-time","description":"Send time. Omit to start immediately."},"recipients":{"type":"array","minItems":1,"description":"Recipients; duplicates are collapsed. The maximum per campaign is set by SMS21 (5 000 by default).","items":{"type":"object","required":["to"],"additionalProperties":false,"properties":{"to":{"type":"string","minLength":8,"maxLength":16,"examples":["919876543210"]},"variables":{"type":"object","description":"Values for this recipient’s {{variables}}; template campaigns only.","additionalProperties":{"type":["string","number","boolean"]}}}}}}},"AddSuppressionRequest":{"type":"object","required":["mobile"],"additionalProperties":false,"properties":{"mobile":{"type":"string","minLength":8,"maxLength":16,"examples":["919876543210"]},"reason":{"type":"string","maxLength":200,"examples":["replied STOP"]}}},"Batch":{"type":"object","required":["batchId","name","status","environment","channel","type","templateId","senderId","scheduledAt","counts","failure","createdAt","startedAt","completedAt","cancelledAt"],"properties":{"batchId":{"type":"string","pattern":"^bat_[0-9a-hjkmnp-tv-z]{26}$","description":"Campaign ID.","examples":["bat_01k55q8y4m3d9v7x2c6b0n1r8t"]},"name":{"type":["string","null"],"description":"Label shown in reports.","examples":["October offers"]},"status":{"$ref":"#/components/schemas/BatchStatus"},"environment":{"$ref":"#/components/schemas/Environment"},"channel":{"type":"string","enum":["sms"]},"type":{"type":"string","enum":["transactional","promotional"]},"templateId":{"type":["string","null"],"pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"Template rendered for every recipient.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"senderId":{"type":["string","null"],"examples":["ACMESHOP"]},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"Send time of a scheduled campaign.","examples":["2026-09-15T10:30:00.000Z",null]},"counts":{"type":"object","required":["recipients","expanded","skipped","pending"],"properties":{"recipients":{"type":"integer","description":"Unique recipients accepted.","examples":[2500]},"expanded":{"type":"integer","description":"Recipients turned into messages.","examples":[2500]},"skipped":{"type":"integer","description":"Recipients not sent to (opted out or blocked destination).","examples":[12]},"pending":{"type":"integer","description":"Not expanded yet.","examples":[0]}}},"failure":{"type":["object","null"],"description":"Why expansion stopped (insufficient funds, suspended tenant).","required":["code","reason"],"properties":{"code":{"type":"string","examples":["INSUFFICIENT_BALANCE"]},"reason":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]},"startedAt":{"type":["string","null"],"format":"date-time","examples":["2026-09-15T10:30:00.000Z",null]},"completedAt":{"type":["string","null"],"format":"date-time","examples":["2026-09-15T10:30:00.000Z",null]},"cancelledAt":{"type":["string","null"],"format":"date-time","examples":[null]}}},"BatchRecipient":{"type":"object","required":["to","status","messageId","reason"],"properties":{"to":{"type":"string","description":"Recipient in E.164 digits without \"+\".","examples":["919876543210"]},"status":{"type":"string","enum":["pending","expanded","skipped","cancelled"]},"messageId":{"type":["string","null"],"pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"The message created for this recipient.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"reason":{"type":["string","null"],"description":"Why the number was skipped.","examples":["Number is on this company’s suppression list",null]}}},"Suppression":{"type":"object","required":["to","reason","source","createdAt"],"properties":{"to":{"type":"string","examples":["919876543210"]},"reason":{"type":["string","null"],"examples":["replied STOP",null]},"source":{"type":"string","examples":["api","dashboard"]},"createdAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]}}},"BatchResponse":{"type":"object","description":"A campaign.","required":["success","batchId","name","status","environment","channel","type","templateId","senderId","scheduledAt","counts","failure","createdAt","startedAt","completedAt","cancelledAt"],"properties":{"success":{"type":"boolean","const":true},"batchId":{"type":"string","pattern":"^bat_[0-9a-hjkmnp-tv-z]{26}$","description":"Campaign ID.","examples":["bat_01k55q8y4m3d9v7x2c6b0n1r8t"]},"name":{"type":["string","null"],"description":"Label shown in reports.","examples":["October offers"]},"status":{"$ref":"#/components/schemas/BatchStatus"},"environment":{"$ref":"#/components/schemas/Environment"},"channel":{"type":"string","enum":["sms"]},"type":{"type":"string","enum":["transactional","promotional"]},"templateId":{"type":["string","null"],"pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"Template rendered for every recipient.","examples":["tmpl_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"senderId":{"type":["string","null"],"examples":["ACMESHOP"]},"scheduledAt":{"type":["string","null"],"format":"date-time","description":"Send time of a scheduled campaign.","examples":["2026-09-15T10:30:00.000Z",null]},"counts":{"type":"object","required":["recipients","expanded","skipped","pending"],"properties":{"recipients":{"type":"integer","description":"Unique recipients accepted.","examples":[2500]},"expanded":{"type":"integer","description":"Recipients turned into messages.","examples":[2500]},"skipped":{"type":"integer","description":"Recipients not sent to (opted out or blocked destination).","examples":[12]},"pending":{"type":"integer","description":"Not expanded yet.","examples":[0]}}},"failure":{"type":["object","null"],"description":"Why expansion stopped (insufficient funds, suspended tenant).","required":["code","reason"],"properties":{"code":{"type":"string","examples":["INSUFFICIENT_BALANCE"]},"reason":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]},"startedAt":{"type":["string","null"],"format":"date-time","examples":["2026-09-15T10:30:00.000Z",null]},"completedAt":{"type":["string","null"],"format":"date-time","examples":["2026-09-15T10:30:00.000Z",null]},"cancelledAt":{"type":["string","null"],"format":"date-time","examples":[null]}}},"SuppressionResponse":{"type":"object","description":"A suppression entry.","required":["success","to","reason","source","createdAt"],"properties":{"success":{"type":"boolean","const":true},"to":{"type":"string","examples":["919876543210"]},"reason":{"type":["string","null"],"examples":["replied STOP",null]},"source":{"type":"string","examples":["api","dashboard"]},"createdAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]}}},"BatchListResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Batch"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"BatchRecipientListResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/BatchRecipient"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"SuppressionListResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Suppression"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"Inbound":{"type":"object","required":["inboundId","from","to","numberId","text","segments","keyword","action","receivedAt"],"properties":{"inboundId":{"type":"string","pattern":"^inb_[0-9a-hjkmnp-tv-z]{26}$","description":"Inbound message ID.","examples":["inb_01k55q8y4m3d9v7x2c6b0n1r8t"]},"from":{"type":"string","description":"Sender in E.164 digits without \"+\". Masked once the retention period passes.","examples":["919876543210"]},"to":{"type":"string","description":"Your number that received it.","examples":["919812345678"]},"numberId":{"type":["string","null"],"pattern":"^num_[0-9a-hjkmnp-tv-z]{26}$","description":"The number, while it is still assigned to you.","examples":["num_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"text":{"type":"string","description":"Text as received. Treat it as untrusted input."},"segments":{"type":"integer","examples":[1]},"keyword":{"type":["string","null"],"description":"First word, upper-cased, when SMS21 acted on it.","examples":["STOP",null]},"action":{"type":"string","enum":["none","opt_out","opt_in"],"description":"What SMS21 did: added the sender to your opt-out list, removed them, or nothing."},"receivedAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]}}},"SimulateInboundRequest":{"type":"object","description":"Test keys only. Delivers a reply through the sandbox as if a handset had sent it.","required":["from","to","text"],"additionalProperties":false,"properties":{"from":{"type":"string","minLength":8,"maxLength":16,"examples":["919876543210"]},"to":{"type":"string","minLength":8,"maxLength":16,"description":"One of your numbers, assigned to this project.","examples":["919812345678"]},"text":{"type":"string","minLength":1,"maxLength":1600,"examples":["STOP"]}}},"InboundResponse":{"type":"object","description":"A reply.","required":["success","inboundId","from","to","numberId","text","segments","keyword","action","receivedAt"],"properties":{"success":{"type":"boolean","const":true},"inboundId":{"type":"string","pattern":"^inb_[0-9a-hjkmnp-tv-z]{26}$","description":"Inbound message ID.","examples":["inb_01k55q8y4m3d9v7x2c6b0n1r8t"]},"from":{"type":"string","description":"Sender in E.164 digits without \"+\". Masked once the retention period passes.","examples":["919876543210"]},"to":{"type":"string","description":"Your number that received it.","examples":["919812345678"]},"numberId":{"type":["string","null"],"pattern":"^num_[0-9a-hjkmnp-tv-z]{26}$","description":"The number, while it is still assigned to you.","examples":["num_01k55q8y4m3d9v7x2c6b0n1r8t",null]},"text":{"type":"string","description":"Text as received. Treat it as untrusted input."},"segments":{"type":"integer","examples":[1]},"keyword":{"type":["string","null"],"description":"First word, upper-cased, when SMS21 acted on it.","examples":["STOP",null]},"action":{"type":"string","enum":["none","opt_out","opt_in"],"description":"What SMS21 did: added the sender to your opt-out list, removed them, or nothing."},"receivedAt":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]}}},"InboundListResponse":{"type":"object","required":["success","data","pagination"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Inbound"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"SimulateInboundResponse":{"type":"object","required":["success","accepted"],"properties":{"success":{"type":"boolean","const":true},"accepted":{"type":"boolean","enum":[true]}}},"MessageEvent":{"type":"object","description":"Sent when a message changes status.","required":["id","event","timestamp","messageId","status","channel","type","environment","failure"],"properties":{"id":{"type":"string","pattern":"^evt_[0-9a-hjkmnp-tv-z]{26}$","description":"Unique per event and identical on every retry of it: store it and ignore events you have already processed.","examples":["evt_01k55q8y4m3d9v7x2c6b0n1r8t"]},"event":{"type":"string","enum":["message.submitted","message.sent","message.delivered","message.failed","message.rejected","message.expired","message.received"],"description":"Event type."},"timestamp":{"type":"string","format":"date-time","description":"When the status changed. Events may arrive out of order; compare timestamps.","examples":["2026-09-15T10:30:00.000Z"]},"messageId":{"type":"string","pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$","description":"The message.","examples":["msg_01k55q8y4m3d9v7x2c6b0n1r8t"]},"status":{"$ref":"#/components/schemas/MessageStatus"},"channel":{"$ref":"#/components/schemas/MessageChannel"},"type":{"$ref":"#/components/schemas/MessageType"},"environment":{"$ref":"#/components/schemas/Environment"},"failure":{"type":["object","null"],"required":["code"],"properties":{"code":{"type":"string","enum":["MESSAGE_REJECTED","DELIVERY_FAILED","MESSAGE_EXPIRED"]}}}}},"InboundEvent":{"type":"object","description":"Sent when someone replies to one of your numbers.","required":["id","event","timestamp","inboundId","from","to","text","keyword","action"],"properties":{"id":{"type":"string","pattern":"^evt_[0-9a-hjkmnp-tv-z]{26}$","description":"Event ID; deduplicate on it.","examples":["evt_01k55q8y4m3d9v7x2c6b0n1r8t"]},"event":{"type":"string","enum":["message.received"]},"timestamp":{"type":"string","format":"date-time","examples":["2026-09-15T10:30:00.000Z"]},"inboundId":{"type":"string","pattern":"^inb_[0-9a-hjkmnp-tv-z]{26}$","description":"Inbound message ID.","examples":["inb_01k55q8y4m3d9v7x2c6b0n1r8t"]},"from":{"type":"string","examples":["919876543210"]},"to":{"type":"string","examples":["919812345678"]},"text":{"type":"string"},"keyword":{"type":["string","null"],"examples":["STOP",null]},"action":{"type":"string","enum":["none","opt_out","opt_in"]}}},"WebhookTestEvent":{"type":"object","description":"Sent on demand from the dashboard to check an endpoint.","required":["id","event","timestamp","endpointId","projectId"],"properties":{"id":{"type":"string","pattern":"^evt_[0-9a-hjkmnp-tv-z]{26}$","description":"Event ID.","examples":["evt_01k55q8y4m3d9v7x2c6b0n1r8t"]},"event":{"type":"string","const":"webhook.test"},"timestamp":{"type":"string","format":"date-time","description":"When the test was requested.","examples":["2026-09-15T10:30:00.000Z"]},"endpointId":{"type":"string","pattern":"^whk_[0-9a-hjkmnp-tv-z]{26}$","description":"The endpoint being tested.","examples":["whk_01k55q8y4m3d9v7x2c6b0n1r8t"]},"projectId":{"type":"string","pattern":"^prj_[0-9a-hjkmnp-tv-z]{26}$","description":"Project of the endpoint.","examples":["prj_01k55q8y4m3d9v7x2c6b0n1r8t"]}}}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the request safe to retry: repeating it with the same key and body returns the original result (with `Idempotent-Replayed: true`) instead of sending again. Use one key per logical message, e.g. derived from your order or login-attempt ID. Reusing a key with a different body is `409 IDEMPOTENCY_KEY_REUSED`.","schema":{"type":"string","pattern":"^[A-Za-z0-9._:-]{1,128}$"},"example":"order-1042-shipped"},"MessageId":{"name":"messageId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64},"example":"msg_01k55q8y4m3d9v7x2c6b0n1r8t"},"Limit":{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"Cursor":{"name":"cursor","in":"query","required":false,"description":"`pagination.nextCursor` of the previous page.","schema":{"type":"string","maxLength":128}}},"headers":{"RequestId":{"description":"Unique request ID (also `requestId` in error bodies). Quote it to support.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Requests allowed per minute for this API key.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying (on `RATE_LIMITED` and `OTP_RESEND_COOLDOWN`).","schema":{"type":"integer","minimum":0}},"IdempotentReplayed":{"description":"Present when the response replays an earlier request with the same Idempotency-Key; nothing new was sent or charged.","schema":{"type":"string","enum":["true"]}}},"responses":{"Unauthorized":{"description":"The API key is missing, malformed, unknown, revoked or expired.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing":{"summary":"AUTHENTICATION_REQUIRED","value":{"success":false,"error":{"code":"AUTHENTICATION_REQUIRED","message":"An API key is required; send it in the X-API-Key header"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"invalid":{"summary":"API_KEY_INVALID","value":{"success":false,"error":{"code":"API_KEY_INVALID","message":"Invalid API key"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"revoked":{"summary":"API_KEY_REVOKED","value":{"success":false,"error":{"code":"API_KEY_REVOKED","message":"This API key has been revoked"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"PaymentRequired":{"description":"Live keys only: the wallet cannot cover the message. Add funds; retrying will not help.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficientBalance":{"summary":"INSUFFICIENT_BALANCE","value":{"success":false,"error":{"code":"INSUFFICIENT_BALANCE","message":"Insufficient wallet balance"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"Forbidden":{"description":"The key may not do this: missing scope, IP not on the allow-list, company, project or key suspended by SMS21, project archived, wallet suspended or destination blocked.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"scope":{"summary":"INSUFFICIENT_SCOPE","value":{"success":false,"error":{"code":"INSUFFICIENT_SCOPE","message":"This API key does not have the required scope","details":[{"message":"requires scope sms:send"}]},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"ip":{"summary":"IP_NOT_ALLOWED","value":{"success":false,"error":{"code":"IP_NOT_ALLOWED","message":"Requests from 198.51.100.7 are not allowed for this API key"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"suspended":{"summary":"COMPANY_SUSPENDED","value":{"success":false,"error":{"code":"COMPANY_SUSPENDED","message":"This company is suspended"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"NotFound":{"description":"Not found, or not visible to the API key project.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"notFound":{"summary":"NOT_FOUND","value":{"success":false,"error":{"code":"NOT_FOUND","message":"Message not found"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"TooManyRequests":{"description":"Rate limited. Wait `Retry-After` seconds, then retry with the same Idempotency-Key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"rateLimited":{"summary":"RATE_LIMITED","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded for this API key"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}},"ServiceUnavailable":{"description":"Temporarily unavailable, or live sending paused by SMS21. Retry later with the same Idempotency-Key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"unavailable":{"summary":"SERVICE_UNAVAILABLE","value":{"success":false,"error":{"code":"SERVICE_UNAVAILABLE","message":"Service temporarily unavailable"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}},"paused":{"summary":"SENDING_PAUSED","value":{"success":false,"error":{"code":"SENDING_PAUSED","message":"SMS sending is paused"},"requestId":"0199491c-8f3a-7c2e-9b1d-5e7f3a2c4b6d"}}}}}}},"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Project API key: `sms21_live_…` or `sms21_test_…`. Server-side use only."},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"SMS21 API key","description":"The same API key sent as `Authorization: Bearer sms21_…`."}}}}