API documentation
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. If you only send an offer, that is a proforma.
Request
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 |
Response
{
"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 |
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.
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"
}'{
"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
idand theIdempotency-Keyyou used (so you can repeat the same request after an interruption); pfr.invoiceNumber,pfr.sdcTimeandpfr.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 anETag.
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.
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"
}
}'{
"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). - Advance:
/v1/advance-cases, because it is a chain of documents with references (Advance). - Proforma and Training:
/v1/proforma-training-workflows(Proforma and Training). - 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 the 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 the 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 the 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 the request |