Stedilica API (0.1.0)

Download OpenAPI specification:

REST API for Stedilica.

Authentication

Protected endpoints require a JWT Bearer token (Lexik JWT Authentication Bundle).

  1. Call POST /api/login_check with JSON {"username":"<email>","password":"<password>"}. The username field is the user's email address.

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

Auth

Register a new account (POST /api/register) or obtain a JWT (POST /api/login_check) via email and password.

Send a 6-digit email verification code

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.

Request Body schema: application/json
required
email
required
string <email>
Default: ""

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "message": "OK"
}

Resend the 6-digit email verification code

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.

Request Body schema: application/json
required
email
required
string <email>
Default: ""

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "message": "OK"
}

Confirm an email address with a 6-digit code

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.

Request Body schema: application/json
required
email
required
string <email>
Default: ""
code
required
string\d{6}
Default: ""

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "code": "123456"
}

Response samples

Content type
application/json
{
  • "message": "OK"
}

Obtain a JWT access token

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

Request Body schema: application/json
required
username
required
string <email>

User email address (json_login username field).

password
required
string <password>

Responses

Request samples

Content type
application/json
{
  • "username": "user@example.com",
  • "password": "secret"
}

Response samples

Content type
application/json
{
  • "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
}

Request a password reset email

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.

Request Body schema: application/json
required
email
required
string <email>
Default: ""

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "message": "OK"
}

Reset the password using a token from the reset email

Request Body schema: application/json
required
token
required
string
Default: ""
password
required
string <password> [ 6 .. 4096 ] characters
Default: ""

Responses

Request samples

Content type
application/json
{
  • "token": "a1B2c3D4...",
  • "password": "secret12"
}

Response samples

Content type
application/json
{
  • "message": "OK"
}

Register a new user account

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.

Request Body schema: application/json
required
username
required
string <email>
Default: ""

User email address (stored as User.email).

password
required
string <password> [ 6 .. 4096 ] characters
Default: ""

Responses

Request samples

Content type
application/json
{
  • "username": "user@example.com",
  • "password": "secret12"
}

Response samples

Content type
application/json
{
  • "message": "Registration successful. Please check your email to verify your account.",
  • "email": "user@example.com"
}

Invoices

List invoices for the current user

Returns a paginated list of invoices owned by the authenticated user, ordered by receipt date/time descending. Each item includes only basic fields.

Authorizations:
bearerAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "page": 1,
  • "limit": 20,
  • "total": 42,
  • "totalPages": 3
}

Create an invoice from a fiscal receipt URL

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
url
required
string <uri>
Default: ""

URL of the fiscal receipt verification page (SUF PURS).

Responses

Request samples

Content type
application/json

Response samples

Content type
application/json
{
  • "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": [ ]
}

Get invoice totals for the current user

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.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "monthTotal": "4.320,50",
  • "totalCount": 137
}

Get a single invoice with line items

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.

Authorizations:
bearerAuth
path Parameters
id
required
integer\d+

Responses

Response samples

Content type
application/json
{
  • "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": [ ]
}

Line items

List line item purchases for a GTIN

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.

Authorizations:
bearerAuth
query Parameters
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)

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "page": 1,
  • "limit": 20,
  • "total": 42,
  • "totalPages": 3,
  • "gtin": "8600123456789"
}

Unit price statistics for a GTIN

Returns the chronological unit-price history for the given GTIN (same series used by the web GTIN detail chart), scoped to the authenticated user.

Authorizations:
bearerAuth
query Parameters
gtin
required
string
Example: gtin=8600123456789

Product barcode (GTIN)

Responses

Response samples

Content type
application/json
{
  • "gtin": "8600123456789",
  • "unitPriceHistory": [
    ],
  • "hasChartData": true
}

Manual receipt verifications

Get manual PURS verify fields for a failed QR scan

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.

Authorizations:
bearerAuth
path Parameters
id
required
integer\d+

Responses

Response samples

Content type
application/json
{
  • "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"
}

Public

Health check

Responses

Response samples

Content type
application/json
{
  • "ping": "pong"
}

Receipt scans

Upload receipt image(s)/PDF(s) for QR scanning

Upload one or more images (JPEG/PNG/WebP) or PDFs of fiscal receipts.

  • Singular field file: one upload; response is a single job object (backward compatible).
  • Array field 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.

  • If QR codes are found: invoiceIds lists created/reused invoices.
  • If QR fails but OCR extracts PURS verify fields: manualVerificationIds / manualVerifications contain data and instructions for https://suf.purs.gov.rs/verify (no Invoice is created).
Authorizations:
bearerAuth
Request Body schema: multipart/form-data
required
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.

Responses

Response samples

Content type
application/json
Example
{
  • "id": 1,
  • "status": "pending",
  • "originalFilename": "receipt.jpg",
  • "mimeType": "image/jpeg",
  • "error": "string",
  • "invoiceIds": [
    ],
  • "manualVerificationIds": [
    ],
  • "manualVerifications": [ ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "completedAt": "2019-08-24T14:15:22Z"
}

Get receipt scan job status

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

Authorizations:
bearerAuth
path Parameters
id
required
integer\d+

Responses

Response samples

Content type
application/json
{
  • "id": 1,
  • "status": "pending",
  • "originalFilename": "receipt.jpg",
  • "mimeType": "image/jpeg",
  • "error": "string",
  • "invoiceIds": [
    ],
  • "manualVerificationIds": [
    ],
  • "manualVerifications": [ ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "completedAt": "2019-08-24T14:15:22Z"
}

Stats

Home dashboard spend statistics

Returns the three spend series shown on the web home page for the authenticated user:

  • monthly spend for the last 24 months (missing months filled with 0)
  • top 20 products by GTIN spend over the last 2 years
  • top 20 locations by spend over the last 2 years
Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "monthlySpend": [
    ],
  • "topByGtin": [
    ],
  • "topByLocation": [
    ],
  • "hasSpendData": true
}

Test

Get current user info (test)

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "email": "user@example.com"
}