Download OpenAPI specification:
REST API for Stedilica.
Protected endpoints require a JWT Bearer token (Lexik JWT Authentication Bundle).
Call POST /api/login_check with JSON {"username":"<email>","password":"<password>"}.
The username field is the user's email address.
Use the returned token value in the Authorization header:
Authorization: Bearer <token>
Endpoints under /api/public/* are anonymous. All other /api/* routes require a valid JWT unless noted otherwise.
Register a new account (POST /api/register) or obtain a JWT (POST /api/login_check) via email and password.
Triggers a 6-digit verification code to the given email, if an account exists for it. The code expires after 10 minutes. Always returns 200 regardless of whether the account exists, to avoid leaking account existence. Rate-limited per email address.
Note: /api/register already sends a code as a side effect of registration, so
mobile clients typically only need this endpoint for the resend case.
| email required | string <email> Default: "" |
{- "email": "user@example.com"
}{- "message": "OK"
}Generates a new 6-digit code (invalidating any previous one) and resends it, if an account exists for the given email. Always returns 200/202-equivalent regardless of whether the account exists. Rate-limited per email address.
| email required | string <email> Default: "" |
{- "email": "user@example.com"
}{- "message": "OK"
}Validates the submitted code against the one most recently sent to the given email.
On a wrong or expired code, returns 422 with a ValidationErrorResponse whose
errors.code key holds the field-level message.
| email required | string <email> Default: "" |
| code required | string\d{6} Default: "" |
{- "email": "user@example.com",
- "code": "123456"
}{- "message": "OK"
}Authenticate with the user's email and password.
On success, returns a signed JWT. Send it on protected endpoints as:
Authorization: Bearer <token>
The username field must contain the account email address (see User entity).
| username required | string <email> User email address (json_login username field). |
| password required | string <password> |
{- "username": "user@example.com",
- "password": "secret"
}{- "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
}Always returns 200, regardless of whether the email belongs to an account — this is intentional (enumeration prevention). If the account exists, a reset token is emailed. Rate-limited per email address.
| email required | string <email> Default: "" |
{- "email": "user@example.com"
}{- "message": "OK"
}| token required | string Default: "" |
| password required | string <password> [ 6 .. 4096 ] characters Default: "" |
{- "token": "a1B2c3D4...",
- "password": "secret12"
}{- "message": "OK"
}Create a new account with email (username field) and password.
On success, a verification email is sent (same flow as the web registration page). The account must be verified before full access, depending on your app configuration.
| username required | string <email> Default: "" User email address (stored as User.email). |
| password required | string <password> [ 6 .. 4096 ] characters Default: "" |
{- "username": "user@example.com",
- "password": "secret12"
}{- "message": "Registration successful. Please check your email to verify your account.",
- "email": "user@example.com"
}Returns a paginated list of invoices owned by the authenticated user, ordered by receipt date/time descending. Each item includes only basic fields.
| page | integer >= 1 Default: 1 Page number (1-based) |
| limit | integer [ 1 .. 100 ] Default: 20 Items per page (max 100) |
| q | string Case-insensitive substring search over the shop name. |
{- "items": [
- {
- "id": 1,
- "enrichmentStatus": "completed",
- "sdcDateTimeLabel": "2024-01-15T14:30:00+01:00",
- "shopFullNameLabel": "Shop Name d.o.o.",
- "totalAmountLabel": "1.200,00"
}
], - "page": 1,
- "limit": 20,
- "total": 42,
- "totalPages": 3
}Submit a fiscal receipt verification url. The backend persists the invoice
immediately and enqueues async enrichment (header fields + line items) to keep
TTFB low. Poll GET /api/invoices/{id} until enrichmentStatus is completed
or failed.
If an invoice with the same URL already exists globally, that invoice is reused and re-queued for enrichment (ownership is not changed). Otherwise a new invoice is created for the authenticated user.
| url required | string <uri> Default: "" URL of the fiscal receipt verification page (SUF PURS). |
{
}{- "id": 1,
- "enrichmentStatus": "pending",
- "enrichmentError": null,
- "enrichedAt": "2019-08-24T14:15:22Z",
- "tinLabel": "123456789",
- "shopFullNameLabel": "Shop Name d.o.o.",
- "addressLabel": "Street 1",
- "cityLabel": "Belgrade",
- "administrativeUnitLabel": "Stari Grad",
- "buyerIdLabel": "string",
- "requestedByLabel": "string",
- "invoiceTypeId": "string",
- "transactionTypeId": "string",
- "totalAmountLabel": "1.200,00",
- "transactionTypeCounterLabel": 1,
- "totalCounterLabel": 10,
- "invoiceCounterExtensionLabel": "string",
- "invoiceNumberLabel": "string",
- "signedByLabel": "string",
- "sdcDateTimeLabel": "2024-01-15T14:30:00+01:00",
- "lineItems": [ ]
}Returns the sum of line item totals for the current calendar month, plus the all-time count of invoices owned by the authenticated user. Computed server-side so it stays accurate regardless of how many invoices have been paginated in.
{- "monthTotal": "4.320,50",
- "totalCount": 137
}Returns full invoice details including line items. Only invoices owned by the authenticated user are accessible; missing or non-owned invoices return 404 with the same message.
| id required | integer\d+ |
{- "id": 1,
- "enrichmentStatus": "pending",
- "enrichmentError": null,
- "enrichedAt": "2019-08-24T14:15:22Z",
- "tinLabel": "123456789",
- "shopFullNameLabel": "Shop Name d.o.o.",
- "addressLabel": "Street 1",
- "cityLabel": "Belgrade",
- "administrativeUnitLabel": "Stari Grad",
- "buyerIdLabel": "string",
- "requestedByLabel": "string",
- "invoiceTypeId": "string",
- "transactionTypeId": "string",
- "totalAmountLabel": "1.200,00",
- "transactionTypeCounterLabel": 1,
- "totalCounterLabel": 10,
- "invoiceCounterExtensionLabel": "string",
- "invoiceNumberLabel": "string",
- "signedByLabel": "string",
- "sdcDateTimeLabel": "2024-01-15T14:30:00+01:00",
- "lineItems": [ ]
}Returns a paginated purchase history for the given GTIN, scoped to the authenticated user. Sortable columns match the web GTIN list page. Missing or unknown GTINs return an empty list.
| gtin required | string Example: gtin=8600123456789 Product barcode (GTIN) |
| sort | string Default: "date" Sort column: name, date, location, quantity, unitPrice, total, taxBaseAmount, vatAmount, label |
| dir | string Default: "DESC" Enum: "ASC" "DESC" Sort direction |
| page | integer >= 1 Default: 1 Page number (1-based) |
| limit | integer [ 1 .. 100 ] Default: 20 Items per page (max 100) |
{- "items": [
- {
- "id": 1,
- "gtin": "8600123456789",
- "label": "Ђ",
- "labelRate": 20,
- "name": "Milk 1L",
- "quantity": 1,
- "taxBaseAmount": 100,
- "total": 120,
- "unitPrice": 120,
- "vatAmount": 20,
- "invoiceId": 42,
- "sdcDateTime": "2024-01-15T14:30:00+01:00",
- "location": "1002288-175 - Maxi"
}
], - "page": 1,
- "limit": 20,
- "total": 42,
- "totalPages": 3,
- "gtin": "8600123456789"
}Returns the chronological unit-price history for the given GTIN (same series used by the web GTIN detail chart), scoped to the authenticated user.
| gtin required | string Example: gtin=8600123456789 Product barcode (GTIN) |
{- "gtin": "8600123456789",
- "unitPriceHistory": [
- {
- "date": "2024-01-15T14:30:00+01:00",
- "unitPrice": 120,
- "location": "1002288-175 - Maxi"
}
], - "hasChartData": true
}Returns extracted fiscal fields for the PURS verify form when QR decoding failed.
Includes Serbian instructions and verifyUrl. Only records owned by the authenticated
user are visible.
| id required | integer\d+ |
{- "id": 1,
- "receiptScanJobId": 12,
- "invoiceNumberSe": "DELH4QA3-DELH4QA3-274419",
- "invoiceCounter": "274292/274419",
- "invoiceCounterExtension": "ПП",
- "totalAmount": "189,90",
- "sdcDateTime": "30.5.2026. 08:07",
- "instructions": "string",
- "reason": "qr_not_found",
- "originalFilename": "receipt.jpg",
- "mimeType": "image/jpeg",
- "createdAt": "2019-08-24T14:15:22Z"
}Upload one or more images (JPEG/PNG/WebP) or PDFs of fiscal receipts.
file: one upload; response is a single job object (backward compatible).files: one or more uploads (max 20); response is { "jobs": [ ... ] }.Each file becomes its own scan job. Poll GET /api/receipt-scans/{id} until status
is completed or failed. On success the uploaded file is deleted.
invoiceIds lists created/reused invoices.manualVerificationIds /
manualVerifications contain data and instructions for https://suf.purs.gov.rs/verify
(no Invoice is created).| file | string <binary> Single receipt image or PDF (max 32MB). Use this OR files. |
| files | Array of strings <binary> <= 20 items [ items <binary > ] Multiple receipt images/PDFs (max 20, each max 32MB). Prefer this for batch uploads. |
{- "id": 1,
- "status": "pending",
- "originalFilename": "receipt.jpg",
- "mimeType": "image/jpeg",
- "error": "string",
- "invoiceIds": [
- 12,
- 13
], - "manualVerificationIds": [
- 3
], - "manualVerifications": [ ],
- "createdAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}Returns scan job status for the authenticated owner. Missing/non-owned jobs return 404.
When QR decoding fails but OCR succeeds, status is completed with
manualVerificationIds / manualVerifications populated (no invoices).
| id required | integer\d+ |
{- "id": 1,
- "status": "pending",
- "originalFilename": "receipt.jpg",
- "mimeType": "image/jpeg",
- "error": "string",
- "invoiceIds": [
- 12,
- 13
], - "manualVerificationIds": [
- 3
], - "manualVerifications": [ ],
- "createdAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}Returns the three spend series shown on the web home page for the authenticated user:
{- "monthlySpend": [
- {
- "month": "2026-08",
- "total": 20474.85
}
], - "topByGtin": [
- {
- "gtin": "5411188116592",
- "label": "Napitak kokos pirinac Alpro 1l/KOM",
- "total": 28363.27
}
], - "topByLocation": [
- {
- "label": "1002288-175 - Maxi",
- "total": 339916.53
}
], - "hasSpendData": true
}