Skip to content

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).

Updated: 29 August 2026 · Contract version 1.0.0

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
curl -X GET "https://api.bokapos.rs/v1/taxpayers" \
  -H "Authorization: Bearer $BOKAPOS_TOKEN"
200 OK
{
  "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
curl -X GET "https://api.bokapos.rs/v1/taxpayers/3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11/business-premises" \
  -H "Authorization: Bearer $BOKAPOS_TOKEN"
200 OK
{
  "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 fieldMeaning
puIdentifierthe business premise identifier from the Tax Administration register; immutable
commerceModealways distance; another mode is not allowed on the V-PFR
paymentModeall (every payment type) or restricted (only OTHER, CASH, WIRE_TRANSFER, VOUCHER)
statusactive, 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
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"
200 OK
{
  "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 fieldMeaning
labelthe label that goes into an item's taxLabels, exactly as written (Cyrillic and Latin differ)
categorythe tax category name as the V-PFR prints it (VAT, N-TAX...)
categoryType0 ordinary rate, 1 aggregate, 2 amount per unit
ratethe rate in percent
currentTaxGroupId, validFromthe 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
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"
  ]
}'
201 Created
{
  "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
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}
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
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
200 OK
{
  "created": 1,
  "updated": 1,
  "total": 2
}
GET /v1/products/export
curl -X GET "https://api.bokapos.rs/v1/products/export?taxpayerId=3f9c2a8e-6b1d-4e5a-9c47-1d2b8e6f0a11" \
  -H "Authorization: Bearer $BOKAPOS_TOKEN" \
  --output katalog.csv
200 OK · text/csv
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

HTTPCodeMeaningWhat to doRetry
404TAXPAYER_NOT_FOUNDThe 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
404BUSINESS_PREMISE_NOT_FOUNDThe 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
409CATALOGUE_SKU_ALREADY_EXISTSThe SKU already exists for that taxpayer.Update the existing product (PUT) or use another SKU.fix the request
422CATALOGUE_IMPORT_TOO_MANY_ROWSThe CSV has more than 1,000 rows.Split the import into several files.fix the request
422CATALOGUE_IMPORT_HEADERS_INVALIDThe CSV header lacks the expected columns.Download GET /v1/products/export as a template.fix the request
503CURRENT_TAX_CONFIGURATION_UNAVAILABLEThe V-PFR did not return a fresh tax configuration, so the request was not even reserved.Retry later with the same key.same key