Paragony fiskalne z systemu sprzedaży (sklep internetowy, POS, ERP) drukowane na drukarkach podłączonych do Ricevio.
https://api.ricevio.com/v1, JSON w UTF-8."89.90".code.2026-10-11T14:20:03+02:00.202 od razu. Wynik przychodzi webhookiem albo z GET /v1/receipts/{id}.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.
POST /v1/receiptsPOST 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
}
| Pole | Opis |
|---|---|
printer | Identyfikator drukarki z GET /v1/printers. |
items | Pozycje, 1-500. |
items[].name | Nazwa towaru, maks. 80 znaków. |
items[].quantity | Opcjonalne. Ilość, domyślnie "1". |
items[].unit_price | Cena jednostkowa brutto. |
items[].vat | Stawka VAT: "23", "8", "5", "0" lub "zw". |
items[].discount.amount | Opcjonalne. Rabat kwotowy. |
items[].discount.name | Opcjonalne. Opis rabatu, maks. 25 znaków. |
payments[].type | Forma płatności: cash, card, transfer, voucher, coupon, credit, cheque, other. |
payments[].amount | Kwota płatności. |
payments[].name | Opcjonalne. Nazwa płatności, maks. 25 znaków. |
total | Suma paragonu, zgodna z sumą pozycji. |
buyer_nip | Opcjonalne. NIP nabywcy. |
e_receipt.buyer_id | Opcjonalne. Identyfikator kupującego - e-paragon zamiast wydruku. |
e_receipt.server | Opcjonalne. 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.
Nagłówek Idempotency-Key jest obowiązkowy, np. numer zamówienia.
202, Idempotent-Replayed: true).422 IDEMPOTENCY_KEY_REUSED.4xx) nie zajmuje klucza.GET /v1/receipts/{id}| Stan | Znaczenie |
|---|---|
queued | W kolejce. |
printing | Na drukarce albo jeszcze nie wiadomo, czy się wydrukował. Nie zostanie wysłany drugi raz. |
printed | Wydrukowany. E-paragon ma e_receipt_id. |
failed | Na pewno niewydrukowany, przyczyna w error. |
expired | Nie wydrukował się przed not_after. |
cancelled | Wycofany 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.code | Przyczyna |
|---|---|
VAT_RATE_LOCKED_FOR_ITEM | Towar był sprzedany w niższej stawce (błąd 2106). |
VAT_RATES_CHANGED | Zmieniły się stawki na drukarce. |
PRINTER_BUSY | Drukarkę zajmowała kasa sklepu. |
PRINTER_UNREACHABLE | Brak połączenia z drukarką. |
DEVICE_ERROR | Błąd drukarki, kod w device_error_code. |
NOT_PRINTED_CONFIRMED | Potwierdzone ręcznie, że się nie wydrukował. |
EXPIRED | Minął termin wydruku. |
POST /v1/receipts/{id}/cancelTylko paragon w kolejce (queued). W innym stanie - 409 RECEIPT_NOT_CANCELLABLE.
GET /v1/printersDrukarki 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 }
}
]
}
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} … }
}
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'] ?? '');
2xx w ciągu 10 s, inaczej ponowimy po: 1 min, 5 min, 30 min, 2 h, 6 h, 12 h.id.{ "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.
| HTTP | Kody | Co zrobić |
|---|---|---|
| 400 | INVALID_REQUEST, INVALID_JSON, IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALID | Popraw żądanie. |
| 401 | INVALID_API_KEY | Sprawdź klucz. |
| 402 | ACCOUNT_BLOCKED, QUOTA_EXCEEDED | Nie ponawiaj. |
| 403 | PRINTER_NOT_IN_SCOPE | Klucz nie obejmuje tej drukarki. |
| 404 | PRINTER_NOT_FOUND, RECEIPT_NOT_FOUND, NOT_FOUND | Sprawdź identyfikator. |
| 409 | VAT_RATES_UNKNOWN, RECEIPT_NOT_CANCELLABLE | Stawki nieznane - ponów za minutę. |
| 413 | REQUEST_TOO_LARGE | Maks. 1 MB. |
| 422 | TOTAL_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_REQUIRED | Popraw pole z field. |
| 429 | RATE_LIMITED, QUEUE_FULL | Ponów po Retry-After. |
| 500 | SERVER_ERROR | Ponów z tym samym kluczem idempotencji. |
402 QUOTA_EXCEEDED.429 RATE_LIMITED.429 QUEUE_FULL.