# BokaPOS API documentation > BokaPOS is a cloud ESIR for distance commerce in Serbia (BOKA GROUP DOO). This text is the complete API documentation in one file, generated from the same source as https://bokapos.rs/en/api. Machine-readable contract: https://api.bokapos.rs/openapi.yaml. Base URL: https://api.bokapos.rs. Token endpoint: https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token. Contract version 1.0.0, updated 2026-08-29. ## Contents - [Getting started](https://bokapos.rs/en/api) - [Authentication](https://bokapos.rs/en/api/autentifikacija) - [Conventions](https://bokapos.rs/en/api/konvencije) - [Normal Sale](https://bokapos.rs/en/api/prodaja) - [Representations and delivery](https://bokapos.rs/en/api/racuni) - [Refund](https://bokapos.rs/en/api/refundacija) - [Advance](https://bokapos.rs/en/api/avans) - [Proforma and Training](https://bokapos.rs/en/api/predracun-i-obuka) - [Catalogue and settings](https://bokapos.rs/en/api/katalog) - [Journal and reports](https://bokapos.rs/en/api/dnevnik) - [Errors](https://bokapos.rs/en/api/greske) - [Production](https://bokapos.rs/en/api/produkcija) - [Operation reference](https://bokapos.rs/en/api/referenca) --- # BokaPOS API One HTTP call, one fiscal receipt. Your web shop, ERP or platform sends the items and payments, BokaPOS fiscalizes them through the Tax Administration's V-PFR and returns the receipt number, the verification link, a PDF and a QR code. Everything an integrator needs is here: a quick start, the rules, the workflows and a reference for every operation. - **Quick start** (#quick-start): From credentials to the first fiscalized receipt in the sandbox, step by step. - **Authentication and environments** (https://bokapos.rs/en/api/autentifikacija): OAuth 2.0 client credentials, one URL for sandbox and production, scopes. - **Conventions** (https://bokapos.rs/en/api/konvencije): Idempotency, document statuses, amounts, time, references, payment types, buyer. - **Operation reference** (https://bokapos.rs/en/api/referenca): Every operation with parameters, body, responses and an example in five languages. ## What the API does - **Issues fiscal receipts for distance commerce**: Normal (sale), refund, advance, proforma, training and copy, with every reference the regulations require. - **Returns the complete receipt package**: PFR number, time and counter, the Tax Administration verification link, the official journal text, PDF in A4, 80 mm and 58 mm, a PNG preview and the QR code. - **Keeps an electronic journal** of every operation, including rejected ones and unknown outcomes, with search, export and a turnover report. - **Runs the complex workflows for you**: a refund with the automatic copy for cash, an advance chain with closure, a proforma with references. You send a business request; BokaPOS composes the fiscal documents. - **E-mails the receipt to the customer** from the platform when the E-mail module is enabled. - **Never invents**: a receipt exists only once the V-PFR returns a signed response and BokaPOS stores it durably. No fake success, no silent retries. > **Who it is for:** Distance commerce: web shops, marketplaces, SaaS platforms, ERPs that issue receipts for online orders. In-person sales need an L-PFR and are not part of this API (see [BokaLPFR](https://bokalpfr.rs)). ## Quick start: the first receipt in the sandbox 1. **Request sandbox access** Write to [office@bokagroup.rs](mailto:office@bokagroup.rs) or use the [contact page](https://bokapos.rs/kontakt). BokaPOS opens the organization, assigns a sandbox security element and issues a **client id** (`boka-sbx-...`) and a **secret**. The secret is shown once; keep it in your server's secrets, never in code or a browser. The sandbox is free. 2. **Get a token** OAuth 2.0 client credentials at `https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token`. The token lives 300 seconds; cache it and refresh before expiry. 3. **Verify access** `GET /v1/runtime` returns your organization, the client id and whether the fiscal adapter is ready. 4. **Find the taxpayer and premise** `GET /v1/taxpayers`, then `GET /v1/taxpayers/{taxpayerId}/business-premises`. Those two identifiers go into every fiscal request; keep them in configuration. 5. **Read the tax labels** `GET /v1/tax-rates` with both identifiers. The labels in the response are the only ones a receipt may carry. Sandbox and production have different sets, so never hard-code them. 6. **Issue the receipt** `POST /v1/fiscal-documents` with an `Idempotency-Key` header. A 201 with `fiscalized: true` is a fiscal receipt; nothing else is. 7. **Fetch the PDF and the link** Take `pfr.verificationUrl` and `receipt.pdfA4Url` from the response and store the document `id` with the order. ### 1. Token ```bash curl -X POST "https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$BOKAPOS_CLIENT_ID" \ -d "client_secret=$BOKAPOS_CLIENT_SECRET" ``` Token response: ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...", "expires_in": 300, "token_type": "Bearer", "scope": "tenant:read operations:read catalogue:read catalogue:write fiscal:read fiscal:write refund:write proforma-training:write advance:write advance:close configuration:read security-elements:read" } ``` Every following call carries `Authorization: Bearer `. In the examples below the token sits in the `BOKAPOS_TOKEN` environment variable. ### 2. Verify access GET /v1/runtime: ```bash curl -X GET "https://api.bokapos.rs/v1/runtime" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "manufacturer": "BOKA GROUP DOO", "productName": "BokaPOS", "esirNumber": "", "softwareVersion": "1.0.0", "buildCommit": "1f0d454e8b2c9a7d6f5e4c3b2a1908f7e6d5c4b3", "instanceId": "api-bokapos-rs", "organizationId": "7c1e9a4b-2d3f-4e5a-b6c7-8d9e0f1a2b3c", "clientId": "boka-sbx-k7m2p9x4q1wz", "fiscalEndpointsEnabled": true, "pfrAdapter": "configured" } ``` ### 3. Taxpayer and premise GET /v1/taxpayers: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` GET /v1/taxpayers/{taxpayerId}/business-premises: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "puIdentifier": "1234567", "name": "Web shop", "commerceMode": "distance", "environment": "sandbox", "paymentMode": "all", "status": "active", "createdAt": "2026-08-21T09:05:00.000Z", "updatedAt": "2026-08-21T09:05:00.000Z" } ] } ``` ### 4. Tax labels GET /v1/tax-rates: ```bash curl -X GET "https://api.bokapos.rs/v1/tax-rates?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "source": "PFR", "environment": "sandbox", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "currentTaxGroupId": 8, "validFrom": "2022-05-01T00:00:00", "fetchedAt": "2026-09-01T08:14:02.118Z", "labels": [ { "label": "F", "category": "ECAL", "categoryType": 0, "rate": 11, "activeFrom": "2022-05-01T00:00:00" }, { "label": "N", "category": "N-TAX", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "P", "category": "PBL", "categoryType": 2, "rate": 0.5, "activeFrom": "2022-05-01T00:00:00" }, { "label": "E", "category": "STT", "categoryType": 0, "rate": 6, "activeFrom": "2022-05-01T00:00:00" }, { "label": "T", "category": "TOTL", "categoryType": 1, "rate": 2, "activeFrom": "2022-05-01T00:00:00" }, { "label": "A", "category": "VAT", "categoryType": 0, "rate": 10, "activeFrom": "2022-05-01T00:00:00" }, { "label": "B", "category": "VAT", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "Ж", "category": "VAT", "categoryType": 0, "rate": 19, "activeFrom": "2022-05-01T00:00:00" }, { "label": "C", "category": "VAT-EXCL", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" } ] } ``` Note: The Tax Administration sandbox carries a generic test label set. In production you get the official Serbian labels (for example Ђ 20%, Е 10%, Г 0%, А non-VAT). Never hard-code labels. ### 5. The first receipt POST /v1/fiscal-documents: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-sale-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashier": { "id": "web-shop", "displayName": "Web shop" }, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ], "metadata": { "orderId": "4127", "channel": "web" } }' ``` Response 201: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` > **What is in the response:** `status: FISCALIZED` and `fiscalized: true` mean the receipt exists. `pfr.invoiceNumber` is the official receipt number, `pfr.verificationUrl` is the Tax Administration verification link (the same one encoded in the QR code), `pfr.journal` is the official receipt text, and `receipt.*` are the representation URLs. Store the `id`: it is the key for copies, refunds and search. ### 6. The receipt PDF GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output racun-ORDER-4127.pdf ``` The same document has seven representations (text, JSON, QR, PDF for three paper formats, PNG). See [Receipt representations and delivery](https://bokapos.rs/api/racuni). ## Where to go next - **Normal Sale receipt** (https://bokapos.rs/en/api/prodaja): Every request field, discounts, buyer (B2B), several payment types, receipt copies. - **Refund** (https://bokapos.rs/en/api/refundacija): Full and partial, per original line, with the copy for cash. - **Advance** (https://bokapos.rs/en/api/avans): Payments before delivery, cancelling a wrong advance, closing with the final receipt. - **Proforma and Training** (https://bokapos.rs/en/api/predracun-i-obuka): An offer without tax effect and test receipts, with reference rules. - **Journal and reports** (https://bokapos.rs/en/api/dnevnik): Search, export, turnover report, operation state, certificates, licence. - **Going to production** (https://bokapos.rs/en/api/produkcija): Certificate, licence, production credential and the checklist. ## What is done in the portal, not through the API An API credential has a fixed set of permissions: fiscalization, catalogue, reading taxpayers, premises, certificates and the licence. The following are deliberately outside the API and are done in the portal at [cloud.bokapos.rs](https://cloud.bokapos.rs) (the organization's Owner or Administrator) or by BokaPOS Administration: - creating taxpayers and premises (the PIB and the premise identifier are immutable), - uploading and activating the production security element (certificate), - issuing and revoking API credentials, - receipt branding (logo, contact, thank-you note) and e-mail delivery settings, - users and roles, the activity log, subscription and invoices. --- # Authentication and environments OAuth 2.0 client credentials over plain TLS. One URL for everyone; sandbox and production differ by credential, not by host. ## URLs | What | Value | | --- | --- | | API | `https://api.bokapos.rs` | | OpenAPI contract | `https://api.bokapos.rs/openapi.yaml` (public, no login) | | Token (client credentials) | `https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token` | | Portal for humans | `https://cloud.bokapos.rs` | There is no separate sandbox host. The same code, URLs and calls work in both environments; only the client id and secret change. ## Credentials BokaPOS Administration issues an API credential (client id and secret) at the request of the organization's Owner, separately for sandbox (`boka-sbx-...`) and production (`boka-prod-...`). The secret is shown once, at issuance, and cannot be read again; if it is lost, the credential is revoked and a new one issued. A credential is bound to one organization and one environment and carries the fixed bundle of scopes described below. > **The secret stays on the server:** A credential must never reach a browser, a mobile app, a public repository or a log. Calls to BokaPOS are always made by your backend. If you suspect the secret leaked, request revocation immediately; an old token is valid for at most another 300 seconds. ## Getting a token A standard `client_credentials` request, `application/x-www-form-urlencoded`. No `scope` parameter is needed; the token receives every scope of the credential. ```bash curl -X POST "https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$BOKAPOS_CLIENT_ID" \ -d "client_secret=$BOKAPOS_CLIENT_SECRET" ``` Response: ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...", "expires_in": 300, "refresh_expires_in": 0, "token_type": "Bearer", "not-before-policy": 0, "scope": "tenant:read operations:read catalogue:read catalogue:write fiscal:read fiscal:write refund:write proforma-training:write advance:write advance:close configuration:read security-elements:read" } ``` - The token is valid for **300 seconds**. Cache it in process memory and refresh it when, say, fewer than 30 seconds remain. Do not request a new token for every call. - A `401` on any call means an expired or invalid token: get a new one and repeat the same request (with the same `Idempotency-Key`). - The token is a JWT with `org_id` (your organization) and `boka_env` (`sandbox` or `production`) claims. You need not read them; the API enforces them. - A wrong client id or secret returns `401` with `error: invalid_client` from the identity server. ## Headers on every call Headers: ```http Authorization: Bearer Content-Type: application/json Idempotency-Key: order-4127-sale-1 (fiscal mutations only) Accept: application/json ``` ## Credential scopes Every API credential receives the same bundle of twelve scopes. They decide which operations it may call; every other operation in the contract belongs to the portal and the BokaPOS console. | Scope | Allows | | --- | --- | | `fiscal:write` | issuing Normal Sale receipts, copies and e-mail delivery | | `fiscal:read` | reading documents, representations, the journal, reports, deliveries, advance cases | | `refund:write` | refunds through `/v1/refund-workflows` | | `advance:write` | opening an advance case, advance payments, cancellations | | `advance:close` | closing an advance case | | `proforma-training:write` | proforma and training | | `operations:read` | operation state | | `catalogue:read`, `catalogue:write` | the product catalogue, import and export | | `configuration:read` | tax rates | | `tenant:read` | taxpayers, premises, runtime, licence | | `security-elements:read` | certificate metadata | ## Sandbox and production - **The credential carries the environment.** A sandbox credential can only work with a sandbox security element, a production credential only with a production one. Crossing them returns `403 CREDENTIAL_ENVIRONMENT_MISMATCH` before anything is reserved. - **BokaPOS assigns the sandbox element** from its own pool; sandbox receipts therefore issue under BOKA GROUP DOO's PIB and verify at `sandbox.suf.purs.gov.rs`. The production element is your company's certificate, uploaded by the Owner in the portal. - **The sandbox is free and never blocked** by licensing. Production requires a valid licence; `GET /v1/license` tells you in advance whether fiscalization is allowed. - **Tax labels differ.** The Tax Administration sandbox carries a generic test set; production carries the official Serbian labels. That is why labels are always read from `GET /v1/tax-rates`. - **A credential sees only its own environment.** Every read is scoped: a sandbox key lists only sandbox taxpayers, premises, security elements and documents, a production key only production ones. An object of the other environment does not exist for you, so fetching it by `id` returns `404`, and setup you register (`POST /v1/taxpayers`) belongs to your key's environment. A tax identifier is unique per environment, so your company can exist once in sandbox and once in production. - **Idempotency keys are unique per organization across both environments.** Reusing a key from sandbox testing in production returns `409 IDEMPOTENCY_KEY_REUSED_IN_OTHER_ENVIRONMENT`; choose a new key rather than retrying. - **Same organization, both environments.** The Owner sees sandbox and production documents side by side in the portal until the organization goes live; from then on the portal hides sandbox data by default (the Owner can show it again in Settings). Your system tells them apart by the credential it called with. Moving to production is described on [Going to production](https://bokapos.rs/api/produkcija): certificate, activation, licence, production credential, swap in configuration. The code does not change. ## Check after signing in GET /v1/runtime: ```bash curl -X GET "https://api.bokapos.rs/v1/runtime" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "manufacturer": "BOKA GROUP DOO", "productName": "BokaPOS", "esirNumber": "", "softwareVersion": "1.0.0", "buildCommit": "1f0d454e8b2c9a7d6f5e4c3b2a1908f7e6d5c4b3", "instanceId": "api-bokapos-rs", "organizationId": "7c1e9a4b-2d3f-4e5a-b6c7-8d9e0f1a2b3c", "clientId": "boka-sbx-k7m2p9x4q1wz", "fiscalEndpointsEnabled": true, "pfrAdapter": "configured" } ``` `pfrAdapter: configured` and `fiscalEndpointsEnabled: true` mean fiscalization is possible for your environment. `esirNumber` is empty until the Tax Administration assigns the approval number; once assigned, it prints on every receipt. --- # 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. ## Identifiers in every request | Field | What it is | Where from | | --- | --- | --- | | `taxpayerId` | the taxpayer (legal entity with a PIB) | `GET /v1/taxpayers` | | `businessPremiseId` | the premise (Tax Administration business premise) with its own security element | `GET /v1/taxpayers/{taxpayerId}/business-premises` | | `clientReference` | your reference: order, invoice or transaction number; searchable in the journal | your system | | `cashier.id` | the cashier or system issuing the receipt; printed on the receipt | your system (for example `web-shop`) | | `Idempotency-Key` | the header that prevents a duplicate receipt | your 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. > **Choosing a key:** Derive it from something already unique in your system that describes the action: `order-4127-sale-1`, `order-4127-refund-2`, `order-5001-advance-3`, `order-4127-email-1`. Do not use random UUIDs you do not persist: when the network drops after sending, you must be able to repeat exactly the same key. Response 409: ```json { "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. | Status | Meaning | HTTP | `fiscalized` | `retryable` | | --- | --- | --- | --- | --- | | `FISCALIZED` | The V-PFR signed the receipt and the response is durably stored. **The only status that means a receipt.** | 201 (200 on replay) | true | false | | `REJECTED` | The V-PFR rejected the request (validation). Not a receipt. `pfrRejection` on a single read names the field. | 422 | false | false | | `NOT_FISCALIZED` | The V-PFR was unavailable before submission; nothing was sent. | 503 | false | true | | `OUTCOME_UNKNOWN` | The request may have been sent; the response did not arrive. Not a receipt, but it may become one. **Never retry.** | 503 | false | false | | `RECONCILING` | BokaPOS is checking with the V-PFR whether an unknown outcome exists (read-only). Transitional. | 503 | false | false | | `RECEIVED`, `VALIDATED`, `SUBMITTING` | Transitional statuses during the synchronous call; visible only in the journal while processing. | n/a | false | false | ## Reading the response 1. **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. **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. **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. **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. **403** Environment or licence. Nothing was issued. Sandbox never gets a 403 for licensing. 6. **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. > **Never two receipts for one sale:** The only thing that can issue a second receipt for the same order is a new key. Generate a new key only once you are certain the previous operation is not, and will not become, a receipt (`REJECTED`, or a `NOT_FISCALIZED` you gave up retrying). `OUTCOME_UNKNOWN` is never such a moment. ## 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](https://bokapos.rs/api/avans). ## Invoice and transaction types | `invoiceType` | `transactionType` | Official | Operation | | --- | --- | --- | --- | | `NORMAL` | `SALE` | Промет Продаја | `POST /v1/fiscal-documents` | | `NORMAL` | `REFUND` | Промет Рефундација | `POST /v1/refund-workflows` | | `COPY` | `SALE` / `REFUND` | Копија | `POST /v1/fiscal-documents/{id}/copies` (and automatically on a cash refund) | | `ADVANCE` | `SALE` / `REFUND` | Аванс | `/v1/advance-cases/...` | | `PROFORMA` | `SALE` / `REFUND` | Предрачун | `POST /v1/proforma-training-workflows` | | `TRAINING` | `SALE` / `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 document | May 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` | Official | Typical 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`. | Prefix | Value | | --- | --- | | `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 | Shape | When | Example | | --- | --- | --- | | `{ "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": "..." }` | 409 | see above | | a fiscal document with `fiscalized: false` | 503 on a fiscal call | the status and `failureCode` say what happened | The full catalogue of codes with what to do is on the [Errors](https://bokapos.rs/api/greske) page. --- # Normal Sale receipt The main call of the integration: the customer paid, you send the items and payments, BokaPOS issues Промет Продаја. Every request field, discounts, buyer, several payment types and receipt copies are here. ## When it is issued A Normal Sale receipt is issued when the supply happens: in distance commerce that is when the goods are delivered or the service performed, or when the payment is received, depending on your business model and the regulations. If the customer pays before delivery and delivery comes later, that is an [advance](https://bokapos.rs/api/avans). If you only send an offer, that is a [proforma](https://bokapos.rs/api/predracun-i-obuka). ## Request POST /v1/fiscal-documents: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-sale-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashier": { "id": "web-shop", "displayName": "Web shop" }, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ], "metadata": { "orderId": "4127", "channel": "web" } }' ``` ### Fields | Field | Required | Rule | | --- | --- | --- | | `taxpayerId`, `businessPremiseId` | yes | the taxpayer and an active distance-commerce premise | | `clientReference` | yes | your order number; need not be unique, but is searchable | | `invoiceType`, `transactionType` | yes | always `NORMAL` and `SALE` here | | `cashier.id` | yes | the cashier or system; `displayName` is optional | | `items[]` | yes, at least one | see below | | `payments[]` | yes, at least one | sum equal to the item total; several payment types are allowed | | `buyer` | no | mandatory when the regulations require buyer identification (B2B with a PIB and other cases) | | `commercialFooter` | no | your text under the fiscal part (up to 2000 characters): thank-you note, complaints, contact | | `metadata` | no | your key-value pairs; stored with the operation, never sent to the V-PFR, not on the receipt | | `reference` | no | only for a copy of an external document (`source: EXTERNAL`); a Normal Sale has no reference | ### Item | Field | Required | Rule | | --- | --- | --- | | `name` | yes | product or service name, up to 2048 characters; printed as `name/unit` | | `unitOfMeasure` | yes | unit of measure (`kom`, `kg`, `h`, `m`...), up to 50 characters; only the prescribed advance items are exempt | | `quantity` | yes | up to three decimals, at least 0.001 | | `unitPrice` | yes | the final gross unit price after discount, two decimals | | `taxLabels` | yes | one or more labels from `GET /v1/tax-rates`; usually one | | `gtin` | no | GTIN/EAN of 8 to 14 digits; printed on the receipt | | `catalogProductId` | no | the `id` of a catalogue product, if you keep one in BokaPOS; items may also be fully inline | | `unitPriceBeforeDiscount` | no | the pre-discount price, must be greater than `unitPrice`; shown outside the fiscal part | > **Tax labels:** Before issuing, read `GET /v1/tax-rates` for that premise and use only labels from the response. The sandbox carries a test set (`F`, `A`, `Ж`...), production the official Serbian labels (for example `Ђ` for the general rate). A label missing from the fresh configuration returns `422 TAX_LABEL_NOT_CURRENT` and nothing is sent. ## Response Response 201: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` If you send the same request again (same body, same `Idempotency-Key`) after a `201`, you get `200` with the same document and the same `id`: the receipt is not issued twice. A new receipt exists only after the first `201` with `fiscalized: true`; a `200` only confirms the one already issued. | Field | Meaning | | --- | --- | | `id` | the document identifier in BokaPOS: store it with the order; it serves copies, refunds, representations and delivery | | `status`, `fiscalized` | `FISCALIZED` and `true` mean the receipt exists | | `pfr.invoiceNumber` | the official receipt number `JID-JID-counter` | | `pfr.sdcTime` | the official receipt time (V-PFR signature) | | `pfr.verificationUrl` | the Tax Administration verification link; hand it to the customer as is or as a QR code | | `pfr.journal` | the official receipt text (journal); may be shown or printed as is | | `pfr.totalAmount` | the total amount the V-PFR signed | | `pfr.totalTax` | the total tax the V-PFR signed (the sum of the per-label tax amounts); may be `null` | | `pfr.totalCounter`, `pfr.transactionTypeCounter`, `pfr.invoiceCounterExtension` | the signed counters and the official document-type mark (`ПП`, `ПР`, `АП`, `АР`...); they may be `null`, so accept that; the counter is always in the journal too | | `receipt.*` | URLs of the seven representations; see [Receipt representations and delivery](https://bokapos.rs/api/racuni) | | `buyerDetails` | the buyer's name and address from the NBS register when the buyer is a domestic company; otherwise `null` | ## Buyer, discount and several payment types The same call covers a sale to a company with a PIB (`buyer.id` with prefix `10:`), an item discount (`unitPriceBeforeDiscount` for display only, `unitPrice` is what gets fiscalized), decimal quantities, several items and a split between wire transfer and card. POST /v1/fiscal-documents: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4128-sale-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4128", "invoiceType": "NORMAL", "transactionType": "SALE", "cashier": { "id": "web-shop" }, "buyer": { "id": "10:106952811" }, "items": [ { "name": "Godišnja licenca", "unitOfMeasure": "kom", "quantity": 2, "unitPrice": 12000, "taxLabels": [ "F" ] }, { "name": "Instalacija", "unitOfMeasure": "h", "quantity": 1.5, "unitPrice": 4000, "unitPriceBeforeDiscount": 5000, "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 20000 }, { "type": "CARD", "amount": 10000 } ], "commercialFooter": "Hvala na kupovini. Reklamacije: podrska@primer.rs" }' ``` Response 201: ```json { "id": "8e7d6c5b-4a39-4210-8765-fedcba987650", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4128-sale-1", "clientReference": "ORDER-4128", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": "10:106952811", "buyerDetails": { "legalName": "PRIMER DOO BEOGRAD", "taxIdentifier": "106952811", "registrationNumber": "20712345", "address": "Bulevar kralja Aleksandra 1", "city": "Beograd", "source": "nbs-jrr", "resolvedAt": "2026-09-01T08:20:11.004Z" }, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1043", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 898, "totalCounter": 1043, "invoiceCounterExtension": "ПП", "totalAmount": 30000, "totalTax": 2972.973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1043\nБројач рачуна: 898/1043ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/official-text", "jsonUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` A discount is always part of the price: the V-PFR has no discount field, so BokaPOS sends `unitPrice` as the final price and shows the difference only outside the fiscal part. Spread an order-level discount across the items before sending. ## What to store in your system - the document `id` and the `Idempotency-Key` you used (so you can repeat the same request after an interruption); - `pfr.invoiceNumber`, `pfr.sdcTime` and `pfr.verificationUrl` (for the customer, accounting and complaints); - the PDF if you wish, but you need not: representations stay available at the `receipt.*` URLs and carry an `ETag`. ## Receipt copy Копија is the official document that repeats a fiscalized receipt (Normal or Advance, sale or refund) when the customer asks for another copy. BokaPOS builds it from the stored original; you send only the cashier. The copy is signed as a new document, prints `ОВО НИЈЕ ФИСКАЛНИ РАЧУН` and references the original. A refund copy carries the customer-signature line. Proforma, Training and Copy documents cannot be copied. POST /v1/fiscal-documents/{fiscalDocumentId}/copies: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/copies" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-copy-1" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" } }' ``` Response 201: ```json { "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-copy-1", "clientReference": "COPY-ORDER-4127", "invoiceType": "COPY", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1044", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 899, "totalCounter": 1044, "invoiceCounterExtension": "КП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1044\nБројач рачуна: 899/1044КП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/official-text", "jsonUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Repeating the same request with the same `Idempotency-Key` returns `200` with the copy already issued; no new copy is signed. ## What does not go through this call - **Refund**: `POST /v1/refund-workflows`, because it verifies the original lines and issues the automatic copy for cash ([Refund](https://bokapos.rs/api/refundacija)). - **Advance**: `/v1/advance-cases`, because it is a chain of documents with references ([Advance](https://bokapos.rs/api/avans)). - **Proforma and Training**: `/v1/proforma-training-workflows` ([Proforma and Training](https://bokapos.rs/api/predracun-i-obuka)). - **In-person sales**: they require an L-PFR; a premise that is not distance commerce returns `422 LPFR_REQUIRED_FOR_IN_PERSON_SALES`. ## Sale errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 422 | `LPFR_REQUIRED_FOR_IN_PERSON_SALES` | The premise is not a distance-commerce premise. BokaPOS fiscalizes only distance commerce through the V-PFR. | In-person sales need an L-PFR (for example BokaLPFR). | never | | 422 | `BUSINESS_PREMISE_INACTIVE` | The premise is suspended or closed. | Activate it in the portal or use another one. | later | | 422 | `PAYMENT_TYPE_NOT_ALLOWED_ON_PREMISE` | The premise runs in the restricted payment mode (OTHER, CASH, WIRE_TRANSFER, VOUCHER) and the request carries another type. | Change the payment type or the premise's payment mode in the portal. | fix-request | | 422 | `ACTIVE_SECURITY_ELEMENT_REQUIRED` | The premise has no active security element. | In sandbox BokaPOS assigns an element; in production the Owner uploads one in the portal and BokaPOS activates it. | later | | 422 | `TAX_LABEL_NOT_CURRENT` | At least one tax label is not in the fresh V-PFR configuration (field `invalidLabels`). | Read `GET /v1/tax-rates` and use only the labels it returns; sandbox and production have different sets. | fix-request | | 422 | `TAX_LABEL_NOT_ALLOWED_OUTSIDE_VAT` | The taxpayer is marked as outside the VAT system and an item carries a VAT label. | Use the non-VAT label or correct the taxpayer's VAT status in the portal. | fix-request | | 422 | `COPY_SOURCE_NOT_COPYABLE` | Copy, Proforma and Training documents cannot be copied. | Copy only a Normal or Advance document. | never | | 422 | `COPY_SOURCE_NOT_FISCALIZED` | The copy's source is not fiscalized. | A copy exists only for a FISCALIZED document. | never | | 503 | `PFR_SUBMISSION_FAULT` | The V-PFR was unavailable before submission; the document is `NOT_FISCALIZED`, `retryable: true`. | Repeat the same request with the same `Idempotency-Key` after a short pause. | same-key | | 503 | `OUTCOME_UNKNOWN` | The request may have reached the V-PFR but the response did not come back. Status `OUTCOME_UNKNOWN`, `retryable: false`. Not a receipt, but it may become one. | Do not send a new request for the same sale. Poll `GET /v1/operations/{id}`; BokaPOS resolves the outcome by reading, never by resubmitting. | never | | 422 | `REJECTED` | The V-PFR rejected the request. The document has status `REJECTED` and `GET /v1/fiscal-documents/{id}` returns `pfrRejection` with the property path and code (2310 unknown label; 2800 to 2808 required, length, range, value, format, list size). | Correct the request and send it with a new key. | fix-request | --- # Receipt representations and delivery One fiscalized document, seven representations: the official text, the JSON package, the QR code, PDF for A4, 80 mm and 58 mm, a PNG preview. Plus e-mail delivery to the customer from the platform. ## Representations `GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}`. The URLs of every representation are already in the issuance response (`receipt.*`). Each is generated from the immutable package (canonical request plus the original V-PFR response) only after their hashes are rechecked, so it is always the same; the response carries `ETag` and `Boka-Receipt-Representation-Version`. | `representationFormat` | Content-Type | What it is | When | | --- | --- | --- | --- | | `official-text` | `text/plain` | the exact official V-PFR journal text, without a single added character | thermal printers, text view in an app, archive | | `canonical-json` | `application/json` | the whole package: request, PFR facts per tax item, journal, hashes | your own receipt rendering, accounting, verification | | `qr-svg` | `image/svg+xml` | the QR code with the verification link | your own receipt or order-page design | | `pdf-a4`, `pdf-80mm`, `pdf-58mm` | `application/pdf` | a finished PDF | printing (from the browser or straight to the printer), e-mail attachment, download, archive | | `preview-png` | `image/png` | an image of the receipt in the 80 mm format | e-mail without PDF, admin-panel preview | `receipt.preferredPaperFormat` (`a4`, `80mm`, `58mm`) is the format the taxpayer chose in the portal at issuance; use it when you do not know what to offer. GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output racun-ORDER-4127.pdf ``` GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200 (text/plain): ```text ============ ФИСКАЛНИ РАЧУН ============ 115711881 BOKA GROUP DOO BokaPOS sandbox Роза Луксембург 16 Београд-Раковица Касир: web-shop ЕСИР број: 1656/1.0.0 -------------ПРОМЕТ ПРОДАЈА------------- Артикли ======================================== Назив Цена Кол. Укупно Bluetooth slušalice/kom (F) 8.990,00 1 8.990,00 ---------------------------------------- Укупан износ: 8.990,00 Платна картица: 8.990,00 ======================================== Ознака Име Стопа Порез F ECAL 11,00% 890,90 ---------------------------------------- Укупан износ пореза: 890,90 ======================================== ПФР време: 01.09.2026. 10:15:32 ПФР број рачуна: JWX4K9PL-JWX4K9PL-1042 Бројач рачуна: 897/1042ПП ======================================== ======== КРАЈ ФИСКАЛНОГ РАЧУНА ========= ``` The canonical JSON is useful when you want to render the receipt yourself or post tax per item: it contains `pfr.taxItems` (label, rate, tax and base), `pfr.invoiceCounter`, the counters and `officialJournal`. The tax per label is signed by the V-PFR; the base (`taxableAmountPerLabel`) is computed by BokaPOS from the receipt lines (the lines carrying that label, minus that label's tax), because the V-PFR response does not carry it. GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "schemaVersion": "boka-receipt-representation-v21", "sourceSha256": "5d41402abc4b2a76b9719d911017c592e99f0d3b4a7c1e6f8b2d9a0c3e5f7a1b", "canonicalRequestSha256": "9b74c9897bac770ffc029102a200c5de3a4b1c6d7e8f9a0b1c2d3e4f5a6b7c8d", "originalPfrResponseSha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "request": { "cashierId": "web-shop", "cashierDisplayName": "Web shop", "buyer": null, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ], "reference": null, "transactionOccurredAt": null, "commercialFooter": null }, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "invoiceCounter": "897/1042ПП", "invoiceCounterExtension": "ПП", "totalCounter": 1042, "transactionTypeCounter": 897, "totalAmount": 8990, "taxGroupRevision": 8, "taxItems": [ { "categoryType": 0, "label": "F", "amount": 890.9009, "rate": 11, "categoryName": "ECAL", "taxableAmountPerLabel": 8099.0991 } ], "businessName": "BOKA GROUP DOO", "tin": "115711881", "locationName": "BokaPOS sandbox", "address": "Роза Луксембург 16", "district": "Београд-Раковица", "mrc": null, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "officialJournal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========", "officialJournalSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "opaqueFiscalDataStored": true }, "branding": null } ``` ## What the customer must receive - In distance commerce the receipt is delivered electronically: at least the **verification link** (`pfr.verificationUrl`) or the QR code, and in practice a PDF or an image of the receipt in the e-mail and on the order page. - The fiscal part of the receipt is not altered: it must not be rewritten, translated or shortened. Your logo, thank-you note and contact go outside it (branding in the portal or `commercialFooter`). - Show the customer only documents with `fiscalized: true`. No representation exists for a rejected or unknown outcome (`404`/`409`). ## Caching Representations are immutable: you may cache them permanently by `ETag`. If you prefer a single call, store the PDF at issuance; every later call returns byte-identical content as long as the representation version (`Boka-Receipt-Representation-Version`) is the same. ## E-mail delivery from the platform Instead of sending the message yourself, you can ask BokaPOS to e-mail the receipt to the customer: a message in the taxpayer's language (`sr-Cyrl`, `sr-Latn` or `en`), with the verification link as an active link and the chosen attachments (`a4`, `80mm`, `58mm`, `png`). Conditions: the document is fiscalized, the taxpayer enabled delivery in the portal (Documents, E-mail receipts) and, in production, the organization has the **E-mail module**. The delivery is queued and sent in the background; its status is never proof of fiscalization. Under the Technical Guideline an advance refund is never issued to the buyer, so it and a copy of it are refused with `RECEIPT_DELIVERY_DOCUMENT_NOT_ISSUED_TO_BUYER`; send the customer the final receipt of the advance case instead. POST /v1/receipt-deliveries: ```bash curl -X POST "https://api.bokapos.rs/v1/receipt-deliveries" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-email-1" \ -H "Content-Type: application/json" \ -d '{ "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ] }' ``` Response 202: ```json { "id": "f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ], "status": "QUEUED", "attempts": 0, "failureCode": null, "createdAt": "2026-09-01T08:15:40.000Z", "deliveredAt": null, "updatedAt": "2026-09-01T08:15:40.000Z" } ``` GET /v1/receipt-deliveries/{receiptDeliveryId}: ```bash curl -X GET "https://api.bokapos.rs/v1/receipt-deliveries/f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ], "status": "DELIVERED", "attempts": 1, "failureCode": null, "createdAt": "2026-09-01T08:15:40.000Z", "deliveredAt": "2026-09-01T08:15:52.418Z", "updatedAt": "2026-09-01T08:15:52.418Z" } ``` | `status` | Meaning | | --- | --- | | `QUEUED` | accepted, waiting to be sent | | `SENDING` | in progress | | `DELIVERED` | handed to the recipient's mail server (`deliveredAt`) | | `FAILED` | rejected after the attempts; `failureCode` says why (for example an invalid address) | `language` and `attachments` are optional and default to the taxpayer's settings. The message content and look are configured in the portal, which has a preview and a test message. ## Representation and delivery errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 403 | `MODULE_NOT_LICENSED` | The base licence is valid but the module in `module` (advance or email) is not enabled. | Enable the module through BokaPOS Administration or do not use that feature in production. | later | | 404 | `FISCAL_DOCUMENT_NOT_FOUND` | The document does not exist in your organization and environment. | Check the identifier; documents of another organization or of the other environment are invisible. | fix-request | | 422 | `RECEIPT_DELIVERY_DISABLED` | The taxpayer has not enabled e-mail delivery in the portal settings. | Enable delivery in the portal (Documents, E-mail receipts) or send the receipt from your own system. | later | | 422 | `RECEIPT_DELIVERY_DOCUMENT_NOT_FISCALIZED` | The document is not fiscalized, so there is nothing to deliver. | Send only documents with `fiscalized: true`. | never | | 422 | `RECEIPT_DELIVERY_DOCUMENT_NOT_ISSUED_TO_BUYER` | An advance refund is never issued to the buyer, so BokaPOS does not send it to a buyer address. The same applies to a copy of one. | Send the final receipt of the advance case (the Normal Sale). The advance refund stays available for printing and in the electronic journal. | never | | 503 | `RECEIPT_DELIVERY_UNAVAILABLE` | The platform mail transport is not configured or unavailable. Nothing was queued. | Retry later with the same key or send the receipt from your own system; fiscalization is already complete. | same-key | --- # Refund A full or partial Промет Рефундација of a receipt BokaPOS issued. You name which lines and how much is returned; BokaPOS verifies the original, writes the reference and, when cash is returned, immediately issues the copy with the signature line. ## Rules - The source is always a receipt BokaPOS issued (`original.source: BOKA`, `fiscalDocumentId`) of type Промет Продаја, on the same taxpayer and premise. Refunding another ESIR's receipt is not supported, because how much was already returned cannot be verified. - Every refund item points to a line of the original receipt (`originalLineIndex`, zero-based) and must repeat its name, unit, price, labels and GTIN. The quantity may be smaller (partial refund); the sum of every refund of that line never exceeds the original quantity. - `buyer.id` is **mandatory**: the regulations require buyer identification on every refund (for example `20:` identity card number or `10:` PIB). - `payments` are the returned funds; the sum must equal the refunded items. If any payment is `CASH`, BokaPOS immediately issues the **Копија Рефундације** with the customer-signature line, as the regulations require. - A refund is never blocked by licensing: an issued receipt can always be unwound. ## Request POST /v1/refund-workflows: ```bash curl -X POST "https://api.bokapos.rs/v1/refund-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-refund-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761" }, "cashier": { "id": "web-shop" }, "buyer": { "id": "20:001234567" }, "items": [ { "originalLineIndex": 0, "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ] }' ``` ## Response Response 201: ```json { "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "status": "COMPLETED", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00", "invoiceType": "NORMAL", "transactionType": "SALE" }, "refund": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1", "clientReference": "ORDER-4127-R1", "invoiceType": "NORMAL", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 900, "totalCounter": 1045, "invoiceCounterExtension": "ПР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1045\nБројач рачуна: 900/1045ПР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T14:40:04.310Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "cashRefundCopy": null, "failureCode": null, "createdAt": "2026-09-01T14:40:04.300Z", "updatedAt": "2026-09-01T14:40:05.120Z" } ``` | `status` | Meaning | What to do | | --- | --- | --- | | `COMPLETED` | the refund (and the copy, if needed) is fiscalized | store `refund.id` and `refund.pfr.*`; if `cashRefundCopy` exists, print it for signature | | `REFUND_PENDING` | the refund was not sent (V-PFR unavailable), HTTP 503 | repeat the same request with the same key | | `REFUND_OUTCOME_UNKNOWN` | the refund was sent, the response did not arrive, HTTP 503 | do not retry; poll `GET /v1/operations/{refund.id}` | | `COPY_PENDING`, `COPY_OUTCOME_UNKNOWN` | the refund exists, the copy is not finished, HTTP 503 | repeat with the same key (pending) or wait (unknown); the refund is already valid | | `FAILED` | the V-PFR rejected; `failureCode` and `refund.pfrRejection` say why | fix and send with a new key | ## Cash refund POST /v1/refund-workflows: ```bash curl -X POST "https://api.bokapos.rs/v1/refund-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-refund-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761" }, "cashier": { "id": "web-shop" }, "buyer": { "id": "20:001234567" }, "items": [ { "originalLineIndex": 0, "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CASH", "amount": 8990 } ] }' ``` Response 201: ```json { "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "status": "COMPLETED", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00", "invoiceType": "NORMAL", "transactionType": "SALE" }, "refund": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1", "clientReference": "ORDER-4127-R1", "invoiceType": "NORMAL", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 900, "totalCounter": 1045, "invoiceCounterExtension": "ПР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1045\nБројач рачуна: 900/1045ПР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "cashRefundCopy": { "id": "6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1:copy", "clientReference": "COPY-ORDER-4127-R1", "invoiceType": "COPY", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1046", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 901, "totalCounter": 1046, "invoiceCounterExtension": "КР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1046\nБројач рачуна: 901/1046КР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/official-text", "jsonUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "pfrNumber": "JWX4K9PL-JWX4K9PL-1045", "pfrTime": "2026-09-01T16:40:05.120+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-01T14:40:04.300Z", "updatedAt": "2026-09-01T14:40:06.902Z" } ``` Note: When cash is returned BokaPOS immediately issues the refund copy too (`cashRefundCopy`) with the customer-signature line; print it and have the customer sign. ## Partial refund To return part of an order, send only the lines being returned, with the quantity being returned. If the original had two lines and the customer returns one of two pieces of the first, `items` holds one entry with `originalLineIndex: 0` and `quantity: 1`. A later refund of the same line may return at most the remaining piece. Prices and labels do not change: what was on the receipt is what gets refunded. ## Refunding other kinds - **Advance**: cancelling a wrong advance payment and closing the case are Аванс Рефундација, through `/v1/advance-cases` ([Advance](https://bokapos.rs/api/avans)). - **Proforma and Training**: the refund must repeat the whole source document, through `/v1/proforma-training-workflows` ([Proforma and Training](https://bokapos.rs/api/predracun-i-obuka)). - **A copy** of an existing refund: `POST /v1/fiscal-documents/{refund.id}/copies`. ## Refund errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 422 | `REFERENCE_DOCUMENT_NOT_FOUND` | The referenced document (`reference.fiscalDocumentId`) does not exist. | Check the identifier from the original receipt's response. | fix-request | | 422 | `REFERENCE_DOCUMENT_NOT_FISCALIZED` | The referenced document is not fiscalized, so it cannot be a reference. | A reference may point only to a FISCALIZED document. | fix-request | | 422 | `REFERENCE_DOCUMENT_SCOPE_MISMATCH` | The referenced document belongs to another taxpayer or premise. | A reference must stay on the same taxpayer and premise. | fix-request | | 422 | `REFERENCE_DOCUMENT_TYPE_NOT_ALLOWED` | This invoice and transaction type may not reference that kind of source document (the official reference matrix). | See the reference table on the Conventions page. | fix-request | | 422 | `REFUND_QUANTITY_EXCEEDS_ORIGINAL` | The returned quantity is larger than the quantity on the original line. | Lower the quantity; a partial refund is allowed. | fix-request | | 422 | `REFUND_CUMULATIVE_QUANTITY_EXCEEDED` | The sum of every refund of that line so far would exceed the original quantity. | Check the earlier refunds in the journal. | never | | 422 | `REFUND_ITEM_MUST_MATCH_ORIGINAL_LINE` | Name, price, labels or GTIN do not match the original line `originalLineIndex`. | Copy the item from the original receipt (`GET /v1/fiscal-documents/{id}/representations/canonical-json`). | fix-request | | 422 | `REFUND_ORIGINAL_LINE_NOT_FOUND` | `originalLineIndex` does not exist on the original receipt. | Indexes start at zero, in the order of the original's items. | fix-request | --- # Advance The customer pays before delivery, sometimes in instalments. The regulations require an Аванс Продаја for every payment, an Аванс Рефундација of the whole advance at delivery and a final Промет Продаја receipt with a reference. BokaPOS runs the entire chain as one case; you send the payments and, at the end, the delivery. > **Advance module:** In production, opening a case and advance payments require the Advance module (`403 MODULE_NOT_LICENSED` otherwise). Closing and cancelling work without it, so every started chain can be finished. In the sandbox everything works. ## Flow 1. **Open a case** `POST /v1/advance-cases` with the order's `clientReference`. The case issues no receipt; it holds the chain. 2. **Fiscalize every payment** `POST /v1/advance-cases/{id}/payments` for every payment received. The first Аванс Продаја has no reference; every next one automatically references the previous. 3. **Cancel a wrong payment (if needed)** `POST /v1/advance-cases/{id}/cancellations` issues an Аванс Рефундација that voids the latest advance sale in full; the case stays open. 4. **Close at delivery** `POST /v1/advance-cases/{id}/close` with the delivered items and the top-up. BokaPOS issues the Аванс Рефундација of the whole advance, then the final Промет Продаја receipt referencing it. ## Opening a case POST /v1/advance-cases: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-case" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001" }' ``` Response 201: ```json { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "OPEN", "advanceSales": [], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": null, "finalSale": null, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-03T09:00:00.000Z" } ``` A case can also take over advances collected before eFiscalization (`externalAdvance`: total amount, label, payment type, number of the last pre-fiscal document, digits only, and its date). The first advance sale then references that document, and the closure refunds the old and new advances together. ## Advance payment The items of an advance sale are not goods but the **prescribed advance literals**: name `10: Аванс (Ђ)` for label Ђ, `11: Аванс (Е)`, `12: Аванс (Г)`, `13: Аванс (А)`, exactly so, without a unit of measure, with `taxLabels` of the same label and `unitPrice` equal to the amount paid for that rate. If the order has items at two rates, the advance splits into two advance items. `commercialFooter` is mandatory: describe what is being paid. POST /v1/advance-cases/{advanceCaseId}/payments: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/payments" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-1" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "items": [ { "name": "10: Аванс (F)", "quantity": 1, "unitPrice": 3000, "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 3000 } ], "paymentOccurredAt": "2026-09-02T11:30:00+02:00", "commercialFooter": "Avans za porudžbinu ORDER-5001. Isporuka po uplati ostatka." }' ``` Response 201: ```json { "id": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1049", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 904, "totalCounter": 1049, "invoiceCounterExtension": "АП", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1049\nБројач рачуна: 904/1049АП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/official-text", "jsonUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-03T09:01:00.000Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Note: In production the item name is the prescribed `10: Аванс (Ђ)` (or 11/12/13 for Е, Г, А) with the same label. The sandbox lacks those labels, so it accepts `10: Аванс (X)` for any label in the fresh configuration. > **Wire transfer received earlier:** When a wire transfer arrived before you learned of it, the official rule allows the advance sale to carry the actual payment time: send `paymentOccurredAt` (it must precede the fiscalization) and at least one payment of type `WIRE_TRANSFER`. Omit the field in every other case. ## Cancelling a wrong payment The official procedure for a wrongly issued advance is an Аванс Рефундација that repeats that whole advance receipt, references it and carries the seller's own PIB as the buyer (`10:`). BokaPOS does that itself: you send the `id` of the latest advance sale. The chain continues from the previous payment; cancelled sales stay visible in `advanceSales`, and `cancelledAdvanceSaleIds` says which were voided. POST /v1/advance-cases/{advanceCaseId}/cancellations: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/cancellations" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-1-cancel" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "advanceSaleFiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80" }' ``` Response 201: ```json { "id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1-cancel", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "10:115711881", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1050", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 905, "totalCounter": 1050, "invoiceCounterExtension": "АР", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1050\nБројач рачуна: 905/1050АР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "pfrNumber": "JWX4K9PL-JWX4K9PL-1049", "pfrTime": "2026-09-03T11:01:00.500+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` ## Closing `finalItems` are the delivered items at full value (as if issuing an ordinary receipt). `remainingPayments` is only the top-up: the final amount less the total fiscalized advance. When there is no top-up, send one payment with amount `0` (the V-PFR requires at least one element). BokaPOS issues two documents in order: the Аванс Рефундација of the whole advance, then Промет Продаја referencing it. Partial delivery from an advance is not supported in this workflow. POST /v1/advance-cases/{advanceCaseId}/close: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/close" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-close" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "finalItems": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "remainingPayments": [ { "type": "CARD", "amount": 5990 } ], "commercialFooter": "Hvala na kupovini." }' ``` Response 201: ```json { "case": { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "CLOSED", "advanceSales": [ { "id": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1049", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 904, "totalCounter": 1049, "invoiceCounterExtension": "АП", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1049\nБројач рачуна: 904/1049АП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/official-text", "jsonUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": { "id": "e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-close:refund", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "10:115711881", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1051", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 906, "totalCounter": 1051, "invoiceCounterExtension": "АР", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1051\nБројач рачуна: 906/1051АР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/official-text", "jsonUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "pfrNumber": "JWX4K9PL-JWX4K9PL-1049", "pfrTime": "2026-09-03T11:01:00.500+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "finalSale": { "id": "f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-close:sale", "clientReference": "ORDER-5001", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1052", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 907, "totalCounter": 1052, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1052\nБројач рачуна: 907/1052ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/official-text", "jsonUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091", "pfrNumber": "JWX4K9PL-JWX4K9PL-1051", "pfrTime": "2026-09-05T13:20:44.010+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-05T11:20:45.300Z" } } ``` Note: The final Sale carries the whole delivered value (8,990) and references the Advance Refund; `remainingPayments` is only the top-up (5,990). > **202: the refund went through, the final receipt did not:** Closing is two documents. If the Аванс Рефундација is fiscalized and the final receipt is confirmed not sent, the response is `202` with state `ADVANCE_REFUND_FISCALIZED_SALE_PENDING`. Repeat **the same request with the same key**: BokaPOS sends only the final receipt. An unknown outcome of either step blocks instead of retrying. ## Case states | `state` | Meaning | Allowed | | --- | --- | --- | | `OPEN` | chain in progress | payment, cancellation, closing | | `ADVANCE_SALE_OUTCOME_UNKNOWN` | the latest payment has an unknown outcome | wait; `GET` the case until resolved | | `CLOSING` | closing in progress | replay with the same key | | `ADVANCE_REFUND_OUTCOME_UNKNOWN` | the closing advance refund has an unknown outcome | wait | | `ADVANCE_REFUND_FISCALIZED_SALE_PENDING` | refund done, final receipt not sent | replay the closure with the same key | | `FINAL_SALE_OUTCOME_UNKNOWN` | the final receipt has an unknown outcome | wait | | `CLOSED` | everything fiscalized | nothing; read | | `FAILED` | the V-PFR rejected a step; `failureCode` | a new case with corrected data | GET /v1/advance-cases/{advanceCaseId}: ```bash curl -X GET "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` GET /v1/advance-cases: ```bash curl -X GET "https://api.bokapos.rs/v1/advance-cases?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&state=OPEN&search=ORDER-5001" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` ## Sandbox and production The Tax Administration sandbox lacks the labels Ђ, Е, Г, А, so in the sandbox BokaPOS accepts the name `10: Аванс (X)` for any label in the fresh configuration (`F` in the examples). In production only the four prescribed names are valid; any other name is rejected. The code does not change: compose the name from the label you read from `GET /v1/tax-rates`. ## Advance errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 403 | `MODULE_NOT_LICENSED` | The base licence is valid but the module in `module` (advance or email) is not enabled. | Enable the module through BokaPOS Administration or do not use that feature in production. | later | | 409 | `ADVANCE_CASE_NOT_OPEN` | The case is closed, failed or waiting for an outcome to be resolved. | Read `GET /v1/advance-cases/{id}` and the `state` field. | never | | 422 | `ADVANCE_CANCELLATION_TARGET_NOT_LATEST` | Only the latest fiscalized Advance Sale can be cancelled. | Send the `id` of the last `advanceSales` entry not listed in `cancelledAdvanceSaleIds`. | fix-request | | 422 | `ADVANCE_CLOSE_TOTAL_MISMATCH` | `remainingPayments` does not equal the final amount less the stored advance. | Compute the difference from `advanceSales` and send it; when zero, one element with amount 0. | fix-request | | 422 | `ADVANCE_CASE_HAS_NO_FISCALIZED_SALE` | A case without any fiscalized Advance Sale cannot be closed (unless it carries `externalAdvance`). | Fiscalize a payment first. | never | | 409 | `ADVANCE_PAYMENT_SUPERSEDED` | A replayed request targets a payment that is no longer the latest in the chain. | Read the case and continue from the current state. | never | | 409 | `ADVANCE_CASE_CLOSE_ALREADY_RESERVED` | A closure was already reserved with a different key. | Replay the closure with the same `Idempotency-Key` it started with. | same-key | | 503 | `ADVANCE_REFUND_NOT_FISCALIZED` | The Tax Administration rejected the closing Advance Refund: the case is `FAILED` with the rejection code, and the reserved final receipt was never sent; it carries this `failureCode`, `NOT_FISCALIZED`, `retryable: false`. | Read the case `failureCode` and `advanceRefund.pfrRejection`, fix the data and open a new case. Replaying the same closure sends nothing. | never | --- # Proforma and Training Предрачун is an offer that looks like a receipt but has no tax effect; Обука is practice for operators and tests. Both go through one bounded workflow with clear reference rules. ## When they are used - **Proforma** (`PROFORMA`): an offer or pro-forma invoice to the customer before payment, for example a B2B order paid by wire transfer. It prints `ОВО НИЈЕ ФИСКАЛНИ РАЧУН`. When the customer pays and the goods ship, a real Normal Sale receipt is issued (which does not reference the proforma). - **Training** (`TRAINING`): a trial document for operator training or testing a flow. It has no tax effect and also prints `ОВО НИЈЕ ФИСКАЛНИ РАЧУН`. In production it is the document with which BokaPOS proves the activation of your certificate. ## Rules | Document | Reference (`original`) | | --- | --- | | Предрачун Продаја | none, or a BokaPOS proforma (sale or refund) | | Предрачун Рефундација | mandatory, a BokaPOS Предрачун Продаја; repeats every item and payment and identifies the buyer | | Обука Продаја | none | | Обука Рефундација | mandatory, a BokaPOS Обука Продаја; repeats the whole source document | A source can be refunded only once and in full; a second attempt returns `409`. External documents are not supported. Tax labels, amounts and payments follow the same rules as a Normal Sale. ## Proforma POST /v1/proforma-training-workflows: ```bash curl -X POST "https://api.bokapos.rs/v1/proforma-training-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: quote-2210-proforma-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "cashier": { "id": "web-shop" }, "buyer": { "id": "10:106952811" }, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 8990 } ], "commercialFooter": "Ponuda važi 7 dana." }' ``` Response 201: ```json { "id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "state": "COMPLETED", "original": null, "document": { "id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "quote-2210-proforma-1", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": "10:106952811", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1047", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 902, "totalCounter": 1047, "invoiceCounterExtension": "ПрП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1047\nБројач рачуна: 902/1047ПрП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/official-text", "jsonUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-02T07:02:10.000Z", "updatedAt": "2026-09-02T07:02:11.204Z" } ``` ## Training POST /v1/proforma-training-workflows: ```bash curl -X POST "https://api.bokapos.rs/v1/proforma-training-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: training-2026-09-02-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "TRAINING-1", "invoiceType": "TRAINING", "transactionType": "SALE", "cashier": { "id": "operater-1" }, "items": [ { "name": "Test artikal", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 100, "taxLabels": [ "F" ] } ], "payments": [ { "type": "CASH", "amount": 100 } ] }' ``` ## Workflow states | `state` | HTTP | Meaning | | --- | --- | --- | | `COMPLETED` | 201 (200 on replay) | the document is signed; `document` carries the PFR facts and representations | | `PENDING` | 202 or 503 | reserved, not sent; replay with the same key | | `OUTCOME_UNKNOWN` | 503 | sent, response missing; do not retry, read `GET /v1/proforma-training-workflows/{id}` | | `REJECTED` | 422 | the V-PFR rejected; fix and send with a new key | | `FAILED` | 503 | an error before sending; `failureCode` | GET /v1/proforma-training-workflows/{proformaTrainingWorkflowId}: ```bash curl -X GET "https://api.bokapos.rs/v1/proforma-training-workflows/0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` ## Proforma and Training errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 403 | `MODULE_NOT_LICENSED` | The base licence is valid but the module in `module` (advance or email) is not enabled. | Enable the module through BokaPOS Administration or do not use that feature in production. | later | | 422 | `TAX_LABEL_NOT_CURRENT` | At least one tax label is not in the fresh V-PFR configuration (field `invalidLabels`). | Read `GET /v1/tax-rates` and use only the labels it returns; sandbox and production have different sets. | fix-request | | 422 | `REFERENCE_DOCUMENT_NOT_FOUND` | The referenced document (`reference.fiscalDocumentId`) does not exist. | Check the identifier from the original receipt's response. | fix-request | | 422 | `REFERENCE_DOCUMENT_TYPE_NOT_ALLOWED` | This invoice and transaction type may not reference that kind of source document (the official reference matrix). | See the reference table on the Conventions page. | fix-request | | 409 | `PROFORMA_TRAINING_WORKFLOW_RESERVATION_CONFLICT` | The source document already has an unresolved or completed refund. | Read the workflow from the `workflowId` in the response. | never | | 409 | `PROFORMA_TRAINING_SOURCE_ALREADY_REFUNDED` | The Proforma or Training document was already refunded in full. | No further action. | never | --- # Catalogue, taxpayers and tax rates The data a fiscal request assumes: the taxpayer and premise (read), the current tax labels (read fresh) and the product catalogue (kept through the API or CSV, optional). ## Taxpayers and premises The taxpayer (PIB, name, VAT status) and the premises (Tax Administration business premise identifier, name, payment mode) are created by BokaPOS when the organization is opened and maintained by the Owner in the portal. The API reads them; the identifiers are stable and can live in your system's configuration. GET /v1/taxpayers: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "taxIdentifier": "115711881", "legalName": "BOKA GROUP DOO", "environment": "sandbox", "status": "active", "registrationNumber": "22196456", "address": "Roze Luksemburg 16", "city": "Beograd", "municipality": "Rakovica", "activityCode": "6201", "activityName": "Računarsko programiranje", "vatStatus": "in_vat", "createdAt": "2026-08-21T09:00:00.000Z", "updatedAt": "2026-08-21T09:00:00.000Z" } ] } ``` GET /v1/taxpayers/{taxpayerId}/business-premises: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "puIdentifier": "1234567", "name": "Web shop", "commerceMode": "distance", "environment": "sandbox", "paymentMode": "all", "status": "active", "createdAt": "2026-08-21T09:05:00.000Z", "updatedAt": "2026-08-21T09:05:00.000Z" } ] } ``` | Premise field | Meaning | | --- | --- | | `puIdentifier` | the business premise identifier from the Tax Administration register; immutable | | `commerceMode` | always `distance`; another mode is not allowed on the V-PFR | | `paymentMode` | `all` (every payment type) or `restricted` (only OTHER, CASH, WIRE_TRANSFER, VOUCHER) | | `status` | `active`, `suspended`, `closed`; fiscalization requires `active` | The taxpayer's `vatStatus` (`in_vat`, `not_in_vat`, `null`) is the Owner's declaration: a taxpayer outside the VAT system may use only the non-VAT label, and an attempt with a VAT label returns `422 TAX_LABEL_NOT_ALLOWED_OUTSIDE_VAT`. ## Tax rates `GET /v1/tax-rates` asks the V-PFR every time, with the exact security element of the premise, and returns the current label group. BokaPOS has no built-in list and remembers no old one; if the V-PFR does not answer, the response is `503`. Call it at start-up and refresh it sensibly (hourly, or on `TAX_LABEL_NOT_CURRENT`), not before every receipt. GET /v1/tax-rates: ```bash curl -X GET "https://api.bokapos.rs/v1/tax-rates?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "source": "PFR", "environment": "sandbox", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "currentTaxGroupId": 8, "validFrom": "2022-05-01T00:00:00", "fetchedAt": "2026-09-01T08:14:02.118Z", "labels": [ { "label": "F", "category": "ECAL", "categoryType": 0, "rate": 11, "activeFrom": "2022-05-01T00:00:00" }, { "label": "N", "category": "N-TAX", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "P", "category": "PBL", "categoryType": 2, "rate": 0.5, "activeFrom": "2022-05-01T00:00:00" }, { "label": "E", "category": "STT", "categoryType": 0, "rate": 6, "activeFrom": "2022-05-01T00:00:00" }, { "label": "T", "category": "TOTL", "categoryType": 1, "rate": 2, "activeFrom": "2022-05-01T00:00:00" }, { "label": "A", "category": "VAT", "categoryType": 0, "rate": 10, "activeFrom": "2022-05-01T00:00:00" }, { "label": "B", "category": "VAT", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "Ж", "category": "VAT", "categoryType": 0, "rate": 19, "activeFrom": "2022-05-01T00:00:00" }, { "label": "C", "category": "VAT-EXCL", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" } ] } ``` Note: The Tax Administration sandbox carries a generic test label set. In production you get the official Serbian labels (for example Ђ 20%, Е 10%, Г 0%, А non-VAT). Never hard-code labels. | Label field | Meaning | | --- | --- | | `label` | the label that goes into an item's `taxLabels`, exactly as written (Cyrillic and Latin differ) | | `category` | the tax category name as the V-PFR prints it (`VAT`, `N-TAX`...) | | `categoryType` | 0 ordinary rate, 1 aggregate, 2 amount per unit | | `rate` | the rate in percent | | `currentTaxGroupId`, `validFrom` | the group identity; changes when the Tax Administration changes rates | ## Product catalogue The catalogue is optional: a receipt line can be fully inline (name, unit, price, labels). If you want BokaPOS to keep the master data (the same for cloud POS operators and for the API), use `/v1/products`. A line that sends `catalogProductId` still carries all its own values; the catalogue is a source for your system, not a substitute for the fields. POST /v1/products: ```bash curl -X POST "https://api.bokapos.rs/v1/products" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ] }' ``` Response 201: ```json { "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ], "isActive": true, "createdAt": "2026-08-22T10:00:00.000Z", "updatedAt": "2026-08-22T10:00:00.000Z" } ``` GET /v1/products: ```bash curl -X GET "https://api.bokapos.rs/v1/products?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&search=slu%C5%A1alice&isActive=true" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` PUT /v1/products/{productId}: ```bash curl -X PUT "https://api.bokapos.rs/v1/products/d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice Pro", "unitOfMeasure": "kom", "grossUnitPrice": 9490, "taxLabels": [ "F" ], "isActive": true }' ``` `PUT` replaces every editable field; `isActive: false` removes the product from selection while receipts that already used it stay unchanged. ### CSV import and export All or nothing: at most 1,000 rows or 5 MB per request. The columns are exactly `sku,name,gtin,unitOfMeasure,grossUnitPrice,taxLabels,isActive`; several labels are separated by `|`. An existing `sku` is updated, a new one created. CSV as produced by a Serbian spreadsheet (BOM, `sep=;`, semicolons and a decimal comma) is accepted too. POST /v1/products/import: ```bash curl -X POST "https://api.bokapos.rs/v1/products/import?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: text/csv" \ --data-binary @katalog.csv ``` Response 200: ```json { "created": 1, "updated": 1, "total": 2 } ``` GET /v1/products/export: ```bash curl -X GET "https://api.bokapos.rs/v1/products/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output katalog.csv ``` Response 200 (text/csv): ```text sku,name,gtin,unitOfMeasure,grossUnitPrice,taxLabels,isActive BT-HP-001,Bluetooth slušalice,8606012345678,kom,8990.00,F,true SRV-INST,Instalacija,,h,4000.00,F,true ``` ## Errors | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 404 | `TAXPAYER_NOT_FOUND` | The taxpayer does not exist in your organization and environment, or is not active. | Read `GET /v1/taxpayers` with the same key and use the exact `id`; a taxpayer of the other environment is invisible. | fix-request | | 404 | `BUSINESS_PREMISE_NOT_FOUND` | The premise does not exist in your environment or does not belong to the given taxpayer. | Read `GET /v1/taxpayers/{taxpayerId}/business-premises`. | fix-request | | 409 | `CATALOGUE_SKU_ALREADY_EXISTS` | The SKU already exists for that taxpayer. | Update the existing product (`PUT`) or use another SKU. | fix-request | | 422 | `CATALOGUE_IMPORT_TOO_MANY_ROWS` | The CSV has more than 1,000 rows. | Split the import into several files. | fix-request | | 422 | `CATALOGUE_IMPORT_HEADERS_INVALID` | The CSV header lacks the expected columns. | Download `GET /v1/products/export` as a template. | fix-request | | 503 | `CURRENT_TAX_CONFIGURATION_UNAVAILABLE` | The V-PFR did not return a fresh tax configuration, so the request was not even reserved. | Retry later with the same key. | same-key | --- # Journal, reports and state Everything BokaPOS did for your organization is searchable: the electronic journal with every attempt, CSV export, the turnover report by PFR time, the state of a single operation, certificates and the licence. ## Searching the journal `GET /v1/fiscal-documents` returns operations of every kind (Normal, Advance, Copy, Proforma, Training; sale and refund), including rejected ones and unknown outcomes. All filters combine. | Filter | Meaning | | --- | --- | | `taxpayerId`, `businessPremiseId` | narrow to a taxpayer or premise | | `status` | one document status (`FISCALIZED`, `REJECTED`, `OUTCOME_UNKNOWN`...) | | `invoiceType`, `transactionType` | invoice and transaction type | | `createdFrom`, `createdTo` | time of receipt by BokaPOS (inclusive, exclusive) | | `pfrFrom`, `pfrTo` | V-PFR signing time; excludes entries without a receipt | | `clientReference`, `idempotencyKey`, `pfrNumber`, `cashierId`, `buyerId` | exact value | | `search` | case-insensitive contains, across safe identifiers | | `cursor`, `pageSize` | pagination; `pageSize` up to 200 | GET /v1/fiscal-documents: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&status=FISCALIZED&createdFrom=2026-09-01T00%3A00%3A00Z&pageSize=50" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ], "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTAxVDA4OjE1OjMyLjQ4M1oiLCJpZCI6IjlmOGU3ZDZjIn0" } ``` Note: Send `nextCursor` as `cursor` for the next page; `null` means the end. The list never returns `pfrRejection`; to diagnose a rejected document, read it individually. GET /v1/fiscal-documents/{fiscalDocumentId}: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4129-sale-1", "clientReference": "ORDER-4129", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "REJECTED", "fiscalized": false, "failureCode": "PFR_VALIDATION_REJECTED", "retryable": false, "pfr": null, "receipt": null, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z", "pfrRejection": { "items": [ { "property": "items[0].unitPrice", "codes": [ "2804" ] } ] } } ``` Note: Code 2804 (format) is what the sandbox returns for a price with more than two decimals. A rejected document is not a receipt; correct the request and send it with a new key. ## Journal export (CSV) The same filters, a deterministic CSV of at most 10,000 rows, without raw PFR data and without secrets. The `Boka-Export-Schema-Version` header carries the format version (`boka-fiscal-journal-csv-v2`). If the filters match more than 10,000 rows, the request fails as a whole (`422`), so narrow the period. GET /v1/fiscal-documents/export: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/export?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&createdFrom=2026-09-01T00%3A00%3A00Z&createdTo=2026-10-01T00%3A00%3A00Z" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output dnevnik-2026-09.csv ``` Response 200 (text/csv): ```text schemaVersion,fiscalDocumentId,taxpayerId,businessPremiseId,idempotencyKey,clientReference,invoiceType,transactionType,cashierId,buyerId,status,fiscalized,failureCode,pfrInvoiceNumber,pfrTime,createdAt,updatedAt,retryable,esirNumber,issuingSoftwareVersion,issuingReceiptRepresentationVersion,issuingBuildCommit boka-fiscal-journal-csv-v2,9f8e7d6c-5b4a-4321-8765-0fedcba98761,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,order-4127-sale-1,ORDER-4127,NORMAL,SALE,web-shop,,FISCALIZED,true,,JWX4K9PL-JWX4K9PL-1042,2026-09-01T08:15:32.483Z,2026-09-01T08:15:31.902Z,2026-09-01T08:15:32.611Z,false,,1.0.0,boka-receipt-representation-v21,1f0d454e8b2c9a7d6f5e4c3b2a1908f7e6d5c4b3 ``` Note: The `Boka-Export-Schema-Version: boka-fiscal-journal-csv-v2` header names the format version. ## Turnover report A local immutable sum of fiscalized Normal and Advance receipts by V-PFR signing time, for one premise and period. Sale and refund are separate positive amounts; per-payment-type amounts come from the verified canonical request, the tax per label from the verified original V-PFR response, and the base and total per label from the receipt lines carrying that label (the total is the gross of those lines, the base is that total minus the tax). Amounts carry four decimals, like the tax the V-PFR signs; round them to two for display. It is your report, not a substitute for the daily report on the Tax Administration's SUF portal. The time bounds accept any RFC 3339 offset (`Z`, `+00:00`, `+02:00`) and are compared as instants. GET /v1/fiscal-documents/turnover-report: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/turnover-report?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&pfrFrom=2026-09-01T00%3A00%3A00%2B02%3A00&pfrTo=2026-09-02T00%3A00%3A00%2B02%3A00" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "source": "BOKA_LOCAL_IMMUTABLE_PFR", "periodBasis": "PFR_SDC_TIME", "amountConvention": "SALE_REFUND_SEPARATE", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "pfrFrom": "2026-08-31T22:00:00Z", "pfrTo": "2026-09-01T22:00:00Z", "documentCount": 2, "firstDocument": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrInvoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "lastDocument": { "fiscalDocumentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "pfrInvoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "pfrTime": "2026-09-01T16:40:05.120+02:00" }, "paymentTotals": [ { "securityElementJid": "JWX4K9PL", "paymentType": "CARD", "saleAmount": 8990, "refundAmount": 8990 } ], "taxTotals": [ { "invoiceType": "NORMAL", "categoryType": 0, "label": "F", "rate": 11, "categoryName": "ECAL", "saleTaxableAmount": 8099.0991, "saleTaxAmount": 890.9009, "saleTotalAmount": 8990, "refundTaxableAmount": 8099.0991, "refundTaxAmount": 890.9009, "refundTotalAmount": 8990 } ] } ``` GET /v1/fiscal-documents/turnover-report/export: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/turnover-report/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&pfrFrom=2026-09-01T00%3A00%3A00%2B02%3A00&pfrTo=2026-09-02T00%3A00%3A00%2B02%3A00" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output promet-2026-09-01.csv ``` ## Operation state `GET /v1/operations/{operationId}` is the lighter shape of a document, from the database, without contacting the V-PFR. The operation identifier equals the document `id`. Use it when a `409` returns an `operationId`, and as the call you poll after `OUTCOME_UNKNOWN`. GET /v1/operations/{operationId}: ```bash curl -X GET "https://api.bokapos.rs/v1/operations/9f8e7d6c-5b4a-4321-8765-0fedcba98761" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f70", "kind": "FISCAL_DOCUMENT", "status": "OUTCOME_UNKNOWN", "fiscalized": false, "failureCode": "PFR_RESPONSE_NOT_OBSERVED", "retryable": false, "resourceUrl": "/v1/fiscal-documents/4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f70", "createdAt": "2026-09-01T09:00:00.000Z", "updatedAt": "2026-09-01T09:00:31.000Z" } ``` `resourceUrl` leads to the full document. When an unknown outcome is resolved, `status` becomes `FISCALIZED` (the receipt existed at the V-PFR) and the order is fiscalized without a single new request. ## Security elements Safe certificate metadata per premise: JID, environment, validity, `certificateExpiryStatus` (`current`, `warning` 30 days before expiry, `critical` 7 days, `expired`) and `replacementRecommended`. Secrets are never returned. Useful for monitoring: warn the Owner before expiry, because the replacement is done by them in the Tax Administration portal and in the BokaPOS portal. GET /v1/security-elements: ```bash curl -X GET "https://api.bokapos.rs/v1/security-elements?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "environment": "sandbox", "jid": "JWX4K9PL", "certificateThumbprint": "A1B2C3D4E5F60718293A4B5C6D7E8F9012345678", "certificateSubject": "CN=JWX4K9PL, O=BOKA GROUP DOO, C=RS", "certificateIssuer": "CN=Sandbox ICA, O=Poreska uprava Republike Srbije, C=RS", "certificateSerialNumber": "3F9C2A8E6B1D", "certificateNotBefore": "2026-08-20T00:00:00Z", "certificateNotAfter": "2028-08-20T00:00:00Z", "certificateExpiryStatus": "current", "replacementRecommended": false, "status": "active", "createdAt": "2026-08-21T09:10:00.000Z", "updatedAt": "2026-08-21T09:12:00.000Z" } ] } ``` ## Licence `GET /v1/license` says whether production fiscalization is currently allowed, which modules are enabled and which prices apply. Call it at start-up and after any `403 LICENSE_*`. The sandbox is never billed and never blocked. GET /v1/license: ```bash curl -X GET "https://api.bokapos.rs/v1/license" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "state": "active", "productionFiscalizationAllowed": true, "modules": { "advance": true, "email": false }, "startsOn": "2026-09-01", "endsOn": null, "prices": { "basePricePerElement": 1600, "includedDocuments": 600, "overageDocumentPrice": 1, "advanceModulePrice": 600, "emailModulePrice": 600, "vatRate": 0.2 } } ``` --- # Errors Every failure has an HTTP status, a code and a clear rule about what may be retried. Here is the catalogue of codes an integrator meets, the V-PFR rejection codes and the recommendations for robust handling. ## Response shapes - `{ "code": "..." }` with an optional `message` and extra fields (`invalidLabels`, `module`, `operationId`): a BokaPOS rule, licence or environment. - An RFC 9457 problem with `errors` per field (`application/problem+json`): validation of the request shape, always 422. - A fiscal document with `fiscalized: false` (503): the V-PFR unavailable or an unknown outcome; `status`, `failureCode` and `retryable` say what next. - A workflow (refund, proforma, advance) with its own `status`/`state` and embedded documents: the error is read from the workflow state. Response 422 (application/problem+json): ```json { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.21", "title": "One or more validation errors occurred.", "status": 422, "errors": { "totals": [ "The item and payment totals must match after the mandated two-decimal currency rounding." ] } } ``` 403: module not in the licence: ```json { "code": "MODULE_NOT_LICENSED", "module": "advance", "message": "This module is not included in the organization's licence. Contact BokaPOS Administration to enable it." } ``` 422: label not in the fresh configuration: ```json { "code": "TAX_LABEL_NOT_CURRENT", "invalidLabels": ["Ђ"], "message": "Every tax label must be present in the freshly fetched current PFR configuration." } ``` Response 503: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "NOT_FISCALIZED", "fiscalized": false, "failureCode": "PFR_SUBMISSION_FAULT", "retryable": true, "pfr": null, "receipt": null, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` ## Code catalogue The **Retry** column: *same key* means the same request with the same `Idempotency-Key` makes sense; *fix the request* means new content and a new key; *later* means the state changes outside your system (licence, settings); *never* means a retry cannot help. | HTTP | Code | Meaning | What to do | Retry | | --- | --- | --- | --- | --- | | 401 | `401` | The token is missing, expired (valid 300 seconds) or was not issued for this API. | Obtain a new token with the client-credentials grant and repeat the call. | same-key | | 403 | `CREDENTIAL_ENVIRONMENT_MISMATCH` | A sandbox credential is trying to use a production security element or the other way round. Rare: the other environment's taxpayers, premises and documents are invisible to your key (`404`), so this appears only when an element and its premise disagree. | Check which credential is configured; the environment is decided by the credential, not the URL. | fix-request | | 403 | `CREDENTIAL_ENVIRONMENT_REQUIRED` | The credential carries no environment (`boka_env`), so it cannot register a taxpayer. Credentials issued by BokaPOS always carry one. | Use a credential issued by BokaPOS Administration; if you have one and still see this, contact support. | never | | 403 | `LICENSE_REQUIRED` | The organization has no licence and the call targets a production element. | Sandbox keeps working. For production contact BokaPOS Administration. | later | | 403 | `LICENSE_NOT_STARTED` | The licence exists but starts later. | Read `GET /v1/license` for the start date. | later | | 403 | `LICENSE_EXPIRED` | The licence has expired. | Contact BokaPOS Administration. Refunds and advance cancellations keep working. | later | | 403 | `LICENSE_SUSPENDED` | BokaPOS suspended the licence. | Contact BokaPOS Administration. | later | | 403 | `MODULE_NOT_LICENSED` | The base licence is valid but the module in `module` (advance or email) is not enabled. | Enable the module through BokaPOS Administration or do not use that feature in production. | later | | 404 | `TAXPAYER_NOT_FOUND` | The taxpayer does not exist in your organization and environment, or is not active. | Read `GET /v1/taxpayers` with the same key and use the exact `id`; a taxpayer of the other environment is invisible. | fix-request | | 404 | `BUSINESS_PREMISE_NOT_FOUND` | The premise does not exist in your environment or does not belong to the given taxpayer. | Read `GET /v1/taxpayers/{taxpayerId}/business-premises`. | fix-request | | 404 | `FISCAL_DOCUMENT_NOT_FOUND` | The document does not exist in your organization and environment. | Check the identifier; documents of another organization or of the other environment are invisible. | fix-request | | 409 | `IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST` | The same `Idempotency-Key` was already used with different request content. The response carries the original's `operationId`. | Read the original through `GET /v1/operations/{operationId}`. A new sale needs a new key. | never | | 409 | `IDEMPOTENCY_KEY_REUSED_IN_OTHER_ENVIRONMENT` | Your organization already used this `Idempotency-Key` with a credential of the other environment (for example during sandbox testing). Keys are unique per organization across both environments. | Send the request again with a new key. Nothing was issued. | never | | 422 | `LPFR_REQUIRED_FOR_IN_PERSON_SALES` | The premise is not a distance-commerce premise. BokaPOS fiscalizes only distance commerce through the V-PFR. | In-person sales need an L-PFR (for example BokaLPFR). | never | | 422 | `BUSINESS_PREMISE_INACTIVE` | The premise is suspended or closed. | Activate it in the portal or use another one. | later | | 422 | `PAYMENT_TYPE_NOT_ALLOWED_ON_PREMISE` | The premise runs in the restricted payment mode (OTHER, CASH, WIRE_TRANSFER, VOUCHER) and the request carries another type. | Change the payment type or the premise's payment mode in the portal. | fix-request | | 422 | `ACTIVE_SECURITY_ELEMENT_REQUIRED` | The premise has no active security element. | In sandbox BokaPOS assigns an element; in production the Owner uploads one in the portal and BokaPOS activates it. | later | | 422 | `TAX_LABEL_NOT_CURRENT` | At least one tax label is not in the fresh V-PFR configuration (field `invalidLabels`). | Read `GET /v1/tax-rates` and use only the labels it returns; sandbox and production have different sets. | fix-request | | 422 | `TAX_LABEL_NOT_ALLOWED_OUTSIDE_VAT` | The taxpayer is marked as outside the VAT system and an item carries a VAT label. | Use the non-VAT label or correct the taxpayer's VAT status in the portal. | fix-request | | 422 | `REFERENCE_DOCUMENT_NOT_FOUND` | The referenced document (`reference.fiscalDocumentId`) does not exist. | Check the identifier from the original receipt's response. | fix-request | | 422 | `REFERENCE_DOCUMENT_NOT_FISCALIZED` | The referenced document is not fiscalized, so it cannot be a reference. | A reference may point only to a FISCALIZED document. | fix-request | | 422 | `REFERENCE_DOCUMENT_SCOPE_MISMATCH` | The referenced document belongs to another taxpayer or premise. | A reference must stay on the same taxpayer and premise. | fix-request | | 422 | `REFERENCE_DOCUMENT_TYPE_NOT_ALLOWED` | This invoice and transaction type may not reference that kind of source document (the official reference matrix). | See the reference table on the Conventions page. | fix-request | | 422 | `REFUND_QUANTITY_EXCEEDS_ORIGINAL` | The returned quantity is larger than the quantity on the original line. | Lower the quantity; a partial refund is allowed. | fix-request | | 422 | `REFUND_CUMULATIVE_QUANTITY_EXCEEDED` | The sum of every refund of that line so far would exceed the original quantity. | Check the earlier refunds in the journal. | never | | 422 | `REFUND_ITEM_MUST_MATCH_ORIGINAL_LINE` | Name, price, labels or GTIN do not match the original line `originalLineIndex`. | Copy the item from the original receipt (`GET /v1/fiscal-documents/{id}/representations/canonical-json`). | fix-request | | 422 | `REFUND_ORIGINAL_LINE_NOT_FOUND` | `originalLineIndex` does not exist on the original receipt. | Indexes start at zero, in the order of the original's items. | fix-request | | 422 | `COPY_SOURCE_NOT_COPYABLE` | Copy, Proforma and Training documents cannot be copied. | Copy only a Normal or Advance document. | never | | 422 | `COPY_SOURCE_NOT_FISCALIZED` | The copy's source is not fiscalized. | A copy exists only for a FISCALIZED document. | never | | 409 | `ADVANCE_CASE_NOT_OPEN` | The case is closed, failed or waiting for an outcome to be resolved. | Read `GET /v1/advance-cases/{id}` and the `state` field. | never | | 422 | `ADVANCE_CANCELLATION_TARGET_NOT_LATEST` | Only the latest fiscalized Advance Sale can be cancelled. | Send the `id` of the last `advanceSales` entry not listed in `cancelledAdvanceSaleIds`. | fix-request | | 422 | `ADVANCE_CLOSE_TOTAL_MISMATCH` | `remainingPayments` does not equal the final amount less the stored advance. | Compute the difference from `advanceSales` and send it; when zero, one element with amount 0. | fix-request | | 422 | `ADVANCE_CASE_HAS_NO_FISCALIZED_SALE` | A case without any fiscalized Advance Sale cannot be closed (unless it carries `externalAdvance`). | Fiscalize a payment first. | never | | 409 | `ADVANCE_PAYMENT_SUPERSEDED` | A replayed request targets a payment that is no longer the latest in the chain. | Read the case and continue from the current state. | never | | 409 | `ADVANCE_CASE_CLOSE_ALREADY_RESERVED` | A closure was already reserved with a different key. | Replay the closure with the same `Idempotency-Key` it started with. | same-key | | 503 | `ADVANCE_REFUND_NOT_FISCALIZED` | The Tax Administration rejected the closing Advance Refund: the case is `FAILED` with the rejection code, and the reserved final receipt was never sent; it carries this `failureCode`, `NOT_FISCALIZED`, `retryable: false`. | Read the case `failureCode` and `advanceRefund.pfrRejection`, fix the data and open a new case. Replaying the same closure sends nothing. | never | | 409 | `PROFORMA_TRAINING_WORKFLOW_RESERVATION_CONFLICT` | The source document already has an unresolved or completed refund. | Read the workflow from the `workflowId` in the response. | never | | 409 | `PROFORMA_TRAINING_SOURCE_ALREADY_REFUNDED` | The Proforma or Training document was already refunded in full. | No further action. | never | | 409 | `CATALOGUE_SKU_ALREADY_EXISTS` | The SKU already exists for that taxpayer. | Update the existing product (`PUT`) or use another SKU. | fix-request | | 422 | `CATALOGUE_IMPORT_TOO_MANY_ROWS` | The CSV has more than 1,000 rows. | Split the import into several files. | fix-request | | 422 | `CATALOGUE_IMPORT_HEADERS_INVALID` | The CSV header lacks the expected columns. | Download `GET /v1/products/export` as a template. | fix-request | | 422 | `JOURNAL_EXPORT_RESULT_LIMIT_EXCEEDED` | More than 10,000 rows match the export filters. | Narrow the period (`createdFrom`, `createdTo`) and export in parts. | fix-request | | 422 | `RECEIPT_DELIVERY_DISABLED` | The taxpayer has not enabled e-mail delivery in the portal settings. | Enable delivery in the portal (Documents, E-mail receipts) or send the receipt from your own system. | later | | 422 | `RECEIPT_DELIVERY_DOCUMENT_NOT_FISCALIZED` | The document is not fiscalized, so there is nothing to deliver. | Send only documents with `fiscalized: true`. | never | | 422 | `RECEIPT_DELIVERY_DOCUMENT_NOT_ISSUED_TO_BUYER` | An advance refund is never issued to the buyer, so BokaPOS does not send it to a buyer address. The same applies to a copy of one. | Send the final receipt of the advance case (the Normal Sale). The advance refund stays available for printing and in the electronic journal. | never | | 503 | `RECEIPT_DELIVERY_UNAVAILABLE` | The platform mail transport is not configured or unavailable. Nothing was queued. | Retry later with the same key or send the receipt from your own system; fiscalization is already complete. | same-key | | 503 | `PFR_SANDBOX_NOT_CONFIGURED` | The fiscal adapter is not configured on this deployment. The response is a document with `fiscalized: false`. | Does not happen on api.bokapos.rs; appears only on local installations without the adapter. | same-key | | 503 | `PFR_ENVIRONMENT_DISABLED` | Fiscal traffic is disabled for this element's environment (production until it is enabled). | Sandbox works; production is enabled by BokaPOS decision. | later | | 503 | `CURRENT_TAX_CONFIGURATION_UNAVAILABLE` | The V-PFR did not return a fresh tax configuration, so the request was not even reserved. | Retry later with the same key. | same-key | | 503 | `PFR_SUBMISSION_FAULT` | The V-PFR was unavailable before submission; the document is `NOT_FISCALIZED`, `retryable: true`. | Repeat the same request with the same `Idempotency-Key` after a short pause. | same-key | | 503 | `OUTCOME_UNKNOWN` | The request may have reached the V-PFR but the response did not come back. Status `OUTCOME_UNKNOWN`, `retryable: false`. Not a receipt, but it may become one. | Do not send a new request for the same sale. Poll `GET /v1/operations/{id}`; BokaPOS resolves the outcome by reading, never by resubmitting. | never | | 422 | `REJECTED` | The V-PFR rejected the request. The document has status `REJECTED` and `GET /v1/fiscal-documents/{id}` returns `pfrRejection` with the property path and code (2310 unknown label; 2800 to 2808 required, length, range, value, format, list size). | Correct the request and send it with a new key. | fix-request | ## V-PFR rejection codes When the V-PFR rejects a request, the document gets status `REJECTED`, `failureCode: PFR_VALIDATION_REJECTED`, and a single read (`GET /v1/fiscal-documents/{id}`) carries `pfrRejection.items` with the property path from your request and the code: | Code | Meaning | Typical cause | | --- | --- | --- | | `2310` | unknown tax label | the label is not in the current group (BokaPOS usually catches this earlier as `TAX_LABEL_NOT_CURRENT`) | | `2800` | required field missing | empty name, cashier, payment | | `2801` | length | a name, cashier or reference too long | | `2802` | range | a quantity or amount outside the allowed range | | `2803` | value | a value outside the enumeration | | `2804` | format | more than two decimals in a price or amount, a wrong time format | | `2805` to `2808` | list size and related checks | an empty item or payment list | ## A robust integration - Derive the `Idempotency-Key` from the order and persist it before sending, so you can repeat the same request after an interruption. - On `503` with `retryable: true`, retry with the same key and exponential back-off (for example 2, 5, 15 seconds), a few times at most; then flag the order for an operator. - On `OUTCOME_UNKNOWN` never send a new request: remember the `id` and poll `GET /v1/operations/{id}` until the status becomes `FISCALIZED` or an operator decides. - On `422` and `REJECTED` record `errors` or `pfrRejection` with the order, fix the data source and send with a new key. - On `401` get a new token and repeat the same request. On `403` read `GET /v1/license`. - Refresh tax labels when you get `TAX_LABEL_NOT_CURRENT`, not before every receipt. - Set a generous HTTP timeout on fiscal calls (for example 60 seconds): the V-PFR usually answers in about two seconds, but a connection dropped on your side turns a certain outcome into an unknown one. - Log `id`, `status`, `failureCode` and `pfr.invoiceNumber`; never log the token or the secret. --- # Going to production The code does not change. The certificate, the licence and the credential do, and a few things that were relaxed in the sandbox become strict. This is the order and the checklist. ## Order 1. **The Owner obtains the security element** The taxpayer's authorized person requests a file-based security element for the distance-commerce premise in the Tax Administration portal and downloads the original ZIP (PFX, password, PAK). BokaPOS never accesses the Tax Administration portal on your behalf. 2. **The Owner uploads it to the BokaPOS portal** In `cloud.bokapos.rs`, Security elements: the original ZIP plus the password and PAK, through an encrypted one-time browser session. The element is bound to the exact taxpayer and premise. 3. **BokaPOS verifies and activates** BokaPOS Administration verifies the chain, JID and PIB and activates the element with one Training document (Обука), which has no tax effect. From then on the element can sign. 4. **Licence** BokaPOS issues the organization's licence (base subscription per production element, plus the Advance and E-mail modules as needed). `GET /v1/license` returns `productionFiscalizationAllowed: true`. 5. **Production credential** BokaPOS issues a `boka-prod-...` client id and secret. Your system receives them as a configuration secret. 6. **Swap and verify** Replace the client id and secret, call `GET /v1/runtime`, then `GET /v1/tax-rates`. Verify the first production receipt at `suf.purs.gov.rs` through `pfr.verificationUrl`. 7. **Sandbox stays available** The sandbox credential and element keep working for further testing. From the moment the production credential is active, the portal hides the organization's sandbox data (the BOKA GROUP DOO test taxpayer, its premise, the test element and every sandbox document) so nobody confuses it with real receipts; the Owner can show it again in Settings. The API is unaffected: each key sees only its own environment. ## What differs in production | Topic | Sandbox | Production | | --- | --- | --- | | URL and code | `api.bokapos.rs` | the same | | Credential | `boka-sbx-...` | `boka-prod-...`; an object of the other environment is `404` for you, and crossing an element returns `403 CREDENTIAL_ENVIRONMENT_MISMATCH` | | Data | sandbox taxpayers, premises, elements and documents only | production ones only; the same PIB may exist once in each environment | | Security element | BokaPOS's from the pool, BOKA GROUP DOO's PIB on the receipt | your certificate, your PIB and name on the receipt | | Verification | `sandbox.suf.purs.gov.rs` | `suf.purs.gov.rs` | | Tax labels | test set (`F`, `A`, `Ж`...) | official Serbian labels (`Ђ`, `Е`, `Г`, `А`...), from `GET /v1/tax-rates` | | Advance items | `10: Аванс (X)` for any label | only `10: Аванс (Ђ)`, `11: Аванс (Е)`, `12: Аванс (Г)`, `13: Аванс (А)` | | Licence | never blocks | sale, copy, proforma, training and advance need a valid licence; refund and cancellation never | | Modules | everything works | Advance and E-mail need the module enabled | | E-mail delivery | works when enabled in the portal | the same, plus the E-mail module | | Price | free | per the [price list](https://bokapos.rs/cena); 600 documents a month included | ## Checklist before the first production receipt - Tax labels are read from `GET /v1/tax-rates`, not from configuration; the mapping of your tax groups to labels is verifiable and covers every rate you sell. - The `Idempotency-Key` is persisted before sending and replayed with the same key after an interruption. - `OUTCOME_UNKNOWN` handling exists and is tested: no new request, polling `GET /v1/operations/{id}`, escalation to an operator. - Amounts have two decimals, quantities three; item and payment totals match. - The customer receives the verification link (and a PDF or image) for every receipt, and the fiscal part is not altered. - Refunds go through `/v1/refund-workflows` with buyer identification; for cash you print the copy for signature. - If you use advances: advance item names are composed from the label, `commercialFooter` is filled, closing sends only the top-up. - Monitoring: `GET /v1/security-elements` (certificate expiry) and `GET /v1/license` (state) are checked periodically; `401`/`403`/`503` raise alerts. - The credential secret lives only on the server, in environment secrets; logs contain no token. - The sandbox test passed: one sale, one partial refund, one journal search, one PDF. ## Support BokaPOS Administration: [office@bokagroup.rs](mailto:office@bokagroup.rs), [+381 69 558 55 88](tel:+381695585588). Send the operation `id` and `clientReference` with a request; never the token, the secret or the certificate. Contract changes are announced in the [OpenAPI file](https://api.bokapos.rs/openapi.yaml) (`info.version`) and on this page. --- # Operation reference Every operation a machine credential can call: parameters, body, responses and an example in five languages. Generated from the OpenAPI contract the API serves at /openapi.yaml. ## Fiscal documents Synchronous Normal Sale issuance, copies, receipt representations, the electronic journal and the turnover report. ### POST /v1/fiscal-documents **Issue a Normal Sale receipt** (operationId `createFiscalDocument`, scope `fiscal:write`, requires `Idempotency-Key` header) The main call. Send the items, payments, cashier and, when needed, the buyer; BokaPOS signs the request with the premise's security element, submits it to the V-PFR and returns 201 only once the signed response is durably stored. Refunds, advances, proforma and training have their own workflows and are rejected here. Contract notes: Accepts a business-level command and succeeds only after a signed V-PFR result has been durably recorded. A 503 response is not a fiscal receipt and may be retried with the same Idempotency-Key. Refund commands are rejected here and must use a server-managed workflow. Advance, Proforma, and Training chains must use their dedicated workflow APIs. An optional unitPriceBeforeDiscount is a Boka-local immutable display/audit fact; unitPrice is always the final reduced gross price sent to V-PFR. No discount field is invented in the supplier request. Repeating the identical command under the same Idempotency-Key after a 201 answers 200 with the stored document and never fiscalizes a second time; only the first 201 with fiscalized=true is the issuing of a receipt. Request body (application/json, FiscalDocumentCreate): - `taxpayerId` (uuid, required) - `businessPremiseId` (uuid, required) - `clientReference` (string, required): Caller-owned order, invoice, or transaction reference. - `invoiceType` (InvoiceType, required): NORMAL | PROFORMA | COPY | TRAINING | ADVANCE - `transactionType` (TransactionType, required): SALE | REFUND - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `buyer` (Buyer) - `id` (string): Official prefix and value, for example 10:123456789. - `optionalField` (string): Official buyer-cost-center prefix and value where applicable. - `reference` (ReferenceTarget) - one of `BokaDocumentReference`: - `source` (const "BOKA", required) - `fiscalDocumentId` (uuid, required) - one of `ExternalFiscalReference`: - `source` (const "EXTERNAL", required) - `pfrNumber` (string, required) - `pfrTime` (date-time, required) - `invoiceType` (InvoiceType, required): NORMAL | PROFORMA | COPY | TRAINING | ADVANCE - `transactionType` (TransactionType, required): SALE | REFUND - `transactionOccurredAt` (date-time): Accepted only for an Advance Sale containing a wire-transfer payment, where the official earlier-payment ESIR-time rule applies; it never overrides PFR time. - `items` (array, required) [min 1 items] - `catalogProductId` (uuid): Optional; arbitrary inline items are permitted. - `name` (string, required) [min 1, max 2048] - `unitOfMeasure` (string): Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit. [min 1, max 50] - `quantity` (number, required): V-PFR Decimal(14,3). [>= 0.001, <= 99999999999.999, step 0.001] - `unitPrice` (number, required): Final gross unit price sent to V-PFR as Decimal(28,4). Boka applies the mandated fiscal rounding rules. [>= 0, step 0.01] - `unitPriceBeforeDiscount` (number): Optional Boka-local immutable gross unit price before discount. When present it must be greater than unitPrice; it is displayed outside the exact PFR journal and is never sent as a supplier field. [>= 0, step 0.0001] - `gtin` (string) [min 8, max 14] - `taxLabels` (array, required) [min 1 items, unique] - `payments` (array, required) [min 1 items] - `type` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `amount` (number, required): At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). [>= 0, step 0.01] - `commercialFooter` (string): Non-fiscal text rendered only in the permitted area outside the fiscal boundary. - `metadata` (object): Non-fiscal caller metadata; never sent as a substitute for a mandated field. Responses: - 200 (FiscalDocument): Idempotent replay of the already fiscalized document under the same Idempotency-Key; no new receipt was issued - 201 (FiscalDocument): Fiscalized document - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (FiscalDocument): No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation Example: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-sale-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashier": { "id": "web-shop", "displayName": "Web shop" }, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ], "metadata": { "orderId": "4127", "channel": "web" } }' ``` Response 201: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Response 409: ```json { "code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST", "operationId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761" } ``` Response 422 (application/problem+json): ```json { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.21", "title": "One or more validation errors occurred.", "status": 422, "errors": { "totals": [ "The item and payment totals must match after the mandated two-decimal currency rounding." ] } } ``` Response 503: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "NOT_FISCALIZED", "fiscalized": false, "failureCode": "PFR_SUBMISSION_FAULT", "retryable": true, "pfr": null, "receipt": null, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Example (buyer): ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4128-sale-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4128", "invoiceType": "NORMAL", "transactionType": "SALE", "cashier": { "id": "web-shop" }, "buyer": { "id": "10:106952811" }, "items": [ { "name": "Godišnja licenca", "unitOfMeasure": "kom", "quantity": 2, "unitPrice": 12000, "taxLabels": [ "F" ] }, { "name": "Instalacija", "unitOfMeasure": "h", "quantity": 1.5, "unitPrice": 4000, "unitPriceBeforeDiscount": 5000, "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 20000 }, { "type": "CARD", "amount": 10000 } ], "commercialFooter": "Hvala na kupovini. Reklamacije: podrska@primer.rs" }' ``` Response 201: ```json { "id": "8e7d6c5b-4a39-4210-8765-fedcba987650", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4128-sale-1", "clientReference": "ORDER-4128", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": "10:106952811", "buyerDetails": { "legalName": "PRIMER DOO BEOGRAD", "taxIdentifier": "106952811", "registrationNumber": "20712345", "address": "Bulevar kralja Aleksandra 1", "city": "Beograd", "source": "nbs-jrr", "resolvedAt": "2026-09-01T08:20:11.004Z" }, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1043", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 898, "totalCounter": 1043, "invoiceCounterExtension": "ПП", "totalAmount": 30000, "totalTax": 2972.973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1043\nБројач рачуна: 898/1043ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/official-text", "jsonUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/8e7d6c5b-4a39-4210-8765-fedcba987650/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` ### GET /v1/fiscal-documents **Search the journal** (operationId `listFiscalDocuments`, scope `fiscal:read`) The organization's electronic journal: every issuance attempt, including rejected ones and unknown outcomes. Filter by taxpayer, premise, status, type, time, reference, PFR number, cashier and buyer; pages continue through the cursor. Contract notes: Returns durable fiscal-operation state ordered by createdAt and id, both descending. createdFrom and pfrFrom are inclusive; createdTo and pfrTo are exclusive. The opaque cursor continues the same ordering. A journal entry whose fiscalized value is false is not a fiscal receipt. Scoped to the caller's environment: a machine credential never sees documents of the other environment, and fetching one by id is 404. Parameters: - `taxpayerId` (query, uuid) - `businessPremiseId` (query, uuid) - `fiscalDocumentId` (query, uuid) - `status` (query, FiscalDocumentStatus): RECEIVED | VALIDATED | SUBMITTING | FISCALIZED | REJECTED | NOT_FISCALIZED | OUTCOME_UNKNOWN | RECONCILING - `invoiceType` (query, InvoiceType): NORMAL | PROFORMA | COPY | TRAINING | ADVANCE - `transactionType` (query, TransactionType): SALE | REFUND - `createdFrom` (query, date-time); Inclusive lower bound for durable operation creation time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. - `createdTo` (query, date-time); Exclusive upper bound for durable operation creation time. Any RFC 3339 offset is accepted and compared as an instant. - `pfrFrom` (query, date-time); Inclusive lower bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. - `pfrTo` (query, date-time); Exclusive upper bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. - `idempotencyKey` (query, string) - `clientReference` (query, string) - `pfrNumber` (query, string) - `cashierId` (query, string) - `buyerId` (query, string) - `search` (query, string); Case-insensitive contains search across safe identifiers only; raw PFR payload, journal, metadata, and secrets are excluded. - `cursor` (query, string); Opaque keyset cursor returned by the preceding journal page. - `pageSize` (query, integer) Responses: - 200 (FiscalDocumentPage): Page of fiscal documents - 400 (ErrorCode): The command violates a Boka or fiscal rule - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&status=FISCALIZED&createdFrom=2026-09-01T00%3A00%3A00Z&pageSize=50" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ], "nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTAxVDA4OjE1OjMyLjQ4M1oiLCJpZCI6IjlmOGU3ZDZjIn0" } ``` Note: Send `nextCursor` as `cursor` for the next page; `null` means the end. ### GET /v1/fiscal-documents/export **Export the journal (CSV)** (operationId `exportFiscalDocuments`, scope `fiscal:read`) The same filters as the search; the result is a deterministic CSV of at most 10,000 rows without raw PFR data. When more rows match the request fails as a whole, so narrow the period. Contract notes: Applies the same tenant-scoped filters and descending createdAt/id ordering as the electronic journal. The export contains at most 10,000 safe projection rows and never includes the raw fiscal command, caller metadata, complete PFR response, official journal text, verification URL, signature data, or secret material. Text cells that could be interpreted as spreadsheet formulas are neutralized. If more than 10,000 rows match, the request fails without returning a partial file. Parameters: - `taxpayerId` (query, uuid) - `businessPremiseId` (query, uuid) - `fiscalDocumentId` (query, uuid) - `status` (query, FiscalDocumentStatus): RECEIVED | VALIDATED | SUBMITTING | FISCALIZED | REJECTED | NOT_FISCALIZED | OUTCOME_UNKNOWN | RECONCILING - `invoiceType` (query, InvoiceType): NORMAL | PROFORMA | COPY | TRAINING | ADVANCE - `transactionType` (query, TransactionType): SALE | REFUND - `createdFrom` (query, date-time); Inclusive lower bound for durable operation creation time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. - `createdTo` (query, date-time); Exclusive upper bound for durable operation creation time. Any RFC 3339 offset is accepted and compared as an instant. - `pfrFrom` (query, date-time); Inclusive lower bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. - `pfrTo` (query, date-time); Exclusive upper bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. - `idempotencyKey` (query, string) - `clientReference` (query, string) - `pfrNumber` (query, string) - `cashierId` (query, string) - `buyerId` (query, string) - `search` (query, string); Case-insensitive contains search across safe identifiers only; raw PFR payload, journal, metadata, and secrets are excluded. Responses: - 200: Complete filtered journal CSV in stable descending order - 422 (ErrorCode): Invalid filters or more than 10,000 matching rows Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/export?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&createdFrom=2026-09-01T00%3A00%3A00Z&createdTo=2026-10-01T00%3A00%3A00Z" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output dnevnik-2026-09.csv ``` Response 200 (text/csv): ```text schemaVersion,fiscalDocumentId,taxpayerId,businessPremiseId,idempotencyKey,clientReference,invoiceType,transactionType,cashierId,buyerId,status,fiscalized,failureCode,pfrInvoiceNumber,pfrTime,createdAt,updatedAt,retryable,esirNumber,issuingSoftwareVersion,issuingReceiptRepresentationVersion,issuingBuildCommit boka-fiscal-journal-csv-v2,9f8e7d6c-5b4a-4321-8765-0fedcba98761,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,order-4127-sale-1,ORDER-4127,NORMAL,SALE,web-shop,,FISCALIZED,true,,JWX4K9PL-JWX4K9PL-1042,2026-09-01T08:15:32.483Z,2026-09-01T08:15:31.902Z,2026-09-01T08:15:32.611Z,false,,1.0.0,boka-receipt-representation-v21,1f0d454e8b2c9a7d6f5e4c3b2a1908f7e6d5c4b3 ``` Note: The `Boka-Export-Schema-Version: boka-fiscal-journal-csv-v2` header names the format version. ### POST /v1/fiscal-documents/{fiscalDocumentId}/copies **Issue a copy** (operationId `createFiscalDocumentCopy`, scope `fiscal:write`, requires `Idempotency-Key` header) An official copy of a Normal or Advance document, built on the server from the stored original and signed as a new document. A refund copy carries the customer-signature line. Proforma, Training and Copy documents cannot be copied. Contract notes: Issues a Copy (Копија Продаја or Копија Рефундација) of a fiscalized Normal or Advance document. The copy is built on the server from the stored original: the same items, payments, buyer identification and stored buyer lines, referenced to the original's PFR number and time, with the cashier given here. It is signed by the V-PFR as a new document and prints ОВО НИЈЕ ФИСКАЛНИ РАЧУН; a Копија Рефундација prints the customer signature line. Copy, Proforma and Training documents cannot be copied (422 COPY_SOURCE_NOT_COPYABLE), nor can a document that is not fiscalized (422 COPY_SOURCE_NOT_FISCALIZED). Same idempotency and outcome rules as createFiscalDocument. Parameters: - `fiscalDocumentId` (path, uuid, required) Request body (application/json, FiscalDocumentCopyCreate): - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `clientReference` (string | null): Defaults to COPY- followed by the original's client reference. [max 200] Responses: - 200 (FiscalDocument): Idempotent replay of the already fiscalized copy under the same Idempotency-Key; no new document was issued - 201 (FiscalDocument): Fiscalized copy - 404: The original does not exist in this organization (code FISCAL_DOCUMENT_NOT_FOUND) - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (FiscalDocument): No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation Example: ```bash curl -X POST "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/copies" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-copy-1" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" } }' ``` Response 201: ```json { "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-copy-1", "clientReference": "COPY-ORDER-4127", "invoiceType": "COPY", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1044", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 899, "totalCounter": 1044, "invoiceCounterExtension": "КП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1044\nБројач рачуна: 899/1044КП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/official-text", "jsonUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` ### GET /v1/fiscal-documents/{fiscalDocumentId} **Read a document** (operationId `getFiscalDocument`, scope `fiscal:read`) The state of one document with PFR data and representation links. A rejected document also carries pfrRejection here, with the property path and the V-PFR code, which the list never returns. Parameters: - `fiscalDocumentId` (path, uuid, required) Responses: - 200 (FiscalDocument): Fiscal document - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-sale-1", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 897, "totalCounter": 1042, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text", "jsonUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Example (rejected): ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4129-sale-1", "clientReference": "ORDER-4129", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "REJECTED", "fiscalized": false, "failureCode": "PFR_VALIDATION_REJECTED", "retryable": false, "pfr": null, "receipt": null, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z", "pfrRejection": { "items": [ { "property": "items[0].unitPrice", "codes": [ "2804" ] } ] } } ``` Note: Code 2804 (format) is what the sandbox returns for a price with more than two decimals. A rejected document is not a receipt; correct the request and send it with a new key. ### GET /v1/fiscal-documents/turnover-report **Turnover report** (operationId `getTurnoverReport`, scope `fiscal:read`) A local immutable sum of fiscalized Normal and Advance receipts by PFR time, with Sale and Refund as separate positive amounts, per payment type and per tax label. Not a substitute for the SUF portal reports. Contract notes: Aggregates only tenant-scoped FISCALIZED Normal (Promet) and Advance (Avans) receipts whose authoritative PFR signing time is within the inclusive pfrFrom and exclusive pfrTo bounds. Sale and Refund amounts remain separate positive totals; the API does not infer a net value. Payment data comes from the hash-verified canonical request and tax bases/tax amounts come from the hash-verified complete PFR response. This is a Boka-local immutable report, not the SUF portal daily report; it does not claim SUF-only knowledge about missing or scanned receipts. Parameters: - `taxpayerId` (query, uuid, required) - `businessPremiseId` (query, uuid, required) - `pfrFrom` (query, date-time, required); Inclusive lower bound for authoritative PFR signing time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. - `pfrTo` (query, date-time, required); Exclusive upper bound for authoritative PFR signing time. Any RFC 3339 offset is accepted and compared as an instant. Responses: - 200 (TurnoverReport): Immutable local turnover report - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (ErrorCode): A selected receipt failed source-integrity validation or totals exceeded the supported decimal range - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/turnover-report?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&pfrFrom=2026-09-01T00%3A00%3A00%2B02%3A00&pfrTo=2026-09-02T00%3A00%3A00%2B02%3A00" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "source": "BOKA_LOCAL_IMMUTABLE_PFR", "periodBasis": "PFR_SDC_TIME", "amountConvention": "SALE_REFUND_SEPARATE", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "pfrFrom": "2026-08-31T22:00:00Z", "pfrTo": "2026-09-01T22:00:00Z", "documentCount": 2, "firstDocument": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrInvoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "lastDocument": { "fiscalDocumentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "pfrInvoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "pfrTime": "2026-09-01T16:40:05.120+02:00" }, "paymentTotals": [ { "securityElementJid": "JWX4K9PL", "paymentType": "CARD", "saleAmount": 8990, "refundAmount": 8990 } ], "taxTotals": [ { "invoiceType": "NORMAL", "categoryType": 0, "label": "F", "rate": 11, "categoryName": "ECAL", "saleTaxableAmount": 8099.0991, "saleTaxAmount": 890.9009, "saleTotalAmount": 8990, "refundTaxableAmount": 8099.0991, "refundTaxAmount": 890.9009, "refundTotalAmount": 8990 } ] } ``` ### GET /v1/fiscal-documents/turnover-report/export **Export the turnover report (CSV)** (operationId `exportTurnoverReport`, scope `fiscal:read`) The same report as the JSON version, as a normalized CSV with SUMMARY, FIRST_DOCUMENT, LAST_DOCUMENT, PAYMENT_TOTAL and TAX_TOTAL rows. Contract notes: Reuses the exact same hash-reverified BOKA_LOCAL_IMMUTABLE_PFR report result as the JSON operation. The normalized CSV contains SUMMARY, FIRST_DOCUMENT, LAST_DOCUMENT, PAYMENT_TOTAL, and TAX_TOTAL record types. Sale and Refund remain separate positive amounts. This is not a SUF portal report and contains no official receipt journal or raw PFR payload. Parameters: - `taxpayerId` (query, uuid, required) - `businessPremiseId` (query, uuid, required) - `pfrFrom` (query, date-time, required); Inclusive lower bound for authoritative PFR signing time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. - `pfrTo` (query, date-time, required); Exclusive upper bound for authoritative PFR signing time. Any RFC 3339 offset is accepted and compared as an instant. Responses: - 200: Complete normalized turnover-report CSV - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (ErrorCode): A selected receipt failed source-integrity validation or totals exceeded the supported decimal range - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/turnover-report/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&pfrFrom=2026-09-01T00%3A00%3A00%2B02%3A00&pfrTo=2026-09-02T00%3A00%3A00%2B02%3A00" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output promet-2026-09-01.csv ``` Response 200 (text/csv): ```text schemaVersion,recordType,source,periodBasis,amountConvention,taxpayerId,businessPremiseId,pfrFrom,pfrTo,documentCount,fiscalDocumentId,pfrInvoiceNumber,pfrTime,securityElementJid,paymentType,invoiceType,categoryType,taxLabel,taxRate,taxCategoryName,saleAmount,refundAmount,saleTaxableAmount,saleTaxAmount,saleTotalAmount,refundTaxableAmount,refundTaxAmount,refundTotalAmount boka-turnover-report-csv-v1,SUMMARY,BOKA_LOCAL_IMMUTABLE_PFR,PFR_SDC_TIME,SALE_REFUND_SEPARATE,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,2026-08-31T22:00:00Z,2026-09-01T22:00:00Z,2,,,,,,,,,,,,,,,,,, boka-turnover-report-csv-v1,FIRST_DOCUMENT,BOKA_LOCAL_IMMUTABLE_PFR,PFR_SDC_TIME,SALE_REFUND_SEPARATE,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,2026-08-31T22:00:00Z,2026-09-01T22:00:00Z,,9f8e7d6c-5b4a-4321-8765-0fedcba98761,JWX4K9PL-JWX4K9PL-1042,2026-09-01T08:15:32.483Z,,,,,,,,,,,,,,, boka-turnover-report-csv-v1,PAYMENT_TOTAL,BOKA_LOCAL_IMMUTABLE_PFR,PFR_SDC_TIME,SALE_REFUND_SEPARATE,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,2026-08-31T22:00:00Z,2026-09-01T22:00:00Z,,,,,JWX4K9PL,CARD,,,,,,8990.00,8990.00,,,,,, boka-turnover-report-csv-v1,TAX_TOTAL,BOKA_LOCAL_IMMUTABLE_PFR,PFR_SDC_TIME,SALE_REFUND_SEPARATE,3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11,b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22,2026-08-31T22:00:00Z,2026-09-01T22:00:00Z,,,,,,,NORMAL,0,F,11,ECAL,,,8099.0991,890.9009,8990.0000,8099.0991,890.9009,8990.0000 ``` ### GET /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat} **Fetch a receipt representation** (operationId `getFiscalDocumentRepresentation`, scope `fiscal:read`) Seven representations of the same fiscalized receipt: the official journal text, canonical JSON, the QR code (SVG), PDF in A4, 80 mm and 58 mm, and a PNG preview. Each is generated from the immutable package and carries an ETag. Contract notes: Generates the selected representation only from a tenant-scoped FISCALIZED operation after rechecking the canonical request and complete original PFR response hashes. official-text is the exact stored supplier journal. canonical-json omits caller metadata and opaque encrypted/signature values. The direct PDF and PNG preview layouts keep the verification QR at 45 mm. Fixed media use only bundled local rendering resources and include no network-loaded content. No representation is generated for an unresolved, rejected, unavailable, corrupt, or non-fiscalized operation. Parameters: - `fiscalDocumentId` (path, uuid, required) - `representationFormat` (path, string, required): canonical-json | official-text | qr-svg | pdf-a4 | pdf-80mm | pdf-58mm | preview-png Responses: - 200 (ReceiptCanonicalPackage): Selected immutable receipt representation - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (ErrorCode): Receipt unavailable or stored source integrity validation failed Example: ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/pdf-a4" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output racun-ORDER-4127.pdf ``` Response 200 (application/pdf): ```text %PDF-1.7 ... (binarni sadržaj, A4 račun) ``` Example (text): ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/official-text" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200 (text/plain): ```text ============ ФИСКАЛНИ РАЧУН ============ 115711881 BOKA GROUP DOO BokaPOS sandbox Роза Луксембург 16 Београд-Раковица Касир: web-shop ЕСИР број: 1656/1.0.0 -------------ПРОМЕТ ПРОДАЈА------------- Артикли ======================================== Назив Цена Кол. Укупно Bluetooth slušalice/kom (F) 8.990,00 1 8.990,00 ---------------------------------------- Укупан износ: 8.990,00 Платна картица: 8.990,00 ======================================== Ознака Име Стопа Порез F ECAL 11,00% 890,90 ---------------------------------------- Укупан износ пореза: 890,90 ======================================== ПФР време: 01.09.2026. 10:15:32 ПФР број рачуна: JWX4K9PL-JWX4K9PL-1042 Бројач рачуна: 897/1042ПП ======================================== ======== КРАЈ ФИСКАЛНОГ РАЧУНА ========= ``` Example (json): ```bash curl -X GET "https://api.bokapos.rs/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761/representations/canonical-json" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "schemaVersion": "boka-receipt-representation-v21", "sourceSha256": "5d41402abc4b2a76b9719d911017c592e99f0d3b4a7c1e6f8b2d9a0c3e5f7a1b", "canonicalRequestSha256": "9b74c9897bac770ffc029102a200c5de3a4b1c6d7e8f9a0b1c2d3e4f5a6b7c8d", "originalPfrResponseSha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127", "invoiceType": "NORMAL", "transactionType": "SALE", "request": { "cashierId": "web-shop", "cashierDisplayName": "Web shop", "buyer": null, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ], "reference": null, "transactionOccurredAt": null, "commercialFooter": null }, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1042", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "invoiceCounter": "897/1042ПП", "invoiceCounterExtension": "ПП", "totalCounter": 1042, "transactionTypeCounter": 897, "totalAmount": 8990, "taxGroupRevision": 8, "taxItems": [ { "categoryType": 0, "label": "F", "amount": 890.9009, "rate": 11, "categoryName": "ECAL", "taxableAmountPerLabel": 8099.0991 } ], "businessName": "BOKA GROUP DOO", "tin": "115711881", "locationName": "BokaPOS sandbox", "address": "Роза Луксембург 16", "district": "Београд-Раковица", "mrc": null, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "officialJournal": "============ ФИСКАЛНИ РАЧУН ============\n115711881\nBOKA GROUP DOO\nBokaPOS sandbox\nРоза Луксембург 16\nБеоград-Раковица\nКасир: web-shop\nЕСИР број: 1656/1.0.0\n-------------ПРОМЕТ ПРОДАЈА-------------\nАртикли\n========================================\nНазив Цена Кол. Укупно\nBluetooth slušalice/kom (F)\n 8.990,00 1 8.990,00\n----------------------------------------\nУкупан износ: 8.990,00\nПлатна картица: 8.990,00\n========================================\nОзнака Име Стопа Порез\nF ECAL 11,00% 890,90\n----------------------------------------\nУкупан износ пореза: 890,90\n========================================\nПФР време: 01.09.2026. 10:15:32\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1042\nБројач рачуна: 897/1042ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========", "officialJournalSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "opaqueFiscalDataStored": true }, "branding": null } ``` ## Refunds Full or partial refund of a receipt BokaPOS issued, with the automatic copy when cash is returned. ### POST /v1/refund-workflows **Refund a receipt** (operationId `createRefundWorkflow`, scope `refund:write`, requires `Idempotency-Key` header) A full or partial refund of a Normal Sale BokaPOS issued. Name the original document, the items by original line index and the returned payments; the buyer is mandatory. When cash is returned, BokaPOS immediately issues the refund copy too. Contract notes: Supports full and partial Normal Refunds. Boka-issued originals are resolved only within the exact taxpayer and premise, and cumulative quantities are prevented from exceeding each stored original line. When any returned payment is cash, Boka automatically issues the required Copy Refundation and renders its customer-signature line. This first bounded workflow accepts only a Boka-stored original; external originals remain unsupported because their cumulative returned quantity cannot be independently proven. Normal fiscal traffic remains governed by the same disabled-by-default provider and uncertain-outcome safeguards. Request body (application/json, RefundWorkflowCreate): - `taxpayerId` (uuid, required) - `businessPremiseId` (uuid, required) - `clientReference` (string, required) - `original` (BokaDocumentReference, required) - `source` (const "BOKA", required) - `fiscalDocumentId` (uuid, required) - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `buyer` (Buyer, required) - `id` (string): Official prefix and value, for example 10:123456789. - `optionalField` (string): Official buyer-cost-center prefix and value where applicable. - `items` (array, required) [min 1 items] - `originalLineIndex` (integer, required): Zero-based index of the exact line on the identified original receipt. [>= 0] - `catalogProductId` (uuid): Optional; must match the Boka-stored original line when source is BOKA. - `name` (string, required) [min 1, max 2048] - `unitOfMeasure` (string): Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit. [min 1, max 50] - `quantity` (number, required): Quantity returned from this original line. [>= 0.001, <= 99999999999.999, step 0.001] - `unitPrice` (number, required): Must match the final gross unit price on the identified original line when source is BOKA. [>= 0, step 0.01] - `unitPriceBeforeDiscount` (number): Optional Boka-local pre-discount price; when the source line has it, the value must match exactly and remain greater than unitPrice. [>= 0, step 0.0001] - `gtin` (string) [min 8, max 14] - `taxLabels` (array, required) [min 1 items, unique] - `payments` (array, required) [min 1 items] - `type` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `amount` (number, required): At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). [>= 0, step 0.01] Responses: - 200 (RefundWorkflow): Idempotent replay of a completed refund workflow - 201 (RefundWorkflow): Completed refund workflow - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (RefundWorkflow): The refund workflow is incomplete; no missing fiscal step is claimed as issued Example: ```bash curl -X POST "https://api.bokapos.rs/v1/refund-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-refund-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761" }, "cashier": { "id": "web-shop" }, "buyer": { "id": "20:001234567" }, "items": [ { "originalLineIndex": 0, "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CARD", "amount": 8990 } ] }' ``` Response 201: ```json { "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "status": "COMPLETED", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00", "invoiceType": "NORMAL", "transactionType": "SALE" }, "refund": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1", "clientReference": "ORDER-4127-R1", "invoiceType": "NORMAL", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 900, "totalCounter": 1045, "invoiceCounterExtension": "ПР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1045\nБројач рачуна: 900/1045ПР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T14:40:04.310Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "cashRefundCopy": null, "failureCode": null, "createdAt": "2026-09-01T14:40:04.300Z", "updatedAt": "2026-09-01T14:40:05.120Z" } ``` Example (cash): ```bash curl -X POST "https://api.bokapos.rs/v1/refund-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-refund-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761" }, "cashier": { "id": "web-shop" }, "buyer": { "id": "20:001234567" }, "items": [ { "originalLineIndex": 0, "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "CASH", "amount": 8990 } ] }' ``` Response 201: ```json { "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-4127-R1", "status": "COMPLETED", "original": { "source": "BOKA", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00", "invoiceType": "NORMAL", "transactionType": "SALE" }, "refund": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1", "clientReference": "ORDER-4127-R1", "invoiceType": "NORMAL", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1045", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 900, "totalCounter": 1045, "invoiceCounterExtension": "ПР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1045\nБројач рачуна: 900/1045ПР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "pfrNumber": "JWX4K9PL-JWX4K9PL-1042", "pfrTime": "2026-09-01T10:15:32.483+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "cashRefundCopy": { "id": "6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-4127-refund-1:copy", "clientReference": "COPY-ORDER-4127-R1", "invoiceType": "COPY", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "20:001234567", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1046", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 901, "totalCounter": 1046, "invoiceCounterExtension": "КР", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1046\nБројач рачуна: 901/1046КР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/official-text", "jsonUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/6f7a8b9c-0d1e-4f2a-9b3c-4d5e6f7a8b9c/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "pfrNumber": "JWX4K9PL-JWX4K9PL-1045", "pfrTime": "2026-09-01T16:40:05.120+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-01T14:40:04.300Z", "updatedAt": "2026-09-01T14:40:06.902Z" } ``` Note: When cash is returned BokaPOS immediately issues the refund copy too (`cashRefundCopy`) with the customer-signature line; print it and have the customer sign. ## Advances A server-managed advance chain: Advance Sales, cancelling an erroneous advance and closing with the final Sale. ### GET /v1/advance-cases **List advance cases** (operationId `listAdvanceCases`, scope `fiscal:read`) Advance chains by last change, with a state filter (OPEN, CLOSED, FAILED) and a search on the reference. Contract notes: Returns tenant-scoped advance chains in most-recently-updated order so an operator can continue a chain without handling an internal identifier. Filters narrow by state, reference text and last change. Parameters: - `taxpayerId` (query, uuid) - `businessPremiseId` (query, uuid) - `state` (query, string): OPEN | CLOSED | FAILED; OPEN is every chain still in progress (including unknown-outcome states the operator must resolve); CLOSED and FAILED are terminal. - `search` (query, string); Case-insensitive substring of the client reference. - `updatedFrom` (query, date-time); Only chains changed at or after this instant. Any RFC 3339 offset is accepted and compared as an instant. - `pageSize` (query, integer) Responses: - 200 (AdvanceCasePage): Recent advance chains - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/advance-cases?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22&state=OPEN&search=ORDER-5001" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "OPEN", "advanceSales": [], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": null, "finalSale": null, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-03T09:01:00.700Z" } ] } ``` ### POST /v1/advance-cases **Open an advance case** (operationId `createAdvanceCase`, scope `advance:write`, requires `Idempotency-Key` header) A case ties every Advance Sale of one order on the same taxpayer and premise. It issues no receipt; the next call does. It can also take over advances collected before eFiscalization. Contract notes: Creates a Boka-owned, same-taxpayer/same-premise chain. External and pre-eFiscalization starting references are deliberately outside this bounded workflow. Request body (application/json, AdvanceCaseCreate): - `taxpayerId` (uuid, required) - `businessPremiseId` (uuid, required) - `clientReference` (string, required) [min 1, max 200] - `externalAdvance` (ExternalAdvance | null): Advances collected before eFiscalization that this case closes. The first Advance Sale then references the last pre-fiscal document as XXXXXXXX-XXXXXXXX-, or the close starts with the Advance Refund referencing it when no Advance Sale was fiscalized, and the closing Advance Refund sums pre-fiscal and fiscal advances alike. - `amount` (number, required): Sum of every advance collected before eFiscalization; at most two decimals. [> 0, step 0.01] - `taxLabel` (string, required): Tax label of the future supply; must be a prescribed advance label. - `paymentType` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `lastDocumentNumber` (string, required): Number of the last pre-fiscal advance document, digits only (1 to 20). It is sent as the `` part of the reference `XXXXXXXX-XXXXXXXX-`; the official examples are `17`, `121` and `159`, and the V-PFR rejects any other form. Anything but digits returns 422 with the `ExternalAdvance.LastDocumentNumber` validation key. [min 1, max 20, pattern ^[0-9]{1,20}$] - `lastDocumentTime` (date-time, required): Issue date of that document; must precede the request. Responses: - 200 (AdvanceCase): Idempotent replay of the same case creation - 201 (AdvanceCase): Advance case - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-case" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001" }' ``` Response 201: ```json { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "OPEN", "advanceSales": [], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": null, "finalSale": null, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-03T09:00:00.000Z" } ``` ### GET /v1/advance-cases/{advanceCaseId} **Read an advance case** (operationId `getAdvanceCase`, scope `fiscal:read`) The whole chain: Advance Sales, cancellations, the closing Advance Refund and the final Sale, plus the state that says what is allowed next. Parameters: - `advanceCaseId` (path, uuid, required) Responses: - 200 (AdvanceCase): Advance case - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "OPEN", "advanceSales": [ { "id": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1049", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 904, "totalCounter": 1049, "invoiceCounterExtension": "АП", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1049\nБројач рачуна: 904/1049АП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/official-text", "jsonUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": null, "finalSale": null, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-03T09:01:00.700Z" } ``` ### POST /v1/advance-cases/{advanceCaseId}/payments **Fiscalize an advance payment** (operationId `fiscalizeAdvancePayment`, scope `advance:write`, requires `Idempotency-Key` header) Issues the next Advance Sale in the chain; BokaPOS writes the reference to the previous one itself. Items are the prescribed advance literals (10: Аванс (Ђ) and siblings); the commercial footer is mandatory. Contract notes: Boka references the immediately preceding fiscalized Advance Sale stored in this exact case. Any unresolved prior outcome blocks another payment, and a changed idempotent replay is rejected. Parameters: - `advanceCaseId` (path, uuid, required) Request body (application/json, AdvancePaymentCreate): - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `buyer` (Buyer) - `id` (string): Official prefix and value, for example 10:123456789. - `optionalField` (string): Official buyer-cost-center prefix and value where applicable. - `paymentOccurredAt` (date-time): Actual earlier payment time for the documented wire-transfer case. When supplied, it must precede the fiscalization attempt and at least one payment must be Wire Transfer. - `items` (array, required) [min 1 items] - `catalogProductId` (uuid): Optional; arbitrary inline items are permitted. - `name` (string, required) [min 1, max 2048] - `unitOfMeasure` (string): Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit. [min 1, max 50] - `quantity` (number, required): V-PFR Decimal(14,3). [>= 0.001, <= 99999999999.999, step 0.001] - `unitPrice` (number, required): Final gross unit price sent to V-PFR as Decimal(28,4). Boka applies the mandated fiscal rounding rules. [>= 0, step 0.01] - `unitPriceBeforeDiscount` (number): Optional Boka-local immutable gross unit price before discount. When present it must be greater than unitPrice; it is displayed outside the exact PFR journal and is never sent as a supplier field. [>= 0, step 0.0001] - `gtin` (string) [min 8, max 14] - `taxLabels` (array, required) [min 1 items, unique] - `payments` (array, required) [min 1 items] - `type` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `amount` (number, required): At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). [>= 0, step 0.01] - `commercialFooter` (string, required): Mandatory non-fiscal commercial area for the Advance Sale. [min 1, max 2000] Responses: - 200 (FiscalDocument): Idempotent replay of the fiscalized Advance Sale - 201 (FiscalDocument): Fiscalized Advance Sale - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (FiscalDocument): No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation Example: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/payments" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-1" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "items": [ { "name": "10: Аванс (F)", "quantity": 1, "unitPrice": 3000, "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 3000 } ], "paymentOccurredAt": "2026-09-02T11:30:00+02:00", "commercialFooter": "Avans za porudžbinu ORDER-5001. Isporuka po uplati ostatka." }' ``` Response 201: ```json { "id": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1049", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 904, "totalCounter": 1049, "invoiceCounterExtension": "АП", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1049\nБројач рачуна: 904/1049АП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/official-text", "jsonUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-03T09:01:00.000Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Note: In production the item name is the prescribed `10: Аванс (Ђ)` (or 11/12/13 for Е, Г, А) with the same label. The sandbox lacks those labels, so it accepts `10: Аванс (X)` for any label in the fresh configuration. ### POST /v1/advance-cases/{advanceCaseId}/cancellations **Cancel the latest advance** (operationId `cancelAdvanceSale`, scope `advance:write`, requires `Idempotency-Key` header) An Advance Refund that voids the latest Advance Sale in full, with the seller's PIB as the buyer, as the official procedure prescribes. The case stays open. Contract notes: Issues an Advance Refund that repeats the complete latest Advance Sale of the open case, references it, and carries the seller's own PIB as the buyer (10:), as the official cancellation procedure prescribes. The case stays open; the next Advance Sale chains to the sale before the cancelled one. Only the latest fiscalized Advance Sale can be cancelled. Parameters: - `advanceCaseId` (path, uuid, required) Request body (application/json, AdvanceCancellationCreate): - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `advanceSaleFiscalDocumentId` (uuid, required): The latest fiscalized Advance Sale of the open case; nothing else can be cancelled. Responses: - 200 (FiscalDocument): Idempotent replay of the fiscalized Advance Refund - 201 (FiscalDocument): Fiscalized Advance Refund cancelling the Advance Sale - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (FiscalDocument): No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation Example: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/cancellations" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-advance-1-cancel" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "advanceSaleFiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80" }' ``` Response 201: ```json { "id": "a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1-cancel", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "10:115711881", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1050", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 905, "totalCounter": 1050, "invoiceCounterExtension": "АР", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1050\nБројач рачуна: 905/1050АР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/official-text", "jsonUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/a9b8c7d6-e5f4-4a3b-8c2d-1e0f9a8b7c6d/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "pfrNumber": "JWX4K9PL-JWX4K9PL-1049", "pfrTime": "2026-09-03T11:01:00.500+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` ### POST /v1/advance-cases/{advanceCaseId}/close **Close an advance case** (operationId `closeAdvanceCase`, scope `advance:close`, requires `Idempotency-Key` header) Two documents in one call: the Advance Refund of the whole advance, then the final Normal Sale with the delivered items and the remaining payment. When the second step never reaches the V-PFR, a 202 says so and a replay with the same key retries only that step. Contract notes: This is a recoverable two-document workflow. If the Advance Refund is fiscalized but the final Sale is confirmed not submitted, the response exposes the pending state and an idempotent replay retries only that stored final operation. An unknown outcome blocks instead of retrying. If the V-PFR rejects the Advance Refund, the case is `FAILED` with the rejection code, the refund document is `REJECTED`, and the final Sale reserved with it is never sent: it is closed as `NOT_FISCALIZED` with `ADVANCE_REFUND_NOT_FISCALIZED` and `retryable: false`. Open a new case to try again; a replay of the failed close re-drives nothing. This bounded operation closes the whole Boka-owned chain; partial and legacy/external realization remain unsupported. The Advance Refund is not a customer-delivery document. Parameters: - `advanceCaseId` (path, uuid, required) Request body (application/json, AdvanceCaseClose): - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `buyer` (Buyer) - `id` (string): Official prefix and value, for example 10:123456789. - `optionalField` (string): Official buyer-cost-center prefix and value where applicable. - `finalItems` (array, required) [min 1 items] - `catalogProductId` (uuid): Optional; arbitrary inline items are permitted. - `name` (string, required) [min 1, max 2048] - `unitOfMeasure` (string): Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit. [min 1, max 50] - `quantity` (number, required): V-PFR Decimal(14,3). [>= 0.001, <= 99999999999.999, step 0.001] - `unitPrice` (number, required): Final gross unit price sent to V-PFR as Decimal(28,4). Boka applies the mandated fiscal rounding rules. [>= 0, step 0.01] - `unitPriceBeforeDiscount` (number): Optional Boka-local immutable gross unit price before discount. When present it must be greater than unitPrice; it is displayed outside the exact PFR journal and is never sent as a supplier field. [>= 0, step 0.0001] - `gtin` (string) [min 8, max 14] - `taxLabels` (array, required) [min 1 items, unique] - `remainingPayments` (array, required): Must equal the final amount less the stored advance amount. Supply one explicit zero-amount element when the remaining balance is zero, because Create Invoice requires at least one payment element. [min 1 items] - `type` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `amount` (number, required): At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). [>= 0, step 0.01] - `commercialFooter` (string) [max 1500] Responses: - 200 (AdvanceCloseResult): Idempotent replay of an already closed case - 201 (AdvanceCloseResult): Closed advance case - 202 (AdvanceCloseResult): Advance Refund is fiscalized; final Sale remains pending - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503 (FiscalDocument): No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation Example: ```bash curl -X POST "https://api.bokapos.rs/v1/advance-cases/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f/close" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-5001-close" \ -H "Content-Type: application/json" \ -d '{ "cashier": { "id": "web-shop" }, "finalItems": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "remainingPayments": [ { "type": "CARD", "amount": 5990 } ], "commercialFooter": "Hvala na kupovini." }' ``` Response 201: ```json { "case": { "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "ORDER-5001", "externalAdvance": null, "state": "CLOSED", "advanceSales": [ { "id": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-advance-1", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1049", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 904, "totalCounter": 1049, "invoiceCounterExtension": "АП", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1049\nБројач рачуна: 904/1049АП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/official-text", "jsonUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ], "cancellations": [], "cancelledAdvanceSaleIds": [], "advanceRefund": { "id": "e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-close:refund", "clientReference": "ORDER-5001", "invoiceType": "ADVANCE", "transactionType": "REFUND", "cashierId": "web-shop", "buyerId": "10:115711881", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1051", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 906, "totalCounter": 1051, "invoiceCounterExtension": "АР", "totalAmount": 3000, "totalTax": 297.2973, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1051\nБројач рачуна: 906/1051АР\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/official-text", "jsonUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "d5e6f7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80", "pfrNumber": "JWX4K9PL-JWX4K9PL-1049", "pfrTime": "2026-09-03T11:01:00.500+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "finalSale": { "id": "f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "order-5001-close:sale", "clientReference": "ORDER-5001", "invoiceType": "NORMAL", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1052", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 907, "totalCounter": 1052, "invoiceCounterExtension": "ПП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1052\nБројач рачуна: 907/1052ПП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/official-text", "jsonUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/f7a8b9c0-d1e2-4f3a-8b4c-5d6e7f8091a2/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "reference": { "fiscalDocumentId": "e6f7a8b9-c0d1-4e2f-9a3b-4c5d6e7f8091", "pfrNumber": "JWX4K9PL-JWX4K9PL-1051", "pfrTime": "2026-09-05T13:20:44.010+02:00" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-03T09:00:00.000Z", "updatedAt": "2026-09-05T11:20:45.300Z" } } ``` Note: The final Sale carries the whole delivered value (8,990) and references the Advance Refund; `remainingPayments` is only the top-up (5,990). ## Proforma and Training The bounded workflow for Proforma and Training documents, with a refund that must repeat the whole source document. ### POST /v1/proforma-training-workflows **Issue a Proforma or Training document** (operationId `createProformaTrainingWorkflow`, scope `proforma-training:write`, requires `Idempotency-Key` header) A Proforma is an offer without tax effect; Training is practice. A Training Sale must not carry a reference; a refund must exactly repeat the source document and identify the buyer. Contract notes: Accepts only Proforma or Training transactions through the dedicated workflow scope. Training Sale forbids a reference. Training Refund must reference an exact Boka-issued Training Sale. Proforma Sale may omit a reference or reference a Boka-issued Proforma Sale or Refund; Proforma Refund must reference a Boka-issued Proforma Sale. Refunds must exactly reproduce every source item and payment, identify the buyer, and only one unresolved or completed full refund may reserve a source. External and legacy references remain outside this bounded workflow. An unknown PFR outcome is terminal for automatic submission and an idempotent replay never creates a second fiscal request. Request body (application/json, ProformaTrainingWorkflowCreate): - `taxpayerId` (uuid, required) - `businessPremiseId` (uuid, required) - `clientReference` (string, required) [min 1, max 200] - `invoiceType` (string, required): PROFORMA | TRAINING - `transactionType` (TransactionType, required): SALE | REFUND - `cashier` (Cashier, required) - `id` (string, required) - `displayName` (string) - `buyer` (Buyer) - `id` (string): Official prefix and value, for example 10:123456789. - `optionalField` (string): Official buyer-cost-center prefix and value where applicable. - `original` (BokaDocumentReference) - `source` (const "BOKA", required) - `fiscalDocumentId` (uuid, required) - `items` (array, required) [min 1 items] - `catalogProductId` (uuid): Optional; arbitrary inline items are permitted. - `name` (string, required) [min 1, max 2048] - `unitOfMeasure` (string): Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit. [min 1, max 50] - `quantity` (number, required): V-PFR Decimal(14,3). [>= 0.001, <= 99999999999.999, step 0.001] - `unitPrice` (number, required): Final gross unit price sent to V-PFR as Decimal(28,4). Boka applies the mandated fiscal rounding rules. [>= 0, step 0.01] - `unitPriceBeforeDiscount` (number): Optional Boka-local immutable gross unit price before discount. When present it must be greater than unitPrice; it is displayed outside the exact PFR journal and is never sent as a supplier field. [>= 0, step 0.0001] - `gtin` (string) [min 8, max 14] - `taxLabels` (array, required) [min 1 items, unique] - `payments` (array, required) [min 1 items] - `type` (PaymentType, required): OTHER | CASH | CARD | CHECK | WIRE_TRANSFER | VOUCHER | INSTANT_PAYMENT - `amount` (number, required): At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). [>= 0, step 0.01] - `commercialFooter` (string) [max 2000] - `metadata` (object): Non-fiscal caller metadata. Responses: - 200 (ProformaTrainingWorkflow): Idempotent replay of a completed workflow - 201 (ProformaTrainingWorkflow): Fiscalized Proforma or Training document - 202 (ProformaTrainingWorkflow): The request is durably reserved but has not been submitted - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409: Idempotency, source-reservation, or terminal workflow conflict - 422: The command violates a Boka or fiscal rule, or PFR definitively rejected the reserved operation - 503: No fiscal receipt is claimed; the workflow is unavailable or its outcome is unresolved Example: ```bash curl -X POST "https://api.bokapos.rs/v1/proforma-training-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: quote-2210-proforma-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "cashier": { "id": "web-shop" }, "buyer": { "id": "10:106952811" }, "items": [ { "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 8990, "gtin": "8606012345678", "taxLabels": [ "F" ] } ], "payments": [ { "type": "WIRE_TRANSFER", "amount": 8990 } ], "commercialFooter": "Ponuda važi 7 dana." }' ``` Response 201: ```json { "id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "state": "COMPLETED", "original": null, "document": { "id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "quote-2210-proforma-1", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": "10:106952811", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1047", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 902, "totalCounter": 1047, "invoiceCounterExtension": "ПрП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1047\nБројач рачуна: 902/1047ПрП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/official-text", "jsonUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-02T07:02:10.000Z", "updatedAt": "2026-09-02T07:02:11.204Z" } ``` Example (training): ```bash curl -X POST "https://api.bokapos.rs/v1/proforma-training-workflows" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: training-2026-09-02-1" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "TRAINING-1", "invoiceType": "TRAINING", "transactionType": "SALE", "cashier": { "id": "operater-1" }, "items": [ { "name": "Test artikal", "unitOfMeasure": "kom", "quantity": 1, "unitPrice": 100, "taxLabels": [ "F" ] } ], "payments": [ { "type": "CASH", "amount": 100 } ] }' ``` Response 201: ```json { "id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "TRAINING-1", "invoiceType": "TRAINING", "transactionType": "SALE", "state": "COMPLETED", "original": null, "document": { "id": "3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "training-2026-09-02-1", "clientReference": "TRAINING-1", "invoiceType": "TRAINING", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": null, "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1048", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 903, "totalCounter": 1048, "invoiceCounterExtension": "ОП", "totalAmount": 100, "totalTax": 9.9099, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1048\nБројач рачуна: 903/1048ОП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/official-text", "jsonUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-02T07:10:00.000Z", "updatedAt": "2026-09-02T07:10:01.100Z" } ``` ### GET /v1/proforma-training-workflows/{proformaTrainingWorkflowId} **Read a Proforma or Training workflow** (operationId `getProformaTrainingWorkflow`, scope `fiscal:read`) The workflow state and its document, including OUTCOME_UNKNOWN which a system must resolve before another attempt. Parameters: - `proformaTrainingWorkflowId` (path, uuid, required) Responses: - 200 (ProformaTrainingWorkflow): Persisted workflow and fiscal-document state - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/proforma-training-workflows/0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "state": "COMPLETED", "original": null, "document": { "id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "idempotencyKey": "quote-2210-proforma-1", "clientReference": "QUOTE-2210", "invoiceType": "PROFORMA", "transactionType": "SALE", "cashierId": "web-shop", "buyerId": "10:106952811", "buyerDetails": null, "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "pfr": { "invoiceNumber": "JWX4K9PL-JWX4K9PL-1047", "sdcTime": "2026-09-01T10:15:32.483+02:00", "requestedBy": "JWX4K9PL", "signedBy": "JWX4K9PL", "transactionTypeCounter": 902, "totalCounter": 1047, "invoiceCounterExtension": "ПрП", "totalAmount": 8990, "totalTax": 890.9009, "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "journal": "============ ФИСКАЛНИ РАЧУН ============\n...\nПФР број рачуна: JWX4K9PL-JWX4K9PL-1047\nБројач рачуна: 902/1047ПрП\n========================================\n======== КРАЈ ФИСКАЛНОГ РАЧУНА =========" }, "receipt": { "textUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/official-text", "jsonUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/canonical-json", "pdfA4Url": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-a4", "pdf80mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-80mm", "pdf58mmUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/pdf-58mm", "previewImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/preview-png", "qrImageUrl": "/v1/fiscal-documents/1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e/representations/qr-svg", "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=A0pXWDRLOVBMSldYNEs5UEwSBAAAEAQAAKCLPAAAAAAAAAABnAqJa1EAAAA...", "preferredPaperFormat": "a4" }, "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" }, "failureCode": null, "createdAt": "2026-09-02T07:02:10.000Z", "updatedAt": "2026-09-02T07:02:11.204Z" } ``` ## Receipt delivery by e-mail Sending a fiscalized receipt to the customer from the platform, with the verification link and PDF attachments (E-mail module). ### GET /v1/receipt-deliveries **List the deliveries of a receipt** (operationId `listReceiptDeliveries`, scope `fiscal:read`) Every e-mail delivery of one fiscal document with status and attempt count. Parameters: - `fiscalDocumentId` (query, uuid, required) Responses: - 200: Deliveries in creation order (at most 100) - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/receipt-deliveries?fiscalDocumentId=9f8e7d6c-5b4a-4321-8765-0fedcba98761" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ], "status": "DELIVERED", "attempts": 1, "failureCode": null, "createdAt": "2026-09-01T08:15:40.000Z", "deliveredAt": "2026-09-01T08:15:52.418Z", "updatedAt": "2026-09-01T08:15:52.418Z" } ] } ``` ### POST /v1/receipt-deliveries **E-mail a receipt** (operationId `createReceiptDelivery`, scope `fiscal:write`, requires `Idempotency-Key` header) Queues one message with the verification link and the chosen PDF attachments, in the taxpayer's language. Works only for a fiscalized document and only when the taxpayer enabled delivery in settings; production requires the E-mail module. A delivery status is never proof of fiscalization. Contract notes: Queues one email delivery of one fiscalized document from the platform mailbox, in the taxpayer's chosen language, with the official verification URL as an active link and the A4 PDF attached. Delivery is queued only after the signed result is durably stored, only when the taxpayer enabled email delivery in its settings (422 RECEIPT_DELIVERY_DISABLED otherwise; the taxpayer then delivers through its own system), and only while the platform transport is configured (503 RECEIPT_DELIVERY_UNAVAILABLE otherwise). A delivery outcome is never evidence of fiscalization. Tenant branding stays outside the fiscal receipt boundary. Request body (application/json, ReceiptDeliveryCreate): - `fiscalDocumentId` (uuid, required) - `channel` (const "EMAIL", required) - `recipient` (email, required) [max 320] - `language` (ReceiptDeliveryLanguage | null): sr-Cyrl | sr-Latn | en; Defaults to the taxpayer's setting. - `attachments` (ReceiptDeliveryAttachments | null): a4 | 80mm | 58mm | png; Defaults to the taxpayer's setting. Responses: - 200 (ReceiptDelivery): Idempotent replay - 202 (ReceiptDelivery): Delivery queued - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (IdempotencyConflict): The key was already used with different canonical content - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503: The platform mail transport is not configured Example: ```bash curl -X POST "https://api.bokapos.rs/v1/receipt-deliveries" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Idempotency-Key: order-4127-email-1" \ -H "Content-Type: application/json" \ -d '{ "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ] }' ``` Response 202: ```json { "id": "f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ], "status": "QUEUED", "attempts": 0, "failureCode": null, "createdAt": "2026-09-01T08:15:40.000Z", "deliveredAt": null, "updatedAt": "2026-09-01T08:15:40.000Z" } ``` ### GET /v1/receipt-deliveries/{receiptDeliveryId} **Read a delivery** (operationId `getReceiptDelivery`, scope `fiscal:read`) The state of one delivery: QUEUED, SENDING, DELIVERED or FAILED with a code. Parameters: - `receiptDeliveryId` (path, uuid, required) Responses: - 200 (ReceiptDelivery): Delivery - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/receipt-deliveries/f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "f6a7b8c9-d0e1-4f2a-9b3c-4d5e6f7a8b90", "fiscalDocumentId": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "channel": "EMAIL", "recipient": "kupac@example.com", "language": "sr-Latn", "attachments": [ "a4" ], "status": "DELIVERED", "attempts": 1, "failureCode": null, "createdAt": "2026-09-01T08:15:40.000Z", "deliveredAt": "2026-09-01T08:15:52.418Z", "updatedAt": "2026-09-01T08:15:52.418Z" } ``` ## Operations The durable state of one fiscal operation, without contacting the V-PFR. ### GET /v1/operations/{operationId} **Read an operation** (operationId `getOperation`, scope `operations:read`) The same state as the fiscal document in a shorter shape, from the database and without contacting the V-PFR. Use it when a 409 returns an operationId or when checking an unknown outcome. Contract notes: Reads Boka's durable PostgreSQL operation state without contacting V-PFR, opening a security element, recovering an outcome, or retrying a fiscal command. A false fiscalized value is never a receipt. Parameters: - `operationId` (path, uuid, required) Responses: - 200 (Operation): Operation - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/operations/9f8e7d6c-5b4a-4321-8765-0fedcba98761" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "9f8e7d6c-5b4a-4321-8765-0fedcba98761", "kind": "FISCAL_DOCUMENT", "status": "FISCALIZED", "fiscalized": true, "failureCode": null, "retryable": false, "resourceUrl": "/v1/fiscal-documents/9f8e7d6c-5b4a-4321-8765-0fedcba98761", "createdAt": "2026-09-01T08:15:31.902Z", "updatedAt": "2026-09-01T08:15:32.611Z" } ``` Example (unknown): ```bash curl -X GET "https://api.bokapos.rs/v1/operations/4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f70" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f70", "kind": "FISCAL_DOCUMENT", "status": "OUTCOME_UNKNOWN", "fiscalized": false, "failureCode": "PFR_RESPONSE_NOT_OBSERVED", "retryable": false, "resourceUrl": "/v1/fiscal-documents/4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f70", "createdAt": "2026-09-01T09:00:00.000Z", "updatedAt": "2026-09-01T09:00:31.000Z" } ``` ## Catalogue Products and services with price, unit of measure, GTIN and tax labels; CSV import and export. ### GET /v1/products/{productId} **Read a product** (operationId `getProduct`, scope `catalogue:read`) One product by identifier. Parameters: - `productId` (path, uuid, required) Responses: - 200 (Product): Product - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/products/d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ], "isActive": true, "createdAt": "2026-08-22T10:00:00.000Z", "updatedAt": "2026-08-22T10:00:00.000Z" } ``` ### PUT /v1/products/{productId} **Update a product** (operationId `updateProduct`, scope `catalogue:write`) Replaces every editable field. The taxpayer is immutable; deactivation preserves the historical snapshots on receipts. Contract notes: The owning taxpayer is immutable; deactivation preserves historical receipt snapshots. Parameters: - `productId` (path, uuid, required) Request body (application/json, ProductUpdate): - `sku` (string, required) [min 1, max 100] - `gtin` (string | null) [max 32] - `name` (string, required) [min 1, max 500] - `unitOfMeasure` (string, required) [min 1, max 50] - `grossUnitPrice` (number, required) [>= 0, step 0.01] - `taxLabels` (array, required) [min 1 items, unique] - `isActive` (boolean, required) Responses: - 200 (Product): Updated product - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (Conflict): The requested resource conflicts with an existing tenant-scoped record - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X PUT "https://api.bokapos.rs/v1/products/d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice Pro", "unitOfMeasure": "kom", "grossUnitPrice": 9490, "taxLabels": [ "F" ], "isActive": true }' ``` Response 200: ```json { "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice Pro", "unitOfMeasure": "kom", "grossUnitPrice": 9490, "taxLabels": [ "F" ], "isActive": true, "createdAt": "2026-08-22T10:00:00.000Z", "updatedAt": "2026-09-04T12:00:00.000Z" } ``` ### POST /v1/products/import **Import the catalogue (CSV)** (operationId `importProducts`, scope `catalogue:write`) All or nothing: at most 1,000 rows or 5 MB, labels separated by |. Serbian spreadsheet CSV (BOM, sep=;, decimal comma) is accepted too. Contract notes: The import is all-or-nothing, accepts at most 1,000 rows or 5 MB, and uses `|` between tax labels. Parameters: - `taxpayerId` (query, uuid, required) Request body (text/csv): Responses: - 200 (ProductImportResult): Import counts - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (Conflict): The requested resource conflicts with an existing tenant-scoped record - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X POST "https://api.bokapos.rs/v1/products/import?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: text/csv" \ --data-binary @katalog.csv ``` Response 200: ```json { "created": 1, "updated": 1, "total": 2 } ``` ### GET /v1/products/export **Export the catalogue (CSV)** (operationId `exportProducts`, scope `catalogue:read`) A deterministic CSV of one taxpayer, the same format as the import. Parameters: - `taxpayerId` (query, uuid, required) Responses: - 200: Catalogue CSV using invariant decimals and `|`-separated tax labels - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/products/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ --output katalog.csv ``` Response 200 (text/csv): ```text sku,name,gtin,unitOfMeasure,grossUnitPrice,taxLabels,isActive BT-HP-001,Bluetooth slušalice,8606012345678,kom,8990.00,F,true SRV-INST,Instalacija,,h,4000.00,F,true ``` ### GET /v1/products **List products** (operationId `listProducts`, scope `catalogue:read`) The catalogue per taxpayer, searchable by name, SKU or GTIN with an active filter. Pages through the cursor. Parameters: - `taxpayerId` (query, uuid) - `search` (query, string) - `isActive` (query, boolean) - `cursor` (query, uuid) - `pageSize` (query, integer) Responses: - 200 (ProductPage): Product page - 400 (ErrorCode): The command violates a Boka or fiscal rule - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/products?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&search=slu%C5%A1alice&isActive=true" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ], "isActive": true, "createdAt": "2026-08-22T10:00:00.000Z", "updatedAt": "2026-08-22T10:00:00.000Z" } ], "nextCursor": null } ``` ### POST /v1/products **Add a product** (operationId `createProduct`, scope `catalogue:write`) A product or service with SKU, name, unit of measure, gross price (two decimals) and tax labels. Receipt lines may, but need not, reference the catalogue. Request body (application/json, ProductCreate): - `taxpayerId` (uuid, required) - `sku` (string, required) [min 1, max 100] - `gtin` (string | null) [max 32] - `name` (string, required) [min 1, max 500] - `unitOfMeasure` (string, required) [min 1, max 50] - `grossUnitPrice` (number, required) [>= 0, step 0.01] - `taxLabels` (array, required) [min 1 items, unique] Responses: - 201 (Product): Product - 409 (Conflict): The requested resource conflicts with an existing tenant-scoped record - 422 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X POST "https://api.bokapos.rs/v1/products" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ] }' ``` Response 201: ```json { "id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "sku": "BT-HP-001", "gtin": "8606012345678", "name": "Bluetooth slušalice", "unitOfMeasure": "kom", "grossUnitPrice": 8990, "taxLabels": [ "F" ], "isActive": true, "createdAt": "2026-08-22T10:00:00.000Z", "updatedAt": "2026-08-22T10:00:00.000Z" } ``` Response 409: ```json { "code": "CATALOGUE_SKU_ALREADY_EXISTS", "message": "A product with this SKU already exists for the taxpayer." } ``` ## Tax rates The current tax labels and rates the V-PFR returns for the exact security element of the premise. ### GET /v1/tax-rates **Current tax rates** (operationId `listTaxRates`, scope `configuration:read`) A fresh query to the V-PFR with the exact security element of the premise. The labels it returns are the only ones a receipt may carry; BokaPOS has no built-in list and no fallback. Contract notes: Performs a fresh authenticated PFR status fetch for the exact active premise security element. The response is not a cache authority and no built-in label or rate fallback exists. Parameters: - `taxpayerId` (query, uuid, required) - `businessPremiseId` (query, uuid, required) Responses: - 200 (CurrentTaxConfiguration): Current fiscal tax configuration - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope - 409 (Conflict): The requested resource conflicts with an existing tenant-scoped record - 422 (ErrorCode): The command violates a Boka or fiscal rule - 503: A fresh authoritative PFR configuration could not be obtained Example: ```bash curl -X GET "https://api.bokapos.rs/v1/tax-rates?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11&businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "source": "PFR", "environment": "sandbox", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "currentTaxGroupId": 8, "validFrom": "2022-05-01T00:00:00", "fetchedAt": "2026-09-01T08:14:02.118Z", "labels": [ { "label": "F", "category": "ECAL", "categoryType": 0, "rate": 11, "activeFrom": "2022-05-01T00:00:00" }, { "label": "N", "category": "N-TAX", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "P", "category": "PBL", "categoryType": 2, "rate": 0.5, "activeFrom": "2022-05-01T00:00:00" }, { "label": "E", "category": "STT", "categoryType": 0, "rate": 6, "activeFrom": "2022-05-01T00:00:00" }, { "label": "T", "category": "TOTL", "categoryType": 1, "rate": 2, "activeFrom": "2022-05-01T00:00:00" }, { "label": "A", "category": "VAT", "categoryType": 0, "rate": 10, "activeFrom": "2022-05-01T00:00:00" }, { "label": "B", "category": "VAT", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" }, { "label": "Ж", "category": "VAT", "categoryType": 0, "rate": 19, "activeFrom": "2022-05-01T00:00:00" }, { "label": "C", "category": "VAT-EXCL", "categoryType": 0, "rate": 0, "activeFrom": "2022-05-01T00:00:00" } ] } ``` Note: The Tax Administration sandbox carries a generic test label set. In production you get the official Serbian labels (for example Ђ 20%, Е 10%, Г 0%, А non-VAT). Never hard-code labels. ## Taxpayers and premises The taxpayer and business-premise identifiers every fiscal request carries. They are created in the portal; the API reads them. ### GET /v1/taxpayers **List taxpayers** (operationId `listTaxpayers`, scope `tenant:read`) The taxpayers (legal entities) of the organization the credential belongs to, in the credential's environment only: a sandbox key sees sandbox taxpayers, a production key production ones. Contract notes: Scoped to the caller's environment: a machine credential sees only the taxpayers of its own environment, a portal user sees both unless the organization hides its sandbox data. Responses: - 200 (TaxpayerPage): Taxpayers - 400 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "taxIdentifier": "115711881", "legalName": "BOKA GROUP DOO", "environment": "sandbox", "status": "active", "registrationNumber": "22196456", "address": "Roze Luksemburg 16", "city": "Beograd", "municipality": "Rakovica", "activityCode": "6201", "activityName": "Računarsko programiranje", "vatStatus": "in_vat", "createdAt": "2026-08-21T09:00:00.000Z", "updatedAt": "2026-08-21T09:00:00.000Z" } ] } ``` ### GET /v1/taxpayers/{taxpayerId} **Read a taxpayer** (operationId `getTaxpayer`, scope `tenant:read`) One taxpayer with PIB, status and VAT status. Parameters: - `taxpayerId` (path, uuid, required) Responses: - 200 (Taxpayer): Taxpayer - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "taxIdentifier": "115711881", "legalName": "BOKA GROUP DOO", "environment": "sandbox", "status": "active", "registrationNumber": "22196456", "address": "Roze Luksemburg 16", "city": "Beograd", "municipality": "Rakovica", "activityCode": "6201", "activityName": "Računarsko programiranje", "vatStatus": "in_vat", "createdAt": "2026-08-21T09:00:00.000Z", "updatedAt": "2026-08-21T09:00:00.000Z" } ``` ### GET /v1/taxpayers/{taxpayerId}/business-premises **List business premises** (operationId `listBusinessPremises`, scope `tenant:read`) The taxpayer's premises (Tax Administration business premises). Each has its own security element and payment mode. Parameters: - `taxpayerId` (path, uuid, required) Responses: - 200 (BusinessPremisePage): Premises - 400 (ErrorCode): The command violates a Boka or fiscal rule Example: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "puIdentifier": "1234567", "name": "Web shop", "commerceMode": "distance", "environment": "sandbox", "paymentMode": "all", "status": "active", "createdAt": "2026-08-21T09:05:00.000Z", "updatedAt": "2026-08-21T09:05:00.000Z" } ] } ``` ### GET /v1/taxpayers/{taxpayerId}/business-premises/{businessPremiseId} **Read a business premise** (operationId `getBusinessPremise`, scope `tenant:read`) One premise with its PU identifier, payment mode and status. Parameters: - `taxpayerId` (path, uuid, required) - `businessPremiseId` (path, uuid, required) Responses: - 200 (BusinessPremise): Business premise - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises/b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "puIdentifier": "1234567", "name": "Web shop", "commerceMode": "distance", "environment": "sandbox", "paymentMode": "all", "status": "active", "createdAt": "2026-08-21T09:05:00.000Z", "updatedAt": "2026-08-21T09:05:00.000Z" } ``` ## Security elements Safe certificate metadata (JID, validity, status) without secrets. For monitoring expiry and environment. ### GET /v1/security-elements **List security elements** (operationId `listSecurityElements`, scope `security-elements:read`) Certificate metadata per taxpayer and premise: JID, environment, validity, expiry status and replacement recommendation. Secrets are never returned. Contract notes: Secret envelope references and plaintext values are never returned. Scoped to the caller's environment like every other tenant read. Parameters: - `taxpayerId` (query, uuid) - `businessPremiseId` (query, uuid) Responses: - 200: Tenant-scoped security elements Example: ```bash curl -X GET "https://api.bokapos.rs/v1/security-elements?businessPremiseId=b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "items": [ { "id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "environment": "sandbox", "jid": "JWX4K9PL", "certificateThumbprint": "A1B2C3D4E5F60718293A4B5C6D7E8F9012345678", "certificateSubject": "CN=JWX4K9PL, O=BOKA GROUP DOO, C=RS", "certificateIssuer": "CN=Sandbox ICA, O=Poreska uprava Republike Srbije, C=RS", "certificateSerialNumber": "3F9C2A8E6B1D", "certificateNotBefore": "2026-08-20T00:00:00Z", "certificateNotAfter": "2028-08-20T00:00:00Z", "certificateExpiryStatus": "current", "replacementRecommended": false, "status": "active", "createdAt": "2026-08-21T09:10:00.000Z", "updatedAt": "2026-08-21T09:12:00.000Z" } ] } ``` ### GET /v1/security-elements/{securityElementId} **Read a security element** (operationId `getSecurityElement`, scope `security-elements:read`) One element by identifier, the same safe set of fields. Parameters: - `securityElementId` (path, uuid, required) Responses: - 200 (SecurityElement): Security element metadata - 404 (ErrorCode): Resource does not exist within the authenticated tenant scope Example: ```bash curl -X GET "https://api.bokapos.rs/v1/security-elements/e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a80" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a80", "taxpayerId": "3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11", "businessPremiseId": "b7d4e2c1-9a3f-4c8e-8f21-6e5a0c9d3b22", "environment": "sandbox", "jid": "JWX4K9PL", "certificateThumbprint": "A1B2C3D4E5F60718293A4B5C6D7E8F9012345678", "certificateSubject": "CN=JWX4K9PL, O=BOKA GROUP DOO, C=RS", "certificateIssuer": "CN=Sandbox ICA, O=Poreska uprava Republike Srbije, C=RS", "certificateSerialNumber": "3F9C2A8E6B1D", "certificateNotBefore": "2026-08-20T00:00:00Z", "certificateNotAfter": "2028-08-20T00:00:00Z", "certificateExpiryStatus": "current", "replacementRecommended": false, "status": "active", "createdAt": "2026-08-21T09:10:00.000Z", "updatedAt": "2026-08-21T09:12:00.000Z" } ``` ## Licence Licence state and enabled modules, so a system knows in advance whether production is allowed. ### GET /v1/license **Licence state** (operationId `getLicenseSummary`, scope `tenant:read`) Whether production fiscalization is currently allowed, which modules are enabled and which prices apply. Sandbox is never billed and never blocked. Contract notes: Any customer human or API client. Reports the licence state (none, active, suspended, not-started, expired), whether production fiscalization is currently allowed, which paid modules (Advance, E-mail) are enabled, and the effective prices in RSD with VAT included. Sandbox elements and sandbox traffic are never gated by licensing. A refused production command returns 403 with LICENSE_REQUIRED, LICENSE_NOT_STARTED, LICENSE_EXPIRED, LICENSE_SUSPENDED or MODULE_NOT_LICENSED before anything is reserved or sent to the V-PFR. Refunds of already fiscalized receipts and advance cancellations are never refused by licensing. Responses: - 200 (LicenseSummary): Licence summary Example: ```bash curl -X GET "https://api.bokapos.rs/v1/license" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "state": "active", "productionFiscalizationAllowed": true, "modules": { "advance": true, "email": false }, "startsOn": "2026-09-01", "endsOn": null, "prices": { "basePricePerElement": 1600, "includedDocuments": 600, "overageDocumentPrice": 1, "advanceModulePrice": 600, "emailModulePrice": 600, "vatRate": 0.2 } } ``` ## Runtime The ESIR identity, version and fiscal-adapter readiness as seen by your credential. ### GET /v1/runtime **Runtime and readiness** (operationId `getRuntime`, scope `tenant:read`) The first call after the token. Returns the manufacturer, the ESIR number (empty until the Tax Administration assigns one), the version, the organization and client id from the token, and whether the fiscal adapter is configured. Use it to verify credentials and in health checks. Responses: - 200 (RuntimeInfo): Runtime context Example: ```bash curl -X GET "https://api.bokapos.rs/v1/runtime" \ -H "Authorization: Bearer $BOKAPOS_TOKEN" ``` Response 200: ```json { "manufacturer": "BOKA GROUP DOO", "productName": "BokaPOS", "esirNumber": "", "softwareVersion": "1.0.0", "buildCommit": "1f0d454e8b2c9a7d6f5e4c3b2a1908f7e6d5c4b3", "instanceId": "api-bokapos-rs", "organizationId": "7c1e9a4b-2d3f-4e5a-b6c7-8d9e0f1a2b3c", "clientId": "boka-sbx-k7m2p9x4q1wz", "fiscalEndpointsEnabled": true, "pfrAdapter": "configured" } ```