Sync vs async

When to use POST /v1/receipts, /verbose, or /async — and how to read an async result with GET /v1/receipts/{id}.

Three ways to submit

Tagjet exposes three POST endpoints under https://api.tagjet.app/v1/receipts. The simple and verbose endpoints are synchronous: they hold the connection open and return the extracted data in the same response. The async endpoint returns immediately with an id and processes the document in the background.

POST /v1/receipts — simple, synchronous

Returns a flattened subset of the most common fields — merchantName, date, totalAmount, taxAmount, currency — plus overallConfidence. Use it when you just need the headline values and want the smallest payload.

bash
curl https://api.tagjet.app/v1/receipts \
  -H "apikey: tj_live_your_key_here" \
  -F "file=@receipt.jpg"

POST /v1/receipts/verbose — full result, synchronous

Returns every field with a per-field confidence, line items (when enabled for your plan), and — on plans with fraud checks enabled — a quality object with image/tamper signals and a fraudFlag. Use it when you need confidences to drive auto-accept vs. human review.

python
import requests

resp = requests.post(
    "https://api.tagjet.app/v1/receipts/verbose",
    headers={"apikey": "tj_live_your_key_here"},
    files={"file": open("receipt.jpg", "rb")},
)
result = resp.json()
print(result["overallConfidence"], result["data"]["totalAmount"])

POST /v1/receipts/async — background, returns an id

Always responds 202 Accepted with a JSON body of { "id": "..." }. The document is processed by a worker and the result is fetched later. Use async for large PDFs, high-volume batches, or any case where you do not want to hold a request open. You may include an optional callbackUrl to receive a webhook when processing finishes (see "Webhooks & polling").

bash
curl https://api.tagjet.app/v1/receipts/async \
  -H "apikey: tj_live_your_key_here" \
  -F "file=@big-invoice.pdf"
# -> 202  { "id": "0e0b1f1a-..." }

GET /v1/receipts/{id} — read an async (or any) result

Fetch a scan by its id. The response uses the verbose shape and includes a status field: while the worker is still processing you will see a non-terminal status; once finished, status is DONE with the full data (or an error object if extraction failed). Poll this endpoint until status is terminal, or rely on a webhook callback instead.

javascript
const res = await fetch(
  `https://api.tagjet.app/v1/receipts/${id}`,
  { headers: { apikey: 'tj_live_your_key_here' } }
)
const scan = await res.json()
if (scan.status === 'DONE') {
  console.log(scan.data.merchantName.value)
}

XML instead of JSON

Any of these endpoints can return XML instead of JSON. Add ?format=xml to the URL, or send an Accept: application/xml header. (JSON bodies may also carry a "format" field, but an explicit ?format= query parameter always wins.)

bash
curl "https://api.tagjet.app/v1/receipts/verbose?format=xml" \
  -H "apikey: tj_live_your_key_here" \
  -F "file=@receipt.jpg"

Choosing

Reach for synchronous /v1/receipts or /verbose for interactive, single-document flows where a couple of seconds of latency is fine. Reach for /async for bulk processing, very large files, or when you would rather be notified than wait. The simple endpoint trims the payload; verbose gives you confidences and (where enabled) fraud signals.