Skip to main content

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

Retry-After header: Always present on 429 responses. Value is seconds to wait.


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."
  }
}

Response headers:

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 /organizationbaseCurrency 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 /organizationlanguage 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 /invoicescustomerId 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:

  • draftsent (action: send)
  • sent or viewedpaid (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 /invoicesitems 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/sendunitPrice 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 /invoicestaxRate 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 /invoicesdueDate 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 /expensesvendorId 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-accountsaccountId 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/reconcilebankTransactionId 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, /vatfrom 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, /vatto 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"
  }
}

Module: Contacts (7xxx)

BILKO-7001 — Contact Not Found

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."
  }
}

BILKO-7002 — Contact Has Active Invoices

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."
  }
}

BILKO-7003 — Contact Has Active Expenses

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 /contactscountry 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 /contactspaymentTerms 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 /accountscode 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 /accountsaccountTypeId 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 /accountsparentAccountId 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