Bounding Boxes (bbox)
bbox locates where on the original page an extracted value came from — coordinates you can use to highlight or crop that region of the image.
bbox is an optional, separately-billed second pass, not something that comes free with every extraction. It is not automatically included in a normal process call or in results unless you explicitly ask for it — and, importantly, it can also be triggered automatically by an account-level setting even if you never call the bbox endpoints yourself. See "Billing and account settings" below before assuming your usage matches only what you explicitly requested.
Triggering annotation
Once a document has finished extracting (status: "succeeded"), trigger the bbox pass explicitly:
curl -X POST https://imagetotable.ai/api/v1/documents/3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f/bbox \
-H "Authorization: Bearer $API_KEY"This spends credits and enqueues a bbox job (202 Accepted) — or, if a bbox job for this document is already running from an earlier call to this same endpoint, returns 200 without enqueuing or charging again. This already_running flag is about a second bbox job for the same document, not about the document's extraction — you can only reach this endpoint on a document that's already status: "succeeded", so extraction itself is never what's "already running" here:
{
"document_id": "3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f",
"group_batch_id": "bx_8a3c2e1f",
"status": "processing",
"already_running": false,
"row_groups": 1
}This endpoint accepts an Idempotency-Key header — see Idempotency, since this is a paid, credit-spending action.
Checking status and results
curl https://imagetotable.ai/api/v1/documents/3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f/bbox \
-H "Authorization: Bearer $API_KEY"{
"document_id": "3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f",
"exists": true,
"status": "succeeded",
"group_batch_id": "bx_8a3c2e1f",
"rows": {
"0": {
"invoice_number": { "x1": 0.121, "y1": 0.084, "x2": 0.312, "y2": 0.108, "unit": "normalized" },
"total_amount": { "x1": 0.702, "y1": 0.611, "x2": 0.881, "y2": 0.639, "unit": "normalized" }
}
}
}exists: false means no bbox job has ever been triggered for this document — a very common, expected state, not an error. Coordinates are x1/y1/x2/y2 floats normalized to the 0–1 range (top-left and bottom-right corners), independent of the original image's actual pixel dimensions.
Instead of polling this endpoint, register a webhook (see the Webhooks guide) on the document's batch — a bbox job reaching a terminal status delivers a document.bbox_completed event to that same callback URL, alongside whatever batch.completed events it already receives. There's no separate registration step for bbox specifically; it reuses the batch's existing webhook.
Getting bbox inline with results
Instead of the standalone endpoints above, you can also ask the batch results endpoint to backfill already-computed bbox data with ?include=bbox:
curl "https://imagetotable.ai/api/v1/batches/260716-4K9P/results?include=bbox" \
-H "Authorization: Bearer $API_KEY"When you do, every field value in line_items changes shape from a bare scalar to {"value": ..., "bbox": ...}, with bbox set to null if nothing has been computed for that field:
{ "invoice_number": { "value": "INV-1042", "bbox": { "x1": 0.121, "y1": 0.084, "x2": 0.312, "y2": 0.108, "unit": "normalized" } } }?include=bbox only reads back data that already exists — it never triggers a new, billable annotation job on your behalf. If a field's bbox hasn't been computed yet, you get null for it, not an implicit charge. Trigger annotation explicitly with POST .../bbox first.
Fields that are entirely a location
Some fields aren't a piece of text with an incidental location — the field's entire purpose is the location (for example, a field asking to "locate the portrait photo" on an ID document). For these, the value comes back as a richer object regardless of whether you asked for ?include=bbox, since the location is the answer, not optional metadata about one:
{
"portrait_location": {
"type": "image_region",
"bbox": { "x1": 0.05, "y1": 0.12, "x2": 0.28, "y2": 0.44, "unit": "normalized" },
"image_url": "https://imagetotable.ai/api/v1/documents/3f9c9e2a-1b7d-4e2b-9a3e-2f5b6c7d8e9f/image?crop=0.03%2C0.10%2C0.30%2C0.46"
}
}image_url points at GET /documents/{document_id}/image?crop=... — a real, fetchable cropped JPEG of that region (padded slightly wider than the raw coordinates so a tight crop is less likely to clip part of what you wanted), not just four numbers you'd have to crop out of the original image yourself. You can also call that endpoint directly with your own ?crop=x1,y1,x2,y2 (same 0–1 normalized convention) against any document, whether or not it came from a bbox job.
Known limitation: coordinates are model-direct, not pixel-verified
bbox coordinates come straight from the underlying model's own visual grounding — there is no pixel-level verification step that checks whether a given box is drawn tightly or loosely around its target. On simple, sparse documents this is usually close enough to use directly. On complex or densely-packed documents — tables with many columns, forms with tightly-spaced fields — precision degrades measurably, and boxes can end up too tight, too loose, or occasionally reference the wrong nearby field. Don't treat bbox output as an exact, pixel-perfect crop source — if your workflow depends on precise cropping (for downstream OCR, legal evidence, etc.), verify the results rather than trusting them blindly, especially on dense layouts.
The image_url convenience crops (both for pure-location fields and for your own ?crop= calls against a bbox-derived box) are generated with a small amount of padding around the reported coordinates, to reduce the chance of a too-tight box clipping real content — this is a forgiveness measure, not a precision fix. The raw bbox coordinates returned in the JSON itself are always exactly what the model reported, unpadded.
Billing and account settings
Whether bbox annotation runs is not 100% determined by whether you call the bbox endpoints yourself. Your account has a setting — auto_annotate_bbox, configured on the web app, not through this API — that, when enabled, automatically triggers (and bills) a bbox pass after every completed extraction, including ones started through v1. If your account has this setting on, you can be billed for bbox annotation without ever calling POST .../bbox yourself. See Account Settings and API Behavior for the full picture of which account-level settings the API does and doesn't let you override per request.