Откуцај API v1
REST АПИ за издавање фискалних рачуна, рефундације и копије, слање рачуна е-поштом, извештаје о промету и рад са артиклима. Све што ради каса у прегледачу може и ваш софтвер: веб продавница, ERP, наплатни систем.
За АИ агенте и језичке моделе
Машински читљиви описи овог АПИ-ја постоје и одржавају се уз ову страницу.
Увод и базна адреса
Базна адреса свих позива:
https://otkucaj.com/api/v1
Сви захтеви и одговори су JSON у UTF-8 кодирању, осим журнала који се враћа као чист текст (text/plain). Комуникација иде искључиво преко HTTPS-а. Износи у JSON-у користе тачку као децимални знак (2400.00), јер је то JSON стандард; на самом рачуну и у журналу износи су у српском формату (2.400,00).
Версионисање. Верзија АПИ-ја је део путање: /api/v1. Постојећа поља и понашање се унутар v1 не мењају уназад некомпатибилно; нове могућности се додају као нова, опциона поља. Ако икада буде потребан прекид уговора, он ће живети на путањи /api/v2, а v1 ће наставити да ради.
Завршна коса црта се игнорише: /api/v1/items/ и /api/v1/items су иста путања. Непозната путања враћа 404 са кодом not_found.
Брзи почетак
Од кључа до првог рачуна у три корака.
-
Проверите везу и стање ПФР-а.
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/status
-
Издајте први рачун.
curl -X POST https://otkucaj.com/api/v1/invoices \ -H "Authorization: Bearer kasir_vas_kljuc" \ -H "Content-Type: application/json" \ -d '{ "invoiceType": "Normal", "transactionType": "Sale", "cashier": "Веб продавница", "payments": [{ "paymentType": "Card", "amount": 2400.00 }], "items": [ { "name": "Мајица", "quantity": 2, "unitPrice": 1200.00, "label": "Ђ" } ] }'Одговор је
201са целим рачуном: запамтитеuuidиpfr.invoiceNumber. Пун облик одговора је код POST /invoices. -
Преузмите журнал (текст рачуна спреман за штампу на 40 колона):
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30/journal
/api/v1/status
Стање апликације и везе са ПФР-ом. Употребите за проверу конфигурације и за надзор.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/status
Одговор
{
"app": "Otkucaj",
"version": "1.2.0",
"esirNumber": "у поступку одобравања/1.0",
"pfr": {
"ok": true,
"mode": "demo",
"uid": "DMX7K2QP",
"message": "Демо ПФР симулатор: активан"
},
"time": "2026-08-19T14:00:00.000+02:00"
}
pfr.mode је demo, lpfr или vpfr, у зависности од подешавања инсталације. Када је режим Л-ПФР или В-ПФР, објекат pfr преноси и податке које врати сам процесор фискалних рачуна; ok: false са поруком значи да ПФР тренутно није доступан. esirNumber се склапа од броја и верзије ЕСИР-а из Подешавања.
- 401 unauthorized
- 429 rate_limited
Аутентификација
Сваки захтев мора да носи заглавље:
Authorization: Bearer <api-kljuc>
Кључеве генерише администратор у апликацији, у одељку Подешавања · АПИ кључеви. Кључ се приказује само једном, у тренутку генерисања: у бази се чува искључиво његов SHA-256 отисак, па изгубљен кључ није могуће поново прочитати, већ се опозива и генерише нов. Опозив је тренутан: сваки следећи захтев са опозваним кључем добија 401.
Кључеви нису појединачно ограничени по правима: сваки активан кључ може да позове сваку путању. Ако желите да раздвојите системе, генеришите засебан кључ по систему и опозовите га појединачно кад затреба; то је ниво изолације који апликација заиста нуди.
/api/v1/me
Подаци о кључу којим је захтев потписан: назив, сачувани префикс, датуми, ограничење броја захтева које се стварно примењује и шта кључ сме. Корисно за проверу да интеграција користи онај кључ који мислите да користи.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/me
Одговор
{
"key": {
"id": 3,
"label": "Веб продавница",
"prefix": "kasir_7f3a",
"active": true,
"createdAt": "2026-08-01 09:12:44",
"lastUsedAt": "2026-08-19 13:58:02"
},
"rateLimit": { "requests": 240, "windowSeconds": 60 },
"scope": "full",
"permissions": [
"invoices:read", "invoices:write", "items:read", "items:write",
"reports:read", "taxLabels:read", "categories:read"
],
"note": "API keys are not scoped individually: every active key may call every route."
}
prefix је онај кратки низ који видите и у Подешавањима; сам кључ се нигде не чува у читљивом облику и никада се не враћа.
scope је увек "full", а permissions набраја шта сваки активан кључ стварно може. То није по кључу подесиво: систем права по кључу не постоји, па га овај одговор ни не глуми.
lastUsedAt је претходни позив, не овај: време текућег позива се уписује тек пошто је ред кључа прочитан.
- 401 unauthorized
- 429 rate_limited
Ограничења и понашање
| Ограничење | Вредност | Шта се дешава при прекорачењу |
|---|---|---|
| Број захтева по кључу | 240 у 60 секунди | 429 rate_limited |
| Слање рачуна е-поштом по кључу | 30 порука на сат | 429 rate_limited, посебна порука |
| Ставки по рачуну | 1 до 500 | 422 validation |
Рачуна по страни (GET /invoices) | 100 | следећа страна кроз page |
Артикала по позиву (GET /items) | 1.000 | вишак се не враћа, страничења нема |
| Дужина назива ставке и артикла | 200 знакова | 422 validation |
Дужина buyerId | 20 знакова, облик ознака:вредност | 422 validation |
Дужина adText | 500 знакова | 422 validation |
Прозор ограничења је фиксан, не клизни: први захтев отвара прозор од 60 секунди и броји до 240; кад прозор истекне, бројач креће испочетка. Прекорачење враћа HTTP 429:
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded (240 requests/minute)." } }
Када добијете 429, сачекајте до истека текућег прозора па наставите. За уобичајен рад продавнице (један рачун по поруџбини) ово ограничење се у пракси не досеже. Ограничење које се примењује и оно које пријављује GET /me долазе из истог места у коду, па не могу да се разиђу.
Окружења: тест и продукција
Свака инсталација Откуцаја има два одвојена окружења, и у сваком тренутку једно је активно. АПИ увек ради у активном окружењу, а које је то види се у одговору GET /status и у свакој листи и извештају.
| Окружење | Шта је | Да ли су рачуни фискални |
|---|---|---|
sandbox | Тест систем Пореске управе (*.sandbox.suf.purs.gov.rs). Служи за техничку проверу пре одобрења и за обуку. | Не. Рачуни не улазе у промет ни у пословне књиге. |
production | Стварни систем Пореске управе. | Да. Сваки издати рачун је фискални рачун. |
Два окружења имају различит безбедносни елемент, различит ПАК и различит ЕСИР број. Апликација одбија да у оба постави исти сертификат и одбија да продукцију усмери на тест адресу Пореске управе (и обрнуто).
Подаци су потпуно раздвојени. Рачун носи ознаку окружења у коме је издат, па GET /invoices, GET /reports/summary и GET /reports/daily враћају само оно што припада активном окружењу. Тест рачун не може да уђе у продукцијски промет.
GET /api/v1/status
{
"app": "Otkucaj",
"version": "1.3.0",
"esirNumber": "123456/1.0",
"environment": "sandbox", // sandbox | production
"fiscal": false, // true само у продукцији уз прави ПФР
"ready": true, // да ли се уопште може издати рачун
"problems": [], // ако није спремно, разлози редом
"pfr": { "ok": true, "mode": "demo", "env": "sandbox" },
"company": { "name": "…", "tin": "…", "location": "…" },
"time": "2026-08-20T14:00:00.000+02:00"
}
Пре него што почнете да шаљете стварне рачуне, проверите environment и fiscal. Ако је fiscal: false, оно што издајете није фискални рачун, ма шта писало на журналу.
Свака издата ставка носи исту информацију:
"pfr": {
"mode": "vpfr",
"environment": "production",
"fiscal": true,
"invoiceNumber": "…",
"verificationUrl": "https://suf.purs.gov.rs/v/?vl=…",
"verificationQRCode": "R0lGODlh…" // base64 GIF који је нацртао сам ПФР
}
Када verificationQRCode постоји, то је QR који припада рачуну: Пореска управа га црта по сопственим параметрима. Не цртајте свој преко њега. У демо режиму тог поља нема, па се QR прави из verificationUrl.
Идемпотентност: како да не издате два пута исти рачун
Ако веза пукне пошто је захтев стигао, а пре него што је одговор дошао назад, изгледа исто као да захтев никада није ни стигао. Понављање тада ствара два фискална рачуна за једну продају, што се не може обрисати него само рефундирати.
Зато сваки POST /invoices сме да носи кључ захтева. Пошаљите га на један од два начина:
POST /api/v1/invoices
Authorization: Bearer <api-kljuc>
Idempotency-Key: order-2026-08-20-4711
Content-Type: application/json
{ "items": [ … ] }
или у самом телу:
{ "clientRequestId": "order-2026-08-20-4711", "items": [ … ] }
Правила су једноставна:
- Кључ сме имати 8 до 64 знака: слова, бројеви и
. _ : -. Све друго враћа422. - Кључ се резервише пре него што се позове ПФР, па два истовремена понављања не могу оба да прођу.
- Понављање са истим кључем враћа
201и исти рачун, са истимuuid-ом и истим ПФР бројем. Нови се не издаје. - Ако ПФР одбије захтев, кључ се ослобађа, па прави покушај поново ради.
Најбоље је узети нешто што већ имате и што је јединствено, на пример број поруџбине из свог система. Тако и мрежни прекид и ваш сопствени ретрај воде истом рачуну.
Позивање из прегледача (CORS)
АПИ дозвољава позиве са било ког порекла (Access-Control-Allow-Origin: *) и одговара на OPTIONS проверу. Аутентификација иде искључиво заглављем Authorization, никада колачићем, а Access-Control-Allow-Credentials се намерно не шаље.
То значи: страни сајт без вашег кључа не може ништа. Али то значи и да кључ не сме да стоји у JavaScript-у јавне странице, јер је тамо видљив свакоме. Из прегледача зовите свој сервер, а свој сервер нека зове Откуцај.
Рачуни
Седам путања око рачуна: издавање, листа, детаљ, журнал, рефундација, копија и слање е-поштом.
/api/v1/invoices
Издавање рачуна. Захтев се валидира, прослеђује ПФР-у, а рачун се уписује у базу тек када га ПФР потпише. Одговор 201 враћа цео рачун, укључујући журнал и линк за проверу.
| Поље | Тип | Обавезно | Опис |
|---|---|---|---|
invoiceType | string | не (подразумевано Normal) | Normal (промет), Advance (аванс), ProForma (предрачун), Training (обука), Copy (копија) |
transactionType | string | не (подразумевано Sale) | Sale (продаја) или Refund (рефундација) |
items[] | array | да | 1 до 500 ставки, збир мора бити већи од 0 |
items[].name | string | да | Назив ставке, до 200 знакова |
items[].unit | string | не | Јединица мере, подразумевано ком |
items[].quantity | number | да | Већа од 0 и највише 999.999; заокружује се на 3 децимале (нпр. 0.375) |
items[].unitPrice | number | да | Јединична цена са ПДВ, 0 или више, заокружује се на 2 децимале |
items[].totalAmount | number | не | Износ реда. Сервер га сам израчунава као количина × цена, послата вредност се не користи |
items[].label | string | да | Пореска ознака из шифарника у важности, види GET /tax-labels. Стопе одређује искључиво ПФР |
items[].gtin | string | не | ГТИН или бар-код, 8 до 14 цифара |
items[].itemId | number | не | ИД артикла из шифарника артикала. Само се памти уз ред рачуна као веза ка артиклу; цена и назив се увек узимају из самог захтева |
payments[] | array | не | Начини плаћања. Ако изостане или је празан, цео износ иде као Cash |
payments[].paymentType | string | да | Cash (готовина), Card (платна картица), Check (чек), WireTransfer (пренос на рачун), Voucher (ваучер), MobileMoney (инстант плаћање), Other (друго безготовинско) |
payments[].amount | number | да | Износ. Ставке са износом 0 или мањим се одбацују. Ако после тога остане тачно једно плаћање, његов износ се сам поставља на укупан износ рачуна; ако их је више, збир мора да одговара збиру ставки (толеранција 0,01) |
cashier | string | не | Име касира на рачуну; за АПИ позиве подразумевано API |
buyerId | string | условно | ИД купца, до 64 знака, облик шифра:вредност. Шифарник: 10 ПИБ, 11 ЈМБГ, 12 ПИБ:ЈБКЈС, 20 лична карта, 23 пасош, 30 страни пасош, 40 страни ТИН. Обавезан за сваку рефундацију |
buyerCostCenterId | string | не | Опционо поље купца (место трошка) |
referentDocumentNumber | string | условно | ПФР број оригиналног рачуна. Обавезан за Refund и за Copy |
referentDocumentDT | string | не | Време оригиналног рачуна, YYYY-MM-DD HH:MM:SS |
dateAndTimeOfIssue | string | не | ЕСИР време, YYYY-MM-DD HH:MM:SS. Користи се за аванс када је уплата стигла раније него што се рачун издаје (нпр. пренос на рачун прокњижен сутрадан); штампа се на рачуну као „ЕСИР време“ |
adText | string | не | Рекламни текст, до 500 знакова, штампа се испод краја рачуна. При затварању аванса овде иде број и датум последњег авансног рачуна |
Захтев
curl -X POST https://otkucaj.com/api/v1/invoices \
-H "Authorization: Bearer kasir_vas_kljuc" \
-H "Content-Type: application/json" \
-d '{
"invoiceType": "Normal",
"transactionType": "Sale",
"cashier": "Веб продавница",
"payments": [{ "paymentType": "Card", "amount": 2400.00 }],
"items": [
{ "name": "Мајица", "unit": "ком", "quantity": 2, "unitPrice": 1200.00,
"label": "Ђ", "gtin": "8600000000000" }
]
}'
Одговор 201
{
"uuid": "9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30",
"invoiceType": "Normal",
"transactionType": "Sale",
"cashier": "Веб продавница",
"total": 2400.0,
"payments": [
{ "paymentType": "Card", "amount": 2400.0 }
],
"items": [
{
"name": "Мајица",
"unit": "ком",
"quantity": 2.0,
"unitPrice": 1200.0,
"totalAmount": 2400.0,
"label": "Ђ",
"gtin": "8600000000000"
}
],
"taxItems": [
{ "label": "Ђ", "categoryName": "О-ПДВ", "rate": 20.0, "amount": 400.0, "total": 2400.0 }
],
"buyerId": null,
"referentDocumentNumber": null,
"referentDocumentDT": null,
"pfr": {
"mode": "demo",
"demo": true,
"requestedBy": "DMX7K2QP",
"signedBy": "DMX7K2QP",
"invoiceNumber": "DMX7K2QP-DMX7K2QP-302",
"invoiceCounter": "143/302ПП",
"sdcDateTime": "2026-08-19 14:02:33",
"verificationUrl": "https://otkucaj.com/verify?id=9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30"
},
"journal": "======== ФИСКАЛНИ РАЧУН =======\n...",
"createdAt": "2026-08-19 14:02:33"
}
Овај облик враћају и GET /invoices/{uuid}, сваки елемент листе рачуна, и путање за рефундацију и копију. Значење ПФР поља: invoiceNumber је ПФР број рачуна (облик JID-JID-N), invoiceCounter је бројач (нпр. 143/302ПП: 143. рачун врсте Промет-Продаја од укупно 302), sdcDateTime је ПФР време, verificationUrl је линк ка страници за проверу рачуна, demo каже да ли је рачун издат у демо режиму. taxItems је пореска рекапитулација по ознакама, са стопом, износом пореза и основицом; стопе долазе искључиво од ПФР-а, никада из вашег захтева.
- 400 bad_request
- 401 unauthorized
- 422 validation
- 429 rate_limited
- 502 pfr_error
На 502 рачун није издат и ништа није уписано у базу.
/api/v1/invoices
Листа рачуна, најновији први, до 100 по страни. Сваки елемент је цео рачун, у истом облику као одговор на POST /invoices, заједно са журналом.
| Параметар | Тип | Опис |
|---|---|---|
from | string | Датум од, YYYY-MM-DD (укључује цео дан од 00:00:00) |
to | string | Датум до, YYYY-MM-DD (укључује цео дан до 23:59:59) |
type | string | Врста рачуна: Normal, Advance, ProForma, Training, Copy |
page | number | Страна, од 1 (подразумевано 1), по 100 рачуна |
from и to се овде не проверавају форматом: неисправан датум неће вратити 422, него празну или неочекивану листу. Непозната вредност у type се тихо игнорише и филтер се не примењује. Шаљите датуме у облику YYYY-MM-DD и врсту из горњег списка.Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ "https://otkucaj.com/api/v1/invoices?from=2026-08-01&to=2026-08-19&type=Normal&page=1"
Одговор
{
"page": 1,
"perPage": 100,
"invoices": [
{ "uuid": "9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30", "invoiceType": "Normal", "...": "..." }
]
}
Празна листа значи да за задате филтере нема рачуна. Укупан број рачуна се не враћа: страничите док не добијете мање од 100 елемената.
- 401 unauthorized
- 429 rate_limited
/api/v1/invoices/{uuid}
Један рачун по UUID-у, у пуном облику описаном код POST /invoices.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30
Одговор
{
"uuid": "9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30",
"invoiceType": "Normal",
"transactionType": "Sale",
"total": 2400.0,
"pfr": { "invoiceNumber": "DMX7K2QP-DMX7K2QP-302", "...": "..." },
"journal": "======== ФИСКАЛНИ РАЧУН =======\n...",
"createdAt": "2026-08-19 14:02:33"
}
UUID који не постоји враћа 404 са кодом not_found. UUID исписан великим словима не одговара облику путање и завршава као непозната путања, такође 404: шаљите га тачно онако како вам га је АПИ вратио.
- 401 unauthorized
- 404 not_found
- 429 rate_limited
/api/v1/invoices/{uuid}/journal
Журнал рачуна као чист текст (text/plain; charset=utf-8), 40 колона, ћирилицом, спреман за термални штампач од 58 или 80 mm. Идентичан журналу који апликација сама штампа. Ово је једини одговор који није JSON.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30/journal
Одговор, скраћено
======== ФИСКАЛНИ РАЧУН =======
106884584
Пример д.о.о.
Продавница 1
Булевар ослобођења 1
Београд
Касир: Веб продавница
ЕСИР број: у поступку одобравања/1.0
------ ПРОМЕТ - ПРОДАЈА -------
Артикли
========================================
Назив Цена Кол. Укупно
Мајица /ком (Ђ)
1.200,00 2 2.400,00
----------------------------------------
За уплату: 2.400,00
Платна картица: 2.400,00
========================================
Ознака Име Стопа Порез
Ђ О-ПДВ 20,00% 400,00
----------------------------------------
Укупан износ пореза: 400,00
========================================
ПФР време: 19.08.2026. 14:02:33
ПФР број рачуна: DMX7K2QP-DMX7K2QP-302
Бројач рачуна: 143/302ПП
======== КРАЈ ФИСКАЛНОГ РАЧУНА ========
Нефискалне врсте (Копија, Предрачун, Обука) уместо заглавља и краја фискалног рачуна носе линију „ОВО НИЈЕ ФИСКАЛНИ РАЧУН“, коју апликација при штампи увећава на двоструку величину фонта. Копија-Рефундација додатно штампа простор „Потпис купца“ за потпис при враћању новца.
- 401 unauthorized
- 404 not_found
- 429 rate_limited
/api/v1/invoices/{uuid}/refund
Рефундација целог постојећег рачуна, једним позивом. Ставке се преузимају са оригинала, референтни број и време показују на оригинал, а нов рачун се издаје кроз исти пут као и сваки други. Ово је исто што ради дугме Рефундација у апликацији.
Normal или Advance, врсте трансакције Sale и у статусу издат. Копија, предрачун, обука и већ издата рефундација се одбијају са 422. Ово су исти услови под којима се дугме приказује у апликацији.| Поље тела | Тип | Подразумевано | Опис |
|---|---|---|---|
buyerId | string | купац са оригинала | Идентификација купца. Обавезна је за рефундацију: ако је нема ни у телу ни на оригиналу, одговор је 422 са поруком која објашњава да за поништавање сопственог рачуна треба унети свој ПИБ |
buyerCostCenterId | string | са оригинала | Место трошка купца |
payments | array | плаћања са оригинала | Начини враћања новца. Збир мора да одговара укупном износу рачуна |
cashier | string | API | Име касира на рачуну рефундације |
adText | string | празно | Рекламни текст, до 500 знакова |
Ставке се увек узимају са оригиналног рачуна, у целости, и не могу се задати телом захтева. За делимичну рефундацију (враћа се само део ставки или мања количина) користите POST /invoices са transactionType: "Refund", где сами наводите шта се враћа.
Захтев
curl -X POST \
https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30/refund \
-H "Authorization: Bearer kasir_vas_kljuc" \
-H "Content-Type: application/json" \
-d '{ "buyerId": "10:123456789", "cashier": "Веб продавница" }'
Одговор 201
{
"uuid": "1c77a0d5-9b12-4f8e-b3aa-0d5e7c9f4411",
"invoiceType": "Normal",
"transactionType": "Refund",
"cashier": "Веб продавница",
"total": 2400.0,
"buyerId": "10:123456789",
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-302",
"referentDocumentDT": "2026-08-19 14:02:33",
"pfr": {
"invoiceNumber": "DMX7K2QP-DMX7K2QP-318",
"invoiceCounter": "12/318РП",
"...": "..."
},
"journal": "======== ФИСКАЛНИ РАЧУН =======\n...",
"createdAt": "2026-08-19 16:41:02"
}
- 400 bad_request
- 401 unauthorized
- 404 not_found
- 422 validation
- 429 rate_limited
- 502 pfr_error
/api/v1/invoices/{uuid}/copy
Издавање копије постојећег рачуна, кроз исти пут који користи дугме Копија у апликацији. Копија носи све ставке и плаћања оригинала, референтни број и време оригинала, и његову идентификацију купца.
422, исто као у апликацији.Захтев
curl -X POST \ https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30/copy \ -H "Authorization: Bearer kasir_vas_kljuc"
Одговор 201
{
"uuid": "6d4b1f92-2a70-4c31-8f5e-7b1c0a93de84",
"invoiceType": "Copy",
"transactionType": "Sale",
"total": 2400.0,
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-302",
"referentDocumentDT": "2026-08-19 14:02:33",
"pfr": { "invoiceNumber": "DMX7K2QP-DMX7K2QP-319", "...": "..." },
"journal": "ОВО НИЈЕ ФИСКАЛНИ РАЧУН\n...",
"createdAt": "2026-08-19 16:43:20"
}
Копија није фискални рачун: у журналу нема заглавља фискалног рачуна, него линија „ОВО НИЈЕ ФИСКАЛНИ РАЧУН“. Копија рефундације додатно носи простор за потпис купца.
- 401 unauthorized
- 404 not_found
- 422 validation
- 429 rate_limited
- 502 pfr_error
/api/v1/invoices/{uuid}/email
Слање рачуна купцу е-поштом. Порука садржи журнал и линк за проверу рачуна, и потпуно је иста као она коју шаље дугме у апликацији, јер обе иду кроз исти код. Слање се уписује у ревизиони траг као invoice.email.
| Поље тела | Тип | Обавезно | Опис |
|---|---|---|---|
to | string | да | Адреса примаоца, исправна е-адреса, највише 190 знакова |
Захтев
curl -X POST \
https://otkucaj.com/api/v1/invoices/9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30/email \
-H "Authorization: Bearer kasir_vas_kljuc" \
-H "Content-Type: application/json" \
-d '{ "to": "[email protected]" }'
Одговор
{
"ok": true,
"uuid": "9b2f6c1e-4d3a-4f0b-9a77-2c8e5d1f6a30",
"to": "[email protected]"
}
Ограничење слања је 30 порука на сат по кључу, независно од општег ограничења од 240 захтева у минуту. Прекорачење враћа 429 са поруком о ограничењу поште. Ако сервер поште одбије поруку, одговор је 502 са кодом mail_error: то није ни грешка ПФР-а ни грешка у захтеву, него неуспело слање, и захтев се сме поновити касније.
- 400 bad_request
- 401 unauthorized
- 404 not_found
- 422 validation
- 429 rate_limited
- 502 mail_error
Токови: продаја, рефундација, копија, предрачун
Сваки законом прописани ток, са пуним телом захтева. Одговор је увек исти облик рачуна као код POST /invoices.
Промет-продаја, са подељеним плаћањем
Најчешћи рачун. На једном рачуну сме више начина плаћања; збир мора одговарати збиру ставки.
{
"invoiceType": "Normal",
"transactionType": "Sale",
"cashier": "Веб продавница",
"payments": [
{ "paymentType": "Cash", "amount": 1000.00 },
{ "paymentType": "Card", "amount": 1400.00 }
],
"items": [
{ "name": "Мајица", "quantity": 2, "unitPrice": 1200.00, "label": "Ђ" }
]
}
Рефундација
Ако враћате цео рачун, најједноставније је позвати POST /invoices/{uuid}/refund. Ручна рефундација кроз POST /invoices мора да носи referentDocumentNumber (ПФР број оригиналног рачуна) и buyerId (идентификацију купца). Без било ког од та два поља захтев се одбија са 422. Ставке и износи су они који се враћају, дакле могу бити и део оригиналног рачуна.
{
"invoiceType": "Normal",
"transactionType": "Refund",
"cashier": "Веб продавница",
"buyerId": "10:123456789",
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-302",
"referentDocumentDT": "2026-08-19 14:02:33",
"payments": [{ "paymentType": "Card", "amount": 1200.00 }],
"items": [
{ "name": "Мајица", "quantity": 1, "unitPrice": 1200.00, "label": "Ђ" }
]
}
Рефундације умањују промет у извештајима. Купцу који прима готовину издаје се и Копија-Рефундација са простором за потпис.
Поништавање сопственог рачуна
Погрешно издат рачун се поништава рефундацијом целог износа, при чему продавац као идентификацију купца уноси свој ПИБ, у облику 10:СВОЈПИБ.
{
"invoiceType": "Normal",
"transactionType": "Refund",
"buyerId": "10:106884584",
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-302",
"referentDocumentDT": "2026-08-19 14:02:33",
"payments": [{ "paymentType": "Card", "amount": 2400.00 }],
"items": [{ "name": "Мајица", "quantity": 2, "unitPrice": 1200.00, "label": "Ђ" }]
}
Копија
Копија постојећег рачуна се најлакше издаје кроз POST /invoices/{uuid}/copy. Ручно, кроз POST /invoices, копија мора да носи referentDocumentNumber, иначе 422.
{
"invoiceType": "Copy",
"transactionType": "Sale",
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-302",
"referentDocumentDT": "2026-08-19 14:02:33",
"payments": [{ "paymentType": "Card", "amount": 2400.00 }],
"items": [{ "name": "Мајица", "quantity": 2, "unitPrice": 1200.00, "label": "Ђ" }]
}
Предрачун, па фискални рачун
Предрачун (ProForma) није фискални рачун: служи као понуда. Када купац плати, издаје се прави Промет-Продаја са референцом на предрачун.
Корак 1, предрачун:
{
"invoiceType": "ProForma",
"transactionType": "Sale",
"payments": [{ "paymentType": "WireTransfer", "amount": 12000.00 }],
"items": [{ "name": "Услуга одржавања", "unit": "мес", "quantity": 1, "unitPrice": 12000.00, "label": "Ђ" }]
}
Корак 2, по уплати, фискални рачун са референцом на предрачун:
{
"invoiceType": "Normal",
"transactionType": "Sale",
"referentDocumentNumber": "DMX7K2QP-DMX7K2QP-310",
"referentDocumentDT": "2026-08-19 09:15:00",
"payments": [{ "paymentType": "WireTransfer", "amount": 12000.00 }],
"items": [{ "name": "Услуга одржавања", "unit": "мес", "quantity": 1, "unitPrice": 12000.00, "label": "Ђ" }]
}
У самој апликацији дугме „Издај фискални рачун“ на предрачуну ради тачно ово: препуни касу и сама постави референцу.
Авансни ток, од прве уплате до коначног рачуна
Авансна ставка носи прописан назив по пореској ознаци: 10: Аванс (Ђ), 11: Аванс (Е), 12: Аванс (Г) или 13: Аванс (А). Количина је увек 1, а цена је износ примљеног аванса. Ток има три врсте докумената: авансни рачун (АП) за сваку уплату, аванс-рефундација (АР) која затвара збир свих аванса, и коначни Промет-Продаја.
-
Први авансни рачун (АП). Уплата од 5.000,00 стигла преносом на рачун 18.08, а књижи се 19.08: зато
dateAndTimeOfIssueноси стварно време уплате (штампа се као „ЕСИР време“).{ "invoiceType": "Advance", "transactionType": "Sale", "dateAndTimeOfIssue": "2026-08-18 11:30:00", "payments": [{ "paymentType": "WireTransfer", "amount": 5000.00 }], "items": [{ "name": "10: Аванс (Ђ)", "quantity": 1, "unitPrice": 5000.00, "label": "Ђ" }] }ПФР врати нпр.
invoiceNumber: "DMX7K2QP-DMX7K2QP-311". -
Додатни авансни рачуни за сваку следећу уплату, сваки са референцом на претходни АП:
{ "invoiceType": "Advance", "transactionType": "Sale", "referentDocumentNumber": "DMX7K2QP-DMX7K2QP-311", "referentDocumentDT": "2026-08-18 11:30:00", "payments": [{ "paymentType": "WireTransfer", "amount": 3000.00 }], "items": [{ "name": "10: Аванс (Ђ)", "quantity": 1, "unitPrice": 3000.00, "label": "Ђ" }] }ПФР врати нпр.
invoiceNumber: "DMX7K2QP-DMX7K2QP-312". -
Затварање: аванс-рефундација (АР) на цео примљени износ, са референцом на последњи АП и обавезним
buyerId, јер је реч о рефундацији:{ "invoiceType": "Advance", "transactionType": "Refund", "buyerId": "10:123456789", "referentDocumentNumber": "DMX7K2QP-DMX7K2QP-312", "referentDocumentDT": "2026-08-19 10:00:00", "payments": [{ "paymentType": "WireTransfer", "amount": 8000.00 }], "items": [{ "name": "10: Аванс (Ђ)", "quantity": 1, "unitPrice": 8000.00, "label": "Ђ" }] }ПФР врати нпр.
invoiceNumber: "DMX7K2QP-DMX7K2QP-313". -
Коначни Промет-Продаја са стварним ставкама испоруке, референцом на АР и рекламним текстом који носи број и датум последњег авансног рачуна:
{ "invoiceType": "Normal", "transactionType": "Sale", "referentDocumentNumber": "DMX7K2QP-DMX7K2QP-313", "referentDocumentDT": "2026-08-19 10:05:00", "adText": "Аванс: DMX7K2QP-DMX7K2QP-312 од 19.08.2026.", "payments": [{ "paymentType": "WireTransfer", "amount": 8000.00 }], "items": [{ "name": "Ормар по мери", "quantity": 1, "unitPrice": 8000.00, "label": "Ђ" }] }
У апликацији дугме „Затвори аванс“ на авансном рачуну само одради кораке 3 и 4: издаје АР на цео износ и препуни касу за коначни рачун са постављеном референцом и рекламним текстом.
Режим по чл. 6 ст. 2 Правилника
Инсталација може да ради у режиму у ком су начини плаћања Card, Check и MobileMoney искључени, а такве наплате се евидентирају као готовина. Једна инсталација ради у тачно једном режиму, који бира администратор у Подешавањима. Када је режим укључен, захтев са неким од искључених начина враћа:
422 { "error": { "code": "validation",
"message": "Начин плаћања „Card“ је искључен подешавањем (режим по чл. 6 ст. 2 Правилника). Унесите као готовину." } }
Интеграција тада шаље {"paymentType": "Cash"} за те наплате.
Артикли
Шифарник артикала који каса нуди на додир. Артикли нису услов за издавање рачуна: POST /invoices прима ставке слободно, са називом и ценом из вашег система.
/api/v1/items
Листа артикала, сортирано по називу, до 1.000 комада. Поље active разликује активне од деактивираних; деактивирани су такође у одговору.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/items
Одговор
{
"items": [
{
"id": 12,
"plu": "1001",
"name": "Мајица",
"unit": "ком",
"price": 1200.0,
"label": "Ђ",
"gtin": "8600000000000",
"active": true
}
]
}
- 401 unauthorized
- 429 rate_limited
/api/v1/items
Додавање артикла у шифарник.
| Поље | Тип | Обавезно | Опис |
|---|---|---|---|
name | string | да | Назив, до 200 знакова |
label | string | да | Пореска ознака из шифарника у важности (GET /tax-labels). Непозната ознака враћа 422 са списком дозвољених |
price | number | не (подразумевано 0) | Цена са ПДВ, 0 или више, заокружује се на 2 децимале |
plu | string | не | Шифра артикла за брзо куцање |
unit | string | не | Јединица мере, подразумевано ком |
gtin | string | не | ГТИН или бар-код, 8 до 14 цифара |
Нов артикал се увек уписује као активан. Поље category се овим позивом не поставља.
Захтев
curl -X POST https://otkucaj.com/api/v1/items \
-H "Authorization: Bearer kasir_vas_kljuc" \
-H "Content-Type: application/json" \
-d '{ "name": "Мајица", "price": 1200.00, "label": "Ђ",
"plu": "1001", "gtin": "8600000000000", "unit": "ком" }'
Одговор 201
{ "id": 12, "ok": true }
- 400 bad_request
- 401 unauthorized
- 422 validation
- 429 rate_limited
/api/v1/items/{id}
Делимична измена артикла: шаљете само поља која мењате, остала остају нетакнута. Оба метода, PUT и PATCH, раде исто.
label уписује онаква каква је послата, без провере да ли таква пореска ознака постоји, за разлику од додавања артикла где се проверава. Артикал са ознаком које нема у шифарнику ће при издавању рачуна произвести 422. Шаљите само ознаке које враћа GET /tax-labels.Захтев
curl -X PUT https://otkucaj.com/api/v1/items/12 \
-H "Authorization: Bearer kasir_vas_kljuc" \
-H "Content-Type: application/json" \
-d '{ "price": 1350.00, "active": true }'
Одговор
{ "ok": true }
- 400 bad_request
- 401 unauthorized
- 404 not_found
- 429 rate_limited
/api/v1/items/{id}
Мека деактивација: артикал се не брише из базе, јер га постојећи рачуни и даље референцирају. Добија active: false и нестаје са касе. Поновно активирање је PUT са {"active": true}.
Захтев
curl -X DELETE https://otkucaj.com/api/v1/items/12 \ -H "Authorization: Bearer kasir_vas_kljuc"
Одговор
{ "ok": true }
{"ok": true}, јер се ништа не мења и ништа не пуца. Ако вам је важно да ли артикал постоји, проверите га претходно кроз GET /items.- 401 unauthorized
- 429 rate_limited
Извештаји
Оба извештаја рачунају промет по истом правилу и истим упитима као страница Извештаји у апликацији, па се бројке не могу разићи.
Normal и Advance у статусу издат. Рефундације се одузимају. Предрачун, копија и обука не улазе ни у један збир. Исто правило враћа и сам одговор, у пољу rule./api/v1/reports/summary
Промет за период, са разрадом по пореској ознаци, начину плаћања и касиру.
| Параметар | Тип | Подразумевано | Опис |
|---|---|---|---|
from | string | данас | Датум од, строго YYYY-MM-DD, укључује цео дан |
to | string | данас | Датум до, строго YYYY-MM-DD, укључује цео дан. Не сме бити ранији од from |
Неисправан облик датума или период у ком је from касније од to враћају 422, за разлику од листе рачуна где се датуми не проверавају.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ "https://otkucaj.com/api/v1/reports/summary?from=2026-08-01&to=2026-08-19"
Одговор
{
"from": "2026-08-01",
"to": "2026-08-19",
"totals": {
"invoices": 142,
"turnover": 386400.0,
"refunds": { "count": 3, "amount": 7200.0 }
},
"byTaxLabel": [
{ "label": "Ђ", "name": "О-ПДВ", "rate": 20.0, "turnover": 351200.0 },
{ "label": "Е", "name": "П-ПДВ", "rate": 10.0, "turnover": 35200.0 }
],
"byPaymentType": [
{ "paymentType": "Card", "amount": 244800.0 },
{ "paymentType": "Cash", "amount": 141600.0 }
],
"byCashier": [
{ "cashier": "Веб продавница", "invoices": 118, "turnover": 302400.0 },
{ "cashier": "Марија", "invoices": 24, "turnover": 84000.0 }
],
"rule": "Turnover counts issued Normal and Advance receipts only; refunds subtract; ProForma, Copy and Training are excluded."
}
Значење поља: totals.invoices је број рачуна у периоду, укључујући и рефундације; totals.turnover је промет умањен за рефундације; totals.refunds посебно приказује број и укупан износ рефундација, као позитиван број. У byTaxLabel су name и rate вредности null ако ознака више не постоји у шифарнику, а рачуни са њом су остали у бази. Начини плаћања се сабирају из самог рачуна, па за подељена плаћања сваки део улази у свој ред.
- 401 unauthorized
- 422 validation
- 429 rate_limited
/api/v1/reports/daily
Промет и број рачуна по данима, за исти период и по истом правилу као сабирни извештај.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ "https://otkucaj.com/api/v1/reports/daily?from=2026-08-01&to=2026-08-05"
Одговор
{
"from": "2026-08-01",
"to": "2026-08-05",
"days": [
{ "date": "2026-08-01", "invoices": 12, "turnover": 28800.0 },
{ "date": "2026-08-02", "invoices": 9, "turnover": 19400.0 },
{ "date": "2026-08-05", "invoices": 15, "turnover": 41250.0 }
],
"rule": "Turnover counts issued Normal and Advance receipts only; refunds subtract; ProForma, Copy and Training are excluded."
}
Дани без промета се изостављају, тачно као у табели у апликацији. Ако вам треба непрекидан низ датума, попуните празнине на својој страни.
- 401 unauthorized
- 422 validation
- 429 rate_limited
Шифарници
Две листе које интеграцији требају да би слала исправне вредности, обе из истог извора који апликација и сама користи.
/api/v1/tax-labels
Пореске ознаке и стопе које су тренутно на снази, из истог извора који користи и валидатор рачуна (само активни редови). Ознака коју врати овај позив је ознака коју ће POST /invoices прихватити.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/tax-labels
Одговор
{
"taxLabels": [
{ "label": "Ђ", "name": "О-ПДВ", "rate": 20.0 },
{ "label": "Е", "name": "П-ПДВ", "rate": 10.0 },
{ "label": "Г", "name": "Без ПДВ", "rate": 0.0 }
]
}
POST /invoices подразумевано не прихвата. Ознаке и стопе се мењају у Подешавањима, а у режиму Л-ПФР или В-ПФР меродаван је списак који даје сам ПФР. Зато читајте овај позив, а не преписујте ознаке у свој код.- 401 unauthorized
- 429 rate_limited
/api/v1/categories
Категорије активних артикала, без празних вредности, сортиране азбучно. Исти упит који каса користи за дугмад категорија.
Захтев
curl -H "Authorization: Bearer kasir_vas_kljuc" \ https://otkucaj.com/api/v1/categories
Одговор
{ "categories": ["Пиће", "Слаткиши", "Храна"] }
Категорије се уносе уз артикал у апликацији. Артикли без категорије не праве празан унос у овој листи.
- 401 unauthorized
- 429 rate_limited
Грешке
Свака грешка има исти JSON облик, без обзира на путању:
{ "error": { "code": "validation", "message": "Рефундација мора имати референтни број оригиналног рачуна (referentDocumentNumber)." } }
| HTTP | code | Узрок | Шта урадити |
|---|---|---|---|
| 400 | bad_request | Тело захтева није исправан JSON | Проверите серијализацију и Content-Type: application/json |
| 401 | unauthorized | Нема Authorization заглавља, кључ је погрешан или опозван | Проверите кључ; по потреби генеришите нов у Подешавањима |
| 404 | not_found | Непозната путања, непостојећи UUID рачуна или ИД артикла при измени | Проверите путању и идентификатор |
| 422 | validation | Захтев не пролази валидацију; порука прецизно каже које поље и зашто | Исправите захтев. Не понављајте исти захтев непромењен: резултат ће увек бити исти |
| 429 | rate_limited | Више од 240 захтева у минуту по кључу, или више од 30 послатих порука на сат | Сачекајте до истека прозора, па наставите смањеним темпом |
| 502 | pfr_error | ПФР је одбио захтев или није доступан | Рачун НИЈЕ издат, ништа није уписано. Проверите GET /status, па поновите исти захтев |
| 502 | mail_error | Порука са рачуном није могла да буде послата | Рачун постоји и није дирнут. Слање се сме поновити касније |
WooCommerce и интеграције
Готов додатак повезује WooCommerce продавницу без иједне линије кода: otkucaj-fiskalizacija.zip.
- У WordPress администрацији отворите Додаци · Додај нови · Отпреми додатак, изаберите преузети zip и активирајте додатак.
- У Откуцају (Подешавања · АПИ кључеви) генеришите кључ и одмах га ископирајте: приказује се само једном.
- Отворите Подешавања · Откуцај фискализација у WordPress-у и унесите: адресу (
https://otkucaj.com), АПИ кључ, статус поруџбине који окида издавање рачуна (нпр. „Завршено“ или „У обради“) и мапирање WooCommerce начина плаћања на фискалне (нпр. картице наCard, поузеће наCash, вирман наWireTransfer). - Готово. Када поруџбина пређе у изабрани статус, додатак издаје рачун, тачно једном по поруџбини, и у саму поруџбину уписује ПФР број рачуна и линк за проверу, видљиве и вама и купцу.
Пореска ознака по производу: додатку се за појединачни производ може задати мета поље otkucaj_label (вредност из GET /tax-labels); производи без њега користе подразумевану ознаку из подешавања додатка.
Пример: чист PHP
function otkucaj_izdaj_racun(string $apiKey, array $body): array
{
$ch = curl_init('https://otkucaj.com/api/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json; charset=utf-8',
],
CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE),
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
$http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($raw === false) {
throw new RuntimeException("Мрежна грешка: $err"); // рачун можда јесте издат, проверити пре понављања
}
$json = json_decode($raw, true);
if ($http === 201) {
return $json; // цео рачун: uuid, pfr, journal...
}
$code = $json['error']['code'] ?? 'unknown';
$msg = $json['error']['message'] ?? $raw;
if ($http === 422) {
throw new InvalidArgumentException("Неисправан захтев ($code): $msg"); // не понављати
}
throw new RuntimeException("Откуцај HTTP $http ($code): $msg"); // 429/502: поновити касније
}
$racun = otkucaj_izdaj_racun($apiKey, [
'invoiceType' => 'Normal',
'transactionType' => 'Sale',
'cashier' => 'Веб продавница',
'payments' => [['paymentType' => 'Card', 'amount' => 2400.00]],
'items' => [['name' => 'Мајица', 'quantity' => 2, 'unitPrice' => 1200.00, 'label' => 'Ђ']],
]);
// сачувати уз поруџбину:
// $racun['uuid'], $racun['pfr']['invoiceNumber'], $racun['pfr']['verificationUrl']
Пример: Node.js
async function izdajRacun(apiKey, body) {
const res = await fetch('https://otkucaj.com/api/v1/invoices', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(30000),
});
const json = await res.json();
if (res.status === 201) return json;
const { code, message } = json.error ?? {};
const e = new Error(`Откуцај HTTP ${res.status} (${code}): ${message}`);
e.retryable = res.status === 429 || res.status === 502; // 422 се никад не понавља
throw e;
}
const racun = await izdajRacun(process.env.OTKUCAJ_KEY, {
invoiceType: 'Normal',
transactionType: 'Sale',
cashier: 'Веб продавница',
payments: [{ paymentType: 'Card', amount: 2400.00 }],
items: [{ name: 'Мајица', quantity: 2, unitPrice: 1200.00, label: 'Ђ' }],
});
console.log(racun.pfr.invoiceNumber, racun.pfr.verificationUrl);
Пример: Python
import requests
def izdaj_racun(api_key: str, body: dict) -> dict:
r = requests.post(
'https://otkucaj.com/api/v1/invoices',
json=body,
headers={'Authorization': f'Bearer {api_key}'},
timeout=30,
)
if r.status_code == 201:
return r.json()
err = r.json().get('error', {})
if r.status_code == 422:
raise ValueError(f"Неисправан захтев: {err.get('message')}") # не понављати
raise RuntimeError(f"Откуцај HTTP {r.status_code} ({err.get('code')}): {err.get('message')}")
racun = izdaj_racun(API_KEY, {
'invoiceType': 'Normal',
'transactionType': 'Sale',
'cashier': 'Веб продавница',
'payments': [{'paymentType': 'Card', 'amount': 2400.00}],
'items': [{'name': 'Мајица', 'quantity': 2, 'unitPrice': 1200.00, 'label': 'Ђ'}],
})
print(racun['pfr']['invoiceNumber'], racun['pfr']['verificationUrl'])
Добра пракса
- Издајте рачун тек по наплати. Фискални рачун прати новац; поруџбина која није плаћена нема шта да фискализује.
- Чувајте
uuidиpfr.invoiceNumberуз поруџбину. Без њих не можете касније да издате копију, рефундацију, да пошаљете рачун е-поштом нити да преузмете журнал. - Читајте шифарнике, не преписујте их. Пореске ознаке узмите са GET /tax-labels при подешавању интеграције, јер се списак разликује од инсталације до инсталације.
- Поставите временско ограничење од 30 секунди, да позив ка АПИ-ју не блокира вашу апликацију унедоглед.
- Никада не позивајте АПИ из прегледача купца ни из мобилне апликације: кључ би био јаван. Сав саобраћај иде са вашег сервера.
- Не шаљите дупле захтеве. Уз сваку поруџбину чувајте свој идемпотентни кључ (нпр. број поруџбине) и пре слања проверите да ли је за њу рачун већ издат. Мрежни прекид после успешног издавања је најчешћи узрок дуплог рачуна: пре понављања проверите GET /invoices за тај дан.
- Развијте и тестирајте у демо режиму. Прелазак на Л-ПФР или В-ПФР је измена подешавања у Откуцају, без иједне измене у вашем коду.
Промене
| Верзија | Новине |
|---|---|
| 1.3 | Одвојена окружења: тест и продукција, свако са својим безбедносним елементом, ПАК-ом и ЕСИР бројем. Рачун носи ознаку окружења, а листе и извештаји приказују само активно. Нова поља: environment и fiscal у GET /status и у сваком рачуну, verificationQRCode (QR који је нацртао сам ПФР), clientRequestId. Идемпотентност: заглавље Idempotency-Key или поље clientRequestId спречава двоструко издавање после прекида везе. CORS је укључен, па се АПИ може звати и из прегледача. buyerId се проверава по облику ознака:вредност, највише 20 знакова. |
| 1.2 | Проширен АПИ: GET /me, GET /tax-labels, GET /categories, POST /invoices/{uuid}/refund, POST /invoices/{uuid}/copy, POST /invoices/{uuid}/email, GET /reports/summary и GET /reports/daily. У самој апликацији: регистрација налога са одобравањем, Cloudflare Turnstile заштита пријаве, Телеграм обавештења о издатим рачунима, нова контролна табла, колапсибилни сајдбар, категорије артикала, попусти по ставци и на цео рачун, конверзија предрачуна у фискални рачун, аутоматско затварање аванса, слање рачуна е-поштом. |
| 1.1 | Офлајн ред на каси (рачун чека у локалном реду и издаје се по повратку везе), подељена плаћања на једном рачуну, WooCommerce додатак. |
| 1.0 | Почетна верзија: издавање рачуна, листа и детаљ рачуна, журнал, артикли, АПИ кључеви. |
Уговор АПИ-ја v1 је стабилан кроз све наведене верзије апликације: додаване су нове могућности, постојећа поља се нису мењала.
Питања и помоћ при интеграцији: [email protected]. Упутство за рад у самој апликацији: Упутство (PDF).