Error Codes Catalog
Bilko Error Codes Catalog
Project: Bilko
Version: 1.0
Date: 2026-02-24
Status: Specification
Applies to: apps/api/ — all modules
Overview
All Bilko API errors follow a consistent JSON structure. Every error response includes:
- An HTTP status code
- A machine-readable
BILKO-XXXX error code
- A human-readable message (in the organization's configured language)
- Optional field-level details for validation errors
Error code ranges by module:
| Range |
Module |
BILKO-1xxx |
Authentication |
BILKO-2xxx |
Organizations |
BILKO-3xxx |
Invoices |
BILKO-4xxx |
Expenses |
BILKO-5xxx |
Banking |
BILKO-6xxx |
Reports |
BILKO-7xxx |
Contacts |
BILKO-8xxx |
Settings & Accounts |
BILKO-9xxx |
General / Cross-cutting |
Error Response Schema
All error responses use this structure:
interface ErrorResponse {
error: {
code: string // e.g., "BILKO-1001"
message: string // Human-readable, localized
details?: Record<string, string[]> // Field-level validation errors (422 only)
requestId?: string // Trace ID for support (production)
}
}
Examples:
// Authentication error
{
"error": {
"code": "BILKO-1001",
"message": "Pogrešan email ili lozinka.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
// Validation error (422) with field details
{
"error": {
"code": "BILKO-9003",
"message": "Validacija nije uspjela.",
"details": {
"email": ["Email adresa nije ispravna."],
"password": ["Lozinka mora imati najmanje 8 znakova.", "Lozinka mora sadržavati barem jedan broj."]
},
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
HTTP Status Codes
| HTTP Code |
Meaning |
When Used |
200 OK |
Successful read or update |
GET, PUT, PATCH |
201 Created |
Resource successfully created |
POST (creates new record) |
204 No Content |
Successful deletion |
DELETE, POST /logout |
400 Bad Request |
Request is well-formed but semantically invalid |
Business rule violations (not validation) |
401 Unauthorized |
Missing, expired, or invalid authentication |
No token, expired JWT, wrong credentials |
403 Forbidden |
Authenticated but insufficient permissions |
Role lacks access to endpoint or action |
404 Not Found |
Resource does not exist within organization scope |
Record not found or belongs to different org |
409 Conflict |
Resource already exists |
Duplicate email, duplicate invoice number |
413 Payload Too Large |
Upload exceeds size limit |
File uploads over 10MB (receipt) or 5MB (CSV) |
422 Unprocessable Entity |
Zod schema validation failed |
Invalid field types, missing required fields |
429 Too Many Requests |
Rate limit exceeded |
Auth: 5/min; writes: 10–50/min; reads: 100/min |
500 Internal Server Error |
Unexpected server-side error |
Unhandled exception, DB error |
503 Service Unavailable |
External dependency unavailable |
SendGrid, ECB API, Cloudflare R2 down |
Retry Guidance
| Error Code |
HTTP |
Retry? |
Strategy |
BILKO-1003 |
401 |
Yes |
Refresh access token via POST /auth/refresh, then retry |
BILKO-9005 |
429 |
Yes |
Wait until Retry-After header value (seconds), then retry |
BILKO-9006 |
500 |
Yes |
Exponential backoff: 1s, 2s, 4s — max 3 retries |
BILKO-9007 |
503 |
Yes |
Exponential backoff: 2s, 5s, 10s — max 3 retries |
All 4xx except above |
— |
No |
Fix request before retrying — these are client errors |
BILKO-1001 |
401 |
No |
Wrong credentials — do not retry automatically |
BILKO-3011 |
500 |
Yes |
SendGrid transient failure — retry once after 5s |
Module: Authentication (1xxx)
BILKO-1001 — Invalid Credentials
| Field |
Value |
| HTTP |
401 |
| Trigger |
POST /auth/login — email not found or password does not match bcrypt hash |
| Retry |
No |
{
"error": {
"code": "BILKO-1001",
"message": "Pogrešan email ili lozinka."
}
}
BILKO-1002 — Account Disabled
| Field |
Value |
| HTTP |
403 |
| Trigger |
POST /auth/login — user has isActive = false |
| Retry |
No — contact support |
{
"error": {
"code": "BILKO-1002",
"message": "Vaš nalog je deaktiviran. Kontaktirajte podršku."
}
}
BILKO-1003 — Access Token Expired
| Field |
Value |
| HTTP |
401 |
| Trigger |
Any authenticated request — JWT exp claim is in the past |
| Retry |
Yes — refresh token first via POST /auth/refresh, then retry original request |
{
"error": {
"code": "BILKO-1003",
"message": "Sesija je istekla. Osvježite token."
}
}
BILKO-1004 — Invalid Token
| Field |
Value |
| HTTP |
401 |
| Trigger |
Any authenticated request — JWT signature invalid or malformed |
| Retry |
No — re-authenticate |
{
"error": {
"code": "BILKO-1004",
"message": "Token nije ispravan. Molimo prijavite se ponovo."
}
}
BILKO-1005 — No Authentication Token
| Field |
Value |
| HTTP |
401 |
| Trigger |
Any authenticated endpoint called without Authorization: Bearer <token> header |
| Retry |
No — add token |
{
"error": {
"code": "BILKO-1005",
"message": "Autentifikacija je obavezna."
}
}
BILKO-1006 — Refresh Token Invalid or Expired
| Field |
Value |
| HTTP |
401 |
| Trigger |
POST /auth/refresh — cookie missing, token blacklisted, or expired |
| Retry |
No — force full re-login |
{
"error": {
"code": "BILKO-1006",
"message": "Sesija je istekla. Molimo prijavite se ponovo."
}
}
BILKO-1007 — Auth Rate Limit Exceeded
| Field |
Value |
| HTTP |
429 |
| Trigger |
POST /auth/login or POST /auth/register — 5+ requests in 60 seconds from same IP |
| Retry |
Yes — after Retry-After header value (900 seconds / 15 min lockout) |
{
"error": {
"code": "BILKO-1007",
"message": "Previše pokušaja prijave. Pokušajte ponovo za 15 minuta."
}
}
Retry-After: 900
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1740399600
BILKO-1008 — Email Already Registered
| Field |
Value |
| HTTP |
409 |
| Trigger |
POST /auth/register — email already exists in users table |
| Retry |
No — use different email or reset password |
{
"error": {
"code": "BILKO-1008",
"message": "Email adresa je već registrirana."
}
}
BILKO-1009 — Weak Password
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /auth/register or PUT /auth/password — password fails strength requirements |
| Retry |
No — fix password |
{
"error": {
"code": "BILKO-1009",
"message": "Lozinka ne zadovoljava sigurnosne zahtjeve.",
"details": {
"password": [
"Lozinka mora imati najmanje 8 znakova.",
"Lozinka mora sadržavati barem jedno veliko slovo.",
"Lozinka mora sadržavati barem jedan broj."
]
}
}
}
BILKO-1010 — Two-Factor Authentication Required
| Field |
Value |
| HTTP |
403 |
| Trigger |
POST /auth/login — user has twoFactorEnabled = true, 2FA code not provided |
| Retry |
No — submit TOTP code via POST /auth/verify-2fa |
{
"error": {
"code": "BILKO-1010",
"message": "Potrebna je dvofaktorska autentifikacija.",
"details": {
"requiresTwoFactor": ["true"]
}
}
}
BILKO-1011 — Invalid 2FA Code
| Field |
Value |
| HTTP |
401 |
| Trigger |
POST /auth/verify-2fa — TOTP code incorrect or expired (outside 30s window) |
| Retry |
No — request new code from authenticator app |
{
"error": {
"code": "BILKO-1011",
"message": "Kod za verifikaciju nije ispravan ili je istekao."
}
}
BILKO-1012 — Invalid Invite Token
| Field |
Value |
| HTTP |
401 |
| Trigger |
User follows invite link after it has expired (7 days) or already been used |
| Retry |
No — request new invitation |
{
"error": {
"code": "BILKO-1012",
"message": "Pozivnica je nevažeća ili je istekla."
}
}
Module: Organizations (2xxx)
BILKO-2001 — Organization Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
Organization ID in JWT does not exist in database (account deleted while token still valid) |
{
"error": {
"code": "BILKO-2001",
"message": "Organizacija nije pronađena."
}
}
BILKO-2002 — Invalid Currency Code
| Field |
Value |
| HTTP |
422 |
| Trigger |
PUT /organization — baseCurrency is not one of EUR, RSD, BAM, HRK |
{
"error": {
"code": "BILKO-2002",
"message": "Neispravna valuta. Podržane valute: EUR, RSD, BAM, HRK.",
"details": {
"baseCurrency": ["Vrijednost mora biti jedna od: EUR, RSD, BAM, HRK."]
}
}
}
BILKO-2003 — Invalid Language Code
| Field |
Value |
| HTTP |
422 |
| Trigger |
PUT /organization — language is not one of sr, bs, hr |
{
"error": {
"code": "BILKO-2003",
"message": "Neispravni jezički kod. Podržani jezici: sr, bs, hr.",
"details": {
"language": ["Vrijednost mora biti jedna od: sr, bs, hr."]
}
}
}
BILKO-2004 — Cannot Change Base Currency
| Field |
Value |
| HTTP |
400 |
| Trigger |
PUT /organization — attempting to change baseCurrency when transactions already exist |
{
"error": {
"code": "BILKO-2004",
"message": "Osnovna valuta ne može se promijeniti jer već postoje finansijske transakcije."
}
}
BILKO-2005 — User Not Found in Organization
| Field |
Value |
| HTTP |
404 |
| Trigger |
PUT /users/:id/role or DELETE /users/:id — user UUID not in caller's organization |
{
"error": {
"code": "BILKO-2005",
"message": "Korisnik nije pronađen u ovoj organizaciji."
}
}
BILKO-2006 — Cannot Modify Owner
| Field |
Value |
| HTTP |
403 |
| Trigger |
PUT /users/:id/role or DELETE /users/:id — target user is owner |
{
"error": {
"code": "BILKO-2006",
"message": "Nije moguće promijeniti ili ukloniti vlasnika organizacije."
}
}
BILKO-2007 — Cannot Remove Self
| Field |
Value |
| HTTP |
403 |
| Trigger |
DELETE /users/:id — user attempts to delete their own account |
{
"error": {
"code": "BILKO-2007",
"message": "Ne možete ukloniti vlastiti korisnički račun."
}
}
BILKO-2008 — Cannot Invite Existing Member
| Field |
Value |
| HTTP |
409 |
| Trigger |
POST /users/invite — email already belongs to a user in this organization |
{
"error": {
"code": "BILKO-2008",
"message": "Korisnik s ovim emailom je već član organizacije."
}
}
Module: Invoices (3xxx)
BILKO-3001 — Invoice Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
GET/PUT/PATCH/DELETE /invoices/:id — ID not found in caller's organization |
{
"error": {
"code": "BILKO-3001",
"message": "Faktura nije pronađena."
}
}
BILKO-3002 — Customer Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
POST /invoices — customerId does not exist in organization's contacts |
{
"error": {
"code": "BILKO-3002",
"message": "Klijent nije pronađen."
}
}
BILKO-3003 — Invoice Not in Draft Status
| Field |
Value |
| HTTP |
400 |
| Trigger |
PUT /invoices/:id — attempting to edit an invoice that is not in draft status |
{
"error": {
"code": "BILKO-3003",
"message": "Faktura se može mijenjati samo u statusu 'nacrt'.",
"details": {
"status": ["Trenutni status: sent. Samo nacrti se mogu uređivati."]
}
}
}
BILKO-3004 — Invalid Invoice Status Transition
| Field |
Value |
| HTTP |
400 |
| Trigger |
PATCH /invoices/:id/status — action is not valid for current invoice status |
Valid transitions:
draft → sent (action: send)
sent or viewed → paid (action: mark-paid)
- Any non-cancelled →
cancelled (action: cancel)
{
"error": {
"code": "BILKO-3004",
"message": "Nevažeća promjena statusa fakture.",
"details": {
"action": ["Akcija 'mark-paid' nije dozvoljena za status 'draft'."]
}
}
}
BILKO-3005 — Customer Has No Email
| Field |
Value |
| HTTP |
400 |
| Trigger |
POST /invoices/:id/send — customer contact has no email field set |
{
"error": {
"code": "BILKO-3005",
"message": "Klijent nema email adresu. Dodajte email u kontakt podatke."
}
}
BILKO-3006 — Invoice Items Required
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /invoices — items array is empty |
{
"error": {
"code": "BILKO-3006",
"message": "Faktura mora imati najmanje jednu stavku.",
"details": {
"items": ["Polje items ne može biti prazno."]
}
}
}
BILKO-3007 — Negative or Zero Amount
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /invoices or POST /invoices/:id/send — unitPrice or quantity is ≤ 0 |
{
"error": {
"code": "BILKO-3007",
"message": "Iznosi moraju biti veći od nule.",
"details": {
"items[0].unitPrice": ["Cijena mora biti pozitivan broj."]
}
}
}
BILKO-3008 — Invalid Tax Rate
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /invoices — taxRate is negative or exceeds 100 |
{
"error": {
"code": "BILKO-3008",
"message": "Stopa poreza mora biti između 0 i 100.",
"details": {
"items[0].taxRate": ["Vrijednost mora biti između 0 i 100."]
}
}
}
BILKO-3009 — Due Date Before Invoice Date
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /invoices — dueDate is before invoiceDate |
{
"error": {
"code": "BILKO-3009",
"message": "Datum dospijeća ne može biti prije datuma fakture.",
"details": {
"dueDate": ["Datum dospijeća mora biti isti ili kasniji od datuma fakture."]
}
}
}
BILKO-3010 — Invoice PDF Not Available
| Field |
Value |
| HTTP |
404 |
| Trigger |
GET /invoices/:id/pdf — PDF has not yet been generated (invoice still in draft) |
{
"error": {
"code": "BILKO-3010",
"message": "PDF faktura nije dostupan. Faktura mora biti poslana da bi se generirao PDF."
}
}
BILKO-3011 — Invoice Email Delivery Failed
| Field |
Value |
| HTTP |
500 |
| Trigger |
POST /invoices/:id/send — SendGrid API returned error |
| Retry |
Yes — once, after 5 seconds |
{
"error": {
"code": "BILKO-3011",
"message": "Slanje emaila nije uspjelo. Pokušajte ponovo.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
Module: Expenses (4xxx)
BILKO-4001 — Expense Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
GET/PUT/PATCH/DELETE /expenses/:id — ID not in caller's organization |
{
"error": {
"code": "BILKO-4001",
"message": "Troškak nije pronađen."
}
}
BILKO-4002 — Expense Not in Pending Status
| Field |
Value |
| HTTP |
400 |
| Trigger |
PUT /expenses/:id or DELETE /expenses/:id — expense is not in pending status |
{
"error": {
"code": "BILKO-4002",
"message": "Troškak se može mijenjati ili brisati samo u statusu 'na čekanju'.",
"details": {
"status": ["Trenutni status: approved."]
}
}
}
BILKO-4003 — Expense Already Processed
| Field |
Value |
| HTTP |
400 |
| Trigger |
PATCH /expenses/:id/approve — expense is already approved, paid, or rejected |
{
"error": {
"code": "BILKO-4003",
"message": "Troškak je već obrađen i ne može se odobriti ponovo.",
"details": {
"status": ["Trenutni status: approved."]
}
}
}
BILKO-4004 — Vendor Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
POST /expenses — vendorId not found in organization's contacts |
{
"error": {
"code": "BILKO-4004",
"message": "Dobavljač nije pronađen."
}
}
BILKO-4005 — Receipt File Too Large
| Field |
Value |
| HTTP |
413 |
| Trigger |
POST /expenses with receiptFile — file exceeds 10MB |
{
"error": {
"code": "BILKO-4005",
"message": "Fajl je prevelik. Maksimalna veličina je 10 MB."
}
}
BILKO-4006 — Invalid Receipt File Type
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /expenses with receiptFile — file is not PDF, PNG, or JPG |
{
"error": {
"code": "BILKO-4006",
"message": "Neispravni tip fajla. Dozvoljeni formati: PDF, PNG, JPG.",
"details": {
"receiptFile": ["Tip fajla 'docx' nije dozvoljen."]
}
}
}
BILKO-4007 — Receipt Upload Failed
| Field |
Value |
| HTTP |
500 |
| Trigger |
Cloudflare R2 upload failed after ClamAV scan passed |
| Retry |
Yes — exponential backoff |
{
"error": {
"code": "BILKO-4007",
"message": "Upload računa nije uspio. Pokušajte ponovo.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
BILKO-4008 — File Rejected (Virus Detected)
| Field |
Value |
| HTTP |
422 |
| Trigger |
ClamAV scan detected malware in uploaded file |
| Retry |
No |
{
"error": {
"code": "BILKO-4008",
"message": "Fajl nije prihvaćen zbog sigurnosnih razloga."
}
}
Module: Banking (5xxx)
BILKO-5001 — Bank Account Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
GET/POST /bank-accounts/:id/* — ID not found in caller's organization |
{
"error": {
"code": "BILKO-5001",
"message": "Bankovni račun nije pronađen."
}
}
BILKO-5002 — GL Account Must Be Asset Type
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /bank-accounts — referenced accountId is not an Asset account type |
{
"error": {
"code": "BILKO-5002",
"message": "Konto za bankovni račun mora biti tipa 'Imovina'.",
"details": {
"accountId": ["Odabrani konto je tipa 'Rashodi'. Odaberite konto tipa 'Imovina'."]
}
}
}
BILKO-5003 — GL Account Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
POST /bank-accounts — accountId does not exist in organization |
{
"error": {
"code": "BILKO-5003",
"message": "Konto nije pronađen."
}
}
BILKO-5004 — Bank Transaction Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
POST /bank-accounts/:id/reconcile — bankTransactionId not found |
{
"error": {
"code": "BILKO-5004",
"message": "Bankovna transakcija nije pronađena."
}
}
BILKO-5005 — Transaction Already Reconciled
| Field |
Value |
| HTTP |
400 |
| Trigger |
POST /bank-accounts/:id/reconcile — either transaction is already reconciled = true |
{
"error": {
"code": "BILKO-5005",
"message": "Transakcija je već usklađena."
}
}
BILKO-5006 — Invalid CSV Format
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /bank-accounts/:id/import — CSV missing required columns or malformed |
{
"error": {
"code": "BILKO-5006",
"message": "Nevažeći format CSV fajla.",
"details": {
"file": ["Obavezne kolone: Date, Description, Amount, Reference."]
}
}
}
BILKO-5007 — CSV File Too Large
| Field |
Value |
| HTTP |
413 |
| Trigger |
POST /bank-accounts/:id/import — CSV file exceeds 5MB |
{
"error": {
"code": "BILKO-5007",
"message": "CSV fajl je prevelik. Maksimalna veličina je 5 MB."
}
}
BILKO-5008 — Invalid CSV Date Format
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /bank-accounts/:id/import — date column values not parseable as ISO 8601 |
{
"error": {
"code": "BILKO-5008",
"message": "Nevažeći format datuma u CSV fajlu.",
"details": {
"file": ["Datumi moraju biti u formatu YYYY-MM-DD (npr. 2026-02-24)."]
}
}
}
Module: Reports (6xxx)
BILKO-6001 — From Date Required
| Field |
Value |
| HTTP |
422 |
| Trigger |
GET /reports/profit-loss, /cash-flow, /vat — from query parameter missing |
{
"error": {
"code": "BILKO-6001",
"message": "Početni datum je obavezan.",
"details": {
"from": ["Parametar 'from' je obavezan."]
}
}
}
BILKO-6002 — To Date Required
| Field |
Value |
| HTTP |
422 |
| Trigger |
GET /reports/profit-loss, /cash-flow, /vat — to query parameter missing |
{
"error": {
"code": "BILKO-6002",
"message": "Krajnji datum je obavezan.",
"details": {
"to": ["Parametar 'to' je obavezan."]
}
}
}
BILKO-6003 — Invalid Date Range
| Field |
Value |
| HTTP |
422 |
| Trigger |
Any report endpoint — from date is after to date |
{
"error": {
"code": "BILKO-6003",
"message": "Nevažeći raspon datuma. Početni datum mora biti prije krajnjeg.",
"details": {
"from": ["Mora biti prije 'to' datuma."]
}
}
}
BILKO-6004 — Trial Balance Not Balanced
| Field |
Value |
| HTTP |
500 |
| Trigger |
GET /reports/trial-balance — internal data integrity check failed (debit ≠ credit totals) |
| Retry |
No — requires manual investigation |
{
"error": {
"code": "BILKO-6004",
"message": "Greška integriteta podataka: probni bilans nije uravnotežen. Kontaktirajte podršku.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
| Field |
Value |
| HTTP |
404 |
| Trigger |
GET/PUT/DELETE /contacts/:id — ID not found in caller's organization |
{
"error": {
"code": "BILKO-7001",
"message": "Kontakt nije pronađen."
}
}
| Field |
Value |
| HTTP |
400 |
| Trigger |
DELETE /contacts/:id — contact has invoices with status other than cancelled |
{
"error": {
"code": "BILKO-7002",
"message": "Kontakt ne može biti deaktiviran jer ima aktivne fakture."
}
}
| Field |
Value |
| HTTP |
400 |
| Trigger |
DELETE /contacts/:id — contact has expenses with status other than rejected |
{
"error": {
"code": "BILKO-7003",
"message": "Kontakt ne može biti deaktiviran jer ima aktivne troškove."
}
}
BILKO-7004 — Invalid ISO Country Code
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST/PUT /contacts — country is not a valid ISO 3166-1 alpha-2 code |
{
"error": {
"code": "BILKO-7004",
"message": "Nevažeći kod države.",
"details": {
"country": ["Mora biti ISO 3166-1 alpha-2 kod (npr. 'RS', 'BA', 'HR')."]
}
}
}
BILKO-7005 — Invalid Payment Terms
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST/PUT /contacts — paymentTerms is negative or not an integer |
{
"error": {
"code": "BILKO-7005",
"message": "Rok plaćanja mora biti pozitivan cijeli broj (dani).",
"details": {
"paymentTerms": ["Mora biti pozitivni cijeli broj."]
}
}
}
Module: Settings & Accounts (8xxx)
BILKO-8001 — Account Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
PUT /accounts/:id — account UUID not found in caller's organization |
{
"error": {
"code": "BILKO-8001",
"message": "Konto nije pronađen."
}
}
BILKO-8002 — Account Code Already Exists
| Field |
Value |
| HTTP |
409 |
| Trigger |
POST /accounts — code is not unique within the organization |
{
"error": {
"code": "BILKO-8002",
"message": "Konto sa ovim kodom već postoji.",
"details": {
"code": ["Kod '1200' je već u upotrebi."]
}
}
}
BILKO-8003 — Invalid Account Type
| Field |
Value |
| HTTP |
422 |
| Trigger |
POST /accounts — accountTypeId is not 1–5 |
{
"error": {
"code": "BILKO-8003",
"message": "Nevažeći tip konta.",
"details": {
"accountTypeId": ["Mora biti između 1 (Imovina) i 5 (Rashodi)."]
}
}
}
BILKO-8004 — Cannot Deactivate Account with Transactions
| Field |
Value |
| HTTP |
400 |
| Trigger |
PUT /accounts/:id — setting isActive = false on account that has transactions |
{
"error": {
"code": "BILKO-8004",
"message": "Konto se ne može deaktivirati jer ima postojeće transakcije."
}
}
BILKO-8005 — Parent Account Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
POST /accounts — parentAccountId not found in organization |
{
"error": {
"code": "BILKO-8005",
"message": "Nadređeni konto nije pronađen."
}
}
BILKO-8006 — Invalid VAT Rate
| Field |
Value |
| HTTP |
422 |
| Trigger |
PUT /settings/tax-rates — any rate value is negative or exceeds 100 |
{
"error": {
"code": "BILKO-8006",
"message": "Stopa PDV-a mora biti između 0 i 100.",
"details": {
"defaultVATRate": ["Vrijednost mora biti između 0 i 100."]
}
}
}
General / Cross-cutting (9xxx)
BILKO-9001 — Insufficient Permissions
| Field |
Value |
| HTTP |
403 |
| Trigger |
Any endpoint — user's role not in allowedRoles for that endpoint |
{
"error": {
"code": "BILKO-9001",
"message": "Nemate dovoljna prava za ovu akciju.",
"details": {
"required": ["owner", "admin"],
"current": ["accountant"]
}
}
}
BILKO-9002 — Resource Not Found
| Field |
Value |
| HTTP |
404 |
| Trigger |
Generic fallback when a specific BILKO-Xxxx code doesn't apply |
{
"error": {
"code": "BILKO-9002",
"message": "Traženi resurs nije pronađen."
}
}
BILKO-9003 — Validation Failed
| Field |
Value |
| HTTP |
422 |
| Trigger |
Generic Zod schema validation failure when no specific BILKO-Xxxx code applies |
{
"error": {
"code": "BILKO-9003",
"message": "Validacija zahtjeva nije uspjela.",
"details": {
"fieldName": ["Opis greške validacije."]
}
}
}
BILKO-9004 — Internal Server Error
| Field |
Value |
| HTTP |
500 |
| Trigger |
Unhandled exception; logged to Sentry with full stack trace |
| Retry |
Yes — exponential backoff |
{
"error": {
"code": "BILKO-9004",
"message": "Došlo je do greške na serveru. Pokušajte ponovo.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
BILKO-9005 — Rate Limit Exceeded
| Field |
Value |
| HTTP |
429 |
| Trigger |
General API rate limit exceeded (100 req/min for reads, 10–50 for writes) |
| Retry |
Yes — after Retry-After header |
{
"error": {
"code": "BILKO-9005",
"message": "Previše zahtjeva. Usporite."
}
}
BILKO-9006 — Database Error
| Field |
Value |
| HTTP |
500 |
| Trigger |
Prisma throws unexpected DB error (connection lost, constraint violation from race condition) |
| Retry |
Yes — exponential backoff |
{
"error": {
"code": "BILKO-9006",
"message": "Greška baze podataka. Pokušajte ponovo.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
BILKO-9007 — External Service Unavailable
| Field |
Value |
| HTTP |
503 |
| Trigger |
SendGrid, Cloudflare R2, or ECB API is unreachable after internal retries exhausted |
| Retry |
Yes — exponential backoff; 503 responses include Retry-After |
{
"error": {
"code": "BILKO-9007",
"message": "Vanjska usluga trenutno nije dostupna. Pokušajte ponovo za nekoliko minuta.",
"requestId": "req_01H9X3K5P2M7N8Q4R6S9T0V1W2"
}
}
BILKO-9008 — Invalid Pagination Parameters
| Field |
Value |
| HTTP |
400 |
| Trigger |
Any list endpoint — page < 1, perPage > 100, or non-integer values |
{
"error": {
"code": "BILKO-9008",
"message": "Nevažeći parametri paginacije.",
"details": {
"perPage": ["Maksimalna vrijednost je 100."],
"page": ["Mora biti pozitivan cijeli broj."]
}
}
}
i18n Error Messages
All error message fields are returned in the organization's configured language (sr, bs, hr, en). Codes and details keys are always in English to enable programmatic handling.
Message localization table (selected codes):
| Code |
SR (Serbian) |
BS (Bosnian) |
HR (Croatian) |
EN (English) |
| BILKO-1001 |
Pogrešan email ili lozinka. |
Pogrešan email ili lozinka. |
Pogrešan email ili lozinka. |
Invalid email or password. |
| BILKO-1003 |
Sesija je istekla. Osvježite token. |
Sesija je istekla. Osvježite token. |
Sesija je istekla. Osvježite token. |
Session expired. Please refresh your token. |
| BILKO-1005 |
Autentifikacija je obavezna. |
Autentifikacija je obavezna. |
Autentifikacija je obavezna. |
Authentication is required. |
| BILKO-1007 |
Previše pokušaja prijave. Pokušajte za 15 min. |
Previše pokušaja prijave. Pokušajte za 15 min. |
Previše pokušaja prijave. Pokušajte za 15 min. |
Too many login attempts. Try again in 15 minutes. |
| BILKO-1008 |
Email adresa je već registrirana. |
Email adresa je već registrirana. |
Email adresa je već registrirana. |
Email address is already registered. |
| BILKO-3001 |
Faktura nije pronađena. |
Faktura nije pronađena. |
Faktura nije pronađena. |
Invoice not found. |
| BILKO-4001 |
Troškak nije pronađen. |
Troškak nije pronađen. |
Trošak nije pronađen. |
Expense not found. |
| BILKO-7001 |
Kontakt nije pronađen. |
Kontakt nije pronađen. |
Kontakt nije pronađen. |
Contact not found. |
| BILKO-9001 |
Nemate dovoljna prava za ovu akciju. |
Nemate dovoljna prava za ovu akciju. |
Nemate dovoljna prava za ovu akciju. |
You do not have permission to perform this action. |
| BILKO-9003 |
Validacija zahtjeva nije uspjela. |
Validacija zahtjeva nije uspjela. |
Provjera zahtjeva nije uspjela. |
Request validation failed. |
| BILKO-9004 |
Došlo je do greške na serveru. |
Došlo je do greške na serveru. |
Došlo je do pogreške na poslužitelju. |
An internal server error occurred. |
| BILKO-9005 |
Previše zahtjeva. Usporite. |
Previše zahtjeva. Usporite. |
Previše zahtjeva. Usporite. |
Too many requests. Please slow down. |
Implementation: Error messages are stored in /src/lib/i18n/errors/ with one file per language (sr.json, bs.json, hr.json, en.json). The message is selected using the organization's language field from the JWT orgId lookup.
// src/lib/i18n/errors/en.json (excerpt)
{
"BILKO-1001": "Invalid email or password.",
"BILKO-1003": "Session expired. Please refresh your token.",
"BILKO-9001": "You do not have permission to perform this action."
}
// src/lib/error-formatter.ts
function formatError(code: string, orgLanguage: string, details?: Record<string, string[]>) {
const messages = require(`./i18n/errors/${orgLanguage}.json`)
return {
error: {
code,
message: messages[code] ?? messages['BILKO-9004'],
...(details && { details }),
}
}
}
Quick Reference — All Error Codes
| Code |
HTTP |
Module |
Short Description |
| BILKO-1001 |
401 |
Auth |
Invalid credentials |
| BILKO-1002 |
403 |
Auth |
Account disabled |
| BILKO-1003 |
401 |
Auth |
Access token expired |
| BILKO-1004 |
401 |
Auth |
Invalid token |
| BILKO-1005 |
401 |
Auth |
No token provided |
| BILKO-1006 |
401 |
Auth |
Refresh token invalid/expired |
| BILKO-1007 |
429 |
Auth |
Auth rate limit exceeded |
| BILKO-1008 |
409 |
Auth |
Email already registered |
| BILKO-1009 |
422 |
Auth |
Weak password |
| BILKO-1010 |
403 |
Auth |
2FA required |
| BILKO-1011 |
401 |
Auth |
Invalid 2FA code |
| BILKO-1012 |
401 |
Auth |
Invalid invite token |
| BILKO-2001 |
404 |
Org |
Organization not found |
| BILKO-2002 |
422 |
Org |
Invalid currency code |
| BILKO-2003 |
422 |
Org |
Invalid language code |
| BILKO-2004 |
400 |
Org |
Cannot change base currency |
| BILKO-2005 |
404 |
Org |
User not found in org |
| BILKO-2006 |
403 |
Org |
Cannot modify owner |
| BILKO-2007 |
403 |
Org |
Cannot remove self |
| BILKO-2008 |
409 |
Org |
User already a member |
| BILKO-3001 |
404 |
Invoices |
Invoice not found |
| BILKO-3002 |
404 |
Invoices |
Customer not found |
| BILKO-3003 |
400 |
Invoices |
Invoice not in draft |
| BILKO-3004 |
400 |
Invoices |
Invalid status transition |
| BILKO-3005 |
400 |
Invoices |
Customer has no email |
| BILKO-3006 |
422 |
Invoices |
Items array empty |
| BILKO-3007 |
422 |
Invoices |
Negative/zero amount |
| BILKO-3008 |
422 |
Invoices |
Invalid tax rate |
| BILKO-3009 |
422 |
Invoices |
Due date before invoice date |
| BILKO-3010 |
404 |
Invoices |
PDF not available |
| BILKO-3011 |
500 |
Invoices |
Email delivery failed |
| BILKO-4001 |
404 |
Expenses |
Expense not found |
| BILKO-4002 |
400 |
Expenses |
Expense not pending |
| BILKO-4003 |
400 |
Expenses |
Expense already processed |
| BILKO-4004 |
404 |
Expenses |
Vendor not found |
| BILKO-4005 |
413 |
Expenses |
Receipt file too large |
| BILKO-4006 |
422 |
Expenses |
Invalid file type |
| BILKO-4007 |
500 |
Expenses |
Upload failed |
| BILKO-4008 |
422 |
Expenses |
Virus detected in file |
| BILKO-5001 |
404 |
Banking |
Bank account not found |
| BILKO-5002 |
422 |
Banking |
GL account must be Asset type |
| BILKO-5003 |
404 |
Banking |
GL account not found |
| BILKO-5004 |
404 |
Banking |
Bank transaction not found |
| BILKO-5005 |
400 |
Banking |
Transaction already reconciled |
| BILKO-5006 |
422 |
Banking |
Invalid CSV format |
| BILKO-5007 |
413 |
Banking |
CSV file too large |
| BILKO-5008 |
422 |
Banking |
Invalid CSV date format |
| BILKO-6001 |
422 |
Reports |
From date required |
| BILKO-6002 |
422 |
Reports |
To date required |
| BILKO-6003 |
422 |
Reports |
Invalid date range |
| BILKO-6004 |
500 |
Reports |
Trial balance not balanced |
| BILKO-7001 |
404 |
Contacts |
Contact not found |
| BILKO-7002 |
400 |
Contacts |
Contact has active invoices |
| BILKO-7003 |
400 |
Contacts |
Contact has active expenses |
| BILKO-7004 |
422 |
Contacts |
Invalid country code |
| BILKO-7005 |
422 |
Contacts |
Invalid payment terms |
| BILKO-8001 |
404 |
Accounts |
Account not found |
| BILKO-8002 |
409 |
Accounts |
Account code already exists |
| BILKO-8003 |
422 |
Accounts |
Invalid account type |
| BILKO-8004 |
400 |
Accounts |
Cannot deactivate account with transactions |
| BILKO-8005 |
404 |
Accounts |
Parent account not found |
| BILKO-8006 |
422 |
Settings |
Invalid VAT rate |
| BILKO-9001 |
403 |
General |
Insufficient permissions |
| BILKO-9002 |
404 |
General |
Resource not found |
| BILKO-9003 |
422 |
General |
Validation failed |
| BILKO-9004 |
500 |
General |
Internal server error |
| BILKO-9005 |
429 |
General |
Rate limit exceeded |
| BILKO-9006 |
500 |
General |
Database error |
| BILKO-9007 |
503 |
General |
External service unavailable |
| BILKO-9008 |
400 |
General |
Invalid pagination parameters |
End of Error Codes Catalog