Calisero SMS API documentation

Everything you need to send transactional SMS through the Calisero API: endpoints, parameters, request samples and every response you can expect.

Introduction

API documentation for sending and managing transactional SMS messages via Calisero.

The API accepts HTTPS connections with TLS 1.2 or TLS 1.3 only. From 1 November 2026, a client that offers only TLS 1.0 or TLS 1.1 is refused during the TLS handshake: it gets no HTTP response, and nothing is sent or billed. Call https:// directly: a request to http:// is redirected, and most HTTP clients turn a redirected POST into a GET without its body.

New to the API? The quick-start guide takes you from an API key to your first delivered SMS, and the delivery webhooks guide shows how to receive the status of every message.

Base URL

https://rest.calisero.ro/api/v1

Authentication

Every request is authenticated with a bearer token. Generate an API key in your account and send it in the Authorization header. Keys are shown only once, at creation, so store yours in a secret manager.

cURL
curl -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  'https://rest.calisero.ro/api/v1/messages'

Daily sending limit

Every account has its own daily sending limit: how many messages it can send in a day. A new account starts with a limit of 200 messages a day and keeps it until we change it. To raise it, write to [email protected] with your account and the daily volume you expect.

What counts, once per message whatever its number of parts:

  • every real message the account creates, through POST /messages or the dashboard;
  • every OTP code sent with POST /verifications;
  • a scheduled message, on the day it is created, not on the day it is sent.

Test messages, from a sandbox account or with a sandbox API key, never count and are never refused.

The day is the calendar day in Romania: the limit resets at midnight, Romania time (Europe/Bucharest), whatever time zone you send from.

While the account has a limit, every message or verification created with POST /messages or POST /verifications is answered with two headers:

Header Description Example
X-Daily-Limit The account's daily limit. 200
X-Daily-Remaining How many messages the account can still send today, after this one. 199

Once the limit is reached, both endpoints refuse new messages until midnight, Romania time, with 429 Too Many Requests; nothing is sent and nothing is billed:

{
    "message": "This account can send at most 200 messages a day. The limit resets at midnight, Romania time (2026-10-01T00:00:00+03:00). Contact us to raise it.",
    "code": "daily_limit_exceeded",
    "daily_limit": 200,
    "daily_remaining": 0,
    "resets_at": "2026-10-01T00:00:00+03:00",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The refusal carries Retry-After (the seconds until midnight, Romania time), X-Daily-Limit and X-Daily-Remaining: 0. Send again after resets_at, or ask us to raise the limit.

The limit, what is left of it and what the account used of it today are also in GET /accounts/{accountId} (daily_limit, daily_remaining, sent_today) and in every delivery status callback (dailyLimit, dailyRemaining, sentToday), as they stood when it was sent.

The request rate limit is another 429: each API user may make 240 requests a minute, on every endpoint. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; above the rate, the API answers 429 with {"message": "Too Many Attempts.", "trace_id": "…"} and no code. Tell the two apart by the body's code:

Daily sending limit Request rate limit
Body {"code": "daily_limit_exceeded", …} {"message": "Too Many Attempts.", "trace_id": "…"}
Headers Retry-After, X-Daily-Limit, X-Daily-Remaining: 0 Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining: 0, X-RateLimit-Reset (a Unix timestamp)
Send again after midnight, Romania time after Retry-After seconds

Trace ID

Every API answer carries an X-Trace-Id header: the id of that request. The messages it creates, their delivery reports and the callbacks they lead to carry the same id.

HTTP/1.1 201 Created
X-Trace-Id: 9b80eef1-49d4-4502-85a8-febb68cc11a7

An error (any 4xx or 5xx answer) also has it in its body, as trace_id: no message was created, so there is no message id to look for.

{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Keep it in your logs. In the dashboard, Developers → Debug shows everything that happened to the request: the API call, the messages, their delivery and the callbacks; the id of a message, a verification or a callback works there too. The trace id is also on each message, API log and callback in the dashboard, and quoting it lets us find the request at once.

Accounts

Retrieve account information

GET /accounts/{accountId}

Parameters

NameInTypeDescription
accountId required path string (uuid) The UUID of the account

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/accounts/{accountId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Account details

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "code": "string",
        "name": "string",
        "description": "string",
        "fiscal_code": "string",
        "registry_number": "string",
        "iban": "string",
        "city": "string",
        "state": "string",
        "country": "string",
        "address": "string",
        "postal_code": 1,
        "email": "string",
        "phone": "string",
        "contact_person": "string",
        "credit": 1,
        "status": "string",
        "sandbox": true,
        "daily_limit": 1,
        "daily_remaining": 1,
        "sent_today": 1,
        "created_at": "2026-07-08T06:42:05.000000Z"
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Messages

Create a new message

POST /messages

Sends an SMS now, or at schedule_at.

Daily sending limit. Every account has its own daily limit; a new account starts with 200 messages a day and keeps it until we raise it on request. Each real message the account creates counts once, whatever its number of parts: the OTP codes of POST /verifications too, and a scheduled message on the day it is created. Test messages (a sandbox account or a sandbox API key) never count and are never refused. The day ends at midnight, Romania time (Europe/Bucharest). While the account has a limit, the answer to a created message carries X-Daily-Limit and X-Daily-Remaining (what is left today, after this message). Once the limit is reached the answer is 429 with "code": "daily_limit_exceeded", Retry-After and X-Daily-Remaining: 0 until midnight, Romania time: nothing is sent and nothing is billed. See Daily sending limit.

Request rate limit. Separately, each API user may make 240 requests a minute. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; above the rate the answer is 429 with {"message": "Too Many Attempts.", "trace_id": "…"}, no code, plus Retry-After and X-RateLimit-Reset.

Request body

FieldTypeDescription
recipient required string Recipient's phone number (in E.164 format)
body required string Text body of the message
visible_body string Visible body for display purposes
validity integer Message validity period in hours
schedule_at string (date-time) Scheduled send time in 'YYYY-MM-DD HH:MM:SS' format
callback_url string (uri) Optional callback URL to receive delivery reports. It must be an http or https address on the public Internet: private, loopback and link-local addresses are refused, and redirects are not followed.
sender string Custom sender ID. It must be a sender already approved for your account (Messages > Senders in the dashboard), written exactly as approved: the match is case-sensitive. A sender that is unknown, pending, rejected or not approved yet is refused with 422 ("The sender is invalid or is not approved.") and nothing is sent. Leave the field out, or send null, to use the default sender. CALISERO in the examples is a placeholder, not a sender you can use.
shorten_urls boolean If true, URLs inside the body are shortened and returned in the response

Request

cURL
curl -X POST 'https://rest.calisero.ro/api/v1/messages' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "recipient": "string",
    "body": "string",
    "visible_body": "string",
    "validity": 1,
    "schedule_at": "2026-07-08T06:42:05.000000Z",
    "callback_url": "string",
    "sender": "string",
    "shorten_urls": true
}'

Responses

Message created successfully

HeaderTypeDescription
X-Daily-Limit integer The account's daily sending limit: how many messages it can send in a day, midnight to midnight, Romania time. Sent only while the account has a limit. Example: 1000.
X-Daily-Remaining integer How many messages the account can still send today, after this one. Sent only while the account has a limit. Example: 873.
X-RateLimit-Limit integer The request rate limit: how many requests the API user may make a minute. Sent with every answer. Example: 240.
X-RateLimit-Remaining integer How many requests the API user may still make this minute. Sent with every answer; 0 on a refusal of the request rate limit. Example: 239.
201 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "recipient": "string",
        "body": "string",
        "parts": 1,
        "created_at": "2026-07-08T06:42:05.000000Z",
        "scheduled_at": "2026-07-08T06:42:05.000000Z",
        "sent_at": "2026-07-08T06:42:05.000000Z",
        "delivered_at": "2026-07-08T06:42:05.000000Z",
        "callback_url": "string",
        "status": "string",
        "sender": "string",
        "shortened_urls": [
            {
                "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
                "original_link": "string",
                "shortened_link": "string",
                "click_count": 1,
                "last_click": "2026-07-08T06:42:05.000000Z",
                "created_at": "2026-07-08T06:42:05.000000Z"
            }
        ]
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The payload failed validation. The response lists the offending fields.

422 response
{
    "message": "The given data was invalid.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    },
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Too many requests, for one of two reasons. The account reached its daily sending limit: the body carries the code daily_limit_exceeded, X-Daily-Remaining is 0 and Retry-After counts the seconds until midnight, Romania time. Or the user exceeded the request rate limit of 240 requests a minute: the body is {"message": "Too Many Attempts.", "trace_id": "…"} without a code, X-RateLimit-Remaining is 0, and Retry-After and X-RateLimit-Reset say when to retry. Nothing is sent and nothing is billed.

HeaderTypeDescription
Retry-After integer Seconds to wait before sending again: until midnight, Romania time, for the daily sending limit; until the request rate limit frees up for that one. Example: 21600.
X-Daily-Limit integer The account's daily sending limit: how many messages it can send in a day, midnight to midnight, Romania time. Sent only while the account has a limit. Example: 1000.
X-Daily-Remaining integer 0 when the daily sending limit refused the message; absent on a refusal of the request rate limit. Example: 0.
X-RateLimit-Limit integer The request rate limit: how many requests the API user may make a minute. Sent with every answer. Example: 240.
X-RateLimit-Remaining integer How many requests the API user may still make this minute. Sent with every answer; 0 on a refusal of the request rate limit. Example: 239.
X-RateLimit-Reset integer Only on a refusal of the request rate limit: when requests are accepted again, as a Unix timestamp. Example: 1790000000.
429 response
{
    "message": "This account can send at most 1,000 messages a day. The limit resets at midnight, Romania time (2026-10-01T00:00:00+03:00). Contact us to raise it.",
    "code": "daily_limit_exceeded",
    "daily_limit": 1000,
    "daily_remaining": 0,
    "resets_at": "2026-10-01T00:00:00+03:00",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Retrieve a list of messages

GET /messages

Parameters

NameInTypeDescription
page query integer Page number for pagination

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/messages' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

List of messages

200 response
{
    "data": [
        {
            "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
            "recipient": "string",
            "body": "string",
            "parts": 1,
            "created_at": "2026-07-08T06:42:05.000000Z",
            "scheduled_at": "2026-07-08T06:42:05.000000Z",
            "sent_at": "2026-07-08T06:42:05.000000Z",
            "delivered_at": "2026-07-08T06:42:05.000000Z",
            "callback_url": "string",
            "status": "string",
            "sender": "string",
            "shortened_urls": [
                {
                    "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
                    "original_link": "string",
                    "shortened_link": "string",
                    "click_count": 1,
                    "last_click": "2026-07-08T06:42:05.000000Z",
                    "created_at": "2026-07-08T06:42:05.000000Z"
                }
            ]
        }
    ],
    "links": {
        "first": "string",
        "last": "string",
        "prev": "string",
        "next": "string"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "string",
        "per_page": 1,
        "to": 1
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Retrieve a single message

GET /messages/{messageId}

Parameters

NameInTypeDescription
messageId required path string (uuid) The UUID of the message

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/messages/{messageId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Message details

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "recipient": "string",
        "body": "string",
        "parts": 1,
        "created_at": "2026-07-08T06:42:05.000000Z",
        "scheduled_at": "2026-07-08T06:42:05.000000Z",
        "sent_at": "2026-07-08T06:42:05.000000Z",
        "delivered_at": "2026-07-08T06:42:05.000000Z",
        "callback_url": "string",
        "status": "string",
        "sender": "string",
        "shortened_urls": [
            {
                "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
                "original_link": "string",
                "shortened_link": "string",
                "click_count": 1,
                "last_click": "2026-07-08T06:42:05.000000Z",
                "created_at": "2026-07-08T06:42:05.000000Z"
            }
        ]
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Delete a message

DELETE /messages/{messageId}

Parameters

NameInTypeDescription
messageId required path string (uuid) The UUID of the message

Request

cURL
curl -X DELETE 'https://rest.calisero.ro/api/v1/messages/{messageId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Message deleted successfully

200 response
[]

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Opt-outs

Retrieve a list of opt-outs

GET /opt-outs

Parameters

NameInTypeDescription
page query integer Page number for pagination

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/opt-outs' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

List of opt-outs

200 response
{
    "data": [
        {
            "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
            "phone": "string",
            "reason": "string",
            "created_at": "2026-07-08T06:42:05.000000Z",
            "updated_at": "2026-07-08T06:42:05.000000Z"
        }
    ],
    "links": {
        "first": "string",
        "last": "string",
        "prev": "string",
        "next": "string"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "string",
        "per_page": 1,
        "to": 1
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Create a new opt-out

POST /opt-outs

Request body

FieldTypeDescription
phone required string Phone number to opt-out (in E.164 format)
reason string Reason for opt-out

Request

cURL
curl -X POST 'https://rest.calisero.ro/api/v1/opt-outs' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "phone": "string",
    "reason": "string"
}'

Responses

Opt-out created successfully

201 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "reason": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "updated_at": "2026-07-08T06:42:05.000000Z"
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The payload failed validation. The response lists the offending fields.

422 response
{
    "message": "The given data was invalid.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    },
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Retrieve a single opt-out

GET /opt-outs/{optOutId}

Parameters

NameInTypeDescription
optOutId required path string (uuid) The UUID of the opt-out

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/opt-outs/{optOutId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Opt-out details

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "reason": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "updated_at": "2026-07-08T06:42:05.000000Z"
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Update an opt-out

PUT /opt-outs/{optOutId}

Parameters

NameInTypeDescription
optOutId required path string (uuid) The UUID of the opt-out

Request body

FieldTypeDescription
phone required string Phone number to opt-out (in E.164 format)
reason string Reason for opt-out

Request

cURL
curl -X PUT 'https://rest.calisero.ro/api/v1/opt-outs/{optOutId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "phone": "string",
    "reason": "string"
}'

Responses

Opt-out updated successfully

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "reason": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "updated_at": "2026-07-08T06:42:05.000000Z"
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The payload failed validation. The response lists the offending fields.

422 response
{
    "message": "The given data was invalid.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    },
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Delete an opt-out

DELETE /opt-outs/{optOutId}

Parameters

NameInTypeDescription
optOutId required path string (uuid) The UUID of the opt-out

Request

cURL
curl -X DELETE 'https://rest.calisero.ro/api/v1/opt-outs/{optOutId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Opt-out deleted successfully

200 response
[]

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Verifications (OTP & 2FA)

Retrieve a list of verifications

GET /verifications

Parameters

NameInTypeDescription
page query integer Page number for pagination
status query string Filter by verification status

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/verifications' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

List of verifications

200 response
{
    "data": [
        {
            "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
            "phone": "string",
            "brand": "string",
            "status": "verified",
            "template": "string",
            "created_at": "2026-07-08T06:42:05.000000Z",
            "expires_at": "2026-07-08T06:42:05.000000Z",
            "verified_at": "2026-07-08T06:42:05.000000Z",
            "attempts": 1,
            "expired": true
        }
    ],
    "links": {
        "first": "string",
        "last": "string",
        "prev": "string",
        "next": "string"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "string",
        "per_page": 1,
        "to": 1
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Create a new verification request

POST /verifications

Generates a code, sends it by SMS to phone and keeps it for PUT /verifications/validate.

Daily sending limit. The code's SMS counts towards the account's daily limit like any message of POST /messages (a new account starts with 200 messages a day); the day ends at midnight, Romania time (Europe/Bucharest). While the account has a limit, the answer to a created verification carries X-Daily-Limit and X-Daily-Remaining. Once the limit is reached the answer is 429 with "code": "daily_limit_exceeded", Retry-After and X-Daily-Remaining: 0 until midnight, Romania time: no code is sent and nothing is billed. See Daily sending limit.

Request rate limit. Separately, each API user may make 240 requests a minute; above the rate the answer is 429 with {"message": "Too Many Attempts.", "trace_id": "…"}, no code, plus Retry-After and X-RateLimit-Reset.

Request body

FieldTypeDescription
phone required string Phone number to verify (E.164 format)
brand string Brand used in default message when template is not provided. It may not contain links (web addresses, domains, emails or IP addresses).
template string Custom SMS template that MUST include {code}. It may not contain links (web addresses, domains, emails or IP addresses): the code is sent from a shared sender.
expires_in integer OTP validity in minutes (1-10). Default 5.

Request

cURL
curl -X POST 'https://rest.calisero.ro/api/v1/verifications' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "phone": "string",
    "brand": "string",
    "template": "string",
    "expires_in": 1
}'

Responses

Verification created successfully

HeaderTypeDescription
X-Daily-Limit integer The account's daily sending limit: how many messages it can send in a day, midnight to midnight, Romania time. Sent only while the account has a limit. Example: 1000.
X-Daily-Remaining integer How many messages the account can still send today, after this one. Sent only while the account has a limit. Example: 873.
X-RateLimit-Limit integer The request rate limit: how many requests the API user may make a minute. Sent with every answer. Example: 240.
X-RateLimit-Remaining integer How many requests the API user may still make this minute. Sent with every answer; 0 on a refusal of the request rate limit. Example: 239.
201 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "brand": "string",
        "status": "verified",
        "template": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "expires_at": "2026-07-08T06:42:05.000000Z",
        "verified_at": "2026-07-08T06:42:05.000000Z",
        "attempts": 1,
        "expired": true
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The payload failed validation. The response lists the offending fields.

422 response
{
    "message": "The given data was invalid.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    },
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Too many requests, for one of two reasons. The account reached its daily sending limit: the body carries the code daily_limit_exceeded, X-Daily-Remaining is 0 and Retry-After counts the seconds until midnight, Romania time. Or the user exceeded the request rate limit of 240 requests a minute: the body is {"message": "Too Many Attempts.", "trace_id": "…"} without a code, X-RateLimit-Remaining is 0, and Retry-After and X-RateLimit-Reset say when to retry. No code is sent and nothing is billed.

HeaderTypeDescription
Retry-After integer Seconds to wait before sending again: until midnight, Romania time, for the daily sending limit; until the request rate limit frees up for that one. Example: 21600.
X-Daily-Limit integer The account's daily sending limit: how many messages it can send in a day, midnight to midnight, Romania time. Sent only while the account has a limit. Example: 1000.
X-Daily-Remaining integer 0 when the daily sending limit refused the message; absent on a refusal of the request rate limit. Example: 0.
X-RateLimit-Limit integer The request rate limit: how many requests the API user may make a minute. Sent with every answer. Example: 240.
X-RateLimit-Remaining integer How many requests the API user may still make this minute. Sent with every answer; 0 on a refusal of the request rate limit. Example: 239.
X-RateLimit-Reset integer Only on a refusal of the request rate limit: when requests are accepted again, as a Unix timestamp. Example: 1790000000.
429 response
{
    "message": "This account can send at most 1,000 messages a day. The limit resets at midnight, Romania time (2026-10-01T00:00:00+03:00). Contact us to raise it.",
    "code": "daily_limit_exceeded",
    "daily_limit": 1000,
    "daily_remaining": 0,
    "resets_at": "2026-10-01T00:00:00+03:00",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Retrieve a single verification

GET /verifications/{verificationId}

Parameters

NameInTypeDescription
verificationId required path string (uuid) The UUID of the verification

Request

cURL
curl -X GET 'https://rest.calisero.ro/api/v1/verifications/{verificationId}' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json'

Responses

Verification details

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "brand": "string",
        "status": "verified",
        "template": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "expires_at": "2026-07-08T06:42:05.000000Z",
        "verified_at": "2026-07-08T06:42:05.000000Z",
        "attempts": 1,
        "expired": true
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Verify a code (OTP) for a phone number

PUT /verifications/validate

Request body

FieldTypeDescription
phone required string Phone number to verify (E.164 format)
code required string 6 character OTP code (case-insensitive)

Request

cURL
curl -X PUT 'https://rest.calisero.ro/api/v1/verifications/validate' \
  -H 'Authorization: Bearer {token}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "phone": "string",
    "code": "string"
}'

Responses

Phone number verified successfully (idempotent if already verified)

200 response
{
    "data": {
        "id": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
        "phone": "string",
        "brand": "string",
        "status": "verified",
        "template": "string",
        "created_at": "2026-07-08T06:42:05.000000Z",
        "expires_at": "2026-07-08T06:42:05.000000Z",
        "verified_at": "2026-07-08T06:42:05.000000Z",
        "attempts": 1,
        "expired": true
    }
}

The API key is missing or invalid.

401 response
{
    "message": "Unauthenticated.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The API key is valid but not allowed to perform this action.

403 response
{
    "message": "This action is unauthorized.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

No resource matches the given identifier.

404 response
{
    "message": "Resource not found!",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

The HTTP method is not supported for this endpoint.

405 response
{
    "message": "Method not allowed.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Validation errors (e.g., code incorrect, expired, attempts exceeded)

422 response
{
    "message": "string",
    "errors": [],
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Something went wrong on our side. Retry, and contact support if it persists.

500 response
{
    "message": "Server error.",
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Webhooks (Callbacks)

Delivery status webhook (Calisero -> Client)

POST https://yoursite.com/your-callback-url

Calisero sends delivery status updates to the callback URL you provide when creating a message (callback_url). This callback documents the JSON payload you should expect to receive. Your endpoint must return any 2xx status code to acknowledge receipt. Only a failed connection or no answer within 2 seconds is retried, at most 5 attempts in total (after 1 s, 10 s, 1 min and 6 min); a non-2xx response is not retried.

Request body

FieldTypeDescription
price required number (float) Price charged for the message part(s).
sender required string Sender ID or phone number.
status required string Delivery status.
messageId required string (uuid) Unique identifier of the message.
recipient required string Recipient MSISDN in E.164 format.
sentAt string (date-time) Timestamp when the message was sent to the network (present for statuses sent or delivered).
deliveredAt string (date-time) Timestamp when the handset confirmed delivery (present only for delivered).
scheduleAt string (date-time) Original scheduled sending time if the message was scheduled.
remainingBalance number (float) Remaining account balance after billing (if available).
dailyLimit integer The account's daily sending limit when the callback was sent; null when no daily limit applies. The same as daily_limit in GET /accounts/{accountId}.
dailyRemaining integer How many messages the account could still send today when the callback was sent; null when no daily limit applies. The same as daily_remaining in GET /accounts/{accountId}.
sentToday integer The real messages the account created today, Romania time, when the callback was sent: OTP codes included, test messages left out, one per message whatever its parts. The same as sent_today in GET /accounts/{accountId}.

Callback Calisero sends you

Calisero performs this call against the callback_url you registered. Your endpoint only has to accept it and answer 200.

cURL
curl -X POST 'https://yoursite.com/your-callback-url' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "price": 1,
    "sender": "string",
    "status": "sent",
    "messageId": "0197e8c5-2c11-72cf-8057-bca8f619bd52",
    "recipient": "string",
    "sentAt": "2026-07-08T06:42:05.000000Z",
    "deliveredAt": "2026-07-08T06:42:05.000000Z",
    "scheduleAt": "2026-07-08T06:42:05.000000Z",
    "remainingBalance": 1,
    "dailyLimit": 1,
    "dailyRemaining": 1,
    "sentToday": 1
}'

Responses

Any 2xx status acknowledges the report. Answer within 2 seconds and do the work afterwards: a slower answer counts as a failure and the report is sent again.