API pentru dezvoltatori
Creează, actualizează și urmărește codurile tale QR direct din sistemul tău, fără să deschizi panoul. API-ul este REST, răspunde în JSON și e disponibil pe planul Business.
Prezentare generală
Toate rutele pornesc de la https://qrstream.pro/api/v1. Trimiți JSON, primești JSON, iar fiecare cerere se autentifică cu o cheie de API — nu există sesiuni și nu există cookie-uri.
Accesul la API face parte din planul Business. Îți creezi cheile din contul tău, la secțiunea „API” de pe pagina /account#api. Cheia se afișează o singură dată, la creare: copiaz-o atunci și păstreaz-o într-un seif de secrete. Dacă ai pierdut-o, o revoci și creezi alta.
Ce trebuie să știi de la început
- Fiecare cheie începe cu qrs_ și ține de contul tău, nu de un anumit cod sau folder.
- Poți avea cel mult 10 chei active în același timp.
- Datele se trimit cu Content-Type: application/json, codificate UTF-8.
- Datele calendaristice sunt ISO 8601 în UTC, de exemplu 2026-09-12T07:41:02.000Z.
- Identificatorii codurilor și ai folderelor sunt UUID-uri.
Autentificare
Trimite cheia în antetul Authorization. Forma cu Bearer este cea recomandată; x-api-key funcționează identic și e utilă acolo unde clientul tău nu poate seta Authorization.
Authorization: Bearer qrs_9f3a1c7b2e…
x-api-key: qrs_9f3a1c7b2e…Cheile sunt pentru comunicarea de la server la server. Nu le pune într-o pagină web, într-o aplicație mobilă sau într-un depozit public: oricine are cheia îți poate citi și modifica toate codurile. Din același motiv API-ul nu trimite antete CORS, deci un browser nu îl poate apela direct.
Drepturi
La creare alegi ce poate face cheia. O cheie „doar citire” răspunde la GET și refuză orice scriere cu forbidden. O cheie „citire și scriere” poate crea, modifica și șterge coduri și foldere.
| Drept | Poate | Nu poate |
|---|---|---|
| Doar citire | GET pe coduri, foldere, statistici și /me | POST, PATCH, DELETE |
| Citire și scriere | Tot ce poate citirea, plus crearea, editarea, pauza și ștergerea | Gestionarea cheilor și a abonamentului |
Domeniul corect
Apelează întotdeauna https://qrstream.pro/api/v1. Dacă folosești un domeniu propriu pentru codurile dinamice, acela servește doar redirecționările de scanare și trimite cererile de API mai departe printr-un redirect — unele librării nu păstrează antetul de autorizare la redirect, iar cererea ajunge neautentificată.
Limite de trafic
Fiecare cheie are 120 de cereri pe minut. Limita se numără pe cheie, nu pe cont, așa că poți separa integrările pe chei diferite ca să nu se blocheze una pe alta.
Fiecare răspuns îți spune unde te afli. Când depășești limita primești 429 cu Retry-After, numărul de secunde după care poți relua.
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
Retry-After: 27| Antet | Înseamnă |
|---|---|
| X-RateLimit-Limit | Cereri permise într-un minut (120). |
| X-RateLimit-Remaining | Câte ți-au mai rămas în minutul curent. |
| Retry-After | Secunde de așteptat, trimis doar la răspunsul 429. |
Pentru importuri mari, distanțează cererile și reia cu o pauză crescătoare la 429 și la 500.
Erori
Orice răspuns cu status 4xx sau 5xx are același plic: un obiect error cu un cod stabil, un mesaj în engleză pentru jurnalele tale și, la validare, lista problemelor găsite. Verifică întotdeauna code, nu textul mesajului — mesajele se pot reformula.
{
"error": {
"code": "validation_failed",
"message": "The request could not be understood.",
"details": [
{ "path": "content.url", "message": "An address is required" }
]
}
}| Cod | Status | Înseamnă |
|---|---|---|
| unauthorized | 401 | Cheie lipsă, greșită, dezactivată sau expirată. |
| forbidden | 403 | Cheia nu are dreptul cerut, de obicei o scriere cu o cheie de citire. |
| plan_required | 403 | Contul nu are acces la API. Treci pe planul Business. |
| account_suspended | 403 | Contul este suspendat. Scrie-ne ca să îl redeschidem. |
| quota_exceeded | 403 | Ai atins o limită a planului, de exemplu numărul de coduri dinamice. |
| not_found | 404 | Resursa nu există sau nu este a ta. Cele două nu se deosebesc, intenționat. |
| conflict | 409 | Există deja un folder cu numele acesta. Alege altul și reia cererea. |
| validation_failed | 422 | Corpul cererii nu e valid; details enumeră câmpurile. |
| rate_limited | 429 | Ai depășit cele 120 de cereri pe minut. |
| render_failed | 500 | Imaginea nu a putut fi generată. Reîncearcă sau cere SVG. |
| internal | 500 | Eroare neașteptată la noi. Reîncearcă; dacă se repetă, scrie-ne. |
Paginare
Listele se parcurg cu un cursor, nu cu numere de pagină, ca să nu sari peste rânduri atunci când se adaugă coduri în timp ce citești.
| Parametru | Valori | Implicit |
|---|---|---|
| limit | 1–100 | 25 |
| cursor | Valoarea nextCursor din răspunsul anterior | — |
{
"data": [],
"nextCursor": "MjAyNi0wOC0xNFQwOToxMjo0NFp8N2YxYzBiNTI"
}Când nextCursor este null ai ajuns la capăt. Trimite-l înapoi ca ?cursor=… ca să ceri pagina următoare.
Contul tău
GET /me
Îți spune cine ești, pe ce plan ești și cât din limite ai consumat. Funcționează cu orice cheie validă și e cel mai simplu apel cu care verifici o cheie nouă.
limits sunt plafoanele planului, iar usage este consumul de acum. O valoare null în limits înseamnă „fără plafon”, nu zero.
curl -s "https://qrstream.pro/api/v1/me" \
-H "Authorization: Bearer $QRS_API_KEY"{
"user": {
"id": "u_3kf9x7b2qd",
"name": "Ana Popescu",
"email": "ana@exemplu.ro"
},
"plan": {
"slug": "business",
"name": "Business",
"source": "subscription",
"periodEnd": "2026-10-01T00:00:00.000Z"
},
"limits": {
"dynamicCodes": null,
"scansPerMonth": null,
"analyticsMonths": 36,
"logo": true,
"csvImport": true,
"customDomain": true,
"googleAnalytics": true,
"users": null,
"storageBins": null,
"binPhotos": 40,
"rackLabels": true,
"apiAccess": true
},
"usage": {
"dynamicCodes": 64,
"storageBins": 3,
"scansThisMonth": 12840
},
"key": {
"id": "k_7d2a1f",
"permissions": {
"codes": [
"read",
"write"
],
"analytics": [
"read"
],
"folders": [
"read",
"write"
]
}
}
}Coduri
GET /codes
Listează codurile contului, cele mai noi întâi. Filtrele se pot combina: type (tipul codului), status (active sau paused), folderId și q pentru căutare după nume.
curl -s "https://qrstream.pro/api/v1/codes?limit=25&type=website&status=active" \
-H "Authorization: Bearer $QRS_API_KEY"{
"data": [
{
"id": "7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33",
"type": "website",
"name": "Meniu de vară",
"dynamic": true,
"status": "active",
"shortUrl": "https://qrstream.pro/r/7Kd2mA",
"scanCount": 1284,
"createdAt": "2026-08-14T09:12:44.000Z"
}
],
"nextCursor": null
}POST /codes
Creează un cod și răspunde cu 201. Câmpurile obligatorii sunt type, name și content; content trebuie să aibă același type ca și codul. design este opțional — fără el codul primește designul implicit.
dynamic este opțional și implicit false. La tipurile care există doar static sau doar dinamic este ignorat: codul primește oricum forma pe care tipul o cere.
| Câmp | Tip | Descriere |
|---|---|---|
| type | text | Tipul codului: website, wifi, vcard, pdf, whatsapp și celelalte. |
| dynamic | boolean | Opțional, implicit false. true creează un link scurt editabil; false scrie conținutul în module. |
| name | text | Numele din panou, 1–120 de caractere. |
| content | obiect | Conținutul, potrivit tipului. Vezi secțiunea de mai jos. |
| design | obiect | Opțional: culori, forme, logo, ramă, nivel de corecție. |
| folderId | uuid sau null | Folderul în care intră codul. |
| password | text sau null | Parolă pentru pagina de scanare; doar la codurile dinamice. |
| expiry | text | none, 30d sau date. |
| expiresOn | dată sau null | Ziua expirării, obligatorie când expiry este date. |
curl -s -X POST "https://qrstream.pro/api/v1/codes" \
-H "Authorization: Bearer $QRS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "website",
"dynamic": true,
"name": "Meniu de vară",
"content": { "type": "website", "url": "https://exemplu.ro/meniu" },
"folderId": null,
"expiry": "none"
}'{
"id": "7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33",
"type": "website",
"name": "Meniu de vară",
"dynamic": true,
"status": "active",
"shortUrl": "https://qrstream.pro/r/7Kd2mA",
"payload": "https://qrstream.pro/r/7Kd2mA",
"target": "https://exemplu.ro/meniu",
"content": { "type": "website", "url": "https://exemplu.ro/meniu" },
"folderId": null,
"hasPassword": false,
"expiresAt": null,
"scanCount": 0,
"createdAt": "2026-09-12T07:41:02.000Z"
}GET /codes/{id}
Un singur cod, cu toate câmpurile. Un id necunoscut sau al altui cont răspunde 404.
curl -s "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33" \
-H "Authorization: Bearer $QRS_API_KEY"PATCH /codes/{id}
Modifică doar câmpurile pe care le trimiți. Conținutul unui cod static nu se poate schimba după creare — modulele sunt deja tipărite — dar numele, designul, folderul și parola da.
curl -s -X PATCH "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33" \
-H "Authorization: Bearer $QRS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Meniu de vară 2026",
"content": { "type": "website", "url": "https://exemplu.ro/meniu?v=2" },
"folderId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}'DELETE /codes/{id}
Răspunde 204, fără corp. Ștergerea este blândă: codul dispare din panou și din listă, iar un cod dinamic nu mai redirecționează.
curl -s -i -X DELETE "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33" \
-H "Authorization: Bearer $QRS_API_KEY"POST /codes/{id}/pause și /resume
Oprește sau repornește un cod dinamic fără să îl ștergi. Răspunsul este codul actualizat, cu status paused sau active.
curl -s -X POST "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/pause" \
-H "Authorization: Bearer $QRS_API_KEY"
curl -s -X POST "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/resume" \
-H "Authorization: Bearer $QRS_API_KEY"Obiectul cod
| Câmp | Tip | Descriere |
|---|---|---|
| id | uuid | Identificatorul codului. |
| type | text | Tipul codului. |
| name | text | Numele din panou. |
| dynamic | boolean | Dacă trece printr-un link scurt. |
| status | text | active sau paused. |
| shortUrl | text sau null | Linkul scurt; null la codurile statice. |
| payload | text | Ce este scris efectiv în modulele codului. |
| target | text sau null | Destinația finală a unui cod dinamic. |
| content | obiect | Conținutul structurat, cu type-ul lui. |
| design | obiect | Designul complet al codului. |
| folderId | uuid sau null | Folderul în care se află. |
| hasPassword | boolean | Dacă pagina de scanare cere o parolă. Parola nu se returnează niciodată. |
| expiresAt | dată sau null | Momentul expirării. |
| campaignStart | dată sau null | Începutul campaniei. |
| campaignEnd | dată sau null | Sfârșitul campaniei. |
| printRun | text sau null | Nota despre tiraj. |
| scanCount | număr | Total scanări. |
| lastScanAt | dată sau null | Ultima scanare. |
| brokenLink | boolean | Destinația nu a mai răspuns la ultima verificare. |
| createdAt | dată | Când a fost creat. |
| updatedAt | dată | Ultima modificare. |
Conținut, pe tipuri
Obiectul content își poartă propriul type, identic cu cel al codului. Mai jos sunt tipurile cele mai folosite; restul sunt descrise în documentul OpenAPI.
website
{ "type": "website", "url": "https://exemplu.ro/meniu" }wifi
encryption poate fi WPA, WEP sau nopass. Lasă password gol la o rețea fără parolă.
{
"type": "wifi",
"ssid": "Cafenea",
"password": "0000pass",
"encryption": "WPA",
"hidden": false
}vcard
Toate câmpurile sunt opționale, dar un cod fără niciunul nu are ce afișa.
{
"type": "vcard",
"first": "Ana",
"last": "Popescu",
"phone": "+40 721 000 000",
"email": "ana@exemplu.ro",
"org": "Cafeneaua Verde",
"title": "Manager",
"url": "https://exemplu.ro/meniu"
}Numărul se trimite în format internațional; message este mesajul precompletat.
{
"type": "whatsapp",
"phone": "+40721000000",
"message": "Bună! Aș vrea o rezervare."
}fileUrl este adresa fișierului deja încărcat. Încărcarea fișierelor nu se face prin API în versiunea 1 — urcă documentul din panou și refolosește adresa.
{
"type": "pdf",
"fileName": "meniu.pdf",
"fileUrl": "https://blob.qrstream.pro/…/meniu.pdf"
}Imagini
Fiecare cod are două rute de imagine. Amândouă acceptă size între 128 și 2048 de pixeli, implicit 1024, și cer dreptul de citire pe coduri.
| Rută | Tip de conținut | Bun pentru |
|---|---|---|
| GET /codes/{id}/image.svg | image/svg+xml | Tipar și orice dimensiune; vectorial, fără pierderi. |
| GET /codes/{id}/image.png | image/png | Ecrane, emailuri, documente. |
curl -s "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/image.svg?size=1024" \
-H "Authorization: Bearer $QRS_API_KEY" -o cod.svg
curl -s "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/image.png?size=512" \
-H "Authorization: Bearer $QRS_API_KEY" -o cod.pngLa PNG logoul este descărcat și încorporat de server înainte de randare, deci apare în imagine. Textul de pe ramă se desenează cu un font de sistem, nu cu fontul de brand, așa că poate arăta puțin diferit față de previzualizarea din panou. Dacă ai nevoie de fidelitate maximă, cere SVG.
Statistici
GET /codes/{id}/analytics
Scanările unui cod, pe zile. range poate fi 7, 30 sau 90 de zile, implicit 30. Datele sunt ISO, iar valorile din breakdowns sunt brute — coduri de sistem de operare și coduri de țară ISO — ca să le poți grupa singur.
Listele country și city din breakdowns sunt tăiate la cele mai active 50 de valori, în ordine descrescătoare. Restul rămân numărate în totals.
curl -s "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/analytics?range=30" \
-H "Authorization: Bearer $QRS_API_KEY"{
"range": 30,
"code": {
"id": "7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33",
"name": "Meniu de vară",
"type": "website",
"scanCount": 1284,
"lastScanAt": "2026-08-15T17:02:11.000Z"
},
"totals": { "scans": 1284, "unique": 902 },
"series": [
{ "date": "2026-08-14", "scans": 42, "unique": 31 },
{ "date": "2026-08-15", "scans": 57, "unique": 40 }
],
"breakdowns": {
"os": [
{ "key": "ios", "value": 812 },
{ "key": "android", "value": 401 }
],
"country": [{ "key": "RO", "value": 1180 }],
"city": [{ "key": "București", "value": 640 }]
}
}GET /codes/{id}/scans.csv
Aceleași scanări, rând cu rând, ca text/csv. Răspunsul se transmite în flux, deci merge și pentru coduri cu multe scanări.
curl -s "https://qrstream.pro/api/v1/codes/7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33/scans.csv" \
-H "Authorization: Bearer $QRS_API_KEY" -o scanari.csvGET /analytics
Aceeași imagine, dar pentru tot contul: totaluri, seria zilnică și codurile cu cele mai multe scanări. Perioada pe care o poți cere depinde de planul tău.
curl -s "https://qrstream.pro/api/v1/analytics?range=90" \
-H "Authorization: Bearer $QRS_API_KEY"{
"range": 90,
"totals": { "scans": 41280, "unique": 28904 },
"series": [{ "date": "2026-06-14", "scans": 402, "unique": 311 }],
"topCodes": [
{ "id": "7f1c0b52-1b7e-4a1e-9f0a-2d5b6c8e1a33", "name": "Meniu de vară", "type": "website", "scans": 1284 }
],
"breakdowns": {
"os": [{ "key": "ios", "value": 24310 }],
"country": [{ "key": "RO", "value": 39002 }],
"city": [{ "key": "București", "value": 18770 }]
}
}Foldere
Folderele grupează codurile în panou. Un folder fără scope este un folder obișnuit; scope poate fi storage pentru cutii sau location pentru etichete de raft. Ștergerea unui folder nu șterge codurile din el.
| Rută | Drept | Face |
|---|---|---|
| GET /folders | foldere: citire | Toate folderele, cu numărul de coduri. |
| POST /folders | foldere: scriere | Creează un folder; răspunde 201. |
| PATCH /folders/{id} | foldere: scriere | Redenumește folderul. |
| DELETE /folders/{id} | foldere: scriere | Șterge folderul; răspunde 204. |
curl -s "https://qrstream.pro/api/v1/folders" \
-H "Authorization: Bearer $QRS_API_KEY"
curl -s -X POST "https://qrstream.pro/api/v1/folders" \
-H "Authorization: Bearer $QRS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Campanii", "scope": null }'
curl -s -X PATCH "https://qrstream.pro/api/v1/folders/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
-H "Authorization: Bearer $QRS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Campanii 2026" }'
curl -s -i -X DELETE "https://qrstream.pro/api/v1/folders/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
-H "Authorization: Bearer $QRS_API_KEY"{
"data": [
{
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"name": "Campanii",
"color": null,
"scope": null,
"createdAt": "2026-08-14T09:12:44.000Z",
"updatedAt": "2026-08-14T09:12:44.000Z"
}
]
}OpenAPI
Descrierea completă a API-ului este publicată ca document OpenAPI 3.1 la https://qrstream.pro/api/v1/openapi.json. Este public, nu are nevoie de cheie, și îl poți da unui generator de clienți sau îl poți deschide în Postman ori Insomnia.
curl -s https://qrstream.pro/api/v1/openapi.json -o openapi.jsonDocumentul este sursa de adevăr pentru schemele de conținut și design ale fiecărui tip de cod. Pagina aceasta descrie doar ce folosești cel mai des.
Server MCP pentru agenți AI
MCP (Model Context Protocol) este felul în care un agent AI află ce unelte are la dispoziție și le folosește singur. Serverul nostru MCP expune aceleași operații ca API-ul REST — câte o unealtă pentru fiecare rută — și lucrează pe contul căruia îi aparține cheia.
Adresa serverului
POST https://qrstream.pro/api/mcp
Authorization: Bearer qrs_9f3a1c7b2e…Serverul cere planul Business, ca și API-ul REST, și consumă aceleași 120 de cereri pe minut ale cheii. Conectarea unui client costă 3 cereri, pentru că agentul face întâi handshake-ul: initialize, notifications/initialized și tools/list.
Claude Code
claude mcp add --transport http qrstream https://qrstream.pro/api/mcp --header "Authorization: Bearer qrs_CHEIA_TA"Cursor — .cursor/mcp.json
{
"mcpServers": {
"qrstream": {
"url": "https://qrstream.pro/api/mcp",
"headers": {
"Authorization": "Bearer qrs_CHEIA_TA"
}
}
}
}Claude Desktop — claude_desktop_config.json
{
"mcpServers": {
"qrstream": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://qrstream.pro/api/mcp",
"--header",
"Authorization: Bearer ${QRSTREAM_KEY}"
],
"env": {
"QRSTREAM_KEY": "qrs_CHEIA_TA"
}
}
}
}Conectorii personalizați din claude.ai cer OAuth, așa că serverul acesta, care se autentifică cu o cheie API, se conectează din Claude Code, Cursor, VS Code și Claude Desktop (prin mcp-remote --header). OAuth rămâne o etapă posibilă mai târziu.
| Tool | Acces | Ce face |
|---|---|---|
| qrstream_get_account | orice cheie | Îți spune pe ce plan ești, cât din limite ai consumat și ce drepturi are cheia. |
| qrstream_list_codes | codes:read | Listează codurile contului, cu filtre după tip, stare, folder și nume. |
| qrstream_get_code | codes:read | Returnează un singur cod, cu toate câmpurile lui. |
| qrstream_create_code | codes:write | Creează un cod nou, static sau dinamic, din conținutul pe care i-l dai. |
| qrstream_update_code | codes:write | Modifică numele, conținutul, designul, folderul sau parola unui cod. |
| qrstream_delete_code | codes:write | Șterge un cod; un cod dinamic nu mai redirecționează după ștergere. |
| qrstream_pause_code | codes:write | Oprește temporar un cod dinamic, fără să îl ștergi. |
| qrstream_resume_code | codes:write | Repornește un cod pus pe pauză. |
| qrstream_get_code_image_svg | codes:read | Returnează imaginea codului ca document SVG, bun pentru tipar. |
| qrstream_get_code_image_png | codes:read | Returnează imaginea codului ca PNG, pentru ecrane, emailuri și documente. |
| qrstream_get_code_analytics | analytics:read | Scanările unui cod pe 7, 30 sau 90 de zile, cu totaluri și defalcări. |
| qrstream_get_code_scans | analytics:read | Scanările unui cod, rând cu rând, ca text CSV. |
| qrstream_get_account_analytics | analytics:read | Aceeași imagine, dar pentru tot contul: totaluri, serie zilnică și codurile cu cele mai multe scanări. |
| qrstream_list_folders | folders:read | Listează folderele contului, cu numărul de coduri din fiecare. |
| qrstream_create_folder | folders:write | Creează un folder nou, obișnuit sau pentru cutii ori etichete de raft. |
| qrstream_rename_folder | folders:write | Redenumește un folder existent. |
| qrstream_delete_folder | folders:write | Șterge un folder; codurile din el rămân, dar ies din folder. |
- Imaginile SVG și PNG merg până la 1024 de pixeli; pentru dimensiuni mai mari folosește rutele de imagine ale API-ului REST.
- Lista de scanări se oprește la 500 de rânduri; exportul complet se ia din ruta CSV a API-ului REST.
- Listele se parcurg cu un cursor: dacă răspunsul are nextCursor, trimite-l înapoi ca să ceri pagina următoare.
- Uneltele care scriu refuză o cheie doar-citire cu un mesaj clar, pe care agentul ți-l arată.