Limai Docs
API ReferenceDocument Processing

Get File Data

Retrieve extracted data and processing status for a single file.

GET/api/v1/document/get-file-data

Retrieve processed document data and check processing status. This endpoint supports both synchronous and asynchronous workflows, returning either the extracted data or processing status based on the job state.

Use this endpoint to:

  • Poll for results after submitting an async processing job
  • Fetch extracted data using metadata from webhook events

Parameters

NameTypeInRequiredDescription
fileIdstringqueryYesThe file ID from the upload response or webhook event payload.
extractionSchemaIdstringqueryNoOptional. The model's ID or the model's extraction schema ID; validates that the file belongs to it. A deployment ID does not match and returns 400.
jobIdstringqueryNoOptional. Validate the read against a specific extraction job (the jobId returned by process-file-async) — recommended when polling a job you just queued. A jobId that does not exist or does not belong to this file and deployment returns 404 Extraction job not found; an explicitly empty jobId returns 400.
includestringqueryNoComma-separated list of optional enrichments to include: confidence, boundingBoxes, validations, metadata. Omit for the plain data payload.

Status Semantics

The status is a promise about the body in the same response:

  • COMPLETED guarantees the extracted data is present and readable in this response. A successful extraction that produced no rows still returns COMPLETED with an empty data.tables — completion is gated on the job's write being committed, never on the row count. You never need a settle delay or an "empty result" re-poll: if the status is COMPLETED, the data you see is the data.
  • A job that is queued but not yet started reports PROCESSING (not COMPLETED with stale or empty data). Keep polling.
  • A job that stalled reports FAILED with the message Processing stalled, rather than a partial COMPLETED. Stall detection is a watchdog verdict, not always the job's last word: a run that was wrongly declared stalled (for example during a transient infrastructure hiccup) can still finish, flipping a FAILED poll to COMPLETED on a later read.

When you re-process a file, its extraction job resets and reports PROCESSING again until the new run finishes — with the same jobId as before. To validate that you are polling the job you queued, pass its jobId (see below).

Optional Enrichments

By default the response contains only the extracted values. Pass include to enrich each cell (and, for validations, the file envelope):

  • confidence — adds a confidence object to every cell with overall, llm, layout, and consensus scores (0–1, null when not computed).
  • boundingBoxes — adds a boundingBoxes array to every cell. Each box is {"page": "2", "bbox": [yMin, xMin, yMax, xMax]} in normalized 0–1000 coordinates with the origin at the top-left. null when boxes were never computed for the cell.
  • validations — adds a validations array to every cell and a file-level validations object carrying the document's status, its failCount, the custom validation results (checks) and the validation rule results (rules). See Validations.
  • metadata — adds a top-level _meta block describing the document the data came from, beside fileId and status. See Metadata.

Every cell's metadata also carries the column slug, alongside the table-level and column-list slugs, so responses can be consumed by stable slug instead of display name.

Note: documents processed through the synchronous process-file endpoint skip the confidence and bounding-box stages, so those fields stay null for such files even when requested. Use the async processing endpoints when you need them.

Request

const response = await fetch(
`https://app.limai.io/api/v1/document/get-file-data?fileId=${fileId}`,
{
  headers: { "Authorization": `Bearer ${API_TOKEN}` }
}
)
const data = await response.json()

Job-scoped polling

After process-file-async, pass the returned jobId when polling. A document has one extraction job per model: re-processing resets that same record back to PROCESSING and returns the same jobId again, so a job-scoped read and a plain read of the same file always agree on status. What jobId buys is validation — you are provably reading the job you queued, and a typo, a stale ID, or a mix-up between files fails loudly with 404 Extraction job not found instead of silently reporting another document's status.

Two consequences worth knowing:

  • Re-queuing a completed document flips its (unchanged) jobId back to PROCESSING — a poller holding that ID correctly starts waiting again.
  • A jobId can stop existing: moving a file between models or cancelling a job with record deletion removes it, so a poll can start returning 404 mid-flight. Treat that as terminal for the ID and fall back to polling without jobId.
const response = await fetch(
`https://app.limai.io/api/v1/document/get-file-data?fileId=${fileId}&jobId=${jobId}`,
{
  headers: { "Authorization": `Bearer ${API_TOKEN}` }
}
)
const data = await response.json()

Response Types

The response format depends on the processing state of the file.

PROCESSING

The job is still running. Continue polling.

Response200
{
  "status": "PROCESSING",
  "fileId": "file_abc123def456",
  "jobId": "job_789xyz012"
}

CLASSIFYING

The file is being classified (when using async classification).

Response200
{
  "status": "CLASSIFYING",
  "fileId": "file_abc123def456",
  "message": "Document is being classified"
}

COMPLETED

Processing finished successfully. Contains the full extracted data.

Response200
{
  "status": "COMPLETED",
  "message": "File data retrieved successfully.",
  "fileId": "file_abc123def456",
  "extractionSchemaId": "schema_xyz789",
  "deployment": {
    "id": "dep_abc123",
    "name": "Invoice Extraction v1"
  },
  "data": {
    "tables": {
      "table_id_1": {
        "id": "table_id_1",
        "name": "Invoices",
        "slug": "invoices",
        "columns": [
          {
            "id": "col_1",
            "name": "Invoice Number",
            "type": "TEXT",
            "slug": "invoice_number"
          }
        ],
        "rows": [
          {
            "id": "row_1",
            "index": "0",
            "status": "PENDING",
            "cells": {
              "Invoice Number": {
                "value": "INV-001",
                "columnId": "col_1",
                "metadata": {
                  "id": "col_1",
                  "type": "TEXT",
                  "description": null,
                  "slug": "invoice_number"
                }
              }
            }
          }
        ]
      }
    }
  }
}

deployment names the model the document belongs to — its id and name — and extractionSchemaId that model's extraction schema ID. This read is not addressed through a deployment, so it reports no deployment pointer; the pointer a document arrived through is on the webhook payloads instead.

COMPLETED with enrichments

With include=confidence,boundingBoxes,validations, each cell carries the requested extras and the envelope gains a file-level validations object.

Response200
{
  "status": "COMPLETED",
  "fileId": "file_abc123def456",
  "data": {
    "tables": {
      "table_id_1": {
        "rows": [
          {
            "id": "row_1",
            "cells": {
              "Invoice Number": {
                "value": "INV-001",
                "columnId": "col_1",
                "metadata": {
                  "id": "col_1",
                  "type": "TEXT",
                  "description": null,
                  "slug": "invoice_number"
                },
                "confidence": {
                  "overall": 0.94,
                  "llm": 0.95,
                  "layout": 0.91,
                  "consensus": null
                },
                "boundingBoxes": [
                  {
                    "page": "1",
                    "bbox": [
                      120,
                      45,
                      160,
                      300
                    ]
                  }
                ],
                "validations": [
                  {
                    "checkKey": "invoice-number-format",
                    "source": "SCRIPT",
                    "passed": true,
                    "message": "Matches expected format",
                    "expected": null,
                    "actual": null,
                    "name": null,
                    "scope": null,
                    "operator": null,
                    "amended": false,
                    "previousValue": null,
                    "amendedValue": null
                  },
                  {
                    "checkKey": "vrule_2a8",
                    "source": "RULE",
                    "passed": true,
                    "message": null,
                    "expected": "non-empty value",
                    "actual": "INV-001",
                    "name": "Invoice Number is not empty",
                    "scope": "FIELD",
                    "operator": "IS_NOT_EMPTY",
                    "amended": false,
                    "previousValue": null,
                    "amendedValue": null
                  }
                ]
              }
            }
          }
        ]
      }
    }
  },
  "validations": {
    "status": "failed",
    "failCount": 1,
    "checks": [
      {
        "key": "invoice-number-format",
        "name": "Invoice number format",
        "source": "STATIC",
        "status": "PASSED",
        "behavior": "SURFACE",
        "findings": [],
        "errorMessage": null,
        "startedAt": "2026-07-21T10:00:00.000Z",
        "completedAt": "2026-07-21T10:00:05.000Z"
      }
    ],
    "rules": {
      "crossField": [
        {
          "ruleId": "vrule_9k1",
          "passed": false,
          "expected": "1250.00",
          "actual": "1180.00",
          "name": "Net Amount vs Gross Amount",
          "scope": "CROSS_FIELD",
          "operator": "EQUALS",
          "message": "Net Amount vs Gross Amount: expected 1250.00, got 1180.00"
        }
      ],
      "row": [],
      "document": []
    }
  }
}

FAILED

Processing failed. Check the error message for details.

Response200
{
  "status": "FAILED",
  "fileId": "file_abc123def456",
  "jobId": "job_789xyz012",
  "errorMessage": "Document format not supported or file is corrupted"
}

Validations

include=validations reports both kinds of check: the model's validation rules and its custom validations (scripts and validator agents). Validations explains the difference, how a rule's name and message are built, and what the status values mean.

Per-cell entries

FieldTypeDescription
checkKeystring | nullThe rule's id for RULE entries, the script's key for SCRIPT and AGENT entries
sourceRULE | SCRIPT | AGENTWhich system produced the entry
passedbooleanWhether the check held for this cell
messagestring | nullFor rules, the composed failure detail, null when the rule passed. For scripts and agents, the finding's own message
expected / actualstring | nullWhat the check required and what it saw
namestring | nullThe rule's name or generated label. null for SCRIPT and AGENT entries, whose name is on the matching checks[] entry
scopestring | nullFIELD or COLUMN for a rule evaluated on this cell; null for custom checks and for rules that no longer exist
operatorstring | nullThe rule's operator; null for custom checks
amendedbooleantrue once a RESEND check has corrected this cell's value. Always false for rules
previousValue / amendedValueany | nullThe value before and after a RESEND correction. null unless amended is true

The file-level object

FieldTypeDescription
statuspending | passed | failed | error | nullThe document's validation status across rules and custom validations
failCountnumber | nullFailing checks on the document, rules and custom checks together. null before anything has been evaluated
checksarrayOne entry per custom validation, with its key, name, source, status, behavior, findings and timings
rulesobject | nullCross-field, row and document rule results, grouped by scope. null when no rule results are stored for the document

Each entry of rules.crossField and rules.document is {ruleId, passed, expected, actual, name, scope, operator, message}. Each entry of rules.row is {ruleId, passed, rowResults, name, scope, operator, message}, with one {rowId, passed, expected, actual} per row. Field and column rule results are on the cells instead.

A rule deleted since its results were written is still listed, with scope and operator set to null and a name derived from the stored result.

The buckets are empty when the model has no validation rules, when the document was extracted before its model had any, and when rule evaluation failed — in that last case status is error.

Metadata

include=metadata adds a reserved _meta key to the response envelope, beside fileId, status and data. It describes the document itself, not its cells, so it is the one enrichment that leaves the extracted values untouched.

Response200
{
  "status": "COMPLETED",
  "fileId": "file_abc123def456",
  "extractionSchemaId": "schema_xyz789",
  "data": {
    "tables": {}
  },
  "_meta": {
    "documentId": "cmm3k7r2v0007",
    "fileName": "broker-quote-2026-09.pdf",
    "submittedAt": "2026-09-01T10:12:33.000Z",
    "submittedBy": {
      "id": "usr_8a2f",
      "name": "Jane Doe",
      "email": "jane@acme.com"
    },
    "extractedAt": "2026-09-01T10:14:02.000Z"
  }
}

Fields of _meta:

FieldDescription
documentIdThe file's ID.
fileNameThe uploaded file name.
submittedAtWhen the file was uploaded, shown as "Date Added" in the grid.
submittedByThe user who uploaded the document, as { id, name, email }. null when the uploader is unknown.
extractedAtWhen the latest extraction run completed. null when no run has completed.

_meta is present whenever the file was found and accessible, whatever its status — a PROCESSING, CLASSIFYING or FAILED response carries it too. A file that does not exist or is not accessible returns no _meta at all.

An unrecognized include value returns a 400 naming the allowed values, metadata among them.

Backwards compatibility

Without metadata in include, the response shape is exactly the one documented under Response Types.

On this page