API documentation
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.
curl -X GET "https://api.bokapos.rs/v1/taxpayers" \
-H "Authorization: Bearer $BOKAPOS_TOKEN"{
"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"
}
]
}curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises" \
-H "Authorization: Bearer $BOKAPOS_TOKEN"{
"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.
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"{
"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"
}
]
}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.
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"
]
}'{
"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"
}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"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.
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{
"created": 1,
"updated": 1,
"total": 2
}curl -X GET "https://api.bokapos.rs/v1/products/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \
-H "Authorization: Bearer $BOKAPOS_TOKEN" \
--output katalog.csvsku,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,trueErrors
| 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 the 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 the request |
| 409 | CATALOGUE_SKU_ALREADY_EXISTS | The SKU already exists for that taxpayer. | Update the existing product (PUT) or use another SKU. | fix the request |
| 422 | CATALOGUE_IMPORT_TOO_MANY_ROWS | The CSV has more than 1,000 rows. | Split the import into several files. | fix the request |
| 422 | CATALOGUE_IMPORT_HEADERS_INVALID | The CSV header lacks the expected columns. | Download GET /v1/products/export as a template. | fix the 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 |