API RIVIL SMS
Trimite și primește SMS-uri de pe telefoanele tale Android, din orice aplicație.
Adresa API-ului: https://sms.rivil.app. Toate cererile și răspunsurile sunt JSON.
Cum funcționează
Mesajele pleacă de pe SIM-ul unui telefon Android pe care rulează aplicația RIVIL SMS. Aplicația ta pune mesajul la coadă prin API, iar telefonul îl preia în câteva secunde.
| Aplicație | Contul tău de API. Primești de la noi o cheie și un secret pentru webhook-uri. |
| Firmă | Un cont de-al tău, cu telefoanele lui. Îl identifici cu propriul tău id. Dacă ai o singură firmă, creezi una singură. |
| Telefon | Un telefon al firmei, eventual legat de o locație. Fiecare firmă are un telefon implicit. |
Primul SMS
1. Creează firma
curl -X PUT https://sms.rivil.app/v1/companies/firma-mea \
-H "Authorization: Bearer $RIVIL_SMS_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Firma Mea" }'
2. Adaugă un telefon
curl -X POST https://sms.rivil.app/v1/companies/firma-mea/devices \
-H "Authorization: Bearer $RIVIL_SMS_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Recepție" }'
Răspunsul conține id, id-ul telefonului.
3. Conectează telefonul
curl -X POST https://sms.rivil.app/v1/companies/firma-mea/devices/$DEVICE_ID/pairing \
-H "Authorization: Bearer $RIVIL_SMS_KEY"
Instalează aplicația de pe pagina de instalare, apoi scrie în ea code din răspuns sau scanează un cod QR făcut din qr. Codul e valabil 15 minute, o singură dată.
4. Trimite
curl -X POST https://sms.rivil.app/v1/companies/firma-mea/messages \
-H "Authorization: Bearer $RIVIL_SMS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: comanda-1024" \
-d '{ "to": "+40712345678", "text": "Comanda ta a fost expediată." }'
{ "id": "1042", "status": "queued" }
Autentificare
Trimite cheia în fiecare cerere, în antetul Authorization:
Authorization: Bearer rs_...
Cheia se arată o singură dată, la creare. Ține-o doar pe server, niciodată într-o aplicație de browser sau de mobil. Dacă ajunge la altcineva, scrie-ne și o înlocuim. Verifici că merge cu GET /v1/me.
Firme
Identificatorul {firma} îl alegi tu: litere, cifre, -, _ și ., cel mult 120 de caractere. Vezi doar firmele aplicației tale.
/v1/companiesToate firmele tale.
/v1/companies/{firma}Creează firma sau îi schimbă datele. Se poate apela de oricâte ori.
| Câmp | Tip | Descriere |
|---|---|---|
name | text, obligatoriu | Numele firmei, cel mult 200 de caractere. |
fallback | boolean | Dacă telefonul locației tace, după 10 minute mesajul poate pleca de pe alt telefon. Implicit false. |
/v1/companies/{firma}Telefoane
/v1/companies/{firma}/devicesTelefoanele firmei, cu starea lor:
{ "devices": [{
"id": "41879465-fc0e-4573-8a8c-8f71803f540a",
"name": "Recepție",
"location_ref": null,
"is_default": true,
"paired": true,
"online": true,
"last_seen_at": "2026-11-04T10:23:20Z",
"battery": 69,
"charging": false,
"signal": 3,
"model": "samsung SM-S921B",
"app_version": "1.0.4",
"settings": { "sim": 1, "start": "08:00", "end": "20:00", "per_minute": 6, "retries": 1, "inbound": true, "calls": false, "outgoing": false, "history": false }
}] }
/v1/companies/{firma}/devices| Câmp | Tip | Descriere |
|---|---|---|
name | text, obligatoriu | Cel mult 80 de caractere. |
location_ref | text | Locația telefonului, cu identificatorul tău. |
is_default | boolean | Primul telefon al unei firme e implicit. |
/v1/companies/{firma}/devices/{id}Schimbă name, location_ref, is_default sau settings. Obiectul settings se trimite întreg, cu toate câmpurile primite la citire.
| Setare | Valori | Descriere |
|---|---|---|
sim | 1 sau 2 | SIM-ul de pe care se trimite. |
start, end | HH:MM | Intervalul de trimitere, ora României. |
per_minute | 1 - 30 | Câte SMS-uri pe minut. |
retries | 0 - 5 | Reîncercări la un mesaj eșuat. |
inbound | boolean | Trimite SMS-urile primite. |
calls | boolean | Trimite jurnalul de apeluri. |
outgoing | boolean | Trimite SMS-urile scrise de mână pe telefon. Oprit implicit. |
history | boolean | Importă o singură dată SMS-urile vechi de pe telefon, primite și trimise. Oprit implicit: fără el, jurnalul începe de la pornire. |
/v1/companies/{firma}/devices/{id}/pairingCod nou de conectare: { "code", "qr", "expires_at" }. Anulează codurile vechi ale telefonului.
/v1/companies/{firma}/devices/{id}Scoate telefonul. Istoricul rămâne.
Mesaje
/v1/companies/{firma}/messagesPune un SMS la coadă. Răspunde 202 cu { "id", "status": "queued" }.
| Câmp | Tip | Descriere |
|---|---|---|
to | text, obligatoriu | Numărul în format internațional, de exemplu +40712345678. |
text | text, obligatoriu | Cel mult 1600 de caractere. Un SMS are 160 de caractere, sau 70 cu diacritice; un text mai lung pleacă pe mai multe părți. |
location_ref | text | Trimite de pe telefonul acestei locații. |
expires_at | dată ISO 8601 | Dacă mesajul n-a fost preluat până atunci, se închide ca failed. Implicit, 24 de ore. |
Alegerea telefonului: telefonul locației; dacă locația nu are telefon sau mesajul nu are locație, telefonul implicit.
Antetul Idempotency-Key (opțional, cel mult 200 de caractere): dacă reiei cererea cu aceeași cheie, nu se trimite încă un SMS. Îl recomandăm la orice reîncercare automată.
Stările unui mesaj
queued | Așteaptă un telefon, sau intervalul lui de trimitere. |
claimed | L-a preluat telefonul. |
sent | A plecat de pe telefon. |
delivered | Operatorul a confirmat livrarea. |
failed | Nu a putut fi trimis sau a expirat; motivul e în error. |
/v1/messages/{id}{ "id": "1042", "status": "delivered", "error": null, "sent_at": "2026-11-04T10:00:03Z", "delivered_at": "2026-11-04T10:00:07Z" }
/v1/companies/{firma}/messagesLista mesajelor, cele mai noi primele. Filtre: status, limit (1 - 100, implicit 50) și before (pentru pagina următoare, ultimul id primit).
Răspunsuri și apeluri
Telefonul trimite aceste date doar dacă are pornite inbound, outgoing, respectiv calls. Listele acceptă limit și before, ca la mesaje.
/v1/companies/{firma}/inbound{ "inbound": [{ "id": "88", "device_id": "...", "from": "+40712345678", "text": "DA", "sim": "SIM 1", "received_at": "2026-11-04T10:05:00Z" }] }
/v1/companies/{firma}/outgoingSMS-urile scrise de mână pe telefon, din momentul în care ai pornit outgoing. Cele trimise prin API nu apar aici.
{ "outgoing": [{ "id": "7", "device_id": "...", "to": "0712345678", "text": "Revin imediat", "sent_at": "2026-11-04T10:06:00Z" }] }
/v1/companies/{firma}/callsdirection este incoming, outgoing, missed sau rejected, iar duration e în secunde.
{ "calls": [{ "id": "12", "device_id": "...", "number": "+40712345678", "direction": "outgoing", "duration": 95, "sim": "SIM 1", "occurred_at": "2026-11-04T09:00:00Z" }] }
Webhook-uri
Dă-ne o adresă HTTPS publică și primești evenimentele pe măsură ce se întâmplă, fără să întrebi API-ul:
POST https://aplicatia-ta.ro/webhook
X-Signature: sha256=5bdcc146...
X-Event: sms:delivered
X-Delivery-Id: 12
{ "event": "sms:delivered", "delivery_id": "12", "created_at": "2026-11-04T10:00:07Z", "data": { "company": "firma-mea", "messageId": "1042" } }
| Eveniment | Câmpuri în data |
|---|---|
sms:sent, sms:delivered | company, messageId |
sms:failed | company, messageId, reason |
sms:received | company, deviceId, locationRef, message, phoneNumber, simTag, receivedAt, history (true pentru un mesaj vechi, importat: nu trebuie să declanșeze nimic) |
sms:outgoing | company, deviceId, locationRef, message, phoneNumber, sentAt |
call:logged | company, deviceId, locationRef, ref, phoneNumber, direction, duration, simTag, occurredAt |
device.offline, device.online | company, deviceId, name, locationRef |
Răspunde cu un cod 2xx în cel mult 15 secunde. Altfel reîncercăm, cu pauze tot mai mari, de cel mult 20 de ori. Evenimentele pot sosi de două ori sau în altă ordine: păstrează delivery_id și ignoră-le pe cele deja văzute. Nu urmăm redirecturi.
Verificarea semnăturii
X-Signature e HMAC-SHA256 peste corpul exact al cererii, cu secretul tău. Verific-o înainte să crezi evenimentul.
import { createHmac, timingSafeEqual } from 'node:crypto'
function valid(rawBody, header, secret) {
const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex'))
const got = Buffer.from(header ?? '')
return got.length === expected.length && timingSafeEqual(got, expected)
}
<?php
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
Erori și limite
O eroare are forma { "error": "mesaj" }.
| Cod | Înseamnă |
|---|---|
400 | Date greșite; motivul e în error. |
401 | Cheie lipsă, greșită sau revocată. |
404 | Nu există, sau nu aparține aplicației tale. |
429 | Prea multe cereri. Reîncearcă peste un minut. |
5xx | Eroare temporară. Reîncearcă, cu același Idempotency-Key. |
Date și consimțământ
Textele și numerele mesajelor se șterg după 30 de zile; starea rămâne pentru statistici. Tu răspunzi pentru destinatari: trimite doar persoanelor care au acceptat să primească mesaje și oprește trimiterea la cei care cer asta. Respectă și condițiile operatorului SIM-ului.