API documentation

REST API for sending and tracking OTP messages from your own systems. Authenticate with your key as a Bearer token — all requests and responses are JSON.

You need an API key from your account to call these endpoints — create one under API in your dashboard, or Sign in · Create account →

Base URL

https://5cotp.com/api/v1

Authentication

Pass your key as a Bearer token:

Authorization: Bearer sk_xxxxxxxxxx_xxxxxxxx…

POST/messages

5cotp is a one-time-code delivery service: every message must carry a code — text is required to contain a literal {otp}, and to must resolve to exactly one recipient (a code shared across several numbers would let any one of them authenticate as any other, which defeats the point of an OTP — there is no bulk sending here).

Two ways to supply the code — set exactly one:

1) generate_otp: true (+ optional otp_length, 4–8, default 6) — we generate a cryptographically random code, substitute it into {otp}, and return it as otp_code in the response.

2) otp_value — you already generated the code on your side; we substitute it into {otp} as-is (any non-empty string up to 10 characters) and echo it back as otp_code for confirmation.

curl -X POST https://5cotp.com/api/v1/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender": "YourBrand",
    "to": "+37120000001",
    "text": "Your verification code is {otp}",
    "generate_otp": true,
    "otp_length": 6
  }'

# 201 Created
{ "ok": true, "sms_id": 43, "accepted": 1, "invalid": 0,
  "mode": "live", "total_cost": "0.0500", "currency": "EUR", "otp_code": "482913" }

Or with your own code:

curl -X POST https://5cotp.com/api/v1/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender": "YourBrand",
    "to": "+37120000001",
    "text": "Your verification code is {otp}",
    "otp_value": "482913"
  }'

Request parameters

ParameterDescription
senderrequired Any sender ID: a name of up to 11 characters — Latin letters, digits, spaces and . _ - &, at least one letter — or a number of 3–15 digits (a leading + and spaces are dropped). One you have not used before is added to your Sender IDs automatically. A value outside these limits is rejected (400 bad_sender); an account holds up to 50 sender IDs (400 too_many_senders). Carriers in some countries may still reject a valid sender under their own local rules.
torequired The recipient's phone number, one only — an array or comma-separated string with more than one number is rejected (400 otp_single_recipient).
textrequired The message body. Must contain a literal {otp} — that's where the code gets substituted. Missing it is rejected (400 missing_otp_placeholder).
generate_otpone of these two Set to true to have us generate the code and substitute it into {otp}.
otp_valueone of these two A code you already generated yourself — used as-is (any non-empty string, up to 10 characters), substituted into {otp}. Passing both this and generate_otp is rejected (400 otp_conflict); passing neither also is (400 otp_required).
otp_lengthoptional Digit count for a generate_otp code — 4 to 8, default 6. Ignored with otp_value.
countryoptional Destination country name (see GET /countries below for the exact accepted strings). When omitted, we guess it from to's calling code — pass it explicitly if you need to be sure, since some calling codes (e.g. +1) are shared by several countries. If we can't guess and none was given: 400 country_required.
idempotency_keyoptional Your own unique value for this specific send attempt (also accepted as an Idempotency-Key header instead of a body field). If a request with the same key arrives again — e.g. your own retry after a timeout — we don't send or charge again; we just return the result of the original attempt. Without one, a genuine network retry could send and charge twice.

Response fields (201 Created)

FieldDescription
sms_idThe id of this message. Use it with GET /messages/<sms_id> to check delivery status.
acceptedRecipients accepted — always 1 on success.
invalidHow many entries in to were not valid phone numbers (skipped). If none is valid, the request fails with 400 no_recipients.
modelive (really sent, billed), test (really sent, free, counted against your trial allowance), or simulated (nothing delivered — live sending is off for this account).
total_cost, currencyThe price of this send. It is debited only when mode is live; test (trial) and simulated sends are free.
otp_codeThe code that was sent — either the one we generated, or your own otp_value echoed back for confirmation.

Other error codes you may see: bad_otp_length, otp_value_too_long, no_recipients (to is not a valid phone number), no_route (that country isn't one we deliver to), and 402 insufficient_funds / test_limit (balance too low / free trial messages used up).

GET/countries

The exact country names country accepts, .

curl https://5cotp.com/api/v1/countries -H "Authorization: Bearer $API_KEY"

{ "ok": true, "countries": ["Afghanistan", "Albania", "Algeria", …] }

GET/messages/<sms_id>

Delivery status of one send.

curl https://5cotp.com/api/v1/messages/43 -H "Authorization: Bearer $API_KEY"

{ "ok": true, "sms_id": 43, "mode": "live", "total": 1,
  "status": { "delivered": 1, "sent": 0, "queued": 0, "undelivered": 0,
              "failed": 0, "simulated": 0 } }

GET/balance

Current wallet balance — poll this to top up before sends start failing with 402 insufficient_funds.

curl https://5cotp.com/api/v1/balance -H "Authorization: Bearer $API_KEY"

{ "ok": true, "balance": "150.50", "currency": "EUR", "test_mode": false, "test_sms_left": null }

Webhooks

When a message reaches a final status (delivered, undelivered or failed), we POST it to this URL — delivery statuses are checked every minute. Requests are HMAC-SHA256 signed with your secret (header X-5cotp-Signature). You can also check any message yourself with GET /messages/{sms_id}. Set the URL and secret under API → Webhook in your dashboard. The same status can occasionally arrive twice (for example after a retry) — deduplicate on message_id + status.

POST https://your-server.example/hooks/5cotp
X-5cotp-Signature: <HMAC-SHA256 of the body with your secret>

{ "event": "message.status", "sms_id": 43, "message_id": 1187, "phone": "37120000001",
  "status": "delivered", "dlr_status": "delivrd", "smsc_message_id": "a1b2c3" }

Getting a key

API keys are created and revoked by the account owner in the dashboard (API section). Create an account yourself, confirm your email, and the key is one click away.