Skip to main content

Invoices API

All invoice endpoints require JWT authentication.

Prefill (Preview)

Generate an invoice preview from ClickHouse data without saving.

POST /api/v1/invoices/prefill

Request

{
"application_key": "APP-001",
"period_start": "2026-02-01",
"period_end": "2026-02-28"
}

period_start and period_end are optional — defaults to the previous calendar month.

Response (200)

{
"data": {
"application_key": "APP-001",
"customer_key": "CUST-001",
"period_start": "2026-02-01",
"period_end": "2026-02-28",
"deal_type": "SUP",
"currency": "UZS",
"base_amount": "15000000.00",
"overtime_hours": "5.50",
"overtime_amount": "825000.00",
"total_amount": "15825000.00",
"total_hours": "45.50",
"is_overtime": true,
"line_items": [ ... ],
"customer": { ... },
"application": { ... }
}
}

Generate (Create Draft)

Save a draft invoice to PostgreSQL.

POST /api/v1/invoices/generate

Request

{
"application_key": "APP-001",
"period_start": "2026-02-01",
"period_end": "2026-02-28",
"deal_type": "SUP",
"currency": "UZS",
"base_amount": "15000000.00",
"total_amount": "15825000.00",
"total_hours": "45.50",
"overtime_hours": "5.50",
"overtime_amount": "825000.00",
"is_overtime": true,
"line_items": [
{
"line_type": "support",
"description": "Monthly support — February 2026",
"time_seconds": 0,
"billable_seconds": 0,
"unit_price": "15000000.00",
"quantity": "1",
"line_total": "15000000.00",
"rate_tier": "standard",
"rate_multiplier": "1.0",
"source": "auto",
"is_auto_generated": true,
"sort_order": 0
}
]
}

Response (201)

Returns the saved invoice with its generated id and display_number.


Generate Bulk

Create draft invoices for all active applications in a period.

POST /api/v1/invoices/generate-bulk

Request

{
"period_start": "2026-02-01",
"period_end": "2026-02-28"
}

Response (200)

{
"data": [
{ "id": "uuid-1", "application_key": "APP-001", "status": "draft", ... },
{ "id": "uuid-2", "application_key": "APP-002", "status": "draft", ... }
]
}

List Invoices

GET /api/v1/invoices

Query Parameters

ParameterTypeDescription
statusstringFilter by status (draft, needs_review, etc.)
application_keystringFilter by application
pageintPage number (default: 1)
per_pageintItems per page (default: 20, max: 100)

Example

curl "http://localhost:8081/api/v1/invoices?status=draft&page=1&per_page=10" \
-H "Authorization: Bearer $TOKEN"

Response (200)

{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"display_number": "INV-1",
"application_key": "APP-001",
"customer_key": "CUST-001",
"period_start": "2026-02-01",
"period_end": "2026-02-28",
"deal_type": "SUP",
"currency": "UZS",
"total_amount": "15825000.00",
"status": "draft",
"created_at": "2026-02-15T10:00:00Z"
}
],
"meta": {
"total": 42,
"page": 1,
"per_page": 10
}
}

Get Invoice

GET /api/v1/invoices/:id

Response (200)

Returns the invoice with all line items included.

{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"display_number": "INV-1",
"application_key": "APP-001",
"customer_key": "CUST-001",
"period_start": "2026-02-01",
"period_end": "2026-02-28",
"deal_type": "SUP",
"currency": "UZS",
"base_amount": "15000000.00",
"overtime_hours": "5.50",
"overtime_amount": "825000.00",
"total_amount": "15825000.00",
"total_hours": "45.50",
"is_overtime": true,
"status": "draft",
"version": 1,
"created_at": "2026-02-15T10:00:00Z",
"updated_at": "2026-02-15T10:00:00Z",
"line_items": [
{
"id": "item-uuid",
"line_type": "task",
"description": "PROJ-123: Fix login bug",
"jira_key": "PROJ-123",
"time_seconds": 3600,
"billable_seconds": 3600,
"unit_price": "150000.00",
"quantity": "1.00",
"line_total": "150000.00",
"rate_tier": "standard",
"rate_multiplier": "1.0",
"source": "auto",
"is_auto_generated": true,
"sort_order": 1,
"linked_issue_key": "SUP-123",
"worklog_date": "2026-02-03"
}
]
}
}

Update Invoice

Update invoice-level fields (only in draft status).

PATCH /api/v1/invoices/:id

Request

{
"base_amount": "16000000.00",
"total_amount": "16825000.00",
"invoice_date": "2026-03-01",
"show_task_list": false,
"show_overtime": true,
"legal_text": "Custom legal paragraph..."
}

All fields are optional. Editable fields: base_amount, total_amount, overtime_hours, overtime_amount, is_overtime, invoice_date, show_task_list, show_tracked_time, show_task_pricing, show_overtime, legal_text, html_override.

Response (200)

Returns the updated invoice.


Update Line Items

Add, update, or remove line items in a single request.

PATCH /api/v1/invoices/:id/line-items

Request

{
"add": [
{
"line_type": "task",
"description": "Additional work",
"time_seconds": 7200,
"billable_seconds": 7200,
"unit_price": "150000.00",
"quantity": "2.00",
"line_total": "300000.00",
"rate_tier": "standard",
"rate_multiplier": "1.0",
"source": "manual",
"is_auto_generated": false,
"sort_order": 10
}
],
"update": [
{
"id": "existing-line-uuid",
"description": "Updated description",
"line_total": "500000.00"
}
],
"remove": [
"line-uuid-to-remove"
]
}

Response (200)

Returns the invoice with updated line items.


Finalize

Generate PDF and transition to needs_review.

POST /api/v1/invoices/:id/finalize

No request body. Only works on draft invoices.

Response (200)

Returns the invoice with status: "needs_review".

Errors

StatusCodeCause
409CONFLICTInvoice not in draft status
500INTERNAL_ERRORPDF generation failed

Regenerate

Re-fetch data from ClickHouse and reset to draft.

PUT /api/v1/invoices/:id/regenerate

No request body. Works on declined, rejected, or approved invoices.

Response (200)

Returns the regenerated invoice with status: "draft".


Get Edit History

GET /api/v1/invoices/:id/edit-history

Response (200)

{
"data": [
{
"id": "history-uuid",
"invoice_id": "invoice-uuid",
"line_item_id": "line-uuid",
"edit_type": "line_modified",
"field_name": "line_total",
"old_value": "300000.00",
"new_value": "500000.00",
"edited_by": "admin",
"edited_at": "2026-02-15T11:00:00Z"
}
]
}

Preview HTML

Render the invoice as server-side HTML for fast draft preview (~200ms). Used by the frontend to show a live preview in a sandboxed iframe.

GET /api/v1/invoices/:id/preview-html

Response (200)

Returns Content-Type: text/html with the rendered invoice HTML. If the invoice has an html_override set, returns that directly instead of rendering from the template. Uses the same template engine as PDF generation but skips PDF conversion.

curl http://localhost:8081/api/v1/invoices/{id}/preview-html \
-H "Authorization: Bearer $TOKEN"

Sync PlanFact

Manually retry PlanFact shipment sync for an approved or sent invoice.

POST /api/v1/invoices/:id/sync-planfact

No request body. Only works on approved or sent invoices.

Response (200)

{
"data": {
"message": "planfact sync complete"
}
}

Errors

StatusCodeCause
400VALIDATION_ERRORInvalid UUID, wrong status, or PlanFact not configured
404NOT_FOUNDInvoice not found

Download PDF

GET /api/v1/invoices/:id/pdf

Response (200)

Returns the PDF binary with Content-Type: application/pdf. Returns 404 if no PDF exists (invoice not yet finalized).

curl http://localhost:8081/api/v1/invoices/{id}/pdf \
-H "Authorization: Bearer $TOKEN" \
-o invoice.pdf