Quickstart
This page is the copy-paste path for the public customer API. It uses the same contracts as the generated OpenAPI reference:
- Upload a PDF or image with
POST /api/v1/upload. - Read the reusable
file_idfromfiles[0].file_id. - Parse the file with
POST /api/v1/parse. - Extract with a Schema V2
fields[]/tables[]schema usingPOST /v1/extract. - Poll
GET /v1/jobs/{job_id}and fetchGET /v1/jobs/{job_id}/resultfor async work.
Supported production upload formats are PDF, PNG, JPEG/JPG, TIFF/TIF, WebP, BMP, and GIF. Native DOCX/XLSX workflows are available through beta APIs by writing to Docspeed; they are not part of the standard production upload path.
Contract summary
| Step | Endpoint | Contract note | | --- | --- | --- | | Upload | POST /api/v1/upload | multipart field name is repeated files; response is { "files": [...] } | | Parse | POST /api/v1/parse | response exposes doc_id, version, markdown, pages, regions, chunks, metadata | | Extract | POST /v1/extract | request schema is Schema V2: doc_class, fields[], tables[] | | Jobs | GET /v1/jobs/{job_id} | status is one of pending, processing, completed, failed | | Result | GET /v1/jobs/{job_id}/result | async result has the same payload shape as the sync endpoint result |
Step 1: Upload
Request:
curl -sS -X POST "https://api.docspeed.ai/api/v1/upload" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-F "files=@sample-invoice.pdf"
Response:
{
"files": [
{
"file_id": "file_123",
"filename": "sample-invoice.pdf",
"content_type": "application/pdf",
"size_bytes": 245812,
"page_count": 1,
"created_at": 1776054000.0
}
]
}
Store files[0].file_id. There is no top-level file_id in the canonical upload response.
Step 2: Parse
Use Parse when you need document markdown, page metadata, drawable regions, and chunk-to-region mappings before extraction.
Request:
curl -sS -X POST "https://api.docspeed.ai/api/v1/parse" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"input": {"file_id": "file_123"},
"execution_mode": "sync",
"profile": "grounded",
"grounding": "regions"
}'
Response fragment:
{
"doc_id": "doc_123",
"version": "0.1.0",
"markdown": "# Invoice\n\nTotal Due: 1240.00",
"pages": [
{
"page_index": 0,
"width": 1200,
"height": 1600,
"image_ref": "/api/v1/files/file_123/pages/0/image",
"ocr_median_line_height": 18.5
}
],
"regions": [
{
"region_id": "p1_total",
"page_index": 0,
"label": "key_value_lines",
"display_label": "1. TEXT BLOCK",
"shape": {"type": "bbox", "x": 80, "y": 320, "w": 420, "h": 80}
}
],
"chunks": [
{
"chunk_id": "p1_total",
"type": "text",
"label": "key_value_lines",
"display_label": "1. TEXT BLOCK",
"markdown": "Total Due: 1240.00",
"page_index": 0,
"region_ids": ["p1_total"]
}
],
"metadata": {"page_count": 1}
}
Parse does not expose OCR line IDs or raw OCR implementation metadata. Use POST /v1/ocr when you need line-level OCR output.
Step 3: Create an Extract Job
Extract accepts Schema V2. The same object returned by POST /v1/schema/auto can be sent as schema.
Request:
curl -sS -X POST "https://api.docspeed.ai/v1/extract" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"input": {"file_id": "file_123"},
"execution_mode": "async",
"grounding": "cell",
"schema": {
"doc_class": "invoice",
"fields": [
{
"key": "invoice.invoice_number",
"description": "Invoice identifier",
"type": "string",
"array": false
},
{
"key": "supplier.name",
"description": "Supplier legal name",
"type": "string",
"array": false
},
{
"key": "invoice.total_due",
"description": "Final amount due",
"type": "number",
"array": false
}
],
"tables": [
{
"key": "line_items",
"description": "Invoice line items",
"fields": [
{
"key": "description",
"description": "Line item description",
"type": "string",
"array": false
},
{
"key": "amount",
"description": "Line item amount",
"type": "number",
"array": false
}
]
}
]
}
}'
Async response:
{
"job_id": "job_123",
"operation": "extract",
"status": "pending",
"execution_mode": "async",
"created_at": 1776054000.0,
"updated_at": 1776054000.0
}
Step 4: Poll for Status
curl -sS \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
"https://api.docspeed.ai/v1/jobs/job_123"
Status response:
{
"job_id": "job_123",
"operation": "extract",
"status": "processing",
"execution_mode": "async",
"created_at": 1776054000.0,
"updated_at": 1776054003.0
}
Poll with exponential backoff. Fetch the result only after status is completed. If status is failed, inspect the error returned by the job result endpoint or retry after correcting the request.
Step 5: Get Results
curl -sS \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
"https://api.docspeed.ai/v1/jobs/job_123/result"
Result fragment:
{
"schema": {
"doc_class": "invoice",
"fields": [
{
"key": "invoice.invoice_number",
"description": "Invoice identifier",
"type": "string",
"array": false
}
],
"tables": []
},
"result": {
"fields": {
"invoice.invoice_number": {
"value": "GST-2026-0042",
"region_ids": ["p1_invoice_number"]
}
},
"tables": {},
"grounding_regions": [
{
"region_id": "p1_invoice_number",
"page_index": 0,
"shape": {"type": "bbox", "x": 92, "y": 118, "w": 240, "h": 34}
}
],
"pages": [
{"page_index": 0, "width": 1200, "height": 1600}
]
},
"grounding_mode": "regions",
"page_count": 1
}
Sync POST /v1/extract returns the same ExtractionResult envelope directly. Async GET /v1/jobs/{job_id}/result returns the completed endpoint payload.
cURL
export DOCSPEED_API_KEY="YOUR_API_KEY"
export DOCSPEED_BASE_URL="https://api.docspeed.ai"
FILE_ID=$(
curl -sS -X POST "${DOCSPEED_BASE_URL}/api/v1/upload" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-F "files=@sample-invoice.pdf" | jq -r '.files[0].file_id'
)
curl -sS -X POST "${DOCSPEED_BASE_URL}/api/v1/parse" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"input\": {\"file_id\": \"${FILE_ID}\"},
\"execution_mode\": \"sync\",
\"profile\": \"grounded\",
\"grounding\": \"regions\"
}"
JOB_ID=$(
curl -sS -X POST "${DOCSPEED_BASE_URL}/v1/extract" \
-H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"input": {"file_id": "'"${FILE_ID}"'"},
"execution_mode": "async",
"grounding": "cell",
"schema": {
"doc_class": "invoice",
"fields": [
{"key": "invoice.invoice_number", "description": "Invoice identifier", "type": "string", "array": false},
{"key": "supplier.name", "description": "Supplier legal name", "type": "string", "array": false},
{"key": "invoice.total_due", "description": "Final amount due", "type": "number", "array": false}
],
"tables": [
{
"key": "line_items",
"description": "Invoice line items",
"fields": [
{"key": "description", "description": "Line item description", "type": "string", "array": false},
{"key": "amount", "description": "Line item amount", "type": "number", "array": false}
]
}
]
}
}' | jq -r '.job_id'
)
until STATUS=$(curl -sS -H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
"${DOCSPEED_BASE_URL}/v1/jobs/${JOB_ID}" | jq -r '.status'); \
[ "${STATUS}" = "completed" ] || [ "${STATUS}" = "failed" ]; do
sleep 2
done
curl -sS -H "Authorization: Bearer ${DOCSPEED_API_KEY}" \
"${DOCSPEED_BASE_URL}/v1/jobs/${JOB_ID}/result"
Python
import time
import requests
BASE_URL = "https://api.docspeed.ai"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
with open("sample-invoice.pdf", "rb") as handle:
upload = requests.post(
f"{BASE_URL}/api/v1/upload",
headers=HEADERS,
files=[("files", ("sample-invoice.pdf", handle, "application/pdf"))],
timeout=120,
)
upload.raise_for_status()
file_id = upload.json()["files"][0]["file_id"]
parse = requests.post(
f"{BASE_URL}/api/v1/parse",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"input": {"file_id": file_id},
"execution_mode": "sync",
"profile": "grounded",
"grounding": "regions",
},
timeout=600,
)
parse.raise_for_status()
schema = {
"doc_class": "invoice",
"fields": [
{"key": "invoice.invoice_number", "description": "Invoice identifier", "type": "string", "array": False},
{"key": "supplier.name", "description": "Supplier legal name", "type": "string", "array": False},
{"key": "invoice.total_due", "description": "Final amount due", "type": "number", "array": False},
],
"tables": [
{
"key": "line_items",
"description": "Invoice line items",
"fields": [
{"key": "description", "description": "Line item description", "type": "string", "array": False},
{"key": "amount", "description": "Line item amount", "type": "number", "array": False},
],
}
],
}
extract = requests.post(
f"{BASE_URL}/v1/extract",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"input": {"file_id": file_id},
"execution_mode": "async",
"grounding": "cell",
"schema": schema,
},
timeout=600,
)
extract.raise_for_status()
job_id = extract.json()["job_id"]
while True:
status = requests.get(f"{BASE_URL}/v1/jobs/{job_id}", headers=HEADERS, timeout=60)
status.raise_for_status()
state = status.json()["status"]
if state in {"completed", "failed"}:
break
time.sleep(2)
result = requests.get(f"{BASE_URL}/v1/jobs/{job_id}/result", headers=HEADERS, timeout=60)
result.raise_for_status()
print(result.json())
TypeScript
Node 20+ has global fetch, FormData, and Blob.
import { readFile } from "node:fs/promises";
const baseUrl = "https://api.docspeed.ai";
const headers = { Authorization: "Bearer YOUR_API_KEY" };
const formData = new FormData();
const fileBytes = await readFile("sample-invoice.pdf");
formData.append("files", new Blob([fileBytes], { type: "application/pdf" }), "sample-invoice.pdf");
const upload = await fetch(`${baseUrl}/api/v1/upload`, {
method: "POST",
headers,
body: formData,
});
if (!upload.ok) throw new Error(await upload.text());
const uploadJson = await upload.json() as { files: Array<{ file_id: string }> };
const fileId = uploadJson.files[0].file_id;
await fetch(`${baseUrl}/api/v1/parse`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
input: { file_id: fileId },
execution_mode: "sync",
profile: "grounded",
grounding: "regions",
}),
});
const schema = {
doc_class: "invoice",
fields: [
{ key: "invoice.invoice_number", description: "Invoice identifier", type: "string", array: false },
{ key: "supplier.name", description: "Supplier legal name", type: "string", array: false },
{ key: "invoice.total_due", description: "Final amount due", type: "number", array: false },
],
tables: [
{
key: "line_items",
description: "Invoice line items",
fields: [
{ key: "description", description: "Line item description", type: "string", array: false },
{ key: "amount", description: "Line item amount", type: "number", array: false },
],
},
],
};
const extract = await fetch(`${baseUrl}/v1/extract`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
input: { file_id: fileId },
execution_mode: "async",
grounding: "cell",
schema,
}),
});
if (!extract.ok) throw new Error(await extract.text());
const { job_id } = await extract.json() as { job_id: string };
let status = "pending";
while (status !== "completed" && status !== "failed") {
await new Promise((resolve) => setTimeout(resolve, 2000));
const poll = await fetch(`${baseUrl}/v1/jobs/${job_id}`, { headers });
status = ((await poll.json()) as { status: string }).status;
}
const result = await fetch(`${baseUrl}/v1/jobs/${job_id}/result`, { headers });
console.log(await result.json());
Troubleshooting
Empty extraction result:
- Confirm you used
files[0].file_idfrom upload, not a top-levelfile_id. - Run Parse and check
markdown,chunks[], andregions[]. - Make Schema V2 field descriptions more specific.
- Use
grounding: "cell"when table cells or line items need review evidence.
Job remains pending or processing:
- Poll with backoff. Do not tight-loop the status endpoint.
- Treat only
completedas ready for normal result processing. - Treat
failedas terminal and inspect the result/error payload before retrying. - Use sync mode only for interactive workloads that fit your timeout budget.
Next Steps
- Authentication & API Keys: configure bearer auth.
- Schema Builder: generate Schema V2 from instructions or files.
- Extract: understand grounded extraction response envelopes.
- Grounding: render evidence regions in review UIs.
- Limits & Quotas: plan file size, timeout, and async behavior.
- API Reference: inspect every public endpoint.