M cenaly.ru
🔌 Кассовый сервер: чеки по API

⚡ API кассового сервера: справочник

Протокол, совместимый с АТОЛ Онлайн v5/v4: getToken, регистрация документа, report, callback, идемпотентность, лимиты и коды ошибок

Documentation

Cash Server API: reference

The protocol is compatible with ATOL Online: the same paths, the same field names, the same error codes. If you already have a module built for ATOL Online, changing the base URL and the access credentials is usually enough.

Base URL (FFD 1.2) https://54fz.cenaly.ru/possystem/v5/
Base URL (FFD 1.05) https://54fz.cenaly.ru/possystem/v4/
Format JSON, UTF-8, Content-Type: application/json
Authentication a 24-hour token, the Token header
Liveness check GET /possystem/health (no token needed)

A product overview and the requirements for the cash register — Cash Server.


1. Authentication: getToken#

curl -X POST https://54fz.cenaly.ru/possystem/v5/getToken \
  -H 'Content-Type: application/json' \
  -d '{"login":"u9f3c1a7d40b2","pass":"••••••••"}'
{ "error": null, "token": "e826899dc378…063086", "timestamp": "04.08.2026 13:37:40" }
  • The login and group_code are issued upon creating a POS server in the admin panel's "POS API" section and look like u<12 hex> and kkt-<12 hex> — custom descriptive values cannot be chosen.
  • The token is valid for 24 hours; there is no need to request it for every receipt — cache it.
  • The token is passed via the Token: <token> header or the ?token=<token> parameter.
  • GET /possystem/v5/getToken?login=…&pass=… is also supported — mirroring ATOL.
  • Invalid credentials → HTTP 401, error code 12.

One login serves one POS group. We store the password only as an argon2id hash — it cannot be recovered, only reissued: "POS API" section → "Change password". Previously issued tokens will continue to work until their 24 hours expire.


2. Registering a document#

POST /possystem/{v5|v4}/{group_code}/{operation}
Operation What document it is v5 v4
sell Sale (income)
sell_refund Refund of a sale
buy Expense
buy_refund Refund of an expense
sell_correction Correction of a sale
buy_correction Correction of an expense
sell_refund_correction Correction of a sale refund
buy_refund_correction Correction of an expense refund

Request body#

{
  "timestamp": "04.08.2026 14:40:00",
  "external_id": "order-10421",
  "service": { "callback_url": "https://shop.example/api/fiscal-callback" },
  "receipt": {
    "client": { "email": "buyer@example.com" },
    "company": {
      "email": "shop@example.com",
      "sno": "usn_income",
      "inn": "7736570901",
      "payment_address": "https://shop.example"
    },
    "items": [
      {
        "name": "Coffee beans, 1 kg",
        "price": 1250.00,
        "quantity": 2,
        "sum": 2500.00,
        "measure": 0,
        "payment_method": "full_payment",
        "payment_object": 1,
        "vat": { "type": "vat20" }
      }
    ],
    "payments": [ { "type": 1, "sum": 2500.00 } ],
    "total": 2500.00
  }
}
Field Required Description
timestamp yes The moment of settlement, format dd.mm.yyyy HH:MM:SS
external_id yes Your document identifier, ≤ 128 characters. The idempotency key (see §5)
service.callback_url no The HTTP(S) address for delivering the result, ≤ 256 characters
receipt yes The receipt body for ordinary operations
correction + correction_info yes The body and the grounds for *_correction operations

The contents of receipt and all value reference tables — Receipt fields.

Response#

{ "uuid": "47b30214-b145-4c3f-abe3-89076225ac4f",
  "timestamp": "04.08.2026 13:39:15", "status": "wait", "error": null }

uuid is the identifier of the document in our queue; the result is requested by it. The wait status means "accepted, waiting for a cash register" — that is the normal response to a successful registration.

Important. A wait response does not yet mean the receipt has been registered. A payment may be treated as fiscalized only on done in the report or in the callback.


3. Result: report#

GET /possystem/{v5|v4}/{group_code}/report/{uuid}
curl "https://54fz.cenaly.ru/possystem/v5/kkt-1f4c9a20b73e/report/47b30214-…" \
  -H "Token: $TOKEN"
{
  "uuid": "47b30214-b145-4c3f-abe3-89076225ac4f",
  "timestamp": "04.08.2026 13:39:28",
  "callback_url": "https://shop.example/api/fiscal-callback",
  "status": "done",
  "group_code": "kkt-1f4c9a20b73e",
  "daemon_code": "cenaly-54fz",
  "device_code": "kkt-1",
  "external_id": "order-10421",
  "error": null,
  "warnings": null,
  "payload": {
    "fiscal_receipt_number": 101,
    "shift_number": 9,
    "receipt_datetime": "04.08.2026 13:39:15",
    "total": 2500.0,
    "fn_number": "9999078902005478",
    "ecr_registration_number": "0000000001056264",
    "fiscal_document_number": 6789,
    "fiscal_document_attribute": 2745081395,
    "fns_site": "www.nalog.gov.ru",
    "ofd_receipt_url": ""
  }
}

Statuses#

status Meaning What to do
wait Queued, POS has not responded yet Wait for callback or poll
done Document issued, payload populated Save details in your order
fail Document not issued, error populated Parse the error code, see Errors

Payload fields#

Field FFD tag Description
fiscal_receipt_number 1042 Receipt number per shift
shift_number 1038 Shift number
receipt_datetime 1012 Document time according to POS clock
total 1020 Receipt total in rubles
fn_number 1041 Factory serial number of the fiscal storage (FN)
ecr_registration_number 1037 Registration number of the POS/ECR (RN KKT)
fiscal_document_number 1040 Fiscal document number (FD)
fiscal_document_attribute 1077 Fiscal document sign/attribute (FPD)
fns_site 1060 FTS website for receipt verification
ofd_receipt_url Link to the receipt in OFD; currently always empty (see below)

The four parameters FD + FPD + FN + RN KKT are what you need to save in the order: they allow locating the receipt in the OFD personal dashboard and in the FTS "Receipt Check" mobile app. The ofd_receipt_url field is reserved by the protocol, but POS drivers do not return the link, so it arrives empty.

Certain POS units may not return some attributes (such as the shift number). In this case, the field returns zero or empty — we deliberately never substitute calculated values into fiscal attributes.

Storage and limits#

  • The result is available for at least 32 days; the document is archived after 60 days.
  • Polling is rate-limited: 1 request per second per POS in the group. Exceeding it returns HTTP 429.
  • Unknown uuid → error 30; document still in queue → HTTP 200 with status: wait.

4. Callback#

If the request contained service.callback_url, then after the document moves to done or fail we send it a POST with the same body the report returns.

  • The request timeout is 10 seconds, success is any 2xx response.
  • Retries: immediately, after 30 seconds, after 120 seconds (3 attempts in total).
  • An invalid or overly long URL does not break the receipt: it is discarded and warnings.callback_url appears in the report.

The handler on your side must be idempotent — key it on uuid or external_id. The callback does not replace polling: if no notification arrived within 300 seconds, request the report yourself.


5. Idempotency#

external_id is unique within a group. Registering the same external_id again returns HTTP 400 and error 33 — together with the original uuid:

{ "uuid": "47b30214-…", "status": "wait",
  "error": { "code": 33, "text": "a document with this external_id has already been accepted in this group", "type": "system" } }

That is the regular answer to a retry, not an emergency: take the uuid from the response and request the report for it.

Idempotency is end-to-end — it reaches the cash register itself. The bridge receives an operation key built from the group and external_id, and the bridge keeps an operation journal on disk: a repeat of the same operation returns the first result and survives a reboot of the cash register PC. If the previous attempt broke off with an unknown outcome (the connection dropped exactly at the closing of the receipt), the repeat is rejected with a demand to verify the last document on the cash register — a second tax document is never generated blindly.

The practical rule: one external_id per payment, and repeat the request with the same value as many times as you like. Create a new external_id only for a new settlement.


6. Limits#

Limit Value
Document registration 10 requests per second per group (soft; above that — HTTP 429)
Report polling 1 rps × the number of cash registers in the group
Request body size 256 KB
Items in a receipt 100
Length of an item's name 128 characters
Length of external_id 128 characters
Length of callback_url 256 characters
Token lifetime 24 hours
Waiting for a free cash register 300 seconds, then fail / timeout

7. Error Codes#

The error object format is consistent across all endpoints:

{ "error": { "error_id": "f546e8d0-…", "code": 33, "text": "…", "type": "system" },
  "status": "fail", "timestamp": "04.08.2026 13:39:15" }

type — one of system, driver, agent, timeout, unknown.

Code HTTP Meaning
0 / 1 400 Unknown error / processing failure
10 401 Token not provided
11 401 Token expired or unknown
12 401 Invalid login or password
13 400 Request validation error
20 401 Token does not match group
21 401 Group does not support this protocol version
30 400 Unknown uuid
31 400 Unsupported operation
32 400 Receipt JSON validation error (text specifies the field)
33 400 external_id already accepted (response contains previous uuid)
34 200 Document has not been processed yet (status: wait)
40 400 Bad request
41 415 Unsupported Content-Type
50 500 Service configuration error

Codes 2001–2003 — no available POS / no matching POS / TIN mismatch:

  • no POS became available within 300 seconds (every POS in the group is offline or busy with other receipts) → status: fail, type: "timeout", code 2001. If a POS did take the receipt but did not answer within 300 s, you get code 1 with the same type: "timeout" — the code tells "no POS at all" from "the POS went silent";
  • receipt TIN does not match group TIN → document is rejected immediately upon registration: HTTP 400, code 2003, text indicates both TINs; it does not enter the queue;
  • POS cannot process this receipt type (marking, electronic receipt, extended buyer requisites) → status: fail, code 2002, type: "driver", with the POS's verbatim rejection in error.text.

Driver errors do not arrive in the registration response, but in the document result — they occur once it has already been accepted into the queue. For a detailed breakdown and resolution steps for each case, see Errors and Diagnostics.


8. Differences between v5 and v4#

v5 (FFD 1.2) v4 (FFD 1.05)
Unit of measurement measure — numeric code (tag 2108), required measurement_unit — string ("pcs", "kg")
Subject of calculation payment_object — number 1…33, required payment_object — string
Payment method payment_method is required Optional; if not passed, tag 1214 is not sent to the POS and it applies its default flag
Marking code mark_code — object with a single format field nomenclature_code — hex string
Return corrections Yes No

They cannot be mixed: measurement_unit in v5 and measure in v4 are rejected by validation with a clear message. v5 is recommended for new integrations — marked goods require FFD 1.2.


9. Quick Start: Full Cycle with curl#

BASE=https://54fz.cenaly.ru/possystem/v5
GROUP=kkt-1f4c9a20b73e

TOKEN=$(curl -s -X POST $BASE/getToken -H 'Content-Type: application/json' \
  -d '{"login":"u9f3c1a7d40b2","pass":"••••"}' | jq -r .token)

UUID=$(curl -s -X POST $BASE/$GROUP/sell -H "Token: $TOKEN" \
  -H 'Content-Type: application/json' -d @receipt.json | jq -r .uuid)

curl -s "$BASE/$GROUP/report/$UUID" -H "Token: $TOKEN" | jq .payload

Next: receipt details · store scenarios · errors.