API Cloud

    Toate casele clienților tăi, de la distanță.

    Din organizația de integrator trimiți comenzi către casele clienților tăi prin FiscalLink Cloud. Cloud-ul le duce la aplicația FiscalLink de pe fiecare casă, iar rezultatul îl citești după aceea.

    Organizația de integrator

    • O creezi în FiscalLink Cloud, ca organizație de integrator; în dashboard tipul ei se numește „Integrare”. Taxa e 100 lei pe an +TVA, doar anual, plătită prin Stripe. Organizația apare abia după plată; dacă abandonezi plata, nu rămâne nimic în urmă.
    • E un produs separat de organizațiile obișnuite: are clienți și case, fără locații, fără date fiscale și fără licențe proprii.
    • În dashboard ai: panoul principal, clienți, coduri de activare, case, comenzi, documentația API, sandbox și setări.
    • Drepturile echipei sunt separate pe acțiuni: vizualizare; clienți (adăugare, modificare, ștergere); case (adăugare, conectare, ștergere, comenzi); documentație; sandbox. Dreptul de a administra casele nu include dreptul de a le trimite comenzi.

    Clienți și coduri de activare

    • Clientul e magazinul pe care îl deservești. Trebuie să existe înainte să activezi o casă pentru el. Numele e unic în organizația ta. Un client care mai are case nefinalizate (care nu sunt Removed) nu poate fi șters.
    • Codul de activare se creează odată cu clientul: un singur cod pentru toate casele clientului, refolosibil. E un GUID cu majuscule, în 8 grupe de câte 4 caractere; liniuțele, spațiile și literele mici nu contează când îl introduce clientul.
    • Linkul de activare poartă codul după #code=, în fragmentul adresei, deci codul nu ajunge la niciun server. Linkul deschide aplicația FiscalLink.
    • Cod nou. Poți genera oricând un cod nou: cel vechi nu mai merge imediat, iar casele deja activate rămân conectate.
    • Cum apar casele. De obicei, singure: clientul folosește codul în aplicația FiscalLink pe fiecare casă, aplicația îi arată numele tău și al clientului și îl întreabă dacă acceptă. Pașii clientului sunt în documentația aplicației (/app/docs).
    • Ce nu poate face un cod. Codul unui client nu poate lua casa altui client de-al tău. O casă deja asociată cu o organizație FiscalLink Cloud nu își pierde legătura din cauza codului tău.

    Acordul casei

    • Când clientul folosește codul tău, acordul e dat: casa începe să primească comenzi.
    • Când activezi tu o casă după serie, prin API-ul de administrare, înregistrarea e Active, dar cu awaitingClientConsent true: casa nu primește comenzi până nu acceptă în aplicația FiscalLink. Interoghează „Citește o casă” până când awaitingClientConsent e false.
    • Aplicația întreabă casa: Conectează sau Refuză. Închiderea dialogului nu e un refuz; întrebarea revine după o pauză.
    • Dacă acel client refuză, seria se șterge din înregistrare, care trece în AwaitingSerial. Poți lega altă serie cu „Leagă o serie”.

    Stările unei case

    StatusDescriere
    ActiveSeria e legată, casa poate primi comenzi (după acord).
    AwaitingSerialCasa a refuzat seria numită de tine și așteaptă alta.
    SuspendedAbonamentul organizației nu mai dă acces: fără comenzi, până se rezolvă plata.
    RemovedScoasă de tine, păstrată pentru istoric. Definitiv: „înregistrează din nou” creează o înregistrare nouă.

    Chei API

    CheiePrefixCe deschideUnde o creezi
    Automatizare (comenzi live)fsk_API-ul de comenzi, pe casele tale activate. Nu poate modifica clienți sau case.Setări → Chei API → Comenzi
    Administrare organizațiefsk_Clienți, coduri de activare, case și utilizare. Nu poate trimite comenzi.Setări → Chei API → API administrare organizație
    Sandboxfsk_test_Doar rutele /sandbox, pe casele de test.Setări → Chei API → Comenzi → Cheie nouă → Sandbox
    • Cheile de comenzi și cele de administrare încep amândouă cu fsk_: prefixul singur nu le deosebește. O cheie de administrare nu e niciodată cheie de sandbox.
    • O cheie de administrare cere un drept separat, pentru că ce creează poate șterge un client sau poate costa bani.
    • Cheia completă apare o singură dată, la creare. Salveaz-o atunci; după aceea se vede doar prefixul.
    • Expirare: niciodată, 30 de zile, 90 de zile, un an sau o dată aleasă (cel puțin de mâine). Primești un avertisment cu 14 zile înainte. O cheie revocată rămâne revocată; o cheie rotită își păstrează data de expirare.

    Adresă și autentificare

    • Adresa API-ului: https://cloud-api.fiscallink.ro.
    • Fiecare cerere poartă cheia în antetul X-Api-Key. Merge și Authorization: Bearer fsk_….
    • {tenantId} din cale trebuie să fie organizația care a creat cheia; altă valoare răspunde 403.
    • Apelează API-ul de pe serverul tău. Browserele de pe alte site-uri sunt blocate (CORS), iar o cheie pusă într-o pagină web o poate citi oricine.
    • Convenții: JSON în camelCase; proprietățile nule vin ca null; datele sunt ISO 8601 în UTC, cu Z. În răspunsuri, enumerările vin ca nume (Active, Succeeded); în corpul comenzilor le trimiți ca numere.
    • Listele sunt paginate cu page (de la 1) și pageSize (implicit 20, între 1 și 100; o valoare din afară e ajustată, nu refuzată).
    • API-ul nu are limită de apeluri pe aceste rute.
    bash
    curl -X POST "https://cloud-api.fiscallink.ro/api/tenants/{tenantId}/integrator/cash-registers/DT000001/commands/print-report-x" \
      -H "X-Api-Key: fsk_..."
    json
    {
      "page": 1,
      "pageSize": 20,
      "total": 1,
      "items": []
    }

    Comenzi permise

    Un integrator poate trimite: raport Z, raport X, bon fiscal, bon nefiscal și citirea cotelor TVA. Programarea cotelor TVA nu e permisă: e contorizată și ireversibilă. Articolele (PLU) nu se pot citi sau programa prin API-ul Cloud de integrator.

    O comandă trimisă către o casă care nu e a ta, nu e Active, n-a dat încă acordul sau al cărei abonament nu dă acces primește același 404, care nu confirmă dacă seria există.

    Ciclul unei comenzi

    1. Trimiți comanda. Răspunsul e imediat 202 cu un commandId, fie că e casa conectată, fie că nu.
    2. Interoghezi „Starea unei comenzi” la fiecare 1–2 secunde până când status e final.
    3. Rezultatul casei e în body, iar codul ei în statusCode.
    StatusDescriere
    PendingÎn coadă: casa e offline sau ocupată.
    ProcessingTrimisă casei, fără răspuns încă.
    SucceededFinal: casa a răspuns cu un cod 2xx.
    FailedFinal: casa sau aplicația au refuzat comanda.
    TimedOutFinal: casa n-a răspuns în 2 încercări de până la 30 de secunde.
    CancelledFinal: comanda a fost anulată.
    SentValoare veche, nu mai e scrisă.
    • Casă offline. Comanda rămâne Pending și nu expiră: pleacă atunci când casa se reconectează. Un raport Z sau un bon din coadă se poate tipări și după câteva zile.
    • O comandă pe rând. Comenzile pentru aceeași casă rulează strict una după alta; case diferite lucrează în paralel.
    • Fără hârtie. Pe o casă live, bonul fiscal și cel nefiscal se retrimit automat la fiecare 60 de secunde, cu un commandId nou. Continuă să interoghezi ID-ul inițial: răspunsul e reîncercarea, cu propriul commandId și cu retryOfCommandId egal cu ID-ul tău.
    • Aceeași comandă nu rulează de două ori. Dacă aceeași comandă ajunge din nou la casă, aplicația răspunde cu rezultatul salvat și nu mai trimite nimic la casă. O trimitere nouă e însă o comandă nouă.

    Trimitere către mai multe case

    Din dashboard, la Comenzi, poți trimite o comandă către până la 100 de case odată. Răspunsul are un rezultat pentru fiecare casă, iar casele duplicate se numără o singură dată. Cu o cheie API trimiți câte o comandă pe casă, cu rutele de mai jos.

    Datele fiscale

    Trimiți comenzi, nu citești datele clientului. Vezi doar comenzile tale și rezultatul lor, niciodată bonurile, rapoartele sau analizele clientului. Clientul vede documentele fiscale pe care le produc comenzile tale, dar nu și rândurile tale de comenzi. Cadrele trimise efectiv la casă le văd doar administratorii platformei: câmpul trace e mereu null pentru cheile API.

    Facturare

    • Organizația: 100 lei pe an +TVA.
    • Casele: le adaugi gratuit, oricâte. Prima comandă reușită pe o casă deschide o fereastră plătită de o lună, din acel moment; toate comenzile din fereastră sunt incluse, iar suma se facturează când fereastra se încheie, pe factura lunii în care s-a încheiat. Comenzile eșuate nu se facturează. Lunile deja folosite rămân facturate și după ce scoți casa.
    • Prețul pe casă depinde de câte case folosești în luna respectivă; toate casele plătesc treapta în care cade totalul:
    Case pe lunăPreț pe casăReducere
    1–929 lei +TVA—
    10–4926,10 lei +TVA−10%
    50–9924,65 lei +TVA−15%
    100–19923,20 lei +TVA−20%
    200+21,75 lei +TVA−25%
    • Sumele le calculează API-ul. Le vezi cu „Utilizarea lunii” (GET …/integrator/usage). O estimare găsești la Prețuri pentru integratori (/integrators/pricing).
    • Pentru comenzile tale, clientul nu are nevoie de licență proprie: plătești tu casa pe care o folosești. O casă aflată pe o înregistrare activă de integrator nu mai poate cumpăra licență proprie; o casă care avea deja licență poate fi preluată și de un integrator.

    Daisy și vânzarea liberă

    O casă Daisy nu poate vinde un produs care nu e programat în ea. Când organizația ta pornește „Vânzare liberă pe casele Daisy” (Setări → Date companie), FiscalLink vinde fiecare linie liberă pe o casă Daisy ca articol programat și îl programează întâi dacă lipsește. Setarea se aplică tuturor bonurilor de pe casele tale Daisy, inclusiv celor care nu trec prin Cloud (Shopify, casa însăși). Nu trimiți nimic în plus în cereri. Doar proprietarii și administratorii o pot schimba. Cu setarea oprită, decide aplicația FiscalLink de pe fiecare casă.

    Referință: API comenzi

    Cheie: comenzi live (fsk_…)

    IdMetodă · rutăModDescriere
    print-fiscal-receiptPOST /api/tenants/{tenantId}/integrator/cash-registers/{serialNumber}/commands/print-fiscal-receiptAsincronPune în coadă un bon fiscal: linii de vânzare, texte, coduri de bare, linii de separare, subtotal și plăți.
    print-non-fiscal-receiptPOST …/cash-registers/{serialNumber}/commands/print-non-fiscal-receiptAsincronPune în coadă un bon nefiscal: texte, coduri de bare și linii de separare.
    print-report-zPOST …/cash-registers/{serialNumber}/commands/print-report-zAsincronPune în coadă raportul Z, care închide ziua fiscală. Fără corp.
    print-report-xPOST …/cash-registers/{serialNumber}/commands/print-report-xAsincronPune în coadă raportul X, fără să închidă ziua. Fără corp.
    read-vat-ratesPOST …/cash-registers/{serialNumber}/commands/read-vat-ratesAsincronPune în coadă citirea cotelor TVA. Nu schimbă nimic pe casă. Fără corp.
    get-commandGET …/cash-registers/{serialNumber}/commands/{commandId}SincronStarea unei comenzi: status, codul casei și răspunsul ei.
    list-register-commandsGET …/cash-registers/{serialNumber}/commandsSincronComenzile tale către această serie, cele mai noi primele, paginat. Filtre: status (repetabil), type, from, to. O serie pe care n-ai comandat-o răspunde cu o pagină goală, nu 404.
    list-commandsGET /api/tenants/{tenantId}/integrator/commandsSincronToate comenzile organizației, pe toată flota, paginat. Filtre: status, type, from, to, serialNumber, registerId.

    … = /api/tenants/{tenantId}/integrator.

    Corpul bonului fiscal

    Corpul are aceleași câmpuri și reguli ca print-fiscal-receipt din API-ul local, în camelCase: openFiscalReceipt, receiptItems[].freeSale, subtotal, payments.

    Cloud-ul verifică doar că bonul e un obiect JSON. Restul îl verifică aplicația FiscalLink de pe casă, iar rezultatul vine în rezultatul comenzii.

    payments[].amount poate lipsi: metoda achită ce mai e de plată. Pe Daisy, o plată într-o anumită metodă poartă mereu suma: trimite amount. Un payments gol nu trimite nicio plată.

    Dacă proprietarul casei a fixat o metodă de plată în FiscalLink Cloud, fiecare plată pleacă cu acea metodă.

    Bonul nefiscal acceptă și openNonFiscalReceipt (operatorCode, operatorPassword): Datecs și Daisy îl ignoră; pe Tremol, dacă îl trimiți, ambele câmpuri sunt obligatorii, iar fără el aplicația citește operatorul 1 de pe casă.

    Cerere — print-fiscal-receipt

    json
    {
      "openFiscalReceipt": { "operatorCode": 1, "operatorPassword": "0001", "pointOfSaleNumber": 1, "invoice": false },
      "receiptItems": [
        { "type": 0, "freeSale": { "productName": "Espresso", "taxCode": 1, "price": 8.5, "quantity": 2, "department": 1, "unit": "buc" } },
        { "type": 1, "pluSale": { "pluNumber": 101, "quantity": 1 } },
        { "type": 2, "text": { "message": "Ref. 128", "alignment": 1 } },
        { "type": 4, "separatingLineType": 1 }
      ],
      "subtotal": { "print": true, "discountType": 2, "discountValue": 5 },
      "payments": [{ "paymentMode": 1 }]
    }

    Răspuns 202

    json
    { "commandId": "c3a9f1e7-5d2b-4c8a-9e61-0f1a2b3c4d5e" }

    Cerere — print-non-fiscal-receipt

    json
    {
      "openNonFiscalReceipt": { "operatorCode": 1, "operatorPassword": "0001" },
      "receiptItems": [
        { "type": 0, "text": { "message": "Ref. 128", "bold": true, "alignment": 1 } },
        { "type": 2, "separatingLineType": 1 },
        { "type": 1, "barCode": { "type": 2, "data": "5940000000820" } }
      ]
    }

    Starea unei comenzi — get-command, 200

    json
    {
      "commandId": "c3a9f1e7-5d2b-4c8a-9e61-0f1a2b3c4d5e",
      "retryOfCommandId": null,
      "commandType": "cashRegister.printFiscalReceipt",
      "serialNumber": "DT000001",
      "status": "Succeeded",
      "createdAt": "2026-09-25T09:15:01.221Z",
      "completedAt": "2026-09-25T09:15:03.870Z",
      "statusCode": 200,
      "contentType": "application/json",
      "body": {
        "StatusCode": 200,
        "Data": {
          "message": "Receipt printed successfully.",
          "receiptNumber": null,
          "subtotal": null,
          "total": null,
          "paymentFinalized": null,
          "vatBreakdown": null,
          "pluItems": null,
          "printedAt": "2026-09-25T09:15:03.870Z"
        }
      },
      "message": null,
      "payload": { "receiptItems": [{ "type": 1, "pluSale": { "pluNumber": 101, "quantity": 2 } }] },
      "trace": null
    }
    CâmpDescriere
    commandIdID-ul comenzii.
    retryOfCommandIdLa o reîncercare automată din lipsă de hârtie: ID-ul primei comenzi, cea primită de tine. null la comenzile obișnuite.
    commandTypecashRegister.printFiscalReceipt, cashRegister.printNonFiscalReceipt, cashRegister.printReportZ, cashRegister.printReportX sau cashRegister.readVatRates.
    statusStarea, vezi „Ciclul unei comenzi”.
    createdAt / completedAtCând a intrat în coadă / când a ajuns într-o stare finală.
    statusCodeCodul raportat de casă: vezi „Rezultatul unei comenzi”.
    bodyRăspunsul casei, așa cum a venit.
    messageTextul casei, din body.message; null la succes.
    payloadCorpul trimis de tine; null la comenzile fără corp.
    traceMereu null pentru cheile API.

    Rezultatul bonului fiscal (în body)

    json
    {
      "StatusCode": 200,
      "Data": {
        "message": "Receipt printed successfully.",
        "receiptNumber": "0000123",
        "subtotal": 42.5,
        "total": 42.5,
        "paymentFinalized": true,
        "vatBreakdown": { "vat0": 42.5 },
        "pluItems": null,
        "printedAt": "2026-09-25T10:15:30.1234567Z"
      }
    }

    Datecs și Daisy completează doar message și printedAt; celelalte câmpuri sunt null. Tremol le citește înapoi, cât poate, deci oricare poate fi tot null. pluItems vine pe Tremol când bonul are linii PLU și când liniile libere s-au vândut ca articole pe Daisy.

    Rezultatul rapoartelor Z și X și al citirii cotelor TVA are aceeași formă ca în API-ul local (vezi „Rapoarte” și „Cote TVA”), cu chei PascalCase în Data.

    Coduri de răspuns la trimitere

    StatusDescriere
    202În coadă. Casa n-a făcut încă nimic; interoghează commandId.
    400Lipsește seria („Register serial is required.”), corpul unui bon lipsește sau nu e obiect JSON („This command needs a JSON object body.”), sau cererea nu se poate citi (application/problem+json cu errors).
    401Fără cheie, cheie necunoscută, revocată sau expirată, ori organizația din cale nu există. Corp gol.
    403Cheia e validă, dar nu e cheie de comenzi live sau e a altei organizații. Corp gol.
    404Nicio casă cu această serie nu poate primi comenzile tale. Intenționat vag.
    500Eșec neașteptat la punerea în coadă.

    Coduri la citire

    200; 400 (un status care nu e numele unei stări); 401; 403; 404 (comandă necunoscută, a altei organizații sau altă serie; la list-commands, un registerId care nu e al tău).

    Referință: API administrare

    Cheie: administrare (fsk_…)

    Sincron: răspunde cu rezultatul

    … = /api/tenants/{tenantId}/integrator.

    IdMetodă · rutăDescriere
    list-clientsGET …/clientsClienții organizației, paginat, după nume. Filtre: search, hasRegisters, createdFrom, createdTo.
    get-clientGET …/clients/{clientId}Un client.
    create-clientPOST …/clientsCreează un client și codul lui de activare. Răspunde 201 cu antetul Location. Numele e unic în organizație.
    update-clientPUT …/clients/{clientId}Înlocuiește toate datele clientului: un câmp opțional lipsă sau null se șterge.
    delete-clientDELETE …/clients/{clientId}Șterge clientul și codul lui. Refuzat cât timp clientul are case care nu sunt Removed.
    list-activationsGET …/activationsClienții cu codul și linkul de activare, paginat. Util în propriul tău flux de înrolare.
    get-client-activationGET …/clients/{clientId}/activationCodul și linkul de activare ale unui client. Dacă clientul nu are cod, citirea îl creează.
    roll-client-activationPOST …/clients/{clientId}/activation/rollGenerează un cod nou. Cel vechi nu mai merge imediat; casele activate rămân conectate.
    list-registersGET …/registersToate înregistrările, inclusiv Removed, paginat, cele mai noi primele. Filtre: search, clientId, activated.
    get-registerGET …/registers/{registerId}O casă, cu starea și conexiunea ei.
    activate-registerPOST …/registersActivează o casă pentru un client, după serie. Înregistrarea e Active cu awaitingClientConsent true; răspunde 201 cu Location. Activarea e gratuită și cere un abonament de integrator activ.
    bind-register-serialPOST …/registers/{registerId}/serialDoar pentru o înregistrare AwaitingSerial: leagă o serie nouă și cere din nou acordul. O serie legată e permanentă.
    move-registerPATCH …/registers/{registerId}Mută casa la alt client de-al tău. Doar evidență: facturarea și conexiunea nu se schimbă.
    remove-registerDELETE …/registers/{registerId}Trece casa în Removed imediat; nu mai primește comenzile tale. Răspunde 202, deși scoaterea e deja în vigoare.
    get-usageGET …/usage?period=yyyy-MMCe plătește organizația pentru o lună și ce case au produs suma: ferestrele încheiate (charges), cele în curs (coverage), ultima fereastră a fiecărei case (latestPerRegister). Sumele sunt zecimale în unități întregi, fără monedă; prețurile sunt null până la prima sincronizare a abonamentului.

    Cerere — create-client

    json
    {
      "name": "Exemplu SRL",
      "cui": "<CIF>",
      "contactName": "<contact-name>",
      "email": "<email>",
      "phone": "<phone>",
      "locality": "<locality>",
      "county": "<county>"
    }

    Câmpuri: name (obligatoriu, maximum 200 de caractere, unic fără a ține cont de majuscule), cui, contactName, email, phone, address, locality, county, zipCode, notes.

    Răspuns — get-client-activation, 200

    json
    {
      "clientId": "0f8a1c2e-6b3d-4e7f-9a10-2c4d6e8f0a12",
      "clientName": "Exemplu SRL",
      "activationCode": "3F2A-9C1D-4E5B-8A70-1122-3344-5566-7788",
      "activationLink": "<activation-page>#code=3F2A-9C1D-4E5B-8A70-1122-3344-5566-7788",
      "registerCount": 3,
      "createdAtUtc": "2026-08-27T09:14:22.512Z",
      "rolledAtUtc": null
    }

    Cerere — activate-register

    json
    { "clientId": "0f8a1c2e-6b3d-4e7f-9a10-2c4d6e8f0a12", "serialNumber": "DT000001" }

    Răspuns — get-register, 200

    json
    {
      "id": "7d2e9b4f-1a3c-4d5e-8f60-b1c2d3e4f5a6",
      "clientId": "0f8a1c2e-6b3d-4e7f-9a10-2c4d6e8f0a12",
      "clientName": "Exemplu SRL",
      "serialNumber": "DT000001",
      "brand": "Datecs",
      "status": "Active",
      "isOnline": true,
      "awaitingClientConsent": false,
      "activatedAtUtc": "2026-08-27T09:14:22.512Z",
      "serialBoundAtUtc": "2026-08-27T09:14:22.512Z",
      "clientDeclinedAtUtc": null,
      "clientDeclinedSerialNumber": null
    }
    CâmpDescriere
    brandMarca, din ultima citire reușită a cotelor TVA; null fără serie sau fără o asemenea citire.
    isOnlinetrue doar când starea e Active, clientul a acceptat și casa a fost văzută ultima dată online.
    awaitingClientConsenttrue după ce ai activat sau legat tu o serie, până acceptă casa.
    clientDeclinedAtUtc / clientDeclinedSerialNumberCând a refuzat casa și ce serie a refuzat (refuzul golește serialNumber).

    Coduri de răspuns

    StatusDescriere
    200 / 201 / 202Succes, după rută (vezi tabelul).
    400Validare: nume gol sau prea lung, serie goală sau prea lungă, abonament de integrator inactiv, createdFrom după createdTo, perioadă care nu e yyyy-MM, client cu case active la ștergere, casă deja Removed.
    401 / 403Ca la API-ul de comenzi; 403 dacă cheia nu e de administrare.
    404Clientul sau casa nu există în organizația ta.
    409Conflict: numele clientului e deja folosit, sau seria e deja pe altă înregistrare a ta care nu e Removed.
    500Scrierea a eșuat.

    Rutele sandbox

    Fiecare rută de comenzi există și cu `/sandbox` după `/integrator`, cu o cheie `fsk_test_`. Detaliile sunt în „Sandbox”.