SDKs & HTTP recipes
HTTP recipes and SDK roadmap
First-party Python and Node SDKs ship in Q3 2026. For now, the HTTP API is the full interface — any client that can POST multipart or JSON can use Docira.
SDK status
| Client | Status | Available now |
|---|---|---|
| docira-python | Q3 2026 | httpx recipe below |
| docira-node | Q3 2026 | fetch recipe below |
| cURL | Available | All examples below |
Until the SDKs ship, the recipes below are copy-paste ready. The full endpoint specification is in the API reference.
File upload
POST /v1/parse/upload — multipart. Accepts PDF, PNG, JPEG, TIFF, HEIC, WEBP, DOCX, PPTX, XLSX up to 50 MB.
curl -X POST https://api.docira.io/v1/parse/upload \
-H "X-API-Key: $DOCIRA_API_KEY" \
-F "file=@report.pdf" \
-F "operation_mode=ocr"import httpx, os
def parse_file(path: str) -> dict:
with open(path, "rb") as f:
r = httpx.post(
"https://api.docira.io/v1/parse/upload",
headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
files={"file": (path, f, "application/pdf")},
data={"operation_mode": "ocr"},
timeout=120.0,
)
r.raise_for_status()
return r.json()
result = parse_file("report.pdf")
for page in result["pages"]:
print(f"Page {page['page_number']} ({page['tier_used']} / {page['provider']})")
print(page["content_markdown"])import { readFileSync } from "fs";
async function parseFile(path) {
const fd = new FormData();
fd.append("file", new Blob([readFileSync(path)]), path);
fd.append("operation_mode", "ocr");
const res = await fetch("https://api.docira.io/v1/parse/upload", {
method: "POST",
headers: { "X-API-Key": process.env.DOCIRA_API_KEY },
body: fd,
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Docira ${res.status}: ${body}`);
}
return res.json();
}
const result = await parseFile("report.pdf");
for (const page of result.pages) {
console.log(`Page ${page.page_number} — ${page.tier_used} / ${page.provider}`);
console.log(page.content_markdown);
}URL parse
POST /v1/parse — JSON body. Docira fetches the document from the URL. The URL must be publicly reachable over HTTPS.
curl -X POST https://api.docira.io/v1/parse \
-H "X-API-Key: $DOCIRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_url": "https://example.com/report.pdf",
"operation_mode": "ocr"
}'import httpx, os
r = httpx.post(
"https://api.docira.io/v1/parse",
headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
json={
"file_url": "https://example.com/report.pdf",
"operation_mode": "ocr",
},
timeout=120.0,
)
r.raise_for_status()
print(r.json()["pages"][0]["content_markdown"])const res = await fetch("https://api.docira.io/v1/parse", {
method: "POST",
headers: {
"X-API-Key": process.env.DOCIRA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
file_url: "https://example.com/report.pdf",
operation_mode: "ocr",
}),
});
const data = await res.json();
console.log(data.pages[0].content_markdown);Batch submission and polling
POST /v1/parse/batch accepts up to 100 documents and returns a batch_id immediately. Poll GET /v1/batch/{batch_id} or pass a webhook_url to get notified on completion.
# Submit
curl -X POST https://api.docira.io/v1/parse/batch \
-H "X-API-Key: $DOCIRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{"file_url": "https://example.com/doc1.pdf", "operation_mode": "ocr"},
{"file_url": "https://example.com/doc2.pdf", "operation_mode": "ocr"}
],
"webhook_url": "https://your.app/webhook"
}'
# Poll status (use batch_id from the response above)
curl https://api.docira.io/v1/batch/batch_01JEXAMPLE \
-H "X-API-Key: $DOCIRA_API_KEY"import httpx, os, time
client = httpx.Client(
headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
base_url="https://api.docira.io",
timeout=30.0,
)
# Submit batch
resp = client.post("/v1/parse/batch", json={
"documents": [
{"file_url": url, "operation_mode": "ocr"}
for url in [
"https://example.com/doc1.pdf",
"https://example.com/doc2.pdf",
]
],
"webhook_url": "https://your.app/webhook",
})
resp.raise_for_status()
batch_id = resp.json()["batch_id"]
print(f"Batch submitted: {batch_id}")
# Poll until complete
while True:
status = client.get(f"/v1/batch/{batch_id}").json()
print(f" {status['status']} — {status['completed_documents']}/{status['total_documents']}")
if status["status"] in ("completed", "failed"):
break
time.sleep(5)const headers = {
"X-API-Key": process.env.DOCIRA_API_KEY,
"Content-Type": "application/json",
};
// Submit
const submit = await fetch("https://api.docira.io/v1/parse/batch", {
method: "POST",
headers,
body: JSON.stringify({
documents: [
{ file_url: "https://example.com/doc1.pdf", operation_mode: "ocr" },
{ file_url: "https://example.com/doc2.pdf", operation_mode: "ocr" },
],
webhook_url: "https://your.app/webhook",
}),
});
const { batch_id } = await submit.json();
console.log("Batch submitted:", batch_id);
// Poll
while (true) {
const status = await (
await fetch(`https://api.docira.io/v1/batch/${batch_id}`, { headers })
).json();
console.log(status.status, status.completed_documents, "/", status.total_documents);
if (["completed", "failed"].includes(status.status)) break;
await new Promise((r) => setTimeout(r, 5000));
}Webhook signatures and retry behaviour are documented on the Webhooks page.
Streaming SSE
POST /v1/parse/stream — same multipart upload as /parse/upload but Docira streams pipeline events back as Server-Sent Events. Each event is a JSON line prefixed data:. The final event is result.final.
import httpx, json, os
with open("report.pdf", "rb") as f, httpx.stream(
"POST",
"https://api.docira.io/v1/parse/stream",
headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
files={"file": ("report.pdf", f, "application/pdf")},
timeout=None,
) as r:
for line in r.iter_lines():
if not line.startswith("data: "):
continue
evt = json.loads(line[6:])
print(evt.get("type"), evt)import { readFileSync } from "fs";
const fd = new FormData();
fd.append("file", new Blob([readFileSync("report.pdf")]), "report.pdf");
const res = await fetch("https://api.docira.io/v1/parse/stream", {
method: "POST",
headers: { "X-API-Key": process.env.DOCIRA_API_KEY },
body: fd,
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
const lines = buf.split("\n");
buf = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
const evt = JSON.parse(line.slice(6));
console.log(evt.type, evt);
}
}
}Error handling
All errors return JSON {"detail": "..."}. Key status codes:
| Code | When | Action |
|---|---|---|
| 401 | Missing or invalid X-API-Key | Check the header and key prefix |
| 402 | Plan limit reached | Upgrade or wait for quota reset |
| 413 | File > 50 MB | Compress or split the file |
| 429 | Rate limit hit | Wait for Retry-After header value (seconds) |
| 500 | Server error | Check request_id in detail, contact support |
| 503 | All providers circuit-broken | Retry with exponential backoff |
import httpx, os, time
def parse_with_retry(path: str, max_attempts: int = 3) -> dict:
for attempt in range(max_attempts):
try:
with open(path, "rb") as f:
r = httpx.post(
"https://api.docira.io/v1/parse/upload",
headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
files={"file": (path, f, "application/pdf")},
data={"operation_mode": "ocr"},
timeout=120.0,
)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", 10))
time.sleep(wait)
continue
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
if exc.response.status_code >= 500 and attempt < max_attempts - 1:
time.sleep(2 ** attempt)
continue
raise
raise RuntimeError("Max retries exceeded")When SDKs ship
The first-party SDKs (Python and Node) planned for Q3 2026 will wrap:
- Auth — API key injection, environment variable detection
- Batch — automatic polling loop with configurable interval and timeout
- Streaming — async iterator over SSE events with typed event objects
- Webhooks — HMAC signature verification helper
- Retries — exponential backoff with jitter, respects Retry-After
- Pydantic v2 models for ParseResponse, VLMResult, UsageSummary, and all sub-objects
Follow github.com/kimhons/Docira for release announcements.