Skip to navigation
Hidden

Extend Agent Context

Context file for AI coding assistants building on the Extend document processing platform.

What is Extend?

Extend is a platform for building, evaluating, and deploying AI-powered document processing. It provides APIs and SDKs for:

  • Extraction — Pull structured data from documents using a JSON Schema
  • Classification — Categorize documents by type
  • Splitting — Divide multi-page documents into sections
  • Parsing — Convert documents into clean, structured text (markdown, etc.)
  • Editing — Detect and fill PDF form fields
  • Workflows — Orchestrate multiple processors into pipelines with conditionals, human review, webhooks, and more

Full documentation: https://docs.extend.ai Searchable docs index: https://docs.extend.ai/llms.txt


Authentication

All API requests require Bearer token authentication and an API version header. If using an SDK, authentication and versioning are handled automatically — the details below apply to raw HTTP requests only.

curl -X POST "https://api.extend.ai/extract" \
-H "Authorization: Bearer sk_YOUR_API_KEY" \
-H "x-extend-api-version: 2026-02-09" \
-H "Content-Type: application/json" \
-d '{ ... }'
HeaderValueRequired
AuthorizationBearer sk_...Yes
x-extend-api-version2026-02-09 (latest)Yes
Content-Typeapplication/jsonFor POST/PUT

Get your API key from the Developers page in the Extend dashboard.

Omitting x-extend-api-version on raw HTTP requests returns an error. SDKs set this automatically.


API Versions

The API is versioned by date via the x-extend-api-version header. The latest version is 2026-02-09. SDKs target the correct version automatically when kept up to date. For the full list of versions, their status, and per-version changes, see the API versioning docs.

If you are on an older version, see the migration guide for breaking changes in 2026-02-09. Key changes:

  • Dedicated endpoints per resource type (/extract, /classify, /split) replace the generic /processor_runs endpoint
  • New ID prefixes: extractors use ex_, extract runs use exr_, classifiers use cl_, splitters use sp_
  • Synchronous processing support on all endpoints (new /extract, /classify, /split sync endpoints)
  • Simplified responses: single objects no longer wrapped in containers; list responses standardized to { "object": "list", "data": [...] }
  • Inline configuration: pass extractor/classifier/splitter config inline without pre-creating a resource — useful for managing schemas entirely in code
  • SDK polling helpers: createAndPoll / create_and_poll methods with exponential backoff built into updated SDKs

Migration path: Update your SDK to the latest version (automatically targets 2026-02-09), then migrate endpoint-by-endpoint. The old /processor_runs and /processors endpoints still work on older API versions but are now under Legacy in the docs.

Docs: https://docs.extend.ai/api-reference/api-versioning


SDKs

Official SDKs are available for Python, TypeScript, Java, and Go. All are generated from the API spec and target the latest API version automatically.

Python:

pip install extend-ai

TypeScript:

npm install extend-ai

Java (Gradle):

dependencies {
implementation 'ai.extend:extend-java-sdk:1.13.0'
}

Go:

go get github.com/extend-hq/extend-go-sdk

Community SDK:

The Python, TypeScript, and Java SDKs include polling helpers (create_and_poll / createAndPoll) for async operations, plus webhook signature verification utilities. The Go SDK ships neither — call Create and poll the run yourself (or use webhooks), and verify webhook signatures manually (HMAC-SHA256).

Docs: https://docs.extend.ai/sdks


CLI

The extend CLI runs every core operation from the shell, with no code to write. Reach for it for one-off tasks, exploration, and agent-driven actions you’d otherwise script with raw HTTP (creating extractors/classifiers/splitters/workflows, running a doc, kicking off an eval set). For durable application code, prefer an SDK above.

Install (any one):

curl -fsSL https://extend.ai/install.sh | sh # install script (macOS, Linux)
brew install extend-hq/tap/extend # Homebrew
npm install -g @extend-ai/cli # npm (or: npx @extend-ai/cli --help)
go install github.com/extend-hq/extend-cli/cmd/extend@latest

Authenticate, then verify with a read-only command:

export EXTEND_API_KEY="sk_..."
extend extractors list

An EXTEND_API_KEY in the environment always wins. Interactive machines can instead run extend setup (browser sign-in via OAuth, or save an API key); check the credentials in effect with extend whoami.

Common commands (each <input> is a local path, a file_... ID, or an https:// URL):

GoalCommand
Parse to markdownextend parse <input>
Extract fieldsextend extract <input> --config <json> or --using <ex_id>
Classifyextend classify <input> --using <cl_id>
Splitextend split <input> --using <spl_id>
Fill a PDF formextend edit <input> --instructions "..." or --schema <file>
Scaffold an edit schemaextend detect-form <input> (schema under output.schema)
Run a workflowextend workflows run <input> --using <wf_id>
Bulk (up to 1,000 files)extend <verb> batch <inputs>... for extract, parse, classify, split; extend workflows run batch for workflows

Inspect runs with the typed commands under each verb (extend extract runs list, extend workflows runs watch <id>, and so on). Manage resources with extend <noun> ... (extractors, classifiers, splitters, workflows, files, evaluations, webhooks). Filter output with -o json --jq '<expr>', and run extend <command> --help for exact flags. The CLI is the source of truth. Teach your harness to use it with extend skill install, which writes the extend-cli skill to ~/.agents/skills/extend-cli/SKILL.md (symlinked into ~/.claude/skills/extend-cli for Claude Code).

Docs: https://docs.extend.ai/agent-quickstart


MCP Server

If your harness connects to MCP (Model Context Protocol) servers, Extend ships one: the full platform (parse, extract, classify, split, edit, workflows, files, webhooks, evaluations) as tools with built-in run polling and structured errors. Pick it over raw HTTP when your agent works through tool calls rather than code.

Connect to https://mcp.extend.ai/mcp and complete the OAuth browser sign-in.

Call get_me first to discover your granted workspace and environment targets; every run-scoped tool requires workspaceId + environment arguments. Once connected, the tool descriptions and server instructions carry the full interaction contract.

Setup instructions per client (Cursor, Claude, ChatGPT, VS Code, and others): https://docs.extend.ai/mcp.md


API Endpoints (2026-02-09)

Note on SDK method names vs REST paths: This document describes the REST API. SDK method names follow language conventions and may differ (e.g., REST POST /extract_runs maps to Python client.extract_runs.create() and TypeScript client.extractRuns.create()). Always confirm exact method signatures against the SDK source or docs when writing code.

Base URL

RegionURL
US1 (default)https://api.extend.ai
US2https://api.us2.extend.app
EU1 (EU data residency)https://api.eu1.extend.ai

SDKs accept a baseUrl (TypeScript) or base_url (Python) parameter to select the region.

Files

MethodEndpointDescription
POST/files/uploadUpload a file (multipart form data)
GET/files/{id}Get file metadata + presigned download URL
GET/filesList files
DELETE/files/{id}Delete a file

Extract

MethodEndpointDescription
POST/extractExtract data (sync, 5-min timeout)
POST/extract_runsExtract data (async)
GET/extract_runs/{id}Get extract run status/output
GET/extract_runsList extract runs
DELETE/extract_runs/{id}Delete an extract run
POST/extract_runs/{id}/cancelCancel an in-progress run
POST/extractorsCreate an extractor
GET/extractors/{id}Get extractor details
POST/extractors/{id}Update an extractor
GET/extractorsList extractors
POST/extractors/{extractorId}/versionsPublish a new version
GET/extractors/{extractorId}/versions/{versionId}Get a version
GET/extractors/{extractorId}/versionsList versions

Classify

MethodEndpointDescription
POST/classifyClassify a file (sync, 5-min timeout)
POST/classify_runsClassify a file (async)
GET/classify_runs/{id}Get classify run
GET/classify_runsList classify runs
DELETE/classify_runs/{id}Delete a classify run
POST/classify_runs/{id}/cancelCancel an in-progress run
POST/classifiersCreate a classifier
GET/classifiers/{id}Get classifier
POST/classifiers/{id}Update classifier
GET/classifiersList classifiers
POST/classifiers/{classifierId}/versionsPublish a new version
GET/classifiers/{classifierId}/versions/{versionId}Get a version
GET/classifiers/{classifierId}/versionsList versions

Split

MethodEndpointDescription
POST/splitSplit a file (sync, 5-min timeout)
POST/split_runsSplit a file (async)
GET/split_runs/{id}Get split run
GET/split_runsList split runs
DELETE/split_runs/{id}Delete a split run
POST/split_runs/{id}/cancelCancel an in-progress run
POST/splittersCreate a splitter
GET/splitters/{id}Get splitter
POST/splitters/{id}Update splitter
GET/splittersList splitters
POST/splitters/{splitterId}/versionsPublish a new version
GET/splitters/{splitterId}/versions/{versionId}Get a version
GET/splitters/{splitterId}/versionsList versions

Parse

MethodEndpointDescription
POST/parseParse a file (sync, 5-min timeout)
POST/parse_runsParse a file (async)
GET/parse_runs/{id}Get parse run
GET/parse_runsList parse runs
DELETE/parse_runs/{id}Delete a parse run
POST/parse_runs/{id}/cancelCancel an in-progress run

Edit

MethodEndpointDescription
POST/editEdit a PDF (sync, 5-min timeout)
POST/edit_runsEdit a PDF (async)
GET/edit_runs/{id}Get edit run
DELETE/edit_runs/{id}Delete an edit run
GET/edit_templates/{id}Get an edit template (source file + default config)
POST/edit_schemas/generateDetect form fields and generate an edit schema (deprecated)
POST/detect_formDetect form fields and generate an edit schema (sync, 5-min timeout)
POST/form_detection_runsStart a form detection run (async)
GET/form_detection_runs/{id}Get a form detection run

Verify

Verify checks a single PDF, JPEG, or PNG for tampering, forgery, AI generation, AI edits, and more.

MethodEndpointDescription
POST/verifyVerify a file (sync; 5-min timeout on both engines, usually surfacing as a 504 with no runId; the run keeps processing, so don’t resubmit. Use /verify_runs for verify_agentic)
POST/verify_runsVerify a file (async)
GET/verify_runs/{id}Get verify run (zero-retention results are delivered once)
GET/verify_runsList verify run summaries (filters: status, source, sourceId, riskLevel, fileNameContains; sortBy, sortDir, nextPageToken, maxPageSize)
POST/verify_runs/{id}/cancelCancel an in-progress verify run
DELETE/verify_runs/{id}Delete a verify run and its derived data (cancels first if processing; input file kept)

Request: file ({url} or {id}; no text/base64), optional config (engine: verify_agentic default or verify_light; engineVersion: latest default or e.g. 0.1.0; advancedOptions.requireParsing: light parses only when true, agentic always parses and rejects false; an existing parse of the file made with Verify’s default parse settings is reused unless the file is zero-retention), optional metadata. Unknown config keys return 400. Every supported check runs on every run; there is no signal allowlist.

Response output (when PROCESSED): riskLevel (LOW/MEDIUM/HIGH; LOW = nothing detected, never authenticity), summary, signals[] each with id (vs_), type (a Public Signal category), riskAssessment ({type: "LEVEL", level}), summary, evidence[] (evidenceId, type in value_comparison | validation | inventory | history | structural_pattern | model_prediction, data, locations[] with page (number from 1, width, height), top-left boundingBox (left/top/right/bottom), objectRefs, byteRange). config echoes engine, engineVersion, and advancedOptions.requireParsing. file is null once the input file is deleted. List items carry riskLevel (null until processed, and null again once a zero-retention result is purged) instead of output. Statuses: PROCESSING, PROCESSED, FAILED, CANCELLED. Failure reasons: FILE_TYPE_NOT_SUPPORTED, NO_APPLICABLE_SIGNALS, PARSE_FAILED, REQUEST_ABORTED, INTERNAL_ERROR.

Workflows

MethodEndpointDescription
POST/workflow_runsRun a workflow
POST/workflow_runs/batchBatch run a workflow
GET/workflow_runs/{id}Get workflow run
POST/workflow_runs/{id}Update workflow run metadata
POST/workflow_runs/{id}/cancelCancel a workflow run
DELETE/workflow_runs/{id}Delete a workflow run
GET/workflow_runsList workflow runs
POST/workflowsCreate a workflow
GET/workflows/{id}Get a workflow
POST/workflows/{id}Update a workflow
GET/workflowsList workflows
POST/workflows/{id}/versionsCreate (deploy) a workflow version
GET/workflows/{id}/versions/{versionId}Get a workflow version
GET/workflows/{id}/versionsList workflow versions

Evaluation Sets

MethodEndpointDescription
POST/evaluation_setsCreate an evaluation set
GET/evaluation_sets/{id}Get an evaluation set
GET/evaluation_setsList evaluation sets
POST/evaluation_sets/{id}/itemsCreate items
GET/evaluation_sets/{id}/items/{itemId}Get an item
POST/evaluation_sets/{id}/items/{itemId}Update an item
DELETE/evaluation_sets/{id}/items/{itemId}Delete an item
GET/evaluation_sets/{id}/itemsList items
POST/evaluation_set_runsCreate (start) an evaluation set run
GET/evaluation_set_runs/{id}Get an evaluation set run

Webhook Endpoints

Manage where Extend delivers events. The create response includes a signingSecret that is returned only once. Store it securely for signature verification.

MethodEndpointDescription
POST/webhook_endpointsCreate a webhook endpoint
GET/webhook_endpointsList webhook endpoints
GET/webhook_endpoints/{id}Get a webhook endpoint
POST/webhook_endpoints/{id}Update a webhook endpoint
DELETE/webhook_endpoints/{id}Delete a webhook endpoint (and its subscriptions)

Webhook Subscriptions

Subscribe an endpoint to events for a specific resource (e.g. a single workflow) instead of the endpoint’s global enabledEvents.

MethodEndpointDescription
POST/webhook_subscriptionsCreate a webhook subscription
GET/webhook_subscriptionsList webhook subscriptions
GET/webhook_subscriptions/{id}Get a webhook subscription
POST/webhook_subscriptions/{id}Update a webhook subscription
DELETE/webhook_subscriptions/{id}Delete a webhook subscription

Common Patterns

Extract (sync) — Python

from extend_ai import Extend
client = Extend(token="sk_...")
# Sync extract -- blocks until complete (5-min timeout)
result = client.extract(
file={"url": "https://example.com/invoice.pdf"},
extractor={"id": "ex_..."},
)
print(result.output)

Extract (async with polling) — Python

result = client.extract_runs.create_and_poll(
file={"url": "https://example.com/invoice.pdf"},
extractor={"id": "ex_..."},
)
print(result.status) # "PROCESSED"
print(result.output)

Extract (sync) — TypeScript

import { ExtendClient } from "extend-ai";
const client = new ExtendClient({ token: "sk_..." });
const result = await client.extract({
file: { url: "https://example.com/invoice.pdf" },
extractor: { id: "ex_..." },
});
console.log(result.output);

Extract (async with polling) — TypeScript

const result = await client.extractRuns.createAndPoll({
file: { url: "https://example.com/invoice.pdf" },
extractor: { id: "ex_..." },
});
console.log(result.status); // "PROCESSED"
console.log(result.output);

Typed extraction with Zod — TypeScript

The TypeScript SDK supports inline Zod schemas with full type inference:

import { ExtendClient, extendDate, extendCurrency } from "extend-ai";
import { z } from "zod";
const client = new ExtendClient({ token: "sk_..." });
const result = await client.extract({
file: { url: "https://example.com/invoice.pdf" },
config: {
schema: z.object({
invoice_number: z.string().nullable().describe("The invoice number"),
invoice_date: extendDate().describe("The invoice date"),
line_items: z.array(z.object({
description: z.string().nullable(),
amount: extendCurrency(),
})).describe("Line items on the invoice"),
total: extendCurrency().describe("Total amount due"),
}),
},
});
console.log(result.output.value.invoice_number); // string | null
console.log(result.output.value.total.amount); // number | null

Extract (sync) — Go

The Go SDK is fully typed. It has no polling helper, so call Extract (sync) or ExtractRuns.Create and poll the run yourself.

package main
import (
"context"
"fmt"
"log"
extend "github.com/extend-hq/extend-go-sdk"
client "github.com/extend-hq/extend-go-sdk/client"
)
func main() {
c := client.NewClient()
result, err := c.Extract(context.TODO(), &extend.ExtractRequest{
File: &extend.ExtractRequestFile{
FileFromURL: &extend.FileFromURL{URL: "https://example.com/invoice.pdf"},
},
Config: &extend.ExtractConfigJSON{
Schema: map[string]any{
"type": "object",
"properties": map[string]any{
"invoice_number": map[string]any{"type": []string{"string", "null"}, "description": "The invoice number"},
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}

Parse a document — Python

result = client.parse(file={"url": "https://example.com/doc.pdf"})
for chunk in result.output.chunks:
print(chunk.content)

Parse (async with polling) — Python

result = client.parse_runs.create_and_poll(
file={"url": "https://example.com/doc.pdf"},
)
for chunk in result.output.chunks:
print(chunk.content)

Run a workflow — Python

result = client.workflow_runs.create_and_poll(
file={"url": "https://example.com/doc.pdf"},
workflow={"id": "wf_..."},
)
for step_run in result.step_runs or []:
print(step_run.step.type)
print(step_run.result)

Run a workflow — TypeScript

const result = await client.workflowRuns.createAndPoll({
file: { url: "https://example.com/doc.pdf" },
workflow: { id: "wf_..." },
});
for (const stepRun of result.stepRuns ?? []) {
console.log(stepRun.step.type);
console.log(stepRun.result);
}

Verify a document (async with polling) — Python

import time
run = client.verify_runs.create(file={"url": "https://example.com/statement.pdf"})
while run.status == "PROCESSING":
time.sleep(2)
run = client.verify_runs.retrieve(run.id)
if run.status == "PROCESSED":
print(run.output.risk_level, run.output.summary) # e.g. HIGH, "Content changed after signing..."
for signal in run.output.signals:
print(signal.type, signal.risk_assessment.level, signal.summary)

Verify a document (sync) — TypeScript

const run = await client.verify({ file: { url: "https://example.com/statement.pdf" } });
console.log(run.output?.riskLevel, run.output?.summary);
for (const signal of run.output?.signals ?? []) {
console.log(signal.type, signal.riskAssessment, signal.summary, signal.evidence.length, "evidence items");
}

Sync vs Async Processing

All processing endpoints (extract, classify, split, parse, edit) support both sync and async modes. Workflows are async-only.

  • Sync (POST /extract / SDK: client.extract()) — Blocks until complete. Has a 5-minute timeout. Best for testing and small files.
  • Async (POST /extract_runs / SDK: client.extractRuns.createAndPoll() or client.extract_runs.create_and_poll()) — Returns immediately with a run ID. Poll with GET /extract_runs/{id} or use webhooks. No timeout limit.

Use async for production workloads. Large documents can exceed the 5-minute sync timeout. SDK createAndPoll / create_and_poll methods are the recommended approach — they handle polling automatically with built-in backoff.

SDK polling helpers use a hybrid strategy: fast polling for 30 seconds, then gradual backoff up to 30-second intervals.

Terminal statuses: PROCESSED, FAILED, CANCELLED (also NEEDS_REVIEW, REJECTED for workflows).

Docs: https://docs.extend.ai/general/async-processing


Processor and workflow configs can get large and deeply nested, and create/update calls validate them strictly. When building these, write the config to a file in the project and submit it from there. Don’t hand-author it inline. If a request fails validation, the API returns the specific field path that’s invalid; edit just that part of the file and resubmit, instead of regenerating the entire config from scratch.

This applies to every config-bearing resource:

ResourceWhat to keep in a file
ExtractionThe extractor config (especially the JSON Schema under config.schema)
ClassificationThe classifier config (types and their descriptions)
SplittingThe splitter config (split classes / rules)
ParsingThe parse config (blockOptions, chunking strategy)
EditingThe edit config / generated edit schema
WorkflowsThe full steps array (step definitions, conditionals, next routing)

Suggested loop:

  1. Save the config as JSON, e.g. extractor.config.json (or save the whole request body if that’s simpler for your call).
  2. Create/update by loading the file, for example --from-file extractor.config.json with the CLI, or json.load(open("extractor.config.json")) / JSON.parse(fs.readFileSync(...)) in the SDKs.
  3. On a validation error, read the returned field path, edit only that part of the file, and resubmit. Keep the file as the source of truth so each fix is incremental.

This keeps iterations cheap and avoids reintroducing earlier mistakes when a single field is wrong. Note that Extend normalizes configs on submit (see the schema rules below), so the stored config the API echoes back may differ slightly from your file. Diff against your file intentionally rather than expecting a byte-for-byte match.


Versioning: processors and workflows

Both processors and workflows have one editable draft and immutable published/deployed versions. Edits always land on the draft; publishing snapshots it. Nothing that references a specific version ever changes behaviour when you publish again.

Processors (extractor / classifier / splitter)Workflows
Edit the draftextractors.update(id, config=...) (same for classifiers/splitters)Studio editor, or deploy steps directly
Publish / deployextractorVersions.create(id, releaseType: "major"|"minor", description?) → "1.0", "1.1", "2.0"workflowVersions.create(id, { name?, steps? }) → "1", "2", …
List versionsextractorVersions.list(id) (draft first, then newest)workflowVersions.list(id)
Reference in a runextractor: { id, version } on extract / extractRuns.createworkflow: { id, version } on workflowRuns.create
version omitted"latest": newest published, else draftNewest deployed, else draft
"draft"Current draft (development only)Current draft (development only)
Pinned"1.1""3"

Major vs minor is only a numbering signal: minor for changes that keep the output shape (descriptions, rules, base processor); major when the schema, classification types, or split identifiers change so downstream code must be updated.

Workflow steps reference processors with an explicit version ("latest", "draft", or "1.1"). Deploying a workflow version freezes that string, but "latest" still resolves at run time, so a fully reproducible deployed workflow needs pinned processor versions in its steps.

Recommended promotion loop: update the draft → run the evaluation set against "draft" → publish → move the version string in your config/workflow step → deploy the workflow.

Docs: https://docs.extend.ai/evaluation/publishing-processors (processors) and https://docs.extend.ai/workflows/workflow-versioning (workflows), all four SDKs.


Test environment

A test API key routes every request to an isolated test environment: same base URL, same code, no production data, no production webhooks. Workflow and processor definitions are shared between environments; runs and webhook deliveries are not. Create test keys and test webhook endpoints with Test Environment toggled on in Studio’s Developers tab. All SDKs read EXTEND_API_KEY; switch environments by switching the key. Docs: https://docs.extend.ai/general/test-environment-guide


Extraction Schema (JSON Schema)

Extractors use JSON Schema to define output structure. Key rules:

  • Root must be "type": "object"
  • All primitive fields must be nullable: use "type": ["string", "null"] not "type": "string"
  • Objects and arrays cannot be nullable
  • Max nesting depth: 5 levels
  • Property names: letters, numbers, underscores, hyphens only
  • "required" arrays and "additionalProperties": false are optional in the schema you submit. Extend normalizes your config on create/update by marking every listed property required and injecting "additionalProperties": false on all objects. The echoed-back config therefore won’t match yours byte-for-byte; that’s expected, not an error. Include them yourself if you want your stored schema to be explicit.

Supported types

JSON Schema TypeNotes
["string", "null"]Nullable string
["number", "null"]Nullable number
["integer", "null"]Nullable integer
["boolean", "null"]Nullable boolean
"object"Nested object (not nullable)
"array"Array of objects or scalars (not nullable)

Special Extend types

TypeUsageOutput format
"extend:type": "date"Add to string fieldsyyyy-mm-dd
"extend:type": "currency"Object with amount + iso_4217_currency_codeStructured currency
"extend:type": "signature"Object with printed_name, signature_date, is_signed, title_or_roleSignature detection

Enums

Enums must include null and only support string values. Use "extend:descriptions" for disambiguation:

{
"status": {
"enum": ["active", "inactive", "pending", null],
"extend:descriptions": ["Currently active", "No longer active", "Awaiting activation"]
}
}

Field descriptions

Use "description" to guide extraction. Use "extend:name" for display names without changing output keys.

Unsupported

anyOf, oneOf, allOf, regex patterns, conditional schemas, const.

Docs: https://docs.extend.ai/extraction/schema

Legacy: Fields Array schema

Extractors created before April 2025 may use the legacy “Fields Array” configuration instead of JSON Schema. Key differences:

  • Fields Array used a fields array with id, name, type, description per field. Output mixed data and metadata together within each field object.
  • JSON Schema uses a standard schema object. Output cleanly separates value (extracted data) from metadata (confidence, citations) using path-based keys.

To migrate: Open your processor in Studio, click the three-dot menu, select “Migrate to JSON Schema.” This creates a new processor with the converted schema while preserving your original.

New extractors should always use JSON Schema. See the migration guide for full details.


Webhooks

Webhooks deliver HTTP POST notifications when processing events complete.

Setup

  1. Create an endpoint in the Extend dashboard under Developers > Webhook Endpoints
  2. Subscribe to events at global, workflow, or processor scope
  3. Choose delivery format: JSON (default) or Signed Download URL (for large payloads)

Key events

The table below lists common events. For the full list (including edit, lifecycle, and CRUD events for all resource types), see the webhook events docs.

EventFires when
extract_run.processedExtraction completes
extract_run.failedExtraction fails
classify_run.processedClassification completes
classify_run.failedClassification fails
split_run.processedSplitting completes
split_run.failedSplitting fails
parse_run.processedParsing completes
parse_run.failedParsing fails
edit_run.processedPDF editing completes
edit_run.failedPDF editing fails
workflow_run.completedWorkflow completes
workflow_run.failedWorkflow fails
workflow_run.needs_reviewWorkflow requires human review
workflow_run.step_run.processedIndividual workflow step completes

Signature verification

Extend signs every webhook with HMAC-SHA256. Use the SDK’s built-in verification:

Python:

event = client.webhooks.verify_and_parse(body=body, headers=headers, signing_secret="wss_...")

TypeScript:

const event = client.webhooks.verifyAndParse(body, headers, "wss_...");

Java:

WebhookEvent event = client.webhooks().verifyAndParse(body, headers, "wss_...");

The Go SDK has no verification helper. Verify the signature manually (steps below).

For manual verification:

  1. Extract x-extend-request-timestamp and x-extend-request-signature headers
  2. Construct message: v0:{timestamp}:{body}
  3. HMAC-SHA256 with your signing secret
  4. Compare signatures; reject if timestamp > 5 minutes old

Docs: https://docs.extend.ai/webhooks/configuration


Workflows

Workflows chain processors into pipelines. Build them visually in Extend Studio or manage them entirely via the API (create, configure, version/deploy, and run — see the Workflows endpoints above).

Capabilities

  • Extraction, classification, splitting steps
  • Conditional routing based on extracted values or classification results
  • Human review steps (pauses workflow for manual review)
  • External data validation (call your API mid-workflow)
  • Webhook response steps
  • Formula calculations
  • Parse step configuration
  • Validation rules

Defining processor steps

EXTRACT, CLASSIFY, and SPLIT workflow steps accept either a saved processor reference or a full inline config. When config is present, provide exactly one source:

StepSaved processorInline config
EXTRACTconfig.extractor: { id, version }config.extractorConfig: { schema, ... }
CLASSIFYconfig.classifier: { id, version }config.classifierConfig: { classifications, ... }
SPLITconfig.splitter: { id, version }config.splitterConfig: { splitClassifications, ... }

Inline extraction configs must include schema; schema-less extraction is not supported inside workflows. Inline classify/split routes use the IDs declared in their inline classifications. CONDITIONAL_EXTRACT rules remain saved-reference only. Full reference: https://docs.extend.ai/workflows/configuring-workflows

Running a workflow via API

Via SDK, use client.workflowRuns.createAndPoll() (TypeScript) or client.workflow_runs.create_and_poll() (Python) — see Common Patterns above. Raw HTTP example:

curl -X POST "https://api.extend.ai/workflow_runs" \
-H "Authorization: Bearer sk_..." \
-H "x-extend-api-version: 2026-02-09" \
-H "Content-Type: application/json" \
-d '{
"workflow": { "id": "wf_..." },
"file": { "url": "https://..." }
}'

Workflow run statuses

StatusMeaning
PENDINGQueued, not yet started
PROCESSINGCurrently executing
PROCESSEDCompleted successfully
FAILEDFailed (check failureReason)
NEEDS_REVIEWPaused for human review
REJECTEDRejected during human review
CANCELLEDCancelled via API

Retryable failure reasons

These failures are transient and safe to retry automatically:

  • INTERNAL_ERROR — Unexpected server error
  • DOCUMENT_PROCESSOR_ERROR — Extraction step failed after retries

Non-retryable:

  • INVALID_WORKFLOW — Workflow configuration error
  • FAILED_TO_PROCESS_FILE — File could not be downloaded (check your URL)

Docs: https://docs.extend.ai/workflows/overview


Error Handling

Error CodeDescriptionRetryable
INVALID_REQUESTBad request body or parametersNo
UNAUTHORIZEDMissing or invalid API keyNo
NOT_FOUNDResource doesn’t existNo
RATE_LIMIT_EXCEEDEDToo many requests — back off and retryYes
USAGE_BLOCKEDOut of creditsNo
ENDPOINT_REMOVEDDeprecated endpoint — check error message for replacementNo
INTERNAL_ERRORServer errorYes

SDKs raise typed exceptions for these errors (e.g., RateLimitError, UnauthorizedError). Error responses include a requestId — provide this when contacting support.

Docs: https://docs.extend.ai/api-reference/error-handling


Rate Limits

All rate limits are per-organization. If you receive 429 Too Many Requests, implement exponential backoff. SDK polling helpers handle backoff automatically; for other SDK calls, add your own retry logic.

Docs: https://docs.extend.ai/general/rate-limits (includes current limits by endpoint)


Evaluation Sets

Evaluation sets benchmark a processor (extractor, classifier, or splitter) against reviewed ground truth; they can also be run against workflows, which this section does not cover. Use the API for this rather than building a custom harness: the loop is four calls and the metrics are computed server-side with the same scoring Studio uses.

StepEndpointSDK (TS / Python)
1. Create a set for a processorPOST /evaluation_sets { name, entityId, description? }evaluationSets.create / evaluation_sets.create
2. Upload each filePOST /files/uploadfiles.upload
3. Add items (≤100 per call)POST /evaluation_sets/{id}/items { items: [{ fileId, expectedOutput }] }evaluationSetItems.create / evaluation_set_items.create
4. Start a runPOST /evaluation_set_runs { evaluationSetId, entity?: { id, version? }, evaluationSetItemIds? }evaluationSetRuns.create / evaluation_set_runs.create
5. Poll until terminalGET /evaluation_set_runs/{id}evaluationSetRuns.retrieve / evaluation_set_runs.retrieve
6. Fix wrong ground truthPOST /evaluation_sets/{id}/items/{itemId} { expectedOutput }evaluationSetItems.update / evaluation_set_items.update

expectedOutput shape depends on the processor type:

  • Extractor: { "value": { ...object conforming to the extractor schema... } } — nest under value, include every schema property. Easiest source: run the extractor on the file, correct the output, save it.
  • Classifier: { "id": "<classification id>", "type": "<classification name>" }
  • Splitter: { "splits": [{ "classificationId", "type", "startPage", "endPage" }] }

Run semantics: entity.version accepts "draft", "latest", or a published version like "1.0"; omitting entity runs the set’s default processor at draft. Status goes PENDING → PROCESSING → PROCESSED | FAILED | CANCELLED. No SDK has a polling helper for evaluation runs; poll every few seconds. Read metrics only when status is PROCESSED.

metrics is discriminated on type: EXTRACT has accuracy and fieldMetrics (field path → { countTotal, countPresent, countExpected, countAccurate, accuracy }, nested fields dot-joined); CLASSIFY has accuracy and classificationMetrics (per-class precision/recall/F1); SPLITTER has precision, recall, f1. The run returns aggregates only: per-document diffs, CSV export, custom matchers (fuzzy, LLM judge, vector), and field exclusion are Studio features.

Docs: https://docs.extend.ai/evaluation/creating-evaluation-sets and https://docs.extend.ai/evaluation/running-evaluation-sets (all four SDKs)


TopicURL
Getting startedhttps://docs.extend.ai/overview
Extraction quick starthttps://docs.extend.ai/extraction/overview
Parsing quick starthttps://docs.extend.ai/parsing/overview
Verify (fraud detection) quick starthttps://docs.extend.ai/verify/overview
Verify signals and evidence codeshttps://docs.extend.ai/verify/signals
JSON Schema referencehttps://docs.extend.ai/extraction/schema
Extraction best practiceshttps://docs.extend.ai/extraction/best-practices/field-names-and-prompt-crafting
Async processinghttps://docs.extend.ai/general/async-processing
Webhook setuphttps://docs.extend.ai/webhooks/configuration
Webhook eventshttps://docs.extend.ai/webhooks/events
Workflow creationhttps://docs.extend.ai/workflows/overview
Code and semantic validation (API and Studio)https://docs.extend.ai/workflows/workflow-steps/validation-step
Ordered router branches and fallback (API and Studio)https://docs.extend.ai/workflows/workflow-steps/router-step
Processors (saved, versioned configs)https://docs.extend.ai/evaluation/processors
Evaluation sets (create)https://docs.extend.ai/evaluation/creating-evaluation-sets
Evaluation sets (run + metrics)https://docs.extend.ai/evaluation/running-evaluation-sets
Workflow versioning (deploy, pin)https://docs.extend.ai/workflows/workflow-versioning
Publishing processor versionshttps://docs.extend.ai/evaluation/publishing-processors
Test environmenthttps://docs.extend.ai/general/test-environment-guide
API versioninghttps://docs.extend.ai/api-reference/api-versioning
2026-02-09 migrationhttps://docs.extend.ai/api-reference/migrations/2026-02-09/overview
JSON Schema migrationhttps://docs.extend.ai/migrating-to-json-schema
SDKshttps://docs.extend.ai/sdks
Error codeshttps://docs.extend.ai/api-reference/error-handling
Rate limitshttps://docs.extend.ai/general/rate-limits
Supported file typeshttps://docs.extend.ai/general/supported-file-types
Creditshttps://docs.extend.ai/general/how-credits-work
Confidence scoreshttps://docs.extend.ai/extraction/confidence-scores
Citationshttps://docs.extend.ai/extraction/response-format
API reference (full)https://docs.extend.ai/api-reference
Searchable docs indexhttps://docs.extend.ai/llms.txt