Back to Documentation

API Reference

Complete API endpoint documentation with cURL and Postman examples

Base URL: https://apiocr.dexaitech.com
Replace with your production URL in production environments.

Authentication Endpoints

User Login

POST/auth/loginAuth: None (Public)

Authenticate a user and receive a JWT access token. The token should be stored and used for subsequent authenticated requests.

Headers

HeaderValueRequired
Content-Typetext/plain (TOON format)Required
Accepttext/plainRequired

Request Body

FieldTypeRequiredDescription
emailstringRequiredUser's email address
passwordstringRequiredUser's password

Example:

email:"user@example.com"
password:"yourpassword123"

Code Examples

curl -X POST "https://apiocr.dexaitech.com/auth/login" \
  -H "Content-Type: text/plain" \
  -H "Accept: text/plain" \
  -d 'email:"user@example.com"
password:"yourpassword123"'
📮Postman Configuration
POST
https://apiocr.dexaitech.com/auth/login
Send
Headers
Body
KEYVALUE
Content-Typetext/plain (TOON format)
Accepttext/plain
email:"user@example.com"
password:"yourpassword123"
Quick Steps: 1. Set method to POST → 2. URL: https://apiocr.dexaitech.com/auth/login → 3. Headers Tab:

Response Example (200 OK)

access_token:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
token_type:"bearer"
user_type:"standard"
roles:["user"]

User Registration

POST/users/registerAuth: None (Public)

Create a new user account. After registration, the user can login to receive an access token.

Headers

HeaderValueRequired
Content-Typetext/plain (TOON format)Required
Accepttext/plainRequired

Request Body

FieldTypeRequiredDescription
first_namestringRequiredUser's first name
last_namestringRequiredUser's last name
emailstringRequiredUser's email address
passwordstringRequiredPassword (min 8 characters)

Example:

first_name:"John"
last_name:"Doe"
email:"john.doe@example.com"
password:"securepassword123"

Code Examples

curl -X POST "https://apiocr.dexaitech.com/users/register" \
  -H "Content-Type: text/plain" \
  -H "Accept: text/plain" \
  -d 'first_name:"John"
last_name:"Doe"
email:"john.doe@example.com"
password:"securepassword123"'
📮Postman Configuration
POST
https://apiocr.dexaitech.com/users/register
Send
Headers
Body
KEYVALUE
Content-Typetext/plain (TOON format)
Accepttext/plain
first_name:"John"
last_name:"Doe"
email:"john.doe@example.com"
password:"securepassword123"
Quick Steps: 1. Set method to POST → 2. URL: https://apiocr.dexaitech.com/users/register → 3. Headers Tab:

Response Example (200 OK)

message:"User registered successfully"
user:{
  id:"uuid-xxx-xxx"
  email:"john.doe@example.com"
  first_name:"John"
  last_name:"Doe"
  api_key:"ak_xxxxxxxxxxxxxx"
}

User Logout

POST/auth/logoutAuth: JWT Bearer Token

Invalidate the current user session. The JWT token will be blacklisted.

Headers

HeaderValueRequired
AuthorizationBearer YOUR_JWT_TOKENRequired

Code Examples

curl -X POST "https://apiocr.dexaitech.com/auth/logout" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
📮Postman Configuration
POST
https://apiocr.dexaitech.com/auth/logout
Send
Authorization
Headers
Type:
Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
KEYVALUE
AuthorizationBearer YOUR_JWT_TOKEN
Quick Steps: 1. Set method to POST → 2. URL: https://apiocr.dexaitech.com/auth/logout → 3. Authorization Tab → Type: Bearer Token → Enter your JWT token

Response Example (200 OK)

message:"Logged out successfully"

User Endpoints

Get User Profile

GET/users/profileAuth: JWT Bearer Token

Retrieve the current authenticated user's profile information including their API key.

Headers

HeaderValueRequired
AuthorizationBearer YOUR_JWT_TOKENRequired
Content-Typetext/plainOptional
Accepttext/plainOptional

Code Examples

curl -X GET "https://apiocr.dexaitech.com/users/profile" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Accept: text/plain"
📮Postman Configuration
GET
https://apiocr.dexaitech.com/users/profile
Send
Authorization
Headers
Type:
Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
KEYVALUE
AuthorizationBearer YOUR_JWT_TOKEN
Content-Typetext/plain
Accepttext/plain
Quick Steps: 1. Set method to GET → 2. URL: https://apiocr.dexaitech.com/users/profile → 3. Authorization Tab → Type: Bearer Token → Enter your JWT token

Response Example (200 OK)

id:"uuid-xxx-xxx"
email:"john.doe@example.com"
first_name:"John"
last_name:"Doe"
api_key:"ak_xxxxxxxxxxxxxxxxxxxxxx"
user_type:"standard"
roles:["user"]
created_at:"2025-01-20T10:30:00Z"

Document Processing API

Submit a document once and retrieve its result whenever you want. The document is classified (Qwen 2.5-VL primary, OCR + keyword fallback) and brokered through RabbitMQ to a worker. The endpoint is hit-and-run (asynchronous): you get a request_id back immediately, and the result is persisted and retained so you can fetch it later — whether processing takes seconds or hours.

End-to-end flow (hit & run)

  1. Submit the document — POST /v1/documents/process-async (file upload or S3/URL). It is classified, stored, and queued on RabbitMQ.
  2. Get a 202 Accepted immediately with request_id, status: processing, a status_url, a document_url preview, and a poll_interval_seconds hint. (A document classified as other is rejected up front with 422 not_processed.)
  3. Fetch the result — poll GET /v1/requests/{request_id} until status: completed (or failed). The result is retained — there is no deadline, so you can retrieve it minutes or hours later without re-uploading.
  4. Read the extracted data from formatted_result. Shape depends on the classified type — see the receipt/invoice/bank-statement schemas below.
  5. Convenience: pass wait=true to block for the result and receive it inline (200 OK) when it finishes in time; otherwise you get the same 202 and fall back to polling.

Base URL

https://apiocr.dexaitech.com/v1/documents

Authentication

Every request requires the external-user API key in the request header.

X-API-Key: YOUR_API_KEY

Response Format

All responses are TOON-encoded with Content-Type: text/plain (not JSON). Snippets below show the decoded TOON.

Process Document (Async)

Classify a document, broker it through RabbitMQ, process it, and return the extracted result in the same call. Accepts a direct file upload (multipart).

POST/v1/documents/process-asyncAuth: X-API-Key

A caller-supplied document_type is treated only as a hint — it shapes the description field on the response but does not override the classifier. Documents classified as other are not stored and not queued (HTTP 422).

Request parameters

ModeFieldTypeRequiredDescription
FormfilefileYes*The document file (PDF or image). *Or send a JSON body with s3_url/url instead.
Formdocument_typestringNoHint, e.g. InvoicePDF. Only affects the description field.
Query / Form / JSONwaitbooleanNoDefault false (hit-and-run → 202). Set true to block for the result and receive it inline (200) when ready in time. Can be passed as a query string (?wait=true, works for both upload modes), a multipart form field, or a JSON body field — the query string wins if more than one is set.
JSONs3_url / url / public_urlstringNoProcess a document by URL instead of uploading: private S3, presigned, public-read S3, or any public http(s) URL.

File upload (multipart/form-data)

Upload the file directly. The document_type field is optional and only used as a hint.

curl -X POST "https://apiocr.dexaitech.com/v1/documents/process-async" \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@/path/to/receipt_scan.jpg" \
  -F "document_type=InvoiceImage"

Response — 202 Accepted (default, hit-and-run). The document is accepted and queued; use status_url to fetch the result later:

request_id: req_77aa11bb22
status: processing
status_url: /v1/requests/req_77aa11bb22
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."
filename: receipt_scan.pdf
document_type: ReceiptPDF
confidence: 0.98
submitted_at: "2026-06-08T10:17:44.882210"
poll_interval_seconds: 5
retry_after: 5
result_retained: true
message: "Document accepted and queued for processing. Retrieve the result anytime via GET status_url by polling until status is 'completed' (or 'failed'). The result is retained and remains available, even hours later."

Fetch the resultGET /v1/requests/{request_id}. Poll until status: completed; the extracted data is under formatted_result (shape depends on the classified type), with a fresh document_url:

request_id: req_77aa11bb22
status: completed
filename: receipt_scan.pdf
submitted_at: "2026-06-08T10:17:44.882210"
completed_at: "2026-06-08T10:18:12.553001"
processing_time_ms: 27671
validation: true
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."
formatted_result:
  multiple_receipts[1]:
    - tableItems[1]{quantity,description,totalAmount,unitPrice,taxAmount,netAmount,discountAmount}:
      1,SAUSAGE AND MASH,12.9,12.9,0.0,12.9,0.0
      documentId: "268472"
      supplierName: THE PLUME OF FEATHERS
      customerName: HANNAH
      date: 15/08/2025
      totalAmount: 12.9
      taxAmount: 0.0
      netAmount: 12.9
      currencyCode: GBP

Response — 200 OK. Only when you submit with wait=true and the worker finishes before the broker timeout; the extracted data is returned inline:

request_id: req_9f8e7d6c5b
status: completed
validation: true
document_type: ReceiptPDF
confidence: 0.98
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."
message: Document processed with status completed.
formatted_result:
  multiple_receipts[1]:
    - tableItems[1]{quantity,description,totalAmount,unitPrice,taxAmount,netAmount,discountAmount}:
      1,SAUSAGE AND MASH,12.9,12.9,0.0,12.9,0.0
      documentId: "268472"
      supplierName: THE PLUME OF FEATHERS
      totalAmount: 12.9
      currencyCode: GBP

Response field reference (success)

FieldTypeDescription
request_idstringUnique id for the request. Use it to fetch the result (GET /v1/requests/{request_id}) and to bind a transaction id.
statusstringprocessing while queued/running; completed when the result is ready; failed on error; not_processed for unsupported docs.
status_urlstring(202) Path to poll for the result: /v1/requests/{request_id}.
document_urlstringPresigned URL to preview/download the stored document. Regenerated on each status fetch so it never expires.
poll_interval_secondsint(202) Suggested polling cadence; also sent as the Retry-After header.
result_retainedbool(202) Always true — the result is persisted and stays retrievable after completion.
submitted_at / completed_atdatetimeWhen the request was accepted and when processing finished.
processing_time_msintTotal processing duration once completed.
document_typestringThe classified type the document was processed as.
confidencefloatClassifier confidence (0.0–1.0).
validationboolMandatory-field validation outcome of the extracted result: true = all mandatory fields present, false = one or more missing. null while still processing / not yet validated. See “Mandatory-field validation” below.
formatted_resultobjectThe extracted data. Shape depends on the classified type (receipt / invoice / bank-statement). Receipts are wrapped in multiple_receipts[].

Unsupported document — 422 (classified as “other”)

When the classifier cannot identify the document as an invoice, receipt, or bank statement it is not processed — nothing is stored or queued. The classifier's reason (from the Qwen model) is returned.

filename: random_photo.jpg
status: not_processed
document_type: other
confidence: 0.88
message: "Document was not processed because it could not be identified as an invoice, receipt, or bank statement. Reason: Qwen2.5-VL: The image shows a landscape photograph with no financial content."

Error responses

StatusBody (TOON)Cause
400error: "Content-Type must be multipart/form-data"Unsupported content type.
400error: "file is required"Form mode without a file.
422status: not_processedDocument classified as other (see above).
500error: "<message>"Unexpected server error.
📮Postman Configuration
POST
https://apiocr.dexaitech.com/v1/documents/process-async
Send

Form mode: Headers → X-API-Key only (Postman sets the multipart boundary automatically). Body → form-data → key file (type: File) and optional key document_type (type: Text).

Toggle wait: add it to the URL as a query string — …/process-async?wait=true to block for the result, or omit it (or ?wait=false) for hit-and-run. This works for both form-data and JSON requests.

The extracted data is returned inline under formatted_result. Keep the request_id to correlate the request or to bind a transaction id (see below).

Fetch Result by request_id

Retrieve the status and final extracted result for a request. This is the endpoint you poll after a hit-and-run submit. The result is retained, so it can be fetched minutes or hours later without re-uploading.

GET/v1/requests/{request_id}Auth: X-API-Key

Pass the request_id returned by POST /v1/documents/process-async as a path parameter. The request is scoped to the authenticated user — a request_id belonging to another user returns 404.

Path parameters

FieldTypeRequiredDescription
request_idstringYesThe id returned at submit time, e.g. req_77aa11bb22.

Code Examples

curl -X GET "https://apiocr.dexaitech.com/v1/requests/req_77aa11bb22" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Accept: text/plain"

Response — still processing. Keep polling at poll_interval_seconds until status: completed (or failed). validation stays null until the result is ready:

request_id: req_77aa11bb22
status: processing
filename: receipt_scan.pdf
submitted_at: "2026-06-08T10:17:44.882210"
completed_at: null
processing_time_ms: null
error_message: null
validation: null
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."

Response — completed. The extracted data is under formatted_result (shape depends on the classified type), and validation reports whether all mandatory fields are present:

request_id: req_77aa11bb22
status: completed
filename: receipt_scan.pdf
submitted_at: "2026-06-08T10:17:44.882210"
completed_at: "2026-06-08T10:18:12.553001"
processing_time_ms: 27671
error_message: null
validation: true
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."
formatted_result:
  multiple_receipts[1]:
    - tableItems[1]{quantity,description,totalAmount,unitPrice,taxAmount,netAmount,discountAmount}:
      1,SAUSAGE AND MASH,12.9,12.9,0.0,12.9,0.0
      documentId: "268472"
      supplierName: THE PLUME OF FEATHERS
      date: 15/08/2025
      totalAmount: 12.9
      taxAmount: 0.0
      netAmount: 12.9
      currencyCode: GBP

Response — failed. error_message carries the reason; validation is false when the result lacks mandatory fields:

request_id: req_77aa11bb22
status: failed
filename: receipt_scan.pdf
submitted_at: "2026-06-08T10:17:44.882210"
completed_at: "2026-06-08T10:18:05.110200"
processing_time_ms: 20228
error_message: "Extraction failed: unreadable document"
validation: false
document_url: "https://bucket.s3.eu-west-2.amazonaws.com/uploads/12/abc.pdf?X-Amz-..."

Error responses

StatusDetailCause
404Request not foundUnknown request_id, or it belongs to another user.
401API key required / Invalid API keyMissing or invalid X-API-Key.

Mandatory-field validation

Every completed document is checked for the mandatory fields of its type. The boolean outcome is surfaced as the validation field on both the wait=true response and the fetch-by-id response. It never alters the extracted data.

validation: true — all mandatory fields for the document type are present.

validation: false — one or more mandatory fields are missing or empty.

validation: null — not yet validated (still processing).

When is a field considered missing?

A mandatory field fails validation when it is absent, null, the string "null", an empty string, whitespace-only, an empty list [], or an empty object {}. Numeric fields may be 0 (valid) but never null.

Mandatory fields by document type

TypeTop-level fieldsEach tableItems entry
InvoicesupplierName, documentId, date, dueDate, currencyCode, totalAmount, taxAmount, netAmount, discountAmount, tableItems (≥ 1 item)description, quantity, unitPrice, netAmount, taxAmount, discountAmount, totalAmount
ReceiptdocumentId, supplierName, date, totalAmount, taxAmount, netAmount, discountAmount, currencyCode, tableItems (≥ 1 item)description, quantity, unitPrice, netAmount, taxAmount, discountAmount, totalAmount
Bank statementbankName, accountHolderName, accountNumber, currencyCode, openingDate, closingDate, openingBalance, closingBalance, tableItems (≥ 1 item)date, description, creditAmount, debitAmount, balanceAmount

Receipts wrapped in multiple_receipts[] validate every receipt — validation is true only if all of them pass. Fields outside the lists above (e.g. invoice orderNumber, receipt paymentMethod) are ignored by validation.

Update Transaction ID

POST/v1/documents/update-transaction-idAuth: X-API-Key

Attach (or overwrite) a caller-supplied transaction_id on an existing processing request. Since the document upload endpoints no longer accept transaction_id as an input, call this endpoint after upload with the returned request_id to correlate the request with your own external transaction. The call is idempotent and scoped to the requests owned by the authenticated user.

Headers

HeaderValueRequired
X-API-KeyYOUR_API_KEYRequired
Content-Typeapplication/jsonRequired
Accepttext/plainOptional

Request Body

FieldTypeRequiredDescription
request_idstringRequiredIdentifier returned by one of the upload endpoints (e.g. req_abc123def456).
transaction_idstringRequiredCaller-supplied identifier to bind to the request. Re-sending the same value is a no-op; sending a different value overwrites the previous one.

Example:

{
  "request_id": "req_abc123def456",
  "transaction_id": "tx_abc123def456"
}

Code Examples

curl -X POST "https://apiocr.dexaitech.com/v1/documents/update-transaction-id" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "req_abc123def456",
    "transaction_id": "tx_abc123def456"
  }'
📮Postman Configuration
POST
https://apiocr.dexaitech.com/v1/documents/update-transaction-id
Send
Authorization
Headers
Body
Type:
Key:X-API-Key
Value:ak_xxxxxxxxxxxxxx
Add to:Header
KEYVALUE
X-API-KeyYOUR_API_KEY
Content-Typeapplication/json
Accepttext/plain
{
  "request_id": "req_abc123def456",
  "transaction_id": "tx_abc123def456"
}
Quick Steps: 1. Set method to POST → 2. URL: https://apiocr.dexaitech.com/v1/documents/update-transaction-id → 3. Headers Tab:

Response Example (200 OK)

success: true
request_id: "req_abc123def456"
transaction_id: "tx_abc123def456"

# Error responses (text/plain, TOON-encoded):
#   400  error: "request_id and transaction_id are required"
#   400  error: "Invalid JSON body"
#   404  error: "Request req_abc123def456 not found"
#   401  error: "API key required"  /  "Invalid API key"
#
# Notes:
#   - Idempotent: re-sending the same payload is safe.
#   - Sending a different transaction_id overwrites the previous value.
#   - Requests belonging to another user return 404 (not 403) to avoid
#     leaking existence.

System Endpoints

Health Check

GET/healthAuth: None (Public)

Check if the API server is running and healthy. This endpoint is public and requires no authentication.

Headers

HeaderValueRequired
Acceptapplication/jsonOptional

Code Examples

curl -X GET "https://apiocr.dexaitech.com/health"
📮Postman Configuration
GET
https://apiocr.dexaitech.com/health
Send
Headers
KEYVALUE
Acceptapplication/json
Quick Steps: 1. Set method to GET → 2. URL: https://apiocr.dexaitech.com/health → 3. No authentication required

Response Example (200 OK)

status:"healthy"
timestamp:"2025-01-27T12:00:00Z"
version:"1.0.0"
uptime_seconds:86400

Error Codes Reference

Status CodeMeaningCommon Causes
200SuccessRequest completed successfully
201CreatedResource created successfully
400Bad RequestMissing required fields, invalid format
401UnauthorizedMissing or invalid token/API key
403ForbiddenValid auth but insufficient permissions
404Not FoundResource doesn't exist
409ConflictResource already exists (e.g., email taken)
429Rate LimitedToo many requests
500Server ErrorInternal server error
503UnavailableService temporarily unavailable
504TimeoutRequest timed out during processing

Need Help?

If you have questions or run into issues, check our support resources.