File size: 8,566 Bytes
efff1ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{
  "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."
        }
      ]
    }
  ]
}