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țieContul 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ă.
TelefonUn 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.

GET /v1/companies

Toate firmele tale.

PUT /v1/companies/{firma}

Creează firma sau îi schimbă datele. Se poate apela de oricâte ori.

CâmpTipDescriere
nametext, obligatoriuNumele firmei, cel mult 200 de caractere.
fallbackbooleanDacă telefonul locației tace, după 10 minute mesajul poate pleca de pe alt telefon. Implicit false.
GET /v1/companies/{firma}

Telefoane

GET /v1/companies/{firma}/devices

Telefoanele 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 }
}] }
POST /v1/companies/{firma}/devices
CâmpTipDescriere
nametext, obligatoriuCel mult 80 de caractere.
location_reftextLocația telefonului, cu identificatorul tău.
is_defaultbooleanPrimul telefon al unei firme e implicit.
PATCH /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.

SetareValoriDescriere
sim1 sau 2SIM-ul de pe care se trimite.
start, endHH:MMIntervalul de trimitere, ora României.
per_minute1 - 30Câte SMS-uri pe minut.
retries0 - 5Reîncercări la un mesaj eșuat.
inboundbooleanTrimite SMS-urile primite.
callsbooleanTrimite jurnalul de apeluri.
outgoingbooleanTrimite SMS-urile scrise de mână pe telefon. Oprit implicit.
historybooleanImportă o singură dată SMS-urile vechi de pe telefon, primite și trimise. Oprit implicit: fără el, jurnalul începe de la pornire.
POST /v1/companies/{firma}/devices/{id}/pairing

Cod nou de conectare: { "code", "qr", "expires_at" }. Anulează codurile vechi ale telefonului.

DELETE /v1/companies/{firma}/devices/{id}

Scoate telefonul. Istoricul rămâne.

Mesaje

POST /v1/companies/{firma}/messages

Pune un SMS la coadă. Răspunde 202 cu { "id", "status": "queued" }.

CâmpTipDescriere
totext, obligatoriuNumărul în format internațional, de exemplu +40712345678.
texttext, obligatoriuCel 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_reftextTrimite de pe telefonul acestei locații.
expires_atdată ISO 8601Dacă 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

queuedAșteaptă un telefon, sau intervalul lui de trimitere.
claimedL-a preluat telefonul.
sentA plecat de pe telefon.
deliveredOperatorul a confirmat livrarea.
failedNu a putut fi trimis sau a expirat; motivul e în error.
GET /v1/messages/{id}
{ "id": "1042", "status": "delivered", "error": null, "sent_at": "2026-11-04T10:00:03Z", "delivered_at": "2026-11-04T10:00:07Z" }
GET /v1/companies/{firma}/messages

Lista 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.

GET /v1/companies/{firma}/inbound
{ "inbound": [{ "id": "88", "device_id": "...", "from": "+40712345678", "text": "DA", "sim": "SIM 1", "received_at": "2026-11-04T10:05:00Z" }] }
GET /v1/companies/{firma}/outgoing

SMS-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" }] }
GET /v1/companies/{firma}/calls

direction 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" } }
EvenimentCâmpuri în data
sms:sent, sms:deliveredcompany, messageId
sms:failedcompany, messageId, reason
sms:receivedcompany, deviceId, locationRef, message, phoneNumber, simTag, receivedAt, history (true pentru un mesaj vechi, importat: nu trebuie să declanșeze nimic)
sms:outgoingcompany, deviceId, locationRef, message, phoneNumber, sentAt
call:loggedcompany, deviceId, locationRef, ref, phoneNumber, direction, duration, simTag, occurredAt
device.offline, device.onlinecompany, 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ă
400Date greșite; motivul e în error.
401Cheie lipsă, greșită sau revocată.
404Nu există, sau nu aparține aplicației tale.
429Prea multe cereri. Reîncearcă peste un minut.
5xxEroare temporară. Reîncearcă, cu același Idempotency-Key.
Limita API-ului e de 600 de cereri pe minut. Viteza reală a SMS-urilor o dă telefonul: 6 pe minut implicit, cel mult 30, iar operatorul poate limita și el un SIM obișnuit. Pentru volume mari, adaugă mai multe telefoane.

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.