DocuClipper logo

Async processing

Every extraction job runs asynchronously. The recommended path is to register a webhook and let DocuClipper push the completed extraction to you. Polling GET /api/v1/agent/jobs/:id is supported as a fallback.

Webhooks (recommended)

Subscribe to the doctype-specific event for your job:

  • bank_statement.extraction.completed — bank statements + check images
  • invoice.extraction.completed — invoices / receipts
  • form.extraction.completed — tax forms
  • document.extraction.failed — always subscribe to catch errors

Full setup, signature verification, retries, and cleanup live on the Webhooks page.

Polling (fallback)

bash
curl -s -H "Authorization: Bearer $PAT" \
  "https://www.docuclipper.com/api/v1/agent/jobs/12345"

Sample response when the job is finished:

json
{
  "id": "12345",
  "name": "sdk-demo-bank",
  "status": "Succeeded",
  "type": "ExtractData",
  "isGeneric": false,
  "isBankMode": true,
  "reviewed": false,
  "createdAt": "2026-05-26T15:27:04.682Z",
  "updatedAt": "2026-05-26T15:27:25.911Z",
  "documents": [
    { "id": "2666907", "originalname": "statement.pdf", "mimetype": "application/pdf", "numPages": 4, "isProcessed": true, "isScanned": false }
  ],
  "summary": { "totalDocuments": 1, "totalPages": 4, "transactionLineCount": 137 }
}

Job statuses

  • Pending — queued, not yet picked up by a worker
  • InProgress — worker actively processing
  • Succeeded — done; webhook fired (if subscribed) and GET /agent/jobs/:id/data is ready
  • PartiallySucceeded — a long file converted in parts: every part is done and some failed (see Long files)
  • Failed — see document.extraction.failed webhook for the error message

Long files

A PDF over 250 pages, or an upload with more pages than one job takes, is converted in parts automatically, as one or more jobs. Pages cost the same. Keep the jobId that POST /agent/jobs returned (its response says "convertsInParts": true):

  • GET /agent/jobs/:id reports the status of the whole upload, Succeeded only once every part is, and lists the parts under upload.parts.
  • /transactions and /data for that id return every part's rows in page order.
  • The document id you uploaded answers for its parts: GET /agent/documents/:documentId returns "convertedInParts": true and the parts with their page ranges.
  • Webhooks fire once for the whole upload, under the jobId you created, when every part is done. The payload covers every part that succeeded, and job.upload lists each part with its status. A failed part converted again later sends another event for the same jobId: each event is a full snapshot, so replace rather than append.
  • If some parts fail, the status is PartiallySucceeded: the data reads and the webhook carry the parts that succeeded, and upload.parts says which failed. Open a failed part in DocuClipper and click Convert again to retry it.

Why webhooks beat polling

  • No wasted requests. Most polls during the average job's ~30 second runtime return InProgress. Skip them.
  • The payload is free. The webhook body already contains the full extracted JSON — you don't need a follow-up GET /agent/jobs/:id/data call to get the result.
  • Failure handling. document.extraction.failed carries the error message inline. Polling makes you reconstruct it from the job row.