Extract

Pull structured fields out of documents — invoices, receipts, and your own schemas.

dev · https://api.dev.oprag.ai

Extract takes a document and returns typed fields rather than an answer. It is a separate service with its own error envelope and its own key requirements: integration keys only, workspace-scoped.

The flow

  1. POST /v1/extract/upload-urls reserves a job and returns a presigned S3 POST.
  2. You POST the file straight to S3, bypassing the API's body limit.
  3. POST /v1/extract/jobs/{jobId}/confirm verifies the object and queues the job.
  4. GET /v1/extract/jobs/{jobId} polls until the status is terminal.

Endpoints

GET /extract/health

Extract service liveness. Returns checks.extract ok when EXTRACT_* env vars are set. Separate from GET /health on the main API Lambda.

Auth Public — no credential

Path parameters

None.

Query parameters

None.Serves a fixed document and takes no input at all.

Body parameters

None.GET requests carry no body.

Response

200 Success

JSON
{
  "status": "ok",
  "version": "0.1.0",
  "checks": { "extract": "ok" }
}

Status codes

Status Meaning
200 Success.
429 Rate limited. See rate limits.

curl

Shell
curl -X GET 'https://api.dev.oprag.ai/extract/health' \
  -H 'X-Oprag-Key: sk_live_...'
POST /v1/extract/upload-urls

Reserve an extraction job and get a presigned S3 POST for the file.

Auth Integration key

Before you call it

  • Uploading straight to S3 is what keeps large files clear of API Gateway's request body limit.
  • Supported formats: PDF, PNG, JPEG, GIF, and WebP (max 10 MB per file). PDFs may be up to 100 MB; the ceiling is enforced by content-length-range on the presigned POST and re-checked on confirm.
  • POST the file to uploadUrl as multipart/form-data — every entry of uploadFields first, then the file — and then call POST /v1/extract/jobs/{jobId}/confirm. The URL is valid for expiresIn seconds.
  • The response is a CreateExtractUploadUrlResponse: jobId, status: "upload_pending", uploadUrl, uploadFields, expiresIn, and pollUrl.
  • Integration keys only, scoped to the workspace. Each call counts toward EXTRACT_DAILY_JOB_LIMIT.
  • Available in dev only until enable_extract is turned on in staging and production.

Path parameters

None.

Query parameters

None.Everything it needs is in the request body.

Body parameters

Name Type Required Description
filename string Required Original file name.
contentType string Optional MIME type. Defaults to application/octet-stream; the extension is used when it disagrees.
schemaId string Optional A built-in id, a custom_ id, or "auto" to classify the document.
allowedSchemas string[] Optional Restricts what "auto" may classify the document as.
clientRef string Optional Your own reference, echoed back on the job.

Request

JSON
{
  "filename": "annual-contract.pdf",
  "contentType": "application/pdf",
  "schemaId": "contract",
  "clientRef": "erp-sync-8842"
}

Response

200 Success

JSON
{
  "jobId": "ext_a1b2c3d4e5f6789012345678abcdef01",
  "status": "upload_pending",
  "uploadUrl": "https://extract-inbox.s3.amazonaws.com/",
  "uploadFields": {
    "key": "inbox/co_abc/ext_a1b2…/annual-contract.pdf",
    "Policy": "…",
    "X-Amz-Signature": "…",
    "Content-Type": "application/pdf"
  },
  "expiresIn": 900,
  "pollUrl": "/v1/extract/jobs/ext_a1b2c3d4e5f6789012345678abcdef01"
}

Status codes

Status Meaning
200 Success.
400 unsupported_format — the declared contentType or filename is not supported.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
429 Rate limited. See rate limits.

curl

Shell
curl -X POST 'https://api.dev.oprag.ai/v1/extract/upload-urls' \
  -H 'X-Oprag-Key: sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"filename": "annual-contract.pdf","contentType": "application/pdf","schemaId": "contract","clientRef": "erp-sync-8842"}'
POST /v1/extract/jobs

Deprecated for file submission — always returns 400 and points at the presigned upload flow.

Auth Integration key

Before you call it

  • Use POST /v1/extract/upload-urls, POST the file to S3 with uploadFields, then POST /v1/extract/jobs/{jobId}/confirm. GET /v1/extract/jobs/{jobId} polls status from there.
  • Integration keys only, scoped to the workspace. Available in dev only until enable_extract is turned on in staging and production.

Path parameters

None.

Query parameters

None.Everything it needs is in the request body.

Body parameters

Defined by the caller.Deprecated for file submission — always returns 400 pointing at the upload-url flow.

Request

JSON
# Direct multipart/fileUrl submit removed — use presigned upload:
curl -X POST "$API_URL/v1/extract/upload-urls" \
  -H "X-Oprag-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename":"invoice.pdf","contentType":"application/pdf","schemaId":"invoice"}'

Response

200 Success

JSON
{
  "error": "Use POST /v1/extract/upload-urls, POST the file to S3 with uploadFields, then POST /v1/extract/jobs/{jobId}/confirm",
  "code": "VALIDATION_ERROR"
}

Status codes

Status Meaning
202 Success.
400 The request body failed validation.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
429 Rate limited. See rate limits.

curl

Shell
curl -X POST 'https://api.dev.oprag.ai/v1/extract/jobs' \
  -H 'X-Oprag-Key: sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '# Direct multipart/fileUrl submit removed — use presigned upload:curl -X POST "$API_URL/v1/extract/upload-urls" \-H "X-Oprag-Key: sk_live_YOUR_KEY" \-H "Content-Type: application/json" \-d '{"filename":"invoice.pdf","contentType":"application/pdf","schemaId":"invoice"}''
POST /v1/extract/jobs/{jobId}/confirm

Confirm the uploaded object and queue the job for extraction.

Auth Integration key

Before you call it

  • Call it after the file has been POSTed to uploadUrl. The object is checked for existence, non-emptiness, size (100 MB for PDFs, 10 MB for images), and a supported format by both MIME type and magic bytes.
  • On success the job moves from upload_pending to queued and { jobId, companyId, schemaId } is enqueued for the worker.
  • The response is a ConfirmExtractJobResponse: jobId, status: "queued", and pollUrl.
  • Confirming a job that is already queued returns the same 202 again, so retrying is safe.

Path parameters

Name Type Required Description
jobId string Required Extract job id.

Query parameters

None.Acts on the resource named in the path; there is nothing to choose.

Body parameters

None.Confirms the object already uploaded for this job id.

Request

JSON
# No body — jobId in path only
curl -X POST "$API_URL/v1/extract/jobs/ext_a1b2c3d4e5f6789012345678abcdef01/confirm" \
  -H "X-Oprag-Key: sk_live_YOUR_KEY"

Response

202 Success

JSON
{
  "jobId": "ext_a1b2c3d4e5f6789012345678abcdef01",
  "status": "queued",
  "pollUrl": "/v1/extract/jobs/ext_a1b2c3d4e5f6789012345678abcdef01"
}

Status codes

Status Meaning
202 Success.
400 The object is missing, empty, or oversize; unsupported_format when its MIME type or magic bytes are wrong.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
404 No such job in this workspace. A malformed job id is also a 404, from the router.
409 The job is already processing or terminal — but not merely queued.
429 Rate limited. See rate limits.
503 The enqueue failed. The body still carries jobId and pollUrl, with status: "failed".

curl

Shell
curl -X POST 'https://api.dev.oprag.ai/v1/extract/jobs/{jobId}/confirm' \
  -H 'X-Oprag-Key: sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '# No body — jobId in path onlycurl -X POST "$API_URL/v1/extract/jobs/ext_a1b2c3d4e5f6789012345678abcdef01/confirm" \-H "X-Oprag-Key: sk_live_YOUR_KEY"'
GET /v1/extract/jobs/{jobId}

Poll an extraction job until it reaches completed or failed.

Auth Integration key

Before you call it

  • status runs upload_pendingqueuedprocessingcompleted or failed. A failed job carries error.code.
  • The shape of data follows schemaId (or detectedSchemaId): one of InvoiceExtractData, ReceiptExtractData, PurchaseOrderExtractData, CreditNoteExtractData, DeliveryNoteExtractData, BankStatementExtractData, PayslipExtractData, UtilityBillExtractData, TicketExtractData, TaxFormExtractData, ContractExtractData, GstInvoiceExtractData, or IdDocumentExtractData — alongside confidence, provenance, and billing.
  • Auto-detected jobs set schemaId to the detected type and add classificationMode, requestedSchemaId, detectedSchemaId, and classification { schemaId, confidence, reason? }, where reason appears only when the model supplies one.
  • Scanned documents that go through OCR add processingTier: "ocr", documentConfidence, validationIssues, provenance.bbox from Textract geometry, and sometimes ocrQualityWarning.
  • Large PDFs — 21 pages or more, over 10 MB, or a bank statement of 4 pages or more — run a chunked pipeline in 20-page chunks. While processing they add jobTier: "large", phase (preprocess, extracting, or merging), totalPages, and progress { chunksCompleted, chunksTotal }.
  • provenance may carry an excerpt, for any schema.
  • Typical latency on dev is 5–15 s for an explicit schema and 10–30 s for auto-detect. Chunked PDFs take minutes — poll and watch progress.

Path parameters

Name Type Required Description
jobId string Required Extract job id.

Query parameters

None.Reads the resource named in the path; there is nothing else to select.

Body parameters

None.GET requests carry no body.

Responses

200 Success

JSON
{
  "jobId": "ext_a1b2c3d4e5f6789012345678abcdef01",
  "status": "completed",
  "schemaId": "invoice",
  "extractedAt": "2026-07-11T21:20:46.573Z",
  "data": {
    "vendorName": "Contoso Ltd.",
    "invoiceNumber": "INV-100",
    "invoiceDate": "11/15/2019",
    "dueDate": "12/15/2019",
    "subtotal": 100,
    "taxAmount": 10,
    "totalAmount": 110,
    "lineItems": [{ "description": "Consulting Services", "quantity": 2, "unitPrice": 30, "amount": 60 }]
  },
  "confidence": { "invoiceNumber": 1, "totalAmount": 1 },
  "documentConfidence": 0.95,
  "processingTier": "digital",
  "billing": { "pagesProcessed": 1, "billable": true }
}

// Large PDF while processing (optional progress fields):
// "status": "processing", "jobTier": "large", "phase": "extracting",
// "totalPages": 80, "progress": { "chunksCompleted": 2, "chunksTotal": 4 }

// Auto-detect completed (requestedSchemaId was "auto"):
// "schemaId": "invoice", "classificationMode": "auto", "requestedSchemaId": "auto",
// "detectedSchemaId": "invoice", "classification": { "schemaId": "invoice", "confidence": 0.91 }

200 Another example

JSON
{
  "jobId": "ext_656c03b65e61454e5bc562567a0f98b3",
  "status": "completed",
  "schemaId": "receipt",
  "clientRef": "invoicehome-receipt",
  "extractedAt": "2026-07-11T22:31:16.976Z",
  "data": {
    "merchantName": "East Repair Inc.",
    "merchantAddress": "1912 Harvest Lane New York, NY 12210",
    "receiptNumber": "US-001",
    "receiptDate": "11/02/2019",
    "subtotal": 145,
    "taxAmount": 9.06,
    "totalAmount": 154.06,
    "currency": "$",
    "lineItems": [
      { "description": "Front and rear brake cables", "quantity": 1, "unitPrice": 100, "amount": 100 },
      { "description": "New set of pedal arms", "quantity": 2, "unitPrice": 15, "amount": 30 },
      { "description": "Labor 3hrs", "quantity": 3, "unitPrice": 5, "amount": 15 }
    ]
  },
  "confidence": { "merchantName": 1, "receiptNumber": 1, "totalAmount": 1, "lineItems": 1 },
  "provenance": [{ "field": "merchantName", "page": 1, "excerpt": "East Repair Inc." }],
  "billing": { "pagesProcessed": 1, "billable": true }
}

Status codes

Status Meaning
200 Success.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
404 No such job in this workspace — another workspace's job id included. A malformed job id is a 404 from the router, not a 400.
429 Rate limited. See rate limits.

curl

Shell
curl -X GET 'https://api.dev.oprag.ai/v1/extract/jobs/{jobId}' \
  -H 'X-Oprag-Key: sk_live_...'
GET /v1/extract/schemas

List the schemas this workspace can extract with.

Auth Integration key

Before you call it

  • Built-in schemas come first, then the workspace's custom ones sorted by schemaId.

Path parameters

None.

Query parameters

Name Type Required Default Description
includeBuiltin boolean Optional true Send the literal string false to list custom schemas only. Every other value keeps the built-in catalog.
status "active" | "archived" | "all" Optional active Which custom schemas to include. An unrecognised value falls back to active rather than erroring.

Body parameters

None.GET requests carry no body.

Response

200 Success

JSON
{
  "schemas": [
    { "schemaId": "invoice", "displayName": "Invoice", "source": "builtin", "status": "active" },
    { "schemaId": "receipt", "displayName": "Receipt", "source": "builtin", "status": "active" },
    {
      "schemaId": "custom_purchase_order",
      "displayName": "Purchase order",
      "source": "custom",
      "status": "active",
      "version": 2,
      "fieldCount": 7,
      "createdAt": "2026-06-01T10:00:00.000Z",
      "updatedAt": "2026-06-14T09:12:00.000Z"
    }
  ]
}

Status codes

Status Meaning
200 Success.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
429 Rate limited. See rate limits.

curl

Shell
curl -X GET 'https://api.dev.oprag.ai/v1/extract/schemas' \
  -H 'X-Oprag-Key: sk_live_...'
POST /v1/extract/schemas

Define a custom extraction schema for your own document type.

Auth Integration key

Path parameters

None.

Query parameters

None.Everything it needs is in the request body.

Body parameters

Name Type Required Description
schemaId string Required Must be custom_-prefixed. Built-in ids are rejected with 409.
displayName string Required Shown in the schema list. Up to 128 characters.
documentDescription string Required What this kind of document is. Up to 512 characters.
extractionHints string Optional Document quirks worth telling the extractor. Up to 512 characters.
fields any[] Required Field definitions: key, type, and description each.

Request

JSON
{
  "schemaId": "custom_purchase_order",
  "displayName": "Purchase order",
  "documentDescription": "A supplier purchase order with line items and totals.",
  "extractionHints": "Totals are on the last page. Currency is always the document currency.",
  "fields": [
    { "key": "poNumber", "type": "string", "description": "Purchase order number" },
    { "key": "orderDate", "type": "date", "description": "Date the order was raised" },
    { "key": "total", "type": "number", "description": "Order total including tax" }
  ]
}

Response

200 Success

JSON
{
  "schemaId": "custom_purchase_order",
  "displayName": "Purchase order",
  "source": "custom",
  "status": "active",
  "version": 1,
  "documentDescription": "A supplier purchase order with line items and totals.",
  "fields": [
    { "key": "poNumber", "type": "string", "description": "Purchase order number" },
    { "key": "orderDate", "type": "date", "description": "Date the order was raised" },
    { "key": "total", "type": "number", "description": "Order total including tax" }
  ],
  "createdAt": "2026-06-01T10:00:00.000Z",
  "updatedAt": "2026-06-01T10:00:00.000Z"
}

Status codes

Status Meaning
201 Created.
400 The request body failed validation.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
409 The schemaId is a built-in name, or already exists.
429 The workspace is at its active custom-schema limit, or is creating schemas too quickly.

curl

Shell
curl -X POST 'https://api.dev.oprag.ai/v1/extract/schemas' \
  -H 'X-Oprag-Key: sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"schemaId": "custom_purchase_order","displayName": "Purchase order","documentDescription": "A supplier purchase order with line items and totals.","extractionHints": "Totals are on the last page. Currency is always the document currency.","fields": [{ "key": "poNumber", "type": "string", "description": "Purchase order number" },{ "key": "orderDate", "type": "date", "description": "Date the order was raised" },{ "key": "total", "type": "number", "description": "Order total including tax" }]}'
GET /v1/extract/schemas/{schemaId}

Fetch one extraction schema, built-in or custom.

Auth Integration key

Before you call it

  • A built-in id returns a short descriptor with a documentationUrl; a custom id returns the full field definitions.

Path parameters

Name Type Required Description
schemaId string Required Extraction schema id — a built-in name or a custom_ id.

Query parameters

None.Reads the resource named in the path; there is nothing else to select.

Body parameters

None.GET requests carry no body.

Response

200 Success

JSON
{
  "schemaId": "invoice",
  "displayName": "Invoice",
  "source": "builtin",
  "status": "active",
  "documentationUrl": "/docs/api/extract/schemas/"
}

Status codes

Status Meaning
200 Success.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
404 Unknown id, malformed id, or a custom schema belonging to another workspace — all indistinguishable by design.
429 Rate limited. See rate limits.

curl

Shell
curl -X GET 'https://api.dev.oprag.ai/v1/extract/schemas/{schemaId}' \
  -H 'X-Oprag-Key: sk_live_...'
PATCH /v1/extract/schemas/{schemaId}

Update a custom schema's name, description, hints, or fields.

Auth Integration key

Before you call it

  • Every accepted change bumps version.

Path parameters

Name Type Required Description
schemaId string Required Extraction schema id — a built-in name or a custom_ id.

Query parameters

None.Everything it needs is in the request body.

Body parameters

Name Type Required Description
displayName string Optional See the request example below.
documentDescription string Optional See the request example below.
extractionHints string Optional See the request example below.
fields any[] Optional Replaces the whole field list and bumps version.

Request

JSON
{
  "displayName": "Supplier purchase order",
  "extractionHints": "Totals are on the last page; ignore the delivery note."
}

Response

200 Success

JSON
{
  "schemaId": "custom_purchase_order",
  "displayName": "Supplier purchase order",
  "source": "custom",
  "status": "active",
  "version": 2,
  "updatedAt": "2026-06-14T09:12:00.000Z"
}

Status codes

Status Meaning
200 Success.
400 The request body failed validation.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
404 No such resource in this workspace.
409 The id is built-in, or the schema is archived. Create a new schemaId instead.
429 Rate limited. See rate limits.

curl

Shell
curl -X PATCH 'https://api.dev.oprag.ai/v1/extract/schemas/{schemaId}' \
  -H 'X-Oprag-Key: sk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"displayName": "Supplier purchase order","extractionHints": "Totals are on the last page; ignore the delivery note."}'
DELETE /v1/extract/schemas/{schemaId}

Archive a custom schema so it can no longer be used for new jobs.

Auth Integration key

Before you call it

  • Archiving is not a hard delete: jobs already extracted with the schema keep their results, and the schemaId can never be reused.

Path parameters

Name Type Required Description
schemaId string Required Extraction schema id — a built-in name or a custom_ id.

Query parameters

None.Acts on the resource named in the path; there is nothing to choose.

Body parameters

None.DELETE requests carry no body.

Response

200 Success

JSON
{
  "schemaId": "custom_purchase_order",
  "status": "archived",
  "archivedAt": "2026-06-20T08:30:00.000Z"
}

Status codes

Status Meaning
200 Success.
400 The request body failed validation.
401 Missing, revoked, or wrong-environment key.
403 Origin or IP not allowed for this project.
404 No such resource in this workspace.
409 Built-in schemas cannot be archived.
429 Rate limited. See rate limits.

curl

Shell
curl -X DELETE 'https://api.dev.oprag.ai/v1/extract/schemas/{schemaId}' \
  -H 'X-Oprag-Key: sk_live_...'

Ready to ship?

Get started free