okra

OkraPDF — upload PDFs, read extracted content, ask questions, extract structured data, and manage collections. Covers MCP, CLI, and HTTP.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "okra" with this command: npx skills add steventsao/okra

OkraPDF

Upload a PDF, get an API. Extract tables, ask questions, get structured JSON — via MCP, CLI, or HTTP.

Designed for subagents. Every document is its own stateless endpoint. Fire off parallel queries to different documents — no shared state, no locks, no ordering issues. Ideal as a tool inside agent loops (Claude, GPT, custom orchestrators).

Setup

MCP (Claude Code, Cursor, OpenCode)

Add to ~/.claude/mcp.json or .cursor/mcp.json:

{
  "mcpServers": {
    "okra-pdf": {
      "type": "url",
      "url": "https://api.okrapdf.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

CLI

npm install -g okrapdf
okra auth set-key YOUR_API_KEY

HTTP

All endpoints use https://api.okrapdf.com with header Authorization: Bearer $OKRA_API_KEY.

Get a free API key at okrapdf.com (Settings > API Keys).


Upload a PDF

CLI

okra extract invoice.pdf
okra extract https://arxiv.org/pdf/2307.09288
okra extract report.pdf --processor llamaparse
okra run report.pdf "What was total revenue?"     # upload + ask in one shot

Options: -o json|markdown|table, --processor, --tables-only, --text-only, -d <dir> (agentic workspace), -q (quiet, for piping)

MCP

upload_document(url: "https://example.com/report.pdf")
upload_document(url: "https://arxiv.org/pdf/2307.09288", wait: true)
upload_document(url: "https://example.com/invoice.pdf", page_images: "lazy")

Parameters: url (required), wait (default: true), document_id (optional), page_images (none/cover/lazy), processor

HTTP

# From URL
curl -X POST https://api.okrapdf.com/v1/documents \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://arxiv.org/pdf/2307.09288"}'

# From file
curl -X POST https://api.okrapdf.com/v1/documents \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -F "file=@report.pdf" -F "page_images=cover"

Response: {"document_id": "doc-abc123", "phase": "extracting", "pages_total": 42}


Check Status

CLI

okra status doc-abc123

MCP

get_document_status(document_id: "doc-abc123")

HTTP

curl https://api.okrapdf.com/v1/documents/doc-abc123/status \
  -H "Authorization: Bearer $OKRA_API_KEY"

Response: {"phase": "complete", "page_count": 42, "total_nodes": 318}

Documents must reach phase: "complete" before reading/asking. Use wait: true on upload (MCP) or poll status.


Read Content

CLI

okra read doc-abc123
okra page get doc-abc123 1
okra toc doc-abc123
okra tree doc-abc123
okra search doc-abc123 "revenue"

MCP

read_document(document_id: "doc-abc123")
read_document(document_id: "doc-abc123", pages: "1-5")
read_document(document_id: "arxiv:2307.09288")

document_id accepts: doc-abc123, arxiv:2307.09288, or https://arxiv.org/pdf/2307.09288.

HTTP

# Full markdown
curl https://api.okrapdf.com/v1/documents/doc-abc123/full.md \
  -H "Authorization: Bearer $OKRA_API_KEY"

# Specific page
curl "https://api.okrapdf.com/v1/documents/doc-abc123/pages/3" \
  -H "Authorization: Bearer $OKRA_API_KEY"

# All pages as JSON
curl https://api.okrapdf.com/v1/documents/doc-abc123/pages \
  -H "Authorization: Bearer $OKRA_API_KEY"

Ask Questions

CLI

okra chat doc-abc123
okra chat send doc-abc123 -m "What are the key findings?"

MCP

ask_document(document_id: "doc-abc123", question: "What was total revenue in 2024?")

Returns answer with page citations (page number + supporting snippet).

HTTP (OpenAI-compatible)

curl -X POST https://api.okrapdf.com/document/doc-abc123/chat/completions \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "user", "content": "What was total revenue in 2024?"}],
    "stream": false
  }'

Supports "stream": true for SSE streaming.


Extract Structured Data

CLI

okra extract report.pdf -o json -q | jq '.entities[] | select(.type == "table")'

MCP

extract_data(
  document_id: "doc-abc123",
  prompt: "Extract all line items from this invoice",
  json_schema: {
    "type": "object",
    "properties": {
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": {"type": "string"},
            "quantity": {"type": "number"},
            "unit_price": {"type": "number"},
            "total": {"type": "number"}
          }
        }
      },
      "grand_total": {"type": "number"}
    }
  }
)

HTTP

curl -X POST https://api.okrapdf.com/document/doc-abc123/chat/completions \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "user", "content": "Extract all line items"}],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "invoice",
        "schema": {
          "type": "object",
          "properties": {
            "line_items": {"type": "array", "items": {"type": "object", "properties": {"description": {"type": "string"}, "amount": {"type": "number"}}}},
            "total": {"type": "number"}
          }
        },
        "strict": true
      }
    },
    "stream": false
  }'

Tables and Entities

CLI

okra tables doc-abc123
okra tables get doc-abc123 table-0
okra entities list doc-abc123
okra entities images doc-abc123
okra query doc-abc123 "table:has(revenue)"

HTTP

curl https://api.okrapdf.com/v1/documents/doc-abc123/entities/tables \
  -H "Authorization: Bearer $OKRA_API_KEY"

curl https://api.okrapdf.com/v1/documents/doc-abc123/entities \
  -H "Authorization: Bearer $OKRA_API_KEY"

Collections

Group documents and query across all of them at once.

Create and manage

CLI:

okra collections create "Q4 Earnings" -d "Quarterly filings" --docs doc-abc123,doc-def456
okra collections list                          # or: okra col ls
okra collections show "Q4 Earnings"
okra collections add "Q4 Earnings" doc-ghi789
okra collections remove "Q4 Earnings" doc-abc123
okra collections delete "Q4 Earnings"

HTTP:

# Create with seed documents
curl -X POST https://api.okrapdf.com/v1/collections \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Q4 Earnings", "document_ids": ["doc-abc123", "doc-def456"]}'

# Add documents
curl -X POST https://api.okrapdf.com/v1/collections/col-xxx/documents \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_ids": ["doc-ghi789"]}'

# List / get / delete
curl https://api.okrapdf.com/v1/collections -H "Authorization: Bearer $OKRA_API_KEY"
curl https://api.okrapdf.com/v1/collections/col-xxx -H "Authorization: Bearer $OKRA_API_KEY"
curl -X DELETE https://api.okrapdf.com/v1/collections/col-xxx -H "Authorization: Bearer $OKRA_API_KEY"

Query across documents

Two modes:

ModeBehaviorBest for
fanout (default)Separate completion per document, NDJSON streamPer-document answers
sandboxSingle LLM with grep/Python over all docsCross-doc search, comparisons

CLI:

okra chat -c "Q4 Earnings" -m "Compare revenue across companies"
okra chat "compare revenue" --doc doc-abc123,doc-def456

HTTP (fanout):

curl -X POST https://api.okrapdf.com/v1/collections/col-xxx/query \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What was total revenue in Q4?"}'

Response (NDJSON):

{"type":"start","query_id":"...","doc_count":7}
{"type":"result","doc_id":"doc-xxx","answer":"..."}
{"type":"done","completed":7,"failed":0}

HTTP (sandbox):

curl -X POST https://api.okrapdf.com/v1/collections/col-xxx/query \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Compare R&D spending. Show a table.", "mode": "sandbox"}'

Export

# NDJSON stream
curl -N "https://api.okrapdf.com/v1/collections/col-xxx/export?format=markdown" \
  -H "Authorization: Bearer $OKRA_API_KEY"

# Zip archive
curl -L "https://api.okrapdf.com/v1/collections/col-xxx/export?format=zip" \
  -H "Authorization: Bearer $OKRA_API_KEY" -o collection.zip

Exports

# CLI
okra extract report.pdf -o json -q > report.json

# HTTP
curl https://api.okrapdf.com/exports/doc-abc123/markdown -H "Authorization: Bearer $OKRA_API_KEY"
curl -o report.xlsx https://api.okrapdf.com/exports/doc-abc123/excel -H "Authorization: Bearer $OKRA_API_KEY"
curl -o report.docx https://api.okrapdf.com/exports/doc-abc123/docx -H "Authorization: Bearer $OKRA_API_KEY"
curl https://api.okrapdf.com/exports/doc-abc123/snapshot -H "Authorization: Bearer $OKRA_API_KEY"

Page Images

Deterministic, CDN-cached URLs:

https://res.okrapdf.com/v1/documents/{id}/pg_1.png
https://res.okrapdf.com/v1/documents/{id}/w_400,h_300/pg_1.png

Document Management

CLI

okra list
okra read doc-abc123
okra status doc-abc123
okra delete doc-abc123
okra auth set-key YOUR_API_KEY
okra auth whoami

HTTP

curl "https://api.okrapdf.com/v1/documents?limit=20" -H "Authorization: Bearer $OKRA_API_KEY"

Subagent & Parallel Patterns

OkraPDF is built for agent-to-agent use. Each document is an isolated Durable Object with its own SQLite — queries to different documents never contend. Run them in parallel freely.

As tools inside an agent loop

Map each document to a callable tool. The orchestrating agent picks which docs to query:

import { createOkra } from '@okrapdf/runtime';

const okra = createOkra({ apiKey: process.env.OKRA_API_KEY! });

const docs = [
  { id: 'doc-abc123', label: 'NVIDIA 10-K' },
  { id: 'doc-def456', label: 'AMD 10-K' },
  { id: 'doc-ghi789', label: 'Intel 10-K' },
];

// Each doc becomes a tool the agent can call
const sessions = Object.fromEntries(
  docs.map((d) => [d.label, okra.sessions.from(d.id)]),
);

// Execute in parallel when agent calls multiple tools at once
const results = await Promise.all(
  toolCalls.map(tc => sessions[tc.name].prompt(tc.input.question))
);

Fan-out: same question across N documents

// SDK — fire all in parallel, collect answers
const question = 'What was total revenue and YoY growth?';
const answers = await Promise.all(
  docIds.map(id => okra.sessions.from(id).prompt(question))
);
# curl — parallel background requests
for doc_id in doc-abc123 doc-def456 doc-ghi789; do
  curl -s -X POST "https://api.okrapdf.com/document/$doc_id/chat/completions" \
    -H "Authorization: Bearer $OKRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"messages\": [{\"role\": \"user\", \"content\": \"$question\"}], \"stream\": false}" &
done
wait
# CLI — multi-doc in one command
okra chat "compare revenue" --doc doc-abc123,doc-def456,doc-ghi789

MCP subagent pattern (Claude Code)

When Claude Code spawns subagents, each can independently call OkraPDF MCP tools:

# Subagent 1: ask_document(document_id: "doc-abc123", question: "What was revenue?")
# Subagent 2: ask_document(document_id: "doc-def456", question: "What was revenue?")
# Subagent 3: ask_document(document_id: "doc-ghi789", question: "What was revenue?")
# All run concurrently — no contention

When to use which pattern

PatternBest for
MCP toolsAgent picks which docs to query dynamically
SDK Promise.allYou know the doc set upfront, want max parallelism
Collections querySame question across a predefined group, server handles fan-out
okra chat --docQuick CLI comparison of 2-5 docs

Piping and Scripting

# Batch extract
for pdf in *.pdf; do okra extract "$pdf" -o json -q > "${pdf%.pdf}.json"; done

# Fan-out: same question to multiple docs in parallel
for doc_id in doc-abc123 doc-def456 doc-ghi789; do
  curl -s -X POST "https://api.okrapdf.com/document/$doc_id/chat/completions" \
    -H "Authorization: Bearer $OKRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"messages\": [{\"role\": \"user\", \"content\": \"What was total revenue?\"}], \"stream\": false}" &
done
wait

Available Processors

ProcessorBest ForSpeed
textlayerNative PDFs with selectable textFast
llamaparseComplex layouts, mixed contentMedium
unstructuredGeneral purposeMedium
azure-diForms, invoices, receiptsMedium
docaiHigh-accuracy OCRSlow
geminiVision-based extractionMedium
qwenOpen-source VLM extractionMedium

Error Codes

StatusMeaning
202Accepted, processing async
400Bad request
401Missing or invalid API key
404Document not found
409Conflict (document exists)
429Rate limited

Links

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

Cloudflare Manager

Manage Cloudflare DNS records, Tunnels (cloudflared), and Zero Trust policies. Use for pointing domains, exposing local services via tunnels, and updating in...

Registry SourceRecently Updated
Coding

Node Red Manager

Manage Node-RED instances via Admin API or CLI. Automate flow deployment, install nodes, and troubleshoot issues. Use when user wants to "build automation", "connect devices", or "fix node-red".

Registry SourceRecently Updated
Coding

Yt Dlp

A robust CLI wrapper for yt-dlp to download videos, playlists, and audio from YouTube and thousands of other sites. Supports format selection, quality control, metadata embedding, and cookie authentication.

Registry SourceRecently Updated
Coding

Daily Dev Agentic

daily.dev Agentic Learning - continuous self-improvement through daily.dev feeds. Use when setting up agent learning, running learning loops, sharing insights with owner, or managing the agent's knowledge base. Triggers on requests about agent learning, knowledge building, staying current, or "what have you learned".

Registry SourceRecently Updated