Skip to content
On this page

ExpenseReceipt

Expense receipt upload and OCR processing

Poll the OCR result for an uploaded receipt

GET /api/v1/expenses/ocr/{receipt_ref}

Requires authentication (Bearer token) and the X-Organization-Id header.

Receipt-OCR poll (Epic #604). The client polls the opaque receipt_ref returned by POST /api/v1/expenses/upload-receipt until the capture is terminal. Scope expenses:read / permission expenses.view. Org-scoped: captures never cross organizations; an unknown or cross-org receipt_ref returns 404. Extracted fields keys equal StoreExpenseRequest exactly; amounts are decimal strings, dates Y-m-d, currency ISO-4217. Provider is self-hosted (EU residency); orphan captures are purged after the retention window (default 30 days).

Parameters

Name In Type Required Description
receipt_ref path string yes Opaque UUID returned by the upload endpoint.
organization_id query string no

Responses

Status Description
202 OCR still processing — keep polling.
200 Terminal capture state (done, needs_review or failed).
404 Unknown receipt_ref or it belongs to another organization.

Example request

curl -X GET "https://app.encryptinvoice.com/api/v1/expenses/ocr/{receipt_ref}" \
  -H "Authorization: Bearer {{access_token}}" \
  -H "X-Organization-Id: {{organization_id}}" \
  -H "Accept: application/json"

Upload a receipt for asynchronous OCR extraction

POST /api/v1/expenses/upload-receipt

Requires authentication (Bearer token) and the X-Organization-Id header.

Receipt-OCR upload (Epic #604, task #607). Stores the receipt on the organization's S3 disk under expenses/receipts/{uuid}.ext, creates a staging capture and enqueues an async OCR job on the ocr queue. Returns 202 with an opaque receipt_ref the client then polls via GET /api/v1/expenses/ocr/{receipt_ref}.

Scope expenses:write / permission expenses.create. Org-scoped — captures never cross organizations.

Idempotency: supply an Idempotency-Key header. A repeated key (same org) returns the same capture with no new storage write and no new job.

Optional expense_id enables post-create mode: OCR enriches that existing expense in place instead of creating a capture-driven expense. The expense_id must belong to the caller's organization.

The extracted fields keys equal StoreExpenseRequest exactly; amounts are decimal strings, dates Y-m-d, currency ISO-4217. Provider is self-hosted (PaddleOCR-VL + local LLM, EU residency, no external AI calls). Orphan captures (never promoted to an expense) are purged after the retention window (default 30 days). No receipt bytes or extracted PII are logged.

Note: an unsupported media type is rejected as 422 (via the mimes validation rule), not 415. An over-size file is 413 when it exceeds the organization storage byte-quota, otherwise 422 (the file max:10240 rule).

Parameters

Name In Type Required Description
Idempotency-Key header string no Optional client-supplied key. Same key (same org) returns the same receipt_ref with no duplicate capture or OCR job.

Responses

Status Description
202 Receipt stored; OCR enqueued. Poll receipt_ref for the result.
503 Storage or OCR provider unavailable — the client should queue and retry.
413 Receipt exceeds the organization storage byte-quota.
422

Example request

curl -X POST "https://app.encryptinvoice.com/api/v1/expenses/upload-receipt" \
  -H "Authorization: Bearer {{access_token}}" \
  -H "X-Organization-Id: {{organization_id}}" \
  -H "Accept: application/json"

We use cookies and privacy-respecting analytics

We use essential cookies for authentication and privacy-respecting analytics (self-hosted, respects Do Not Track). No advertising or third-party tracking. Learn more in our Privacy Policy