Skip to content

API documentation

Conventions

Rules that apply to every call: identifiers, idempotency, document statuses and how to read them, amounts, time, invoice types, references, payment types, buyer, pagination and the shape of errors.

Updated: 29 August 2026 · Contract version 1.0.0

Identifiers in every request

FieldWhat it isWhere from
taxpayerIdthe taxpayer (legal entity with a PIB)GET /v1/taxpayers
businessPremiseIdthe premise (Tax Administration business premise) with its own security elementGET /v1/taxpayers/{taxpayerId}/business-premises
clientReferenceyour reference: order, invoice or transaction number; searchable in the journalyour system
cashier.idthe cashier or system issuing the receipt; printed on the receiptyour system (for example web-shop)
Idempotency-Keythe header that prevents a duplicate receiptyour system, from clientReference and the kind of action

Idempotency-Key

Every fiscal mutation (POST to /v1/fiscal-documents, /copies, /v1/refund-workflows, /v1/advance-cases*, /v1/proforma-training-workflows, /v1/receipt-deliveries) requires an Idempotency-Key header of 16 to 200 characters. The key is unique within your organization and is bound to the canonical content of the request:

  • same key, same content: the API returns the original operation (200 instead of 201) and never issues a second receipt, however many times you repeat;
  • same key, different content: 409 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST with the original's operationId;
  • new key: a new operation, and potentially a new receipt.
409 Conflict: key reused with different content
{
  "code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST",
  "operationId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761"
}

Document statuses

Every fiscal request becomes a durable operation with a status. The status is the truth; the HTTP code is only its summary.

StatusMeaningHTTP`fiscalized``retryable`
FISCALIZEDThe V-PFR signed the receipt and the response is durably stored. The only status that means a receipt.201 (200 on replay)truefalse
REJECTEDThe V-PFR rejected the request (validation). Not a receipt. pfrRejection on a single read names the field.422falsefalse
NOT_FISCALIZEDThe V-PFR was unavailable before submission; nothing was sent.503falsetrue
OUTCOME_UNKNOWNThe request may have been sent; the response did not arrive. Not a receipt, but it may become one. Never retry.503falsefalse
RECONCILINGBokaPOS is checking with the V-PFR whether an unknown outcome exists (read-only). Transitional.503falsefalse
RECEIVED, VALIDATED, SUBMITTINGTransitional statuses during the synchronous call; visible only in the journal while processing.n/afalsefalse

Reading the response

  1. 01

    201 and fiscalized: true

    The receipt exists. Store id, pfr.invoiceNumber, pfr.sdcTime, pfr.verificationUrl and, if needed, the PDF. The order is fiscalized.

  2. 02

    503 and retryable: true

    Nothing was sent (NOT_FISCALIZED, for example PFR_SUBMISSION_FAULT or CURRENT_TAX_CONFIGURATION_UNAVAILABLE). Wait a few seconds and send the same request with the same key. Cap the number of attempts, then hand over to an operator.

  3. 03

    503 and status OUTCOME_UNKNOWN

    Do not send a new request for the same sale. Remember the id and poll GET /v1/operations/{id} (every 30 seconds, then less often). BokaPOS resolves the outcome in the background purely by reading from the V-PFR; once found, the status becomes FISCALIZED. If it stays unknown, an operator decides in the portal.

  4. 04

    422

    The request breaks a rule (ours or the V-PFR's). Nothing was issued. Fix the request and send it with a new key, because the old key is bound to the wrong content.

  5. 05

    403

    Environment or licence. Nothing was issued. Sandbox never gets a 403 for licensing.

  6. 06

    409

    A conflict: the key reused with different content, or an action not allowed in the current workflow state (advance, proforma). Read the state and continue from it.

Amounts, quantities and rounding

  • All prices and amounts are gross, in dinars, with at most two decimals (8990.00). More decimals are rejected by the V-PFR (code 2804).
  • unitPrice is the final unit price after discount. You do not send tax: BokaPOS computes it from the tax label by the official rules and the V-PFR signs it.
  • unitPriceBeforeDiscount is an optional pre-discount price, displayed only outside the fiscal part of the receipt; it must be greater than unitPrice.
  • quantity has up to three decimals (1.5), minimum 0.001.
  • The sum of quantity × unitPrice over the items, rounded to two decimals, must equal the sum of payments. Otherwise 422 with an error on totals.
  • Several payment types on one receipt are allowed (payments is a list).

Time

  • All times are ISO 8601. createdAt and updatedAt are UTC (Z). pfr.sdcTime is the V-PFR signing time with the offset the V-PFR sent (+02:00 or +01:00); it is the official receipt time.
  • The pfrFrom/pfrTo filters in the journal and reports refer to sdcTime; createdFrom/createdTo to the moment BokaPOS received the request. The lower bound is inclusive, the upper exclusive.
  • You do not send the receipt time. The only exception is an advance payment by wire transfer received earlier (paymentOccurredAt), per the official rule; see Advance.

Invoice and transaction types

`invoiceType``transactionType`OfficialOperation
NORMALSALEПромет ПродајаPOST /v1/fiscal-documents
NORMALREFUNDПромет РефундацијаPOST /v1/refund-workflows
COPYSALE / REFUNDКопијаPOST /v1/fiscal-documents/{id}/copies (and automatically on a cash refund)
ADVANCESALE / REFUNDАванс/v1/advance-cases/...
PROFORMASALE / REFUNDПредрачунPOST /v1/proforma-training-workflows
TRAININGSALE / REFUNDОбукаPOST /v1/proforma-training-workflows

POST /v1/fiscal-documents accepts only NORMAL SALE; everything else has its own workflow that BokaPOS runs on the server, with the correct references and order.

References between documents

A refund, a copy and the final receipt after an advance must reference the source document (PFR number and time). When the source is a document BokaPOS issued, you send only its fiscalDocumentId ({ "source": "BOKA", "fiscalDocumentId": "..." }) and BokaPOS fills in the exact PFR facts; it must be on the same taxpayer and premise. A reference to another ESIR's document (source: EXTERNAL) requires the exact PFR number, time and type and is supported only for a copy through POST /v1/fiscal-documents; refunds and advances do not accept external documents.

New documentMay reference
Промет РефундацијаПромет Продаја (BokaPOS source)
Копијаa fiscalized Промет or Аванс, sale or refund
Аванс Продаја (next payment)the previous Аванс Продаја of the same case (automatic)
Аванс Рефундацијаthe latest Аванс Продаја (automatic)
Промет Продаја (final receipt)the closing Аванс Рефундација (automatic)
Предрачун РефундацијаПредрачун Продаја (BokaPOS source)
Обука РефундацијаОбука Продаја (BokaPOS source)

Payment types

`type`OfficialTypical use
CARDПлатна картицаcard payment online
WIRE_TRANSFERПренос на рачунbank transfer, payment slip, e-banking
INSTANT_PAYMENTИнстант плаћањеIPS QR, instant transfer
CASHГотовинаcash on delivery; a cash refund requires the signed copy
VOUCHERВаучерvoucher, gift card, corporate card (with buyer optional field 50:)
CHECKЧекrare
OTHERДруго безготовинско плаћањеany other non-cash payment

A premise may run in the restricted payment mode (only OTHER, CASH, WIRE_TRANSFER, VOUCHER); other types then return 422 PAYMENT_TYPE_NOT_ALLOWED_ON_PREMISE. The mode is the premise's paymentMode field.

Buyer identification

buyer.id is the official prefix:value. It is mandatory on every refund, on sales to companies that need the PIB on the receipt, and in the other cases the regulations list. For a domestic company (10:, 12: or 14: with a valid PIB) BokaPOS automatically prints the buyer's name and address from the NBS register under the identification, on every representation; it also returns them in buyerDetails.

PrefixValue
10:PIB of a domestic legal entity or entrepreneur
11:JMBG of a domestic natural person conducting an independent activity
12:PIB and JBKJS of a budget user, PIB:JBKJS
13:pensioner card number
14: / 15: / 16:PIB, JMBG or BPG of an agricultural holding
20:identity card number
21:refugee document number
22:EBS of a foreigner residing in Serbia
23:domestic passport number
30:foreign passport number
31: to 36:diplomatic and foreign identity cards per the official list
40:foreign tax identification number (TIN)

buyer.optionalField is the buyer's optional field, also prefix:value: 20: SNPDV, 21: LNPDV, 30: to 33: PPO-PDV forms, 50: corporate card number (payment is VOUCHER), 60: corporate-card refund period ddMMyyyy_ddMMyyyy. The same prefix means different things in the two fields.

Pagination

Journal lists use a stable keyset: the response carries nextCursor, which you send as cursor for the next page; null is the end. pageSize goes up to 200 (default 50). The product list uses the same pattern with a UUID cursor. Ordering is by createdAt then id, descending, so a new page never skips or repeats an entry.

Error shapes

ShapeWhenExample
{ "code": "...", "message"?: "..." }a BokaPOS or V-PFR rule, 4xx and 5xx{ "code": "TAX_LABEL_NOT_CURRENT", "invalidLabels": ["Ђ"] }
RFC 9457 problem (application/problem+json)field validation, 422{ "status": 422, "errors": { "items[0].unitOfMeasure": ["..."] } }
{ "code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST", "operationId": "..." }409see above
a fiscal document with fiscalized: false503 on a fiscal callthe status and failureCode say what happened

The full catalogue of codes with what to do is on the Errors page.