API paragonów

Paragony fiskalne z systemu sprzedaży (sklep internetowy, POS, ERP) drukowane na drukarkach podłączonych do Ricevio.

Podstawy

Klucz API

Klucz generujemy w panelu Ricevio (Firma → Klucze API). Wygenerowany klucz jest przypisany do lokalizacji.

Authorization: Bearer rcv_…

Klucz przesyłany jest w nagłówku - brak lub błędny powoduje 401 INVALID_API_KEY.

Zlecenie paragonu - POST /v1/receipts

POST https://api.ricevio.com/v1/receipts
Authorization: Bearer rcv_…
Idempotency-Key: order-10482
Content-Type: application/json

{
  "printer": "3f2a8c1e-6b4d-4e2a-9f1c-7d5e0b9a4c21",
  "items": [
    {
      "name": "Kawa ziarnista 1 kg",
      "quantity": "2",
      "unit_price": "89.90",
      "vat": "23",
      "discount": { "amount": "17.98", "name": "Promocja" }
    },
    { "name": "Dostawa", "quantity": "1", "unit_price": "14.99", "vat": "23" }
  ],
  "payments": [{ "type": "card", "amount": "176.81" }],
  "total": "176.81",
  "buyer_nip": "1112223344"
}
HTTP/1.1 202 Accepted
Location: /v1/receipts/9b2d4e61-0f7a-4c3e-8d15-6a0c2f9e7b48

{
  "id": "9b2d4e61-0f7a-4c3e-8d15-6a0c2f9e7b48",
  "status": "queued",
  "idempotency_key": "order-10482",
  "printer": "3f2a8c1e-6b4d-4e2a-9f1c-7d5e0b9a4c21",
  "total": "176.81",
  "created_at": "2026-10-11T13:20:03+02:00",
  "not_after": "2026-10-11T14:20:03+02:00",
  "finished_at": null,
  "error": null
}

Pola

PoleOpis
printerIdentyfikator drukarki z GET /v1/printers.
itemsPozycje, 1-500.
items[].nameNazwa towaru, maks. 80 znaków.
items[].quantityOpcjonalne. Ilość, domyślnie "1".
items[].unit_priceCena jednostkowa brutto.
items[].vatStawka VAT: "23", "8", "5", "0" lub "zw".
items[].discount.amountOpcjonalne. Rabat kwotowy.
items[].discount.nameOpcjonalne. Opis rabatu, maks. 25 znaków.
payments[].typeForma płatności: cash, card, transfer, voucher, coupon, credit, cheque, other.
payments[].amountKwota płatności.
payments[].nameOpcjonalne. Nazwa płatności, maks. 25 znaków.
totalSuma paragonu, zgodna z sumą pozycji.
buyer_nipOpcjonalne. NIP nabywcy.
e_receipt.buyer_idOpcjonalne. Identyfikator kupującego - e-paragon zamiast wydruku.
e_receipt.serverOpcjonalne. Adres serwera e-paragonów.

Nieznane pole - 400. Paragon, którego drukarka nie przyjmie - 422 z polem w field. Niewydrukowany w 60 min paragon wygasa.

Klucz idempotencji

Nagłówek Idempotency-Key jest obowiązkowy, np. numer zamówienia.

Stan paragonu - GET /v1/receipts/{id}

StanZnaczenie
queuedW kolejce.
printingNa drukarce albo jeszcze nie wiadomo, czy się wydrukował. Nie zostanie wysłany drugi raz.
printedWydrukowany. E-paragon ma e_receipt_id.
failedNa pewno niewydrukowany, przyczyna w error.
expiredNie wydrukował się przed not_after.
cancelledWycofany z kolejki.
{
  "id": "9b2d4e61-0f7a-4c3e-8d15-6a0c2f9e7b48",
  "status": "failed",
  …
  "error": {
    "code": "VAT_RATE_LOCKED_FOR_ITEM",
    "message": "Drukarka nie sprzeda towaru o tej nazwie w wyższej stawce VAT niż wcześniej (błąd 2106). Paragonu nie wydrukowano.",
    "device_error_code": 2106
  }
}
error.codePrzyczyna
VAT_RATE_LOCKED_FOR_ITEMTowar był sprzedany w niższej stawce (błąd 2106).
VAT_RATES_CHANGEDZmieniły się stawki na drukarce.
PRINTER_BUSYDrukarkę zajmowała kasa sklepu.
PRINTER_UNREACHABLEBrak połączenia z drukarką.
DEVICE_ERRORBłąd drukarki, kod w device_error_code.
NOT_PRINTED_CONFIRMEDPotwierdzone ręcznie, że się nie wydrukował.
EXPIREDMinął termin wydruku.

Wycofanie - POST /v1/receipts/{id}/cancel

Tylko paragon w kolejce (queued). W innym stanie - 409 RECEIPT_NOT_CANCELLABLE.

Drukarki - GET /v1/printers

Drukarki w zakresie klucza: stawki VAT i to, czy przyjmują paragony.

{
  "printers": [
    {
      "id": "3f2a8c1e-6b4d-4e2a-9f1c-7d5e0b9a4c21",
      "name": "Kasa 1",
      "external_id": null,
      "location": { "id": "…", "name": "Magazyn Wrocław" },
      "model": "POSNET THERMAL XL2 ONLINE 2.01",
      "accepts_receipts": true,
      "reason": null,
      "vat_rates": ["23", "8", "5", "0", "zw"],
      "vat_rates_checked_at": "2026-10-11T06:00:12+02:00",
      "limits": { "max_items": 500, "max_item_name_length": 80 },
      "features": { "e_receipt": true }
    }
  ]
}

Webhooki

POST na Twój adres, gdy paragon będzie printed, failed albo expired. Adres dodajemy w panelu (Firma → Webhooki), tylko HTTPS.

POST https://sklep.example/ricevio/webhook
Content-Type: application/json; charset=utf-8
Ricevio-Event: receipt.printed
Ricevio-Delivery: 5d0f8a27-4f1b-4c55-9b3e-0c6e2a7d9f10
Ricevio-Signature: t=1760181603,v1=6f1c…

{
  "id": "5d0f8a27-4f1b-4c55-9b3e-0c6e2a7d9f10",
  "type": "receipt.printed",
  "created_at": "2026-10-11T13:20:03+02:00",
  "data": { … jak w GET /v1/receipts/{id} … }
}

Podpis

v1 to HMAC-SHA256 sekretem whsec_… z t, kropki i surowej treści. Odrzucaj powiadomienia starsze niż 5 min.

import hashlib, hmac, time

def zweryfikuj(tresc: bytes, naglowek: str, sekret: str) -> bool:
    pola = dict(czesc.split("=", 1) for czesc in naglowek.split(","))
    czas = int(pola["t"])
    if abs(time.time() - czas) > 300:
        return False
    oczekiwany = hmac.new(sekret.encode(), f"{czas}.".encode() + tresc, hashlib.sha256).hexdigest()
    return hmac.compare_digest(oczekiwany, pola["v1"])
<?php
$tresc = file_get_contents('php://input');
$pola = [];
foreach (explode(',', $_SERVER['HTTP_RICEVIO_SIGNATURE'] ?? '') as $czesc) {
    [$klucz, $wartosc] = array_pad(explode('=', $czesc, 2), 2, '');
    $pola[$klucz] = $wartosc;
}
$czas = (int) ($pola['t'] ?? 0);
$oczekiwany = hash_hmac('sha256', $czas . '.' . $tresc, $sekret);
$poprawny = abs(time() - $czas) <= 300 && hash_equals($oczekiwany, $pola['v1'] ?? '');

Doręczanie

Błędy

{ "code": "TOTAL_MISMATCH", "message": "Suma 176.80 różni się od policzonej z pozycji tak, jak liczy drukarka: 176.81. …", "field": "total" }

Błąd 400 ma też listę errors z polami.

HTTPKodyCo zrobić
400INVALID_REQUEST, INVALID_JSON, IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALIDPopraw żądanie.
401INVALID_API_KEYSprawdź klucz.
402ACCOUNT_BLOCKED, QUOTA_EXCEEDEDNie ponawiaj.
403PRINTER_NOT_IN_SCOPEKlucz nie obejmuje tej drukarki.
404PRINTER_NOT_FOUND, RECEIPT_NOT_FOUND, NOT_FOUNDSprawdź identyfikator.
409VAT_RATES_UNKNOWN, RECEIPT_NOT_CANCELLABLEStawki nieznane - ponów za minutę.
413REQUEST_TOO_LARGEMaks. 1 MB.
422TOTAL_MISMATCH, PAYMENTS_TOO_LOW, VAT_RATE_NOT_ON_PRINTER, ITEM_NAME_TOO_LONG, TOO_MANY_ITEMS, TEXT_TOO_LONG, TEXT_EMPTY, TEXT_NOT_PRINTABLE, INVALID_AMOUNT, INVALID_QUANTITY, DISCOUNT_TOO_HIGH, EMPTY_RECEIPT, IDEMPOTENCY_KEY_REUSED, PRINTER_IS_CASH_REGISTER, PRINTER_DISABLED, RECEIPTS_NOT_SUPPORTED, AGENT_UPDATE_REQUIREDPopraw pole z field.
429RATE_LIMITED, QUEUE_FULLPonów po Retry-After.
500SERVER_ERRORPonów z tym samym kluczem idempotencji.

Limity