Documentația API SMS Calisero

Tot ce îți trebuie ca să trimiți SMS-uri tranzacționale prin API-ul Calisero: endpoint-uri, parametri, exemple de cereri și fiecare răspuns la care te poți aștepta.

Introducere

Documentația API pentru trimiterea și gestionarea SMS-urilor tranzacționale prin Calisero.

API-ul acceptă doar conexiuni HTTPS cu TLS 1.2 sau TLS 1.3. Din 1 noiembrie 2026, un client care oferă doar TLS 1.0 sau TLS 1.1 este refuzat la negocierea TLS: nu primește niciun răspuns HTTP și nu se trimite și nu se facturează nimic. Apelează direct https://: o cerere către http:// este redirecționată, iar majoritatea clienților HTTP transformă un POST redirecționat într-un GET fără corp.

Abia începi cu API-ul? Ghidul de integrare te duce de la o cheie API la primul SMS livrat, iar ghidul pentru webhook-urile de livrare îți arată cum primești statusul fiecărui mesaj.

URL de bază

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

Autentificare

Fiecare cerere se autentifică cu un token de tip bearer. Generează o cheie API în contul tău și trimite-o în header-ul Authorization. Cheile se afișează o singură dată, la creare, așa că păstrează-o într-un manager de secrete.

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

Limita zilnică de trimitere

Fiecare cont are propria limită zilnică de trimitere: câte mesaje poate trimite într-o zi. Un cont nou pornește cu o limită de 200 de mesaje pe zi și o păstrează până când o schimbăm noi. Ca s-o mărești, scrie-ne la [email protected], cu contul tău și volumul zilnic la care te aștepți.

Ce se numără, o singură dată per mesaj, oricâte părți ar avea:

  • fiecare mesaj real creat de cont, prin POST /messages sau din dashboard;
  • fiecare cod OTP trimis cu POST /verifications;
  • un mesaj programat, în ziua în care este creat, nu în ziua în care este trimis.

Mesajele de test, dintr-un cont în sandbox sau cu o cheie API de sandbox, nu se numără niciodată și nu sunt refuzate niciodată.

Ziua este ziua calendaristică din România: limita se resetează la miezul nopții, ora României (Europe/Bucharest), indiferent din ce fus orar trimiți.

Cât timp contul are o limită, fiecare mesaj sau verificare creată cu POST /messages sau POST /verifications primește în răspuns două headere:

Header Descriere Exemplu
X-Daily-Limit Limita zilnică a contului. 200
X-Daily-Remaining Câte mesaje mai poate trimite contul azi, după acesta. 199

După atingerea limitei, ambele endpoint-uri refuză mesajele noi până la miezul nopții, ora României, cu 429 Too Many Requests; nu se trimite și nu se facturează nimic:

{
    "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"
}

Refuzul conține Retry-After (secundele până la miezul nopții, ora României), X-Daily-Limit și X-Daily-Remaining: 0. Trimite din nou după resets_at sau cere-ne să mărim limita.

Limita, cât a mai rămas din ea și cât a folosit contul azi apar și în GET /accounts/{accountId} (daily_limit, daily_remaining, sent_today) și în fiecare callback cu statusul livrării (dailyLimit, dailyRemaining, sentToday), așa cum erau în momentul trimiterii lui.

Limita de cereri este un alt 429: fiecare utilizator API poate face 240 de cereri pe minut, pe toate endpoint-urile. Fiecare răspuns conține X-RateLimit-Limit și X-RateLimit-Remaining; peste această rată, API-ul răspunde 429 cu {"message": "Too Many Attempts.", "trace_id": "…"} și fără code. Le deosebești după code din corp:

Limita zilnică de trimitere Limita de cereri
Corp {"code": "daily_limit_exceeded", …} {"message": "Too Many Attempts.", "trace_id": "…"}
Headere Retry-After, X-Daily-Limit, X-Daily-Remaining: 0 Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining: 0, X-RateLimit-Reset (un timestamp Unix)
Trimite din nou după miezul nopții, ora României după Retry-After secunde

Trace ID

Fiecare răspuns al API-ului conține header-ul X-Trace-Id: ID-ul acelei cereri. Mesajele pe care le creează, rapoartele lor de livrare și callback-urile la care duc poartă același ID.

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

O eroare (orice răspuns 4xx sau 5xx) îl are și în corp, ca trace_id: nu s-a creat niciun mesaj, deci nu există un ID de mesaj după care să cauți.

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

Păstrează-l în logurile tale. În dashboard, Dezvoltatori → Debug arată tot ce s-a întâmplat cu cererea: apelul API, mesajele, livrarea lor și callback-urile; acolo merge și ID-ul unui mesaj, al unei verificări sau al unui callback. Trace ID-ul apare și pe fiecare mesaj, log API și callback din dashboard, iar dacă ni-l spui găsim cererea imediat.

Conturi

Informațiile contului

GET /accounts/{accountId}

Parametri

NumeÎnTipDescriere
accountId obligatoriu path string (uuid) UUID-ul contului

Cerere

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

Răspunsuri

Detaliile contului

Răspuns 200
{
    "data": {
        "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
        "code": "ABC1234",
        "name": "My Company",
        "description": "Company description",
        "fiscal_code": "RO12345678",
        "registry_number": "J40/123456/2023",
        "iban": "RO49AAAA1B31007593840000",
        "city": "Bucharest",
        "state": "Bucharest",
        "country": "RO",
        "address": "Main Street 123",
        "postal_code": 123456,
        "email": "[email protected]",
        "phone": "+40721***456",
        "contact_person": "John Doe",
        "credit": 100.5,
        "status": "active",
        "sandbox": false,
        "daily_limit": 1000,
        "daily_remaining": 873,
        "sent_today": 127,
        "created_at": "2024-09-20T12:48:55.000000Z"
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - permisiuni insuficiente

Răspuns 403
{
    "message": "You don't have permission to access this resource."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Mesaje

Creează un mesaj nou

POST /messages

Trimite un SMS acum sau la schedule_at.

Limita zilnică de trimitere. Fiecare cont are propria limită zilnică; un cont nou pornește cu 200 de mesaje pe zi și o păstrează până când o mărim, la cerere. Fiecare mesaj real creat de cont se numără o singură dată, oricâte părți ar avea: și codurile OTP din POST /verifications, iar un mesaj programat, în ziua în care este creat. Mesajele de test (un cont în sandbox sau o cheie API de sandbox) nu se numără niciodată și nu sunt refuzate niciodată. Ziua se încheie la miezul nopții, ora României (Europe/Bucharest). Cât timp contul are o limită, răspunsul la un mesaj creat conține X-Daily-Limit și X-Daily-Remaining (cât mai rămâne azi, după acest mesaj). După atingerea limitei, răspunsul este 429 cu "code": "daily_limit_exceeded", Retry-After și X-Daily-Remaining: 0 până la miezul nopții, ora României: nu se trimite și nu se facturează nimic. Vezi Limita zilnică de trimitere.

Limita de cereri. Separat, fiecare utilizator API poate face 240 de cereri pe minut. Fiecare răspuns conține X-RateLimit-Limit și X-RateLimit-Remaining; peste această rată, răspunsul este 429 cu {"message": "Too Many Attempts.", "trace_id": "…"}, fără code, plus Retry-After și X-RateLimit-Reset.

Expeditor. sender trebuie să fie un sender aprobat pentru contul tău, scris exact ca la aprobare. Un sender necunoscut, aflat încă în aprobare, respins sau al altui cont este refuzat cu 422 și "The sender is invalid or is not approved." pe câmpul sender: nu se trimite și nu se facturează nimic. Omite sender ca să folosești expeditorul implicit.

Corpul cererii

CâmpTipDescriere
recipient obligatoriu string Numărul de telefon al destinatarului (în format E.164)
body obligatoriu string Textul mesajului
visible_body string Textul afișat, pentru prezentare
validity integer Perioada de valabilitate a mesajului, în ore
schedule_at string (date-time) Momentul programat al trimiterii, în formatul 'YYYY-MM-DD HH:MM:SS'
callback_url string (uri) URL de callback opțional pentru rapoartele de livrare. Trebuie să fie o adresă http sau https din internetul public: adresele private, loopback și link-local sunt refuzate, iar redirecționările nu sunt urmate.
sender string Sender personalizat. Trebuie să fie un sender deja aprobat pentru contul tău (Mesaje > Senderi în dashboard), scris exact ca la aprobare: se face diferența între litere mari și mici. Un sender necunoscut, în aprobare, respins sau încă neaprobat este refuzat cu 422 („The sender is invalid or is not approved.”) și nu se trimite nimic. Omite câmpul sau trimite null ca să folosești expeditorul implicit. CALISERO din exemple ține doar locul numelui, nu este un sender pe care îl poți folosi.
shorten_urls boolean Dacă este true, URL-urile din text sunt scurtate și întoarse în răspuns

Cerere

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
}'

Răspunsuri

Mesaj creat cu succes

HeaderTipDescriere
X-Daily-Limit integer Limita zilnică de trimitere a contului: câte mesaje poate trimite într-o zi, de la miezul nopții la miezul nopții, ora României. Trimis doar cât timp contul are o limită. Exemplu: 1000.
X-Daily-Remaining integer Câte mesaje mai poate trimite contul azi, după acesta. Trimis doar cât timp contul are o limită. Exemplu: 873.
X-RateLimit-Limit integer Limita de cereri: câte cereri poate face utilizatorul API pe minut. Trimis la fiecare răspuns. Exemplu: 240.
X-RateLimit-Remaining integer Câte cereri mai poate face utilizatorul API în acest minut. Trimis la fiecare răspuns; 0 la un refuz al limitei de cereri. Exemplu: 239.
Răspuns 201
{
    "data": {
        "id": "9e2574e8-3615-4090-9b5a-0fc812079da8",
        "recipient": "40752******",
        "body": "Test message!",
        "parts": 1,
        "created_at": "2025-02-06T10:18:43.000000Z",
        "scheduled_at": "2025-02-06T10:18:43.000000Z",
        "sent_at": null,
        "delivered_at": null,
        "callback_url": "https://yoursite.com/your-callback-url",
        "status": "scheduled",
        "sender": "CALISERO",
        "shortened_urls": [
            {
                "id": "019adfbb-40a1-71ee-bcb5-8d551b8cfdae",
                "original_link": "https://www.google.com/search?q=calisero&rlz=1C5GCEM_enRO1174RO1174&oq=calisero&gs_lcrp=EgZjaHJvbWUqBggAEEUYOzIGCAAQRRg7MgYIARBFGDwyBggCEEUYPDIGCAMQRRg7MgcIBBAAGO8FMgoIBRAAGIAEGKIEMgoIBhAAGIAEGKIEMgcIBxAAGO8F0gEJMTcxNWowajE1qAIIsAIB&sourceid=chrome&ie=UTF-8",
                "shortened_link": "https://calisero.ro/s/ghJKPV",
                "click_count": 0,
                "last_click": null,
                "created_at": "2025-12-02T15:43:02.000000Z"
            },
            {
                "id": "019adfbb-40a4-70af-ba6c-55b7a07826e6",
                "original_link": "https://calisero.ro/stop/019999b5-5687-71cd-ae81-baf6eea9d86f/+40742***350?reason=Unsubscribe+from+SMS",
                "shortened_link": "https://calisero.ro/s/qpmHfD",
                "click_count": 0,
                "last_click": null,
                "created_at": "2025-12-02T15:43:02.000000Z"
            }
        ]
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Mesajul nu a trecut validarea și nu a fost creat: nu se trimite și nu se facturează nimic. `errors` numește fiecare câmp greșit. Pe lângă un câmp lipsă sau greșit (`recipient`, `body`, `schedule_at`, `callback_url`…), acesta este răspunsul pentru un `sender` care nu este aprobat pentru contul tău: necunoscut, aflat încă în aprobare, respins sau al altui cont. Este refuzat cu `"The sender is invalid or is not approved."` pe câmpul `sender`. Omite `sender` sau trimite `null` ca să folosești expeditorul implicit până când al tău este aprobat.

Răspuns 422
{
    "message": "The sender is invalid or is not approved.",
    "errors": {
        "sender": [
            "The sender is invalid or is not approved."
        ]
    },
    "trace_id": "9b80eef1-49d4-4502-85a8-febb68cc11a7"
}

Prea multe cereri, din unul dintre două motive. Contul a atins limita zilnică de trimitere: corpul conține codul daily_limit_exceeded, X-Daily-Remaining este 0, iar Retry-After numără secundele până la miezul nopții, ora României. Sau utilizatorul a depășit limita de 240 de cereri pe minut: corpul este {"message": "Too Many Attempts.", "trace_id": "…"} fără code, X-RateLimit-Remaining este 0, iar Retry-After și X-RateLimit-Reset spun când poți reîncerca. Nu se trimite și nu se facturează nimic.

HeaderTipDescriere
Retry-After integer Secundele de așteptat înainte de o nouă trimitere: până la miezul nopții, ora României, pentru limita zilnică de trimitere; până când limita de cereri se eliberează, pentru aceasta. Exemplu: 21600.
X-Daily-Limit integer Limita zilnică de trimitere a contului: câte mesaje poate trimite într-o zi, de la miezul nopții la miezul nopții, ora României. Trimis doar cât timp contul are o limită. Exemplu: 1000.
X-Daily-Remaining integer 0 când limita zilnică de trimitere a refuzat mesajul; lipsește la un refuz al limitei de cereri. Exemplu: 0.
X-RateLimit-Limit integer Limita de cereri: câte cereri poate face utilizatorul API pe minut. Trimis la fiecare răspuns. Exemplu: 240.
X-RateLimit-Remaining integer Câte cereri mai poate face utilizatorul API în acest minut. Trimis la fiecare răspuns; 0 la un refuz al limitei de cereri. Exemplu: 239.
X-RateLimit-Reset integer Doar la un refuz al limitei de cereri: când se acceptă din nou cereri, ca timestamp Unix. Exemplu: 1790000000.
Răspuns 429
{
    "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"
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Lista mesajelor

GET /messages

Parametri

NumeÎnTipDescriere
page query integer Numărul paginii, pentru paginare

Cerere

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

Răspunsuri

Lista mesajelor

Răspuns 200
{
    "data": [
        {
            "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
            "recipient": "40752******",
            "body": "Mesaje de test anonimizat ***",
            "parts": 1,
            "created_at": "2024-09-20T12:48:55.000000Z",
            "scheduled_at": "2024-09-20T12:48:55.000000Z",
            "sent_at": "2024-09-20T12:49:07.000000Z",
            "delivered_at": null,
            "callback_url": "https://yoursite.com/your-callback-url",
            "status": "sent",
            "sender": "CALISERO",
            "shortened_urls": [
                {
                    "id": "019adfbb-40a1-71ee-bcb5-8d551b8cfdae",
                    "original_link": "https://www.google.com/search?q=calisero&rlz=1C5GCEM_enRO1174RO1174&oq=calisero&gs_lcrp=EgZjaHJvbWUqBggAEEUYOzIGCAAQRRg7MgYIARBFGDwyBggCEEUYPDIGCAMQRRg7MgcIBBAAGO8FMgoIBRAAGIAEGKIEMgoIBhAAGIAEGKIEMgcIBxAAGO8F0gEJMTcxNWowajE1qAIIsAIB&sourceid=chrome&ie=UTF-8",
                    "shortened_link": "https://calisero.ro/s/ghJKPV",
                    "click_count": 0,
                    "last_click": null,
                    "created_at": "2025-12-02T15:43:02.000000Z"
                }
            ]
        }
    ],
    "links": {
        "first": "https://rest.calisero.ro/api/v1/messages?page=1",
        "last": null,
        "prev": null,
        "next": "https://rest.calisero.ro/api/v1/messages?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "https://rest.calisero.ro/api/v1/messages",
        "per_page": 50,
        "to": 50
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Un singur mesaj

GET /messages/{messageId}

Parametri

NumeÎnTipDescriere
messageId obligatoriu path string (uuid) UUID-ul mesajului

Cerere

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

Răspunsuri

Detaliile mesajului

Răspuns 200
{
    "data": {
        "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
        "recipient": "40752******",
        "body": "Mesaje de test anonimizat ***",
        "parts": 1,
        "created_at": "2024-09-20T12:48:55.000000Z",
        "scheduled_at": "2024-09-20T12:48:55.000000Z",
        "sent_at": "2024-09-20T12:49:07.000000Z",
        "delivered_at": null,
        "callback_url": "https://yoursite.com/your-callback-url",
        "status": "sent",
        "sender": "CALISERO",
        "shortened_urls": [
            {
                "id": "019adfbb-40a1-71ee-bcb5-8d551b8cfdae",
                "original_link": "https://www.google.com/search?q=calisero&rlz=1C5GCEM_enRO1174RO1174&oq=calisero&gs_lcrp=EgZjaHJvbWUqBggAEEUYOzIGCAAQRRg7MgYIARBFGDwyBggCEEUYPDIGCAMQRRg7MgcIBBAAGO8FMgoIBRAAGIAEGKIEMgoIBhAAGIAEGKIEMgcIBxAAGO8F0gEJMTcxNWowajE1qAIIsAIB&sourceid=chrome&ie=UTF-8",
                "shortened_link": "https://calisero.ro/s/ghJKPV",
                "click_count": 0,
                "last_click": null,
                "created_at": "2025-12-02T15:43:02.000000Z"
            }
        ]
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Șterge un mesaj

DELETE /messages/{messageId}

Parametri

NumeÎnTipDescriere
messageId obligatoriu path string (uuid) UUID-ul mesajului

Cerere

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

Răspunsuri

Mesaj șters cu succes

Răspuns 200
[]

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - neautorizat sau resursa nu poate fi ștearsă

Răspuns 403
{
    "message": "You are not authorized to delete this resource or the resource has been queued, sent or delivered"
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Dezabonări

Lista dezabonărilor

GET /opt-outs

Parametri

NumeÎnTipDescriere
page query integer Numărul paginii, pentru paginare

Cerere

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

Răspunsuri

Lista dezabonărilor

Răspuns 200
{
    "data": [
        {
            "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
            "phone": "40752******",
            "reason": "User requested opt-out",
            "created_at": "2024-09-20T12:48:55.000000Z",
            "updated_at": "2024-09-20T12:48:55.000000Z"
        }
    ],
    "links": {
        "first": "https://rest.calisero.ro/api/v1/opt-outs?page=1",
        "last": null,
        "prev": null,
        "next": "https://rest.calisero.ro/api/v1/opt-outs?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "https://rest.calisero.ro/api/v1/opt-outs",
        "per_page": 50,
        "to": 50
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Creează o dezabonare

POST /opt-outs

Corpul cererii

CâmpTipDescriere
phone obligatoriu string Numărul de telefon dezabonat (în format E.164)
reason string Motivul dezabonării

Cerere

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"
}'

Răspunsuri

Dezabonare creată cu succes

Răspuns 201
{
    "data": {
        "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
        "phone": "40752******",
        "reason": "User requested opt-out",
        "created_at": "2024-09-20T12:48:55.000000Z",
        "updated_at": "2024-09-20T12:48:55.000000Z"
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare de validare (de exemplu câmpuri obligatorii lipsă, cerere SMS duplicată detectată)

Răspuns 422
{
    "message": "The recipient field is required.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    }
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

O singură dezabonare

GET /opt-outs/{optOutId}

Parametri

NumeÎnTipDescriere
optOutId obligatoriu path string (uuid) UUID-ul dezabonării

Cerere

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

Răspunsuri

Detaliile dezabonării

Răspuns 200
{
    "data": {
        "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
        "phone": "40752******",
        "reason": "User requested opt-out",
        "created_at": "2024-09-20T12:48:55.000000Z",
        "updated_at": "2024-09-20T12:48:55.000000Z"
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Actualizează o dezabonare

PUT /opt-outs/{optOutId}

Parametri

NumeÎnTipDescriere
optOutId obligatoriu path string (uuid) UUID-ul dezabonării

Corpul cererii

CâmpTipDescriere
phone obligatoriu string Numărul de telefon dezabonat (în format E.164)
reason string Motivul dezabonării

Cerere

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"
}'

Răspunsuri

Dezabonare actualizată cu succes

Răspuns 200
{
    "data": {
        "id": "9d0e0bd3-c805-4dd0-bafb-07c5ded0a8e1",
        "phone": "40752******",
        "reason": "Customer asked to stop the reminders",
        "created_at": "2024-09-20T12:48:55.000000Z",
        "updated_at": "2024-10-02T08:15:31.000000Z"
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare de validare (de exemplu câmpuri obligatorii lipsă, cerere SMS duplicată detectată)

Răspuns 422
{
    "message": "The recipient field is required.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    }
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Șterge o dezabonare

DELETE /opt-outs/{optOutId}

Parametri

NumeÎnTipDescriere
optOutId obligatoriu path string (uuid) UUID-ul dezabonării

Cerere

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

Răspunsuri

Dezabonare ștearsă cu succes

Răspuns 200
[]

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Verificări (OTP și 2FA)

Lista verificărilor

GET /verifications

Parametri

NumeÎnTipDescriere
page query integer Numărul paginii, pentru paginare
status query string Filtrează după statusul verificării

Cerere

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

Răspunsuri

Lista verificărilor

Răspuns 200
{
    "data": [
        {
            "id": "019a62f1-66b7-7387-a64f-2742c12a2860",
            "phone": "+40742**350",
            "brand": "Calisero",
            "status": "verified",
            "template": null,
            "created_at": "2025-11-08T10:09:38.000000Z",
            "expires_at": "2025-11-08T10:12:38.000000Z",
            "verified_at": "2025-11-08T10:10:18.000000Z",
            "attempts": 1,
            "expired": false
        }
    ],
    "links": {
        "first": "https://rest.calisero.ro/api/v1/verifications?page=1",
        "last": null,
        "prev": null,
        "next": "https://rest.calisero.ro/api/v1/verifications?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "https://rest.calisero.ro/api/v1/verifications",
        "per_page": 50,
        "to": 50
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - permisiuni insuficiente

Răspuns 403
{
    "message": "You don't have permission to access this resource."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Creează o cerere de verificare

POST /verifications

Generează un cod, îl trimite prin SMS la phone și îl păstrează pentru PUT /verifications/validate.

Limita zilnică de trimitere. SMS-ul cu codul se numără în limita zilnică a contului ca orice mesaj din POST /messages (un cont nou pornește cu 200 de mesaje pe zi); ziua se încheie la miezul nopții, ora României (Europe/Bucharest). Cât timp contul are o limită, răspunsul la o verificare creată conține X-Daily-Limit și X-Daily-Remaining. După atingerea limitei, răspunsul este 429 cu "code": "daily_limit_exceeded", Retry-After și X-Daily-Remaining: 0 până la miezul nopții, ora României: nu se trimite niciun cod și nu se facturează nimic. Vezi Limita zilnică de trimitere.

Limita de cereri. Separat, fiecare utilizator API poate face 240 de cereri pe minut; peste această rată, răspunsul este 429 cu {"message": "Too Many Attempts.", "trace_id": "…"}, fără code, plus Retry-After și X-RateLimit-Reset.

Corpul cererii

CâmpTipDescriere
phone obligatoriu string Numărul de telefon de verificat (format E.164)
brand string Brandul folosit în mesajul implicit când nu dai un șablon. Nu poate conține linkuri (adrese web, domenii, emailuri sau adrese IP).
template string Șablon SMS propriu care TREBUIE să conțină {code}. Nu poate conține linkuri (adrese web, domenii, emailuri sau adrese IP): codul se trimite de pe un sender comun.
expires_in integer Valabilitatea codului OTP, în minute (1-10). Implicit 5.

Cerere

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
}'

Răspunsuri

Verificare creată cu succes

HeaderTipDescriere
X-Daily-Limit integer Limita zilnică de trimitere a contului: câte mesaje poate trimite într-o zi, de la miezul nopții la miezul nopții, ora României. Trimis doar cât timp contul are o limită. Exemplu: 1000.
X-Daily-Remaining integer Câte mesaje mai poate trimite contul azi, după acesta. Trimis doar cât timp contul are o limită. Exemplu: 873.
X-RateLimit-Limit integer Limita de cereri: câte cereri poate face utilizatorul API pe minut. Trimis la fiecare răspuns. Exemplu: 240.
X-RateLimit-Remaining integer Câte cereri mai poate face utilizatorul API în acest minut. Trimis la fiecare răspuns; 0 la un refuz al limitei de cereri. Exemplu: 239.
Răspuns 201
{
    "data": {
        "id": "019a62f1-66b7-7387-a64f-2742c12a2860",
        "phone": "+40742**350",
        "brand": "Calisero",
        "status": "unverified",
        "template": null,
        "created_at": "2025-11-08T10:09:38.000000Z",
        "expires_at": "2025-11-08T10:12:38.000000Z",
        "verified_at": null,
        "attempts": 0,
        "expired": false
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - permisiuni insuficiente

Răspuns 403
{
    "message": "You don't have permission to access this resource."
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare de validare (de exemplu câmpuri obligatorii lipsă, cerere SMS duplicată detectată)

Răspuns 422
{
    "message": "The recipient field is required.",
    "errors": {
        "recipient": [
            "The recipient field is required."
        ]
    }
}

Prea multe cereri, din unul dintre două motive. Contul a atins limita zilnică de trimitere: corpul conține codul daily_limit_exceeded, X-Daily-Remaining este 0, iar Retry-After numără secundele până la miezul nopții, ora României. Sau utilizatorul a depășit limita de 240 de cereri pe minut: corpul este {"message": "Too Many Attempts.", "trace_id": "…"} fără code, X-RateLimit-Remaining este 0, iar Retry-After și X-RateLimit-Reset spun când poți reîncerca. Nu se trimite niciun cod și nu se facturează nimic.

HeaderTipDescriere
Retry-After integer Secundele de așteptat înainte de o nouă trimitere: până la miezul nopții, ora României, pentru limita zilnică de trimitere; până când limita de cereri se eliberează, pentru aceasta. Exemplu: 21600.
X-Daily-Limit integer Limita zilnică de trimitere a contului: câte mesaje poate trimite într-o zi, de la miezul nopții la miezul nopții, ora României. Trimis doar cât timp contul are o limită. Exemplu: 1000.
X-Daily-Remaining integer 0 când limita zilnică de trimitere a refuzat mesajul; lipsește la un refuz al limitei de cereri. Exemplu: 0.
X-RateLimit-Limit integer Limita de cereri: câte cereri poate face utilizatorul API pe minut. Trimis la fiecare răspuns. Exemplu: 240.
X-RateLimit-Remaining integer Câte cereri mai poate face utilizatorul API în acest minut. Trimis la fiecare răspuns; 0 la un refuz al limitei de cereri. Exemplu: 239.
X-RateLimit-Reset integer Doar la un refuz al limitei de cereri: când se acceptă din nou cereri, ca timestamp Unix. Exemplu: 1790000000.
Răspuns 429
{
    "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"
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

O singură verificare

GET /verifications/{verificationId}

Parametri

NumeÎnTipDescriere
verificationId obligatoriu path string (uuid) UUID-ul verificării

Cerere

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

Răspunsuri

Detaliile verificării

Răspuns 200
{
    "data": {
        "id": "019a62f1-66b7-7387-a64f-2742c12a2860",
        "phone": "+40742**350",
        "brand": "Calisero",
        "status": "verified",
        "template": null,
        "created_at": "2025-11-08T10:09:38.000000Z",
        "expires_at": "2025-11-08T10:12:38.000000Z",
        "verified_at": "2025-11-08T10:10:18.000000Z",
        "attempts": 1,
        "expired": false
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - permisiuni insuficiente

Răspuns 403
{
    "message": "You don't have permission to access this resource."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Verifică un cod (OTP) pentru un număr de telefon

PUT /verifications/validate

Corpul cererii

CâmpTipDescriere
phone obligatoriu string Numărul de telefon de verificat (format E.164)
code obligatoriu string Cod OTP din 6 caractere (nu contează literele mari sau mici)

Cerere

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"
}'

Răspunsuri

Număr de telefon verificat cu succes (idempotent dacă era deja verificat)

Răspuns 200
{
    "data": {
        "id": "019a62f1-66b7-7387-a64f-2742c12a2860",
        "phone": "+40742**350",
        "brand": "Calisero",
        "status": "verified",
        "template": null,
        "created_at": "2025-11-08T10:09:38.000000Z",
        "expires_at": "2025-11-08T10:12:38.000000Z",
        "verified_at": "2025-11-08T10:10:18.000000Z",
        "attempts": 1,
        "expired": false
    }
}

Neautorizat - token Bearer invalid sau lipsă

Răspuns 401
{
    "message": "Unauthenticated."
}

Interzis - permisiuni insuficiente

Răspuns 403
{
    "message": "You don't have permission to access this resource."
}

Resursa nu a fost găsită

Răspuns 404
{
    "message": "Resource not found!"
}

Metodă nepermisă

Răspuns 405
{
    "message": "This method is not supported for the requested route."
}

Erori de validare (de exemplu cod greșit, expirat, încercări depășite)

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

Eroare internă de server

Răspuns 500
{
    "message": "Internal server error!"
}

Webhook-uri (callback-uri)

Webhook pentru statusul livrării (Calisero -> client)

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

Calisero trimite actualizările de status ale livrării către URL-ul de callback pe care îl dai la crearea mesajului (callback_url). Acest callback descrie payload-ul JSON pe care îl vei primi. Endpoint-ul tău trebuie să răspundă cu orice cod 2xx ca să confirme primirea. Se reîncearcă doar o conexiune eșuată sau lipsa unui răspuns în 2 secunde, de cel mult 5 ori în total (după 1 s, 10 s, 1 min și 6 min); un răspuns care nu este 2xx nu se reîncearcă.

Corpul cererii

CâmpTipDescriere
price obligatoriu number (float) Prețul facturat pentru partea sau părțile mesajului.
sender obligatoriu string Senderul sau numărul de telefon.
status obligatoriu string Statusul livrării.
messageId obligatoriu string (uuid) Identificatorul unic al mesajului.
recipient obligatoriu string MSISDN-ul destinatarului, în format E.164.
sentAt string (date-time) Momentul în care mesajul a fost trimis în rețea (prezent pentru statusurile sent sau delivered).
deliveredAt string (date-time) Momentul în care telefonul a confirmat livrarea (prezent doar pentru delivered).
scheduleAt string (date-time) Momentul inițial programat pentru trimitere, dacă mesajul a fost programat.
remainingBalance number (float) Creditul rămas în cont după facturare (dacă este disponibil).
dailyLimit integer Limita zilnică de trimitere a contului când a fost trimis callback-ul; null când nu se aplică o limită zilnică. La fel ca daily_limit din GET /accounts/{accountId}.
dailyRemaining integer Câte mesaje mai putea trimite contul azi când a fost trimis callback-ul; null când nu se aplică o limită zilnică. La fel ca daily_remaining din GET /accounts/{accountId}.
sentToday integer Mesajele reale create azi de cont, ora României, când a fost trimis callback-ul: inclusiv codurile OTP, fără mesajele de test, câte unul per mesaj oricâte părți ar avea. La fel ca sent_today din GET /accounts/{accountId}.

Callback-ul pe care ți-l trimite Calisero

Calisero face acest apel către callback_url pe care l-ai dat. Endpoint-ul tău trebuie doar să-l accepte și să răspundă cu 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
}'

Răspunsuri

Orice status 2xx confirmă raportul. Răspunde în 2 secunde și fă procesarea după aceea: un răspuns mai lent se consideră eșec și raportul se trimite din nou.