Complete REST API documentation for nullInvoice.
Authentication Required: All API endpoints require either:
- Bearer token in
Authorizationheader (recommended for integrations) - Active session (if logged in via web UI)
Generate an API key from the Admin dashboard and include it in the Authorization header:
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoicesIf you're logged in via the web UI, your session is automatically used for API requests.
- Login to the web UI
- Navigate to Admin (user dropdown menu)
- Scroll to "API Keys" section
- Enter optional description and click "Generate Key"
- Copy the key immediately - it won't be shown again
- The key is displayed as
Authorization: Bearer {key}format
- API keys are hashed in the database (BCrypt)
- Keys can be revoked at any time from the Admin dashboard
- Last used timestamp is tracked for each key
- Generate separate keys for different applications/environments
POST /api/v1/invoices/generate
Authentication required - Include Bearer token in Authorization header.
- Requires
supplier_idandclient. response_typesupportsnumber(default) orpdf.number: Returns JSON with invoice metadata onlypdf: Returns PDF file directly with metadata in response headers
- Status is always
issuedfor API-generated invoices.
curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "number",
"supplier_id": 1,
"client": { "id": 42 },
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
],
"issue_date": "2026-01-16",
"due_date": "2026-01-30",
"currency_code": "EUR",
"notes": "Thank you"
}'curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "number",
"supplier_id": 1,
"client": {
"name": "Client Co",
"addressLine1": "2 Side St",
"city": "Burgas",
"country": "BG",
"taxId": "123",
"vatId": "BG123"
},
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}'{
"status": "issued",
"message": "invoice generated",
"invoiceNumber": "INV-000001",
"issueDate": "2026-01-16"
}curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "pdf",
"supplier_id": 1,
"client": {
"name": "Client Co",
"addressLine1": "2 Side St",
"city": "Plovdiv",
"country": "BG",
"taxId": "123",
"vatId": "BG123"
},
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}' \
-o invoice.pdf -iHTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="INV-000001.pdf"
X-Invoice-Number: INV-000001
X-Invoice-Status: issued
X-Invoice-Issue-Date: 2026-01-16
[PDF binary data]
The PDF response includes invoice metadata in custom response headers (X-Invoice-Number, X-Invoice-Status, X-Invoice-Issue-Date), allowing your application to store the invoice details while receiving the PDF file directly.
GET /api/v1/invoices
Authentication required - Include Bearer token in Authorization header.
- Optional filter:
status=unpaidorstatus=issued
Example request:
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices?status=unpaidExample response (filtered):
[
{ "invoiceNumber": "INV-000002", "status": "unpaid" }
]Authentication required - Include Bearer token in Authorization header.
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000001curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000001/pdf \
-o invoice.pdfThe async endpoints are an alternative to POST /api/v1/invoices/generate for callers that don't want to wait for invoice generation to complete in-line. A request is persisted to a queue, a background worker generates the invoice, and clients poll for status.
When to use the queue vs. the synchronous endpoint:
- Synchronous (
/api/v1/invoices/generate) - fastest round-trip, fire-and-receive. Recommended for most integrations. - Async (
/api/v1/invoice-requests) - useful if your caller can't tolerate the request being open for the full generation time, or if you want decoupled retry handling.
Both paths produce the same Invoices rows with the same numbering guarantees - the queue worker funnels through the same supplier-row lock as the sync endpoint, so invoice numbers stay sequential and conflict-free across both paths.
POST /api/v1/invoice-requests
Authentication required - Include Bearer token in Authorization header.
Request body: identical to POST /api/v1/invoices/generate. The response_type field is ignored - async requests only persist the queue row; once the request reaches COMPLETED, fetch the invoice using the standard retrieval endpoints with the invoiceNumber returned in the status response.
curl -X POST http://localhost:8080/api/v1/invoice-requests \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"supplier_id": 1,
"client": { "id": 42 },
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}'Response (201 Created):
{
"requestId": 42,
"status": "PENDING",
"message": "invoice generation request queued",
"createdAt": "2026-05-17T10:00:00"
}The submit returns as soon as the queue row is persisted - invoice generation has not started yet at this point.
GET /api/v1/invoice-requests/{requestId}
Authentication required - Include Bearer token in Authorization header.
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoice-requests/42Response while pending:
{
"requestId": 42,
"status": "PENDING",
"attempts": 0,
"createdAt": "2026-05-17T10:00:00"
}Response on success:
{
"requestId": 42,
"status": "COMPLETED",
"invoiceId": 107,
"invoiceNumber": "INV-000042",
"attempts": 1,
"createdAt": "2026-05-17T10:00:00",
"completedAt": "2026-05-17T10:00:02"
}Response on permanent failure:
{
"requestId": 42,
"status": "FAILED",
"errorMessage": "supplier not found",
"attempts": 3,
"createdAt": "2026-05-17T10:00:00",
"completedAt": "2026-05-17T10:00:06"
}Once the status endpoint reports COMPLETED, use the invoiceNumber it returns with the standard retrieval endpoints:
# JSON metadata
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000042
# PDF
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000042/pdf \
-o invoice.pdfThe async queue is purely about generation mechanics - the invoice itself is a regular Invoices row indistinguishable from one produced by the sync endpoint, so retrieval uses the same surface.
new request -> PENDING
worker succeeds -> COMPLETED
worker error, attempts < max -> PENDING (will retry)
attempts >= max_attempts -> FAILED (terminal)
PENDINGis the only retry-eligible state. A crash mid-processing rolls back cleanly and the row stays atPENDING- the crash does not count toward the retry budget.attemptsonly increments after a caught exception, in a separate transaction. TheerrorMessagefield carries the most recent failure reason.- Default
max_attemptsis 3, configurable vianullinvoice.queue.max-attempts(see Configuration).
- The worker polls every 2 seconds by default. A typical request completes within 2–4 seconds end-to-end.
- Clients should poll with a backoff - e.g. every 2 seconds for the first 30 seconds, then less frequently.
- The status endpoint is cheap (single indexed lookup); no rate-limit concerns under normal load.
Authentication required - Include Bearer token in Authorization header.
GET /api/v1/parties/client?taxId=...&vatId=... (requires one of taxId/vatId)
GET /api/v1/parties/clients/search?q=... (minimum 2 characters)
GET /api/v1/parties/suppliers
Example:
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/parties/suppliersGET /api/v1/health (no authentication required)
- Status values are
unpaidandissued.issuedis considered paid and final. - API invoice creation always results in
issuedstatus, and the API does not accept status overrides. - UI invoice creation can mark an invoice as
unpaidonly when a due date is set. - Unpaid invoices can be marked as
issuedfrom the invoice details page. - Issued invoices cannot be reverted back to unpaid.
- Suppliers: set up supplier details first. The supplier profile drives locale, currency, invoice numbering, and default tax rate.
- Templates: create a template for custom branding and set a default. Use a global default, or set a supplier-specific default to override the global choice.
- Clients (optional): you can add clients manually, but invoice generation also creates/updates clients from the entered details.
- Select active supplier: choose a default supplier in the UI, which sets a cookie used by invoice creation.
- Supplier ID for API: open a supplier in edit mode and use the supplier ID shown in the top-left of the supplier page.
- Invoices: list, search, and filter invoices; open an invoice to review details and mark unpaid invoices as issued/paid.
- Generate invoice: enter client details or search for an existing client, add line items, and set per-item tax. If an item omits tax, the supplier default tax rate applies.
- Discounts and notes: enter a flat discount or use the discount % calculator; add notes and generate the invoice to see the overview page.
Invoice generation uses a pessimistic write lock on the supplier record to avoid race conditions when calculating the next invoice number. This blocks concurrent requests for the same supplier until the number is assigned.
Interactive API Documentation:
- OpenAPI JSON specification:
/openapi - Swagger UI:
/swagger
Accessing Swagger UI:
- Login to the web UI
- Click user dropdown menu > "API Docs"
- Or navigate directly to
/swagger(requires login)
Testing Endpoints in Swagger:
- Click the "Authorize" button (lock icon) in the top right
- Enter your API key (generate from Admin > API Keys if needed)
- Click "Authorize"
- All requests will now include the Bearer token automatically
- Use "Try it out" to test endpoints interactively
Note: Swagger UI requires authentication to access and is only available to logged-in admin users.