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.
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.
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").
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.
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.)
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.
