instance-2 / docqa.postman_collection.json
validops-east-3's picture
Deploy 72701a1d9ec33b1842d0a516ffdee753aaa754a9
efff1ff verified
Raw History Blame Contribute Delete
8.57 kB
{
"info": {
"_postman_id": "docqa-collection",
"name": "docqa — Document Data Extraction API",
"description": "Extract user-defined keys from invoices, receipts and scanned documents.\n\nNo response bodies are pre-filled here on purpose — run against a live server to see real values.\n\nHOW TO READ `status`\n extracted confidence >= threshold. Use it.\n low_confidence a span was found but fell below the threshold. Route to a human; do NOT treat as fact.\n not_found nothing usable on the page.\n error the key could not be processed.\n\nIMPORTANT: impira/layoutlm-invoices is extractive and has NO null-answer head. It returns *some* span for every input, including questions the document cannot answer. Measured hallucination rate on deliberately unanswerable invoice questions: 16.7%, with 0% abstention. That is why `status` must be checked before `value`, and why `low_confidence` means \"a human should look\", not \"this is the answer\".\n\nKEYS ARE FREE-FORM. `INVOICE NO`, `Total Amount` and `GSTIN` are all just strings; nothing is hard-coded. Spaces are significant: `Total Amount` != `TotalAmount`. Duplicates are preserved and returned index-aligned with the request, so zip the two lists rather than looking up by key.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{ "key": "baseUrl", "value": "http://127.0.0.1:7860" }
],
"item": [
{
"name": "Health",
"request": {
"method": "GET",
"header": [],
"url": { "raw": "{{baseUrl}}/health", "host": ["{{baseUrl}}"], "path": ["health"] },
"description": "Liveness plus model readiness. `status: ok` means the model is loaded and inference will work; `degraded` means the process is up but the model is not loaded, so extraction will 503."
}
},
{
"name": "Config (limits in effect)",
"request": {
"method": "GET",
"header": [],
"url": { "raw": "{{baseUrl}}/v1/config", "host": ["{{baseUrl}}"], "path": ["v1", "config"] },
"description": "Shows the model, device, batch size and the key/page/upload limits the server is currently enforcing. Useful for confirming an env var actually took effect."
}
},
{
"name": "Extract (multipart upload)",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [], "description": "Attach a real PDF, PNG, JPEG, TIFF or BMP" },
{ "key": "keys", "value": "INVOICE NO,Vendor Id,Invoice Date,Total Amount,GSTIN", "type": "text", "description": "COMMA-SEPARATED here. Whitespace inside a key is significant: 'Total Amount' != 'TotalAmount'." },
{ "key": "confidence_threshold", "value": "0.5", "type": "text", "description": "Optional 0..1. At or above -> extracted; below -> low_confidence." },
{ "key": "max_pages", "value": "3", "type": "text", "description": "Optional. LayoutLM's effective context is ~512 tokens, so page 2+ is often truncated anyway." }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] },
"description": "The main endpoint. Digital PDFs use the text layer; scanned PDFs and images are rasterised and OCR'd (poppler-utils and tesseract-ocr are installed in the image).\n\nErrors: 400 invalid keys, 413 over max_upload_mb (25 MB default), 415 unsupported media type, 422 unprocessable, 503 engine not ready, 504 over request_timeout_s."
}
},
{
"name": "Extract (base64 JSON)",
"request": {
"method": "POST",
"header": [{ "key": "Content-Type", "value": "application/json" }],
"body": {
"mode": "raw",
"raw": "{\n \"document_base64\": \"<base64 of a real PDF or image>\",\n \"keys\": [\"INVOICE NO\", \"Invoice Date\", \"Total Amount\", \"GSTIN\"],\n \"confidence_threshold\": 0.5,\n \"max_pages\": 3\n}",
"options": { "raw": { "language": "json" } }
},
"url": { "raw": "{{baseUrl}}/v1/extract/json", "host": ["{{baseUrl}}"], "path": ["v1", "extract", "json"] },
"description": "For clients that already hold the file in memory and want to skip multipart encoding. `keys` is a JSON ARRAY here, not a comma-separated string. A `data:...;base64,` URI prefix is accepted and stripped.\n\nGenerate the base64 with: `[Convert]::ToBase64String([IO.File]::ReadAllBytes('invoice.pdf'))`"
}
},
{
"name": "Edge cases",
"item": [
{
"name": "Empty keys -> 400",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [] },
{ "key": "keys", "value": " , ,", "type": "text" }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] }
},
"description": "Keys that are only whitespace or commas collapse to an empty list. Must be rejected rather than returning an empty result that looks like success."
},
{
"name": "threshold=1.0 suppresses all extractions",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [] },
{ "key": "keys", "value": "INVOICE NO,Total Amount", "type": "text" },
{ "key": "confidence_threshold", "value": "1.0", "type": "text" }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] }
},
"description": "Cheap regression check on the gating logic: at threshold 1.0 no field can come back as `extracted`. If any do, the threshold is being applied to the value instead of the status."
},
{
"name": "Duplicate keys returned separately",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [] },
{ "key": "keys", "value": "Total Amount,Total Amount", "type": "text" }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] }
},
"description": "Duplicates are NOT collapsed. Two keys in means two results out, index-aligned, so callers can zip without a key lookup. De-duplicate before sending if you do not want the wasted inference."
},
{
"name": "Unanswerable key (documents the no-abstention behaviour)",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [], "description": "Any ordinary invoice" },
{ "key": "keys", "value": "Passport Number of the Director", "type": "text" }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] }
},
"description": "The model ALWAYS returns a span, even when the answer is not on the page. Expect `low_confidence` with a near-zero confidence and a `value` that is some real token from the document. This is the single most important behaviour to understand before trusting the API, and the reason `status` must be checked before `value`."
},
{
"name": "Non-document file -> error",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{ "key": "file", "type": "file", "src": [], "description": "Attach a .txt file" },
{ "key": "keys", "value": "Total Amount", "type": "text" }
]
},
"url": { "raw": "{{baseUrl}}/v1/extract", "host": ["{{baseUrl}}"], "path": ["v1", "extract"] }
},
"description": "Unsupported media types must be rejected, not silently parsed into zero words."
}
]
}
]
}