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.

DreptPoateNu poate
Doar citireGET pe coduri, foldere, statistici și /mePOST, PATCH, DELETE
Citire și scriereTot ce poate citirea, plus crearea, editarea, pauza și ștergereaGestionarea 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-LimitCereri permise într-un minut (120).
X-RateLimit-RemainingCâte ți-au mai rămas în minutul curent.
Retry-AfterSecunde 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" }
    ]
  }
}
CodStatusÎnseamnă
unauthorized401Cheie lipsă, greșită, dezactivată sau expirată.
forbidden403Cheia nu are dreptul cerut, de obicei o scriere cu o cheie de citire.
plan_required403Contul nu are acces la API. Treci pe planul Business.
account_suspended403Contul este suspendat. Scrie-ne ca să îl redeschidem.
quota_exceeded403Ai atins o limită a planului, de exemplu numărul de coduri dinamice.
not_found404Resursa nu există sau nu este a ta. Cele două nu se deosebesc, intenționat.
conflict409Există deja un folder cu numele acesta. Alege altul și reia cererea.
validation_failed422Corpul cererii nu e valid; details enumeră câmpurile.
rate_limited429Ai depășit cele 120 de cereri pe minut.
render_failed500Imaginea nu a putut fi generată. Reîncearcă sau cere SVG.
internal500Eroare 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.

ParametruValoriImplicit
limit1–10025
cursorValoarea 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âmpTipDescriere
typetextTipul codului: website, wifi, vcard, pdf, whatsapp și celelalte.
dynamicbooleanOpțional, implicit false. true creează un link scurt editabil; false scrie conținutul în module.
nametextNumele din panou, 1–120 de caractere.
contentobiectConținutul, potrivit tipului. Vezi secțiunea de mai jos.
designobiectOpțional: culori, forme, logo, ramă, nivel de corecție.
folderIduuid sau nullFolderul în care intră codul.
passwordtext sau nullParolă pentru pagina de scanare; doar la codurile dinamice.
expirytextnone, 30d sau date.
expiresOndată sau nullZiua 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âmpTipDescriere
iduuidIdentificatorul codului.
typetextTipul codului.
nametextNumele din panou.
dynamicbooleanDacă trece printr-un link scurt.
statustextactive sau paused.
shortUrltext sau nullLinkul scurt; null la codurile statice.
payloadtextCe este scris efectiv în modulele codului.
targettext sau nullDestinația finală a unui cod dinamic.
contentobiectConținutul structurat, cu type-ul lui.
designobiectDesignul complet al codului.
folderIduuid sau nullFolderul în care se află.
hasPasswordbooleanDacă pagina de scanare cere o parolă. Parola nu se returnează niciodată.
expiresAtdată sau nullMomentul expirării.
campaignStartdată sau nullÎnceputul campaniei.
campaignEnddată sau nullSfârșitul campaniei.
printRuntext sau nullNota despre tiraj.
scanCountnumărTotal scanări.
lastScanAtdată sau nullUltima scanare.
brokenLinkbooleanDestinația nu a mai răspuns la ultima verificare.
createdAtdatăCând a fost creat.
updatedAtdată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"
}

whatsapp

Numărul se trimite în format internațional; message este mesajul precompletat.

{
  "type": "whatsapp",
  "phone": "+40721000000",
  "message": "Bună! Aș vrea o rezervare."
}

pdf

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ținutBun pentru
GET /codes/{id}/image.svgimage/svg+xmlTipar și orice dimensiune; vectorial, fără pierderi.
GET /codes/{id}/image.pngimage/pngEcrane, 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.png

La 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.csv

GET /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ăDreptFace
GET /foldersfoldere: citireToate folderele, cu numărul de coduri.
POST /foldersfoldere: scriereCreează un folder; răspunde 201.
PATCH /folders/{id}foldere: scriereRedenumeș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.json

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

ToolAccesCe face
qrstream_get_accountorice cheieÎți spune pe ce plan ești, cât din limite ai consumat și ce drepturi are cheia.
qrstream_list_codescodes:readListează codurile contului, cu filtre după tip, stare, folder și nume.
qrstream_get_codecodes:readReturnează un singur cod, cu toate câmpurile lui.
qrstream_create_codecodes:writeCreează un cod nou, static sau dinamic, din conținutul pe care i-l dai.
qrstream_update_codecodes:writeModifică numele, conținutul, designul, folderul sau parola unui cod.
qrstream_delete_codecodes:writeȘterge un cod; un cod dinamic nu mai redirecționează după ștergere.
qrstream_pause_codecodes:writeOprește temporar un cod dinamic, fără să îl ștergi.
qrstream_resume_codecodes:writeRepornește un cod pus pe pauză.
qrstream_get_code_image_svgcodes:readReturnează imaginea codului ca document SVG, bun pentru tipar.
qrstream_get_code_image_pngcodes:readReturnează imaginea codului ca PNG, pentru ecrane, emailuri și documente.
qrstream_get_code_analyticsanalytics:readScanările unui cod pe 7, 30 sau 90 de zile, cu totaluri și defalcări.
qrstream_get_code_scansanalytics:readScanările unui cod, rând cu rând, ca text CSV.
qrstream_get_account_analyticsanalytics:readAceeași imagine, dar pentru tot contul: totaluri, serie zilnică și codurile cu cele mai multe scanări.
qrstream_list_foldersfolders:readListează folderele contului, cu numărul de coduri din fiecare.
qrstream_create_folderfolders:writeCreează un folder nou, obișnuit sau pentru cutii ori etichete de raft.
qrstream_rename_folderfolders:writeRedenumește un folder existent.
qrstream_delete_folderfolders: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ă.