Upload a document, define what to extract, and get structured JSON back through the API.
The heart of DocuPipe is its ability to convert any document to a standard output that has consistently defined fields. We call this extraction process standardization.
To make standardization useful, you define the structure you want back. We call this set of definitions a schema. Think of a schema as a collection of slots for information you expect to find (e.g., a rental lease might need monthly rent and lease end date values).
The workflow consists of three parts:
- Upload a document.
- Define what you want to extract from this document type. This is a Schema.
- Extract results from many documents, using your schema. Each result is a Standardization.
This guide follows a toy problem of standardizing rental leases. You can use this example lease or any document of your own.
Get an API Key
Sign up, then copy your API key from the Developers card on the Settings page. Every request sends it in the X-API-Key header, and every endpoint lives under https://app.docupipe.ai.
Upload a Document
Submit a Document for Processing with the file base64-encoded. Supported formats include PDF, Word, Excel, images (JPG, PNG, WEBP), text files and JSON. Replace YOUR_API_KEY with your key.
import base64
import time
import requests
API_URL = "https://app.docupipe.ai"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}
with open("lease_trimmed.pdf", "rb") as f:
contents = base64.b64encode(f.read()).decode()
payload = {"document": {"file": {"contents": contents, "filename": "lease_trimmed.pdf"}}}
response = requests.post(f"{API_URL}/document", json=payload, headers=HEADERS)
response.raise_for_status()
upload = response.json()
print(upload)import fs from "node:fs";
const API_URL = "https://app.docupipe.ai";
const HEADERS = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" };
const contents = fs.readFileSync("lease_trimmed.pdf").toString("base64");
const payload = { document: { file: { contents, filename: "lease_trimmed.pdf" } } };
const uploadResponse = await fetch(`${API_URL}/document`, { method: "POST", headers: HEADERS, body: JSON.stringify(payload) });
if (!uploadResponse.ok) throw new Error(await uploadResponse.text());
const upload = await uploadResponse.json();
console.log(upload);The response holds the document ID and the ID of the job that processes it:
{"documentId": "96dde1aa", "jobId": "42ace16a", "status": "processing", "workflowResponse": null, "workflowV2RunJobId": null}Wait for the Upload to Finish
As soon as you upload a document, DocuPipe reads its text and tables. This can take seconds or minutes, depending on the file size. Poll Get Job until the job's status is no longer processing:
def wait_for_job(job_id):
while True:
response = requests.get(f"{API_URL}/job/{job_id}", headers=HEADERS)
response.raise_for_status()
job = response.json()
if job["status"] == "completed":
return job
if job["status"] == "error":
raise RuntimeError(job["errorMessage"])
time.sleep(3)
wait_for_job(upload["jobId"])async function waitForJob(jobId) {
while (true) {
const response = await fetch(`${API_URL}/job/${jobId}`, { headers: HEADERS });
if (!response.ok) throw new Error(await response.text());
const job = await response.json();
if (job.status === "completed") return job;
if (job.status === "error") throw new Error(job.errorMessage);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
await waitForJob(upload.jobId);You can skip polling by registering a webhook, which notifies you as soon as parsing or standardization completes. Learn more in the webhooks guide. To wait on several jobs in one call, use Wait For Jobs.
Define a Schema
A schema is a JSON Schema that names the fields you want and describes each one. Create a Schema with the fields for a lease:
lease_schema = {
"type": "object",
"properties": {
"tenantName": {"type": "string", "description": "Full name of the tenant"},
"rentalAddress": {"type": "string", "description": "Address of the rented property"},
"monthlyRent": {"type": "number", "description": "Monthly rent amount"},
"leaseStartDate": {"type": "string", "description": "Lease start date, YYYY-MM-DD"},
"leaseEndDate": {"type": "string", "description": "Lease end date, YYYY-MM-DD"},
"petsAllowed": {"type": "boolean", "description": "Whether the lease allows pets"}
}
}
payload = {"schemaName": "Rental lease", "jsonSchema": lease_schema}
response = requests.post(f"{API_URL}/schema", json=payload, headers=HEADERS)
response.raise_for_status()
schema_id = response.json()["schemaId"]const leaseSchema = {
type: "object",
properties: {
tenantName: { type: "string", description: "Full name of the tenant" },
rentalAddress: { type: "string", description: "Address of the rented property" },
monthlyRent: { type: "number", description: "Monthly rent amount" },
leaseStartDate: { type: "string", description: "Lease start date, YYYY-MM-DD" },
leaseEndDate: { type: "string", description: "Lease end date, YYYY-MM-DD" },
petsAllowed: { type: "boolean", description: "Whether the lease allows pets" }
}
};
const schemaResponse = await fetch(`${API_URL}/schema`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ schemaName: "Rental lease", jsonSchema: leaseSchema })
});
if (!schemaResponse.ok) throw new Error(await schemaResponse.text());
const { schemaId } = await schemaResponse.json();The schema is ready as soon as the call returns. Save its schemaId and reuse it for every lease you process.
You don't have to write the schema by hand. You can build one interactively in the dashboard from a sample document (see the Quick Start), or leave out
schemaIdwhen you standardize and DocuPipe drafts a schema from the document for you.
Standardize the Document
Standardize the document with your schema. The call returns a jobId to wait on and the standardizationId of the result:
payload = {"documentId": upload["documentId"], "schemaId": schema_id}
response = requests.post(f"{API_URL}/v3/standardize", json=payload, headers=HEADERS)
response.raise_for_status()
run = response.json()
wait_for_job(run["jobId"])
response = requests.get(f"{API_URL}/standardization/{run['standardizationId']}", headers=HEADERS)
response.raise_for_status()
print(response.json()["data"])const runResponse = await fetch(`${API_URL}/v3/standardize`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ documentId: upload.documentId, schemaId })
});
if (!runResponse.ok) throw new Error(await runResponse.text());
const run = await runResponse.json();
await waitForJob(run.jobId);
const resultResponse = await fetch(`${API_URL}/standardization/${run.standardizationId}`, { headers: HEADERS });
if (!resultResponse.ok) throw new Error(await resultResponse.text());
const result = await resultResponse.json();
console.log(result.data);Retrieve a Standardization returns the extracted fields in data, shaped exactly like your schema:
{
"tenantName": "Uri Merchav, Meital Bendet",
"rentalAddress": "2720 COLLEGE AVENUE - APT. 02 BERKELEY, CA 94705",
"monthlyRent": 3579.87,
"leaseStartDate": "2019-11-05",
"leaseEndDate": "2020-01-15",
"petsAllowed": null
}A field the document doesn't answer comes back as null.
Schemas can be as detailed as your use case needs: nested objects, arrays of line items, enums, and descriptions that tell DocuPipe exactly how to read each field.
Using a coding agent such as Claude Code, Codex or Cursor? See Using DocuPipe from Claude Code, Codex or Cursor.
Next Steps
- Classify documents by type so you can route each one to the right schema.
- Split long files into sub-documents, with AI deciding where one ends and the next begins.
- Turn on
citationsin Standardize to get the page location and a confidence level for every extracted value. - Use Workflows to run a sequence such as upload, classify and standardize from a single upload call. See the workflow code sample.
- Use Webhooks to receive results as soon as they're ready instead of polling.
