Process File (Async)
Queue a document for asynchronous extraction and poll for results.
/api/v1/document/{extractionSchemaId}/process-file-async/{fileId}ASYNCQueue a document processing job asynchronously. The job runs in the background and you can poll for results using the get-file-data endpoint, or receive a webhook notification when complete.
Parameters
| Name | Type | In | Required | Description |
|---|---|---|---|---|
extractionSchemaId | string | path | Yes | The model's ID, the ID of a deployment that points at it, or the model's extraction schema ID. The extraction schema ID is returned when listing a project's deployments, and is also shown at the bottom of the data page in the dashboard. |
fileId | string | path | Yes | The file ID returned from the get-url endpoint after uploading. |
Body parameters
Every body parameter is optional. An empty JSON object {} is a valid body. Note that it does not guarantee a clean run: if this file was extracted before with overrides, they are stored on its job and are applied again when you send none. See Extraction Overrides.
| Name | Type | In | Required | Description |
|---|---|---|---|---|
columnDescriptions | object | body | No | Replacement extraction instructions for this run, keyed by column ID. |
tableInstructions | object | body | No | Replacement table instructions for this run, keyed by table ID. |
columnsOverride | object[] | body | No | Values you supply yourself. Each array item is a single-key object mapping one column ID to its value; the column is then not extracted. |
These are per-request overrides: they change this extraction without editing the model's schema, and they persist on the document's job for later re-runs. See Extraction Overrides for shapes, precedence, and limits.
Request
const response = await fetch(
`https://app.limai.io/api/v1/document/${SCHEMA_ID}/process-file-async/${fileId}`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${API_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({})
}
)
const { jobId, fileId: queuedFileId } = await response.json()Response
Job Queued (200)
{
"message": "Job queued for processing",
"fileId": "file_abc123def456",
"jobId": "job_789xyz012",
"modelId": "dep_abc123",
"viaDeploymentId": "dpl_prod01",
"schemaVersionId": "sv_7"
}modelId is the model the request resolved to, viaDeploymentId the deployment it arrived through (null when the request named the model directly), and schemaVersionId the schema version that deployment pins (null when it tracks the draft).
Duplicate Job (409)
If a processing job is already in progress for this file, a 409 Conflict is returned, carrying the id of the job already running.
{
"error": "Job already in progress for this file",
"jobId": "job_existing123"
}Paused Deployment (409)
A paused deployment takes no new extraction work. The same request addressed to the model, or through another active deployment on it, is unaffected.
{
"error": "Deployment dpl_prod01 on model dep_abc123 is PAUSED. Resume it before processing documents through it.",
"code": "DEPLOYMENT_PAUSED",
"deploymentId": "dpl_prod01",
"modelId": "dep_abc123",
"status": "PAUSED"
}Polling for Results
After queuing a job, poll the get-file-data endpoint until the status is COMPLETED or FAILED. Pass the returned jobId to validate that you are polling the job you queued — a wrong or stale ID fails with 404 instead of silently reading another document's job. COMPLETED guarantees the extracted data is present in the same response, so no settle delay or empty-result retry is needed. Re-processing a file reuses its jobId, flipping it back to PROCESSING.
async function pollForResults(fileId, jobId) {
const maxAttempts = 120
let attempts = 0
while (attempts < maxAttempts) {
const response = await fetch(
`https://app.limai.io/api/v1/document/get-file-data?fileId=${fileId}&jobId=${jobId}`,
{ headers: { "Authorization": `Bearer ${API_TOKEN}` } }
)
if (!response.ok) {
throw new Error(`get-file-data returned ${response.status}`)
}
const data = await response.json()
if (data.status === "COMPLETED") return data
if (data.status === "FAILED") throw new Error(data.errorMessage)
attempts++
await new Promise(r => setTimeout(r, 5000))
}
throw new Error("Polling timeout")
}Polling Best Practices
- Use 5-10 second intervals to balance responsiveness and server load
- Set a maximum timeout to prevent infinite polling
- Handle network errors with retry logic
- Consider using webhooks for real-time notifications instead of polling
- Store the
jobIdfor debugging purposes