API Reference

Docira REST API

Eleven endpoints. JSON in, JSON out. Sync, async, and streaming variants for every common workflow. Live OpenAPI: https://api.docira.io/openapi.json.

Authentication

Every request needs an API key in the X-API-Key header. Get one from the dashboard under API Keys. Keys are scoped to a single account and roll over without downtime.

Header
X-API-Key: docira_live_sk_...

The /v1/health endpoint doesn't require auth.

Operation modes

Most parse endpoints take an operation_mode that controls what the VLM does on each page:

ModeWhat it does
ocrDefault. Returns Markdown faithful to the page layout.
extractSchema-driven JSON output. Provide extraction_schema (a JSON schema) on /parse or /parse/stream.
hybridReturns both Markdown and the JSON extraction in one pass.

Tier selection (fast / pro / premium) is decided automatically by the router on a per-page basis from the page's complexity score. Each page's response includes tier_used and provider so you can see what was picked.

Errors & rate limits

Conventional HTTP status codes. FastAPI returns errors as JSON {"detail": "..."} for top-level failures, or a structured validation list for schema errors.

CodeMeaning
400Bad request — malformed body, missing fields, unsupported file type
401Missing or invalid API key (X-API-Key header)
402Plan limit reached — upgrade or wait for the next billing cycle
404Request ID not found OR belongs to another tenant
413File exceeds max upload size (default 50 MB)
422Document accepted but pipeline failed sanity checks (e.g., empty pages)
429Rate limit exceeded — retry after the Retry-After header
500Server error — request_id is included, please report
503All providers in the requested tier are circuit-broken

Rate limits scale with plan. Free: 10 req/min, 25 pages/day. Pro: 60 req/min, 10k pages/day. Scale: custom — talk to us.

POST/v1/parse/upload

Parse an uploaded file

Multipart file upload. Runs the full pipeline (ingest → classify → route → VLM → verify → deliver) and returns the result synchronously. The most common entry point.

Request body

Multipart fields:
  file                 (required) — PDF, image, DOCX, PPTX, XLSX, HTML, MD
  operation_mode       ocr | extract | hybrid              default: ocr
  maintain_format      bool — preserve heading/list shape  default: true
  max_pages            int — cap pages processed           default: null
  enable_verification  bool — re-route low-confidence pgs  default: null (auto)
  dpi                  int — rasterization resolution      default: null (auto)

Response

{
  "id": "req_01JEXAMPLE...",
  "status": "completed",
  "document": {
    "hash": "612b65b...",
    "pages": 3,
    "format": "pdf",
    "cached": false,
    "filename": "invoice.pdf",
    "size_bytes": 200838
  },
  "pages": [
    {
      "page_number": 1,
      "content_markdown": "# Heading\n\nBody text...",
      "content_json": null,
      "tier_used": "pro",
      "provider": "anthropic",
      "model": "claude-sonnet-4.5",
      "confidence": {
        "layout_score": 0.94,
        "ocr_score": 0.91,
        "verbatim_score": 0.92,
        "table_score": null,
        "mean_grade": "good",
        "low_grade": "good"
      },
      "grounding": [{"text": "...", "bbox": {...}, "element_type": "paragraph"}],
      "tokens": {"input_tokens": 1240, "output_tokens": 380},
      "latency_ms": 2840,
      "re_routed": false,
      "re_route_result": null
    }
  ],
  "extracted": null,
  "usage": {
    "total_input_tokens": 12480,
    "total_output_tokens": 3210,
    "estimated_cost_usd": 0.018,
    "pages_by_tier": {"fast": 2, "pro": 1}
  },
  "timing": {
    "total_ms": 4210,
    "classification_ms": 1120,
    "routing_ms": 2,
    "vlm_processing_ms": 2630,
    "verification_ms": 458
  },
  "document_type": null,
  "created_at": "2026-05-01T17:42:18Z"
}

Examples

curl
curl -X POST https://api.docira.io/v1/parse/upload \
  -H "X-API-Key: $DOCIRA_API_KEY" \
  -F "file=@invoice.pdf" \
  -F "operation_mode=ocr"
Python (httpx)
import httpx, os

with open("invoice.pdf", "rb") as f:
    r = httpx.post(
        "https://api.docira.io/v1/parse/upload",
        headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
        files={"file": ("invoice.pdf", f, "application/pdf")},
        data={"operation_mode": "ocr"},
        timeout=120.0,
    )
r.raise_for_status()
print(r.json()["pages"][0]["content_markdown"])
JavaScript (fetch)
const fd = new FormData();
fd.append("file", fileBlob, "invoice.pdf");
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,
});
const data = await res.json();
console.log(data.pages[0].content_markdown);
POST/v1/parse

Parse from a URL

Same pipeline as /parse/upload, but Docira fetches the document for you. The URL must be publicly reachable and respond with a supported content-type.

Request body

{
  "file_url": "https://example.com/report.pdf",  // required
  "operation_mode": "ocr",                        // default: "ocr"
  "extraction_schema": null,                      // JSON schema for extract mode
  "maintain_format": true,
  "webhook_url": null,                            // POST result here when done
  "max_pages": null
}

Response

Identical shape to /v1/parse/upload.

Examples

curl
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"
  }'
Python
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,
)
print(r.json())
POST/v1/parse/stream

Parse with live progress (SSE)

Same multipart upload as /parse/upload, but Docira streams pipeline events back as Server-Sent Events. Use this to render a progress UI without polling. The final event is always result.final carrying the full ParseResponse.

Request body

Multipart fields (same as /parse/upload, plus extraction_schema):
  file                 (required)
  operation_mode       default: "ocr"
  maintain_format      default: true
  max_pages            default: null
  extraction_schema    JSON schema for extract mode
  enable_verification  default: null (auto)
  dpi                  default: null (auto)

Response

data: {"event":"ingest.start","page_count":3}
data: {"event":"classify.page","page":1,"complexity":0.42}
data: {"event":"route.page","page":1,"tier":"pro","provider":"anthropic"}
data: {"event":"vlm.page.done","page":1,"confidence":0.94}
data: {"event":"result.final","payload":{ ...full ParseResponse... }}

Examples

JavaScript (fetch + ReadableStream)
const fd = new FormData();
fd.append("file", fileBlob, "doc.pdf");

const res = await fetch("https://api.docira.io/v1/parse/stream", {
  method: "POST",
  headers: { "X-API-Key": apiKey },
  body: fd,
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += decoder.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.event, evt);
    }
  }
}
Python (httpx stream)
import httpx, json, os

with open("doc.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": ("doc.pdf", f, "application/pdf")},
    timeout=None,
) as r:
    for line in r.iter_lines():
        if line.startswith("data: "):
            evt = json.loads(line[6:])
            print(evt["event"], evt)
POST/v1/parse/structured

Extract tables, equations, and figures

Returns typed structured blocks (tables as cell grids, equations as LaTeX, figures with captions and bboxes) instead of free-form Markdown. Use this when downstream code needs to consume tables programmatically.

Request body

Multipart fields:
  file        (required)
  max_pages   int — cap pages processed   default: null
  model       string — extractor VLM      default: "claude-sonnet-4-6"

Response

{
  "document": {...},
  "blocks": [
    { "type": "table", "table_id": "t1", "page": 2,
      "cells": [{"row":0,"col":0,"text":"Q1","row_span":1,"col_span":1}, ...],
      "bbox": [120, 540, 880, 720] },
    { "type": "equation", "equation_id": "eq1", "page": 4,
      "latex": "\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}",
      "bbox": [...] },
    { "type": "figure", "figure_id": "f1", "page": 3,
      "caption": "Figure 1: ...", "bbox": [...] }
  ],
  "warnings": [],
  "usage": {...},
  "timing": {...}
}

Examples

curl
curl -X POST https://api.docira.io/v1/parse/structured \
  -H "X-API-Key: $DOCIRA_API_KEY" \
  -F "file=@statement.pdf"
POST/v1/parse/estimate

Estimate cost before parsing

Runs only the ingestion + classification stages (cheap) and returns the projected per-tier mix and dollar cost. Useful for previewing batch jobs.

Request body

Multipart fields:
  file       (required)
  max_pages  int — cap pages   default: null

Response

{
  "page_count": 42,
  "tier_mix": { "fast": 28, "pro": 12, "premium": 2 },
  "estimated_cost_usd": 0.094,
  "doc_type": "textbook",
  "applied_preset": { "tier_floor": "pro", "return_grounding": true }
}

Examples

curl
curl -X POST https://api.docira.io/v1/parse/estimate \
  -H "X-API-Key: $DOCIRA_API_KEY" \
  -F "file=@textbook.pdf"
POST/v1/compare

Compare 1–4 providers side-by-side

Runs the same document through multiple providers in parallel. The providers field is a JSON-encoded array of {id, model} entries. Returns one merged payload tagged by provider+model so you can render a side-by-side compare. Powers the playground compare view.

Request body

Multipart fields:
  file            (required)
  providers       (required) JSON-encoded array of 1–4 entries:
                  [{"id":"aimlapi","model":"gpt-4o"},
                   {"id":"nvidia","model":"meta/llama-3.2-90b-vision-instruct"}]
  operation_mode  default: "ocr"
  max_pages       default: null

Response

{
  "compare_id": "cmp_b96f552bd249",
  "filename": "doc.pdf",
  "providers_requested": [{"id":"aimlapi","model":"gpt-4o"}, ...],
  "runs": [
    {
      "provider_id": "aimlapi",
      "model_id": "gpt-4o",
      "request_id": "req_...",
      "status": "completed",
      "response": { ...full ParseResponse for this provider... }
    }
  ]
}

Examples

curl
curl -X POST https://api.docira.io/v1/compare \
  -H "X-API-Key: $DOCIRA_API_KEY" \
  -F "file=@page.pdf" \
  -F 'providers=[{"id":"aimlapi","model":"gpt-4o"},{"id":"nvidia","model":"meta/llama-3.2-90b-vision-instruct"}]'
Python
import httpx, json, os

with open("page.pdf", "rb") as f:
    r = httpx.post(
        "https://api.docira.io/v1/compare",
        headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
        files={"file": ("page.pdf", f, "application/pdf")},
        data={
            "providers": json.dumps([
                {"id": "aimlapi", "model": "gpt-4o"},
                {"id": "nvidia",  "model": "meta/llama-3.2-90b-vision-instruct"},
            ]),
        },
        timeout=240.0,
    )
print(r.json()["runs"])
POST/v1/parse/batch

Submit a batch (async)

Submit up to settings.max_batch_documents documents at once for background processing (currently 100). Each entry is a full ParseRequest. Returns a batch ID immediately. Poll /v1/batch/{batch_id} or set webhook_url for delivery.

Request body

{
  "documents": [
    {
      "file_url": "https://example.com/doc1.pdf",
      "operation_mode": "ocr",
      "maintain_format": true
    },
    {
      "file_url": "https://example.com/doc2.pdf",
      "operation_mode": "extract",
      "extraction_schema": { "type": "object", "properties": { ... } }
    }
  ],
  "webhook_url": "https://your.app/webhook",
  "priority": "normal"
}

Response

{
  "batch_id": "batch_01JEXAMPLE...",
  "status": "queued",
  "item_count": 2
}

Examples

Python
import httpx, os

r = httpx.post(
    "https://api.docira.io/v1/parse/batch",
    headers={"X-API-Key": os.environ["DOCIRA_API_KEY"]},
    json={
        "documents": [
            {"file_url": u, "operation_mode": "ocr"} for u in urls
        ],
        "webhook_url": "https://your.app/webhook",
        "priority": "normal",
    },
)
batch_id = r.json()["batch_id"]
GET/v1/parse/{request_id}

Retrieve a parse result

Fetch a previously-completed (or in-progress) parse by ID. Returns 404 for both 'not found' and 'found but belongs to another user' to prevent existence leakage across tenants.

Response

Same shape as POST /v1/parse/upload, plus a status field while in progress.

Examples

curl
curl https://api.docira.io/v1/parse/req_01JEXAMPLE \
  -H "X-API-Key: $DOCIRA_API_KEY"
GET/v1/batch/{batch_id}

Retrieve batch status

Returns counts (queued / in_progress / completed / failed) and per-item request IDs once each item starts processing.

Response

{
  "batch_id": "batch_01JEXAMPLE",
  "status": "in_progress",
  "totals": { "queued": 0, "in_progress": 12, "completed": 88, "failed": 0 },
  "items": [
    { "request_id": "req_...", "status": "completed", "file_url": "https://..." }
  ]
}

Examples

curl
curl https://api.docira.io/v1/batch/batch_01JEXAMPLE \
  -H "X-API-Key: $DOCIRA_API_KEY"
GET/v1/providers

List provider pool + circuit-breaker state

Operational endpoint. Shows which VLM providers are currently registered in the pool and their circuit-breaker state (CLOSED / HALF_OPEN / OPEN / IDLE). Requires authentication.

Response

{
  "providers": [
    { "provider_id": "nvidia",   "circuit_breaker_state": "IDLE",   "healthy": true },
    { "provider_id": "together", "circuit_breaker_state": "IDLE",   "healthy": true },
    { "provider_id": "aimlapi",  "circuit_breaker_state": "CLOSED", "healthy": true }
  ]
}

Examples

curl
curl https://api.docira.io/v1/providers \
  -H "X-API-Key: $DOCIRA_API_KEY"
GET/v1/health

Health check

Liveness check used by Fly.io and uptime monitoring. No auth required.

Response

{
  "status": "healthy",
  "version": "0.1.0",
  "uptime_seconds": 73821.4,
  "providers": {
    "groq":    { "state": "CLOSED", "healthy": true },
    "aimlapi": { "state": "CLOSED", "healthy": true }
  }
}

Examples

curl
curl https://api.docira.io/v1/health

Need help?

Open an issue on GitHub, or email hello@docira.io. Security issues: security@docira.io.