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.
https://5cotp.com/api/v1
Pass your key as a Bearer token:
Authorization: Bearer sk_xxxxxxxxxx_xxxxxxxx…
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"
}'
| Parameter | Description | |
|---|---|---|
sender | required | 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. |
to | required | The recipient's phone number, one only — an array or comma-separated string with more than one number is rejected (400 otp_single_recipient). |
text | required | The message body. Must contain a literal {otp} — that's where the code gets substituted. Missing it is rejected (400 missing_otp_placeholder). |
generate_otp | one of these two | Set to true to have us generate the code and substitute it into {otp}. |
otp_value | one 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_length | optional | Digit count for a generate_otp code — 4 to 8, default 6. Ignored with otp_value. |
country | optional | 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_key | optional | 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. |
| Field | Description |
|---|---|
sms_id | The id of this message. Use it with GET /messages/<sms_id> to check delivery status. |
accepted | Recipients accepted — always 1 on success. |
invalid | How many entries in to were not valid phone numbers (skipped). If none is valid, the request fails with 400 no_recipients. |
mode | live (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, currency | The price of this send. It is debited only when mode is live; test (trial) and simulated sends are free. |
otp_code | The 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).
The exact country names country accepts, .
curl https://5cotp.com/api/v1/countries -H "Authorization: Bearer $API_KEY"
{ "ok": true, "countries": ["Afghanistan", "Albania", "Algeria", …] }
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 } }
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 }
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" }
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.