OCRQueen API

Quickstart

60 seconds from API key to typed JSON.

Quickstart

1. Get a key

Sign in at ocrqueen.com → open API keys → click New key. The plaintext is shown once — copy it.

2. Submit a document

bash
curl https://api.ocrqueen.com/v1/extract \
  -H "Authorization: Bearer pk_test_xxx" \
  -F "file=@invoice.pdf"

You get back a job, queued for extraction:

json
{
  "job_id": "job_a3f9c2",
  "status": "queued",
  "status_url": "https://api.ocrqueen.com/v1/jobs/job_a3f9c2",
  "estimated_seconds": 12
}

3. Poll for the result

bash
curl https://api.ocrqueen.com/v1/jobs/job_a3f9c2 \
  -H "Authorization: Bearer pk_test_xxx"

Once status is completed (1-30s for most docs), the response carries the full document + a markdown render. Drop the markdown straight into an LLM prompt, or walk the typed blocks yourself.

Skip the polling — use ?sync=true

For documents that finish quickly (the common case), add sync=trueand we'll hold the connection open up to 25 seconds. If the worker finishes in time you get the completed result inline — one HTTP round-trip instead of two. If the doc runs long, the response falls back to 202 with a Retry-After header and you poll as usual.

bash
curl https://api.ocrqueen.com/v1/extract \
  -H "Authorization: Bearer pk_test_xxx" \
  -F "file=@invoice.pdf" \
  -F "sync=true"
# → 200 OK with the full { document, markdown, ... } inline.

Our SDKs enable sync by default and fall back to polling for long docs automatically — you write client.extract("invoice.pdf") and never think about it. See Python and Node / TypeScript.

That's it. Two endpoints, one optional polling loop. SDKs ship in Python and Node — see the cookbooks for the patterns customers reach for most.

Authentication

Pass your key as a bearer token on every request:

bash
Authorization: Bearer pk_test_xxx

Two prefixes:

  • pk_test_* — sandboxed. Real extraction runs, no charges, useful for CI.
  • pk_live_* — production. Real billing applies.

Keys are shown exactly once at creation. Lost it? Revoke + reissue— there's no recovery flow.

Response shape

A completed job carries document.pages[].blocks[] — every block has a stable type, bbox (normalized 0..1), and text_source so you can trust the provenance of every character.

json
{
  "job_id": "job_a3f9c2",
  "status": "completed",
  "document": {
    "source": { "kind": "pdf", "filename": "invoice.pdf", "page_count": 2 },
    "pages": [
      {
        "number": 1,
        "blocks": [
          {
            "type": "heading",
            "level": 1,
            "text": "Invoice #INV-3034",
            "bbox": [0.08, 0.04, 0.62, 0.09],
            "text_source": "pdf_text_layer"
          },
          {
            "type": "paragraph",
            "text": "Vendor: Acme Corp",
            "text_source": "pdf_text_layer"
          }
        ]
      }
    ]
  },
  "markdown": "# Invoice #INV-3034\n\nVendor: Acme Corp\n..."
}

text_source tells you where the text came from: pdf_text_layer and pptx_native are byte-perfect; llm_vision_ocr is probabilistic — pair with confidence for high-stakes use.

Idempotency

Send a unique Idempotency-Key header on POST /v1/extract and retrying the same key returns the original job — no re-extraction, no double billing. Critical for at-least-once delivery from job queues and retry wrappers.

bash
curl https://api.ocrqueen.com/v1/extract \
  -H "Authorization: Bearer pk_test_xxx" \
  -H "Idempotency-Key: invoice-3034-2026-05-14" \
  -F "file=@invoice.pdf"

On a replay, the response carries Idempotent-Replayed: true so you can distinguish it from a fresh extraction in logs and metrics.

  • Keys are scoped to your account. Two customers can use the same key string without colliding.
  • The first call wins. If you retry with the same key but a different file, you still get the original job back — the idempotency contract is the key, not the body.
  • Use any stable string, 1-255 chars. A UUID, your internal job ID, or sha256(file)-timestampall work. Don't reuse keys across logically distinct calls.

Both SDKs accept it as a per-call option:

Pythonpython
result = client.extract(
    "invoice.pdf",
    idempotency_key="invoice-3034-2026-05-14",
)
Nodetypescript
const result = await client.extract("invoice.pdf", {
  idempotencyKey: "invoice-3034-2026-05-14",
});

Errors

Every error uses the same envelope:

json
{
  "error": {
    "code": "PDF_PASSWORD_PROTECTED",
    "message": "Cannot extract from a password-protected PDF.",
    "request_id": "req_xxx"
  }
}

The codes you're most likely to see:

  • INVALID_API_KEY — bad/missing/revoked key
  • UNSUPPORTED_FILE_TYPE — only PDF, PPTX, PPT are accepted
  • FILE_TOO_LARGE — max 100 MB
  • PDF_PASSWORD_PROTECTED — strip the password first
  • RATE_LIMITED — back off, retry

Rate limits

Per API key, per minute (fixed window):

  • New account (no usage yet, no funds) — 10 req/min
  • Any account that has used a free page or topped up — 60 req/min
  • Enterprise — custom (talk to us)

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until the window resets). A 429 means slow down; need more headroom? Talk to us.


Ready for real workflows? Read the cookbooks — RAG ingest, patent extraction with profile=advanced, and batch processing with webhooks.

Questions? Email support@ocrqueen.com.