setup
Setup DeepRead
You are an AI agent helping a developer get started with DeepRead — an AI-native OCR API that extracts text and structured data from documents (PDFs, images) with 97%+ accuracy.
API: https://api.deepread.tech
Dashboard: https://www.deepread.tech
Docs: https://www.deepread.tech/docs
Step 1: Get an API Key (Device Authorization Flow)
You (the agent) obtain an API key on behalf of the user. The user never needs to copy/paste a key — it goes directly to you.
CRITICAL — run the entire device flow as ONE Bash tool call. Shell variables do not persist between tool calls. If you split this across multiple calls, the
device_codewill be lost and you will accidentally call/v1/agent/device/codeagain, getting a new code the user has never seen. Do it all in one script.
The complete flow — get code, open browser, poll, print key — in a single script:
# Get device code
dr_response=$(curl -s -X POST https://api.deepread.tech/v1/agent/device/code \
-H "Content-Type: application/json" \
-d '{"agent_name": "Claude Code"}')
dr_device_code=$(echo "$dr_response" | jq -r '.device_code')
dr_user_code=$(echo "$dr_response" | jq -r '.user_code')
dr_uri=$(echo "$dr_response" | jq -r '.verification_uri_complete')
dr_interval=$(echo "$dr_response" | jq -r '.interval')
# Validate the response before proceeding
if [ "$dr_device_code" = "null" ] || [ -z "$dr_device_code" ]; then
echo "ERROR: API did not return a device_code. Response: $dr_response"
exit 1
fi
echo "Opening browser: $dr_uri"
open "$dr_uri" 2>/dev/null || xdg-open "$dr_uri" 2>/dev/null || echo "Open manually: $dr_uri"
echo "Waiting for approval of code: $dr_user_code"
# Poll until approved (use dr_ prefix — 'status' is reserved in zsh)
dr_api_key=""
for dr_i in $(seq 1 72); do
sleep "$dr_interval"
dr_result=$(curl -s -X POST https://api.deepread.tech/v1/agent/device/token \
-H "Content-Type: application/json" \
-d "{\"device_code\": \"$dr_device_code\"}")
dr_api_key=$(echo "$dr_result" | jq -r '.api_key')
dr_error=$(echo "$dr_result" | jq -r '.error')
dr_prefix=$(echo "$dr_result" | jq -r '.key_prefix')
if [ "$dr_api_key" != "null" ] && [ -n "$dr_api_key" ]; then
echo "SUCCESS key_prefix=$dr_prefix"
echo "DEEPREAD_API_KEY=$dr_api_key"
break
elif [ "$dr_error" = "access_denied" ]; then echo "DENIED"; break
elif [ "$dr_error" = "expired_token" ]; then echo "EXPIRED"; break
else echo "attempt=$dr_i pending..."; fi
done
Variable naming: Always use a unique prefix (e.g. dr_) for all variables in this script. Never use bare status, result, or interval — these are reserved or commonly overloaded in zsh/bash and will cause read-only variable errors.
Never show the device_code to the user. Only show user_code and the browser URL.
Responses from the token endpoint (all fields always present — check api_key != null for success):
error |
api_key |
Meaning | Action |
|---|---|---|---|
"authorization_pending" |
null |
User hasn't approved yet | Wait interval seconds, poll again |
null |
"sk_live_..." |
User approved | Save the key, stop polling |
"access_denied" |
null |
User clicked Deny | Stop, inform user |
"expired_token" |
null |
Code expired (15 min) | Start over from the top |
Step 1d: Store the key safely
The api_key is returned exactly once — the server clears it after retrieval. Save it immediately.
Safe .env append — always use printf to guarantee a leading newline:
printf "\nDEEPREAD_API_KEY=%s\n" "$dr_api_key" >> .env
Never use echo "KEY=val" >> .env — if the file doesn't end with a newline, the key merges with the previous line.
What happens on the user's side
Already logged in: Opens the URL → code is auto-validated → sees your agent name + Approve/Deny → clicks Approve → redirected to dashboard. The key arrives on your next poll.
Not logged in: Signs in or creates an account → redirected back with code pre-filled → clicks Approve.
In both cases the key goes directly to you — it never appears in chat.
Step 2: Send Your First Document
IMPORTANT: Split submit and poll into SEPARATE Bash tool calls. Long-running poll loops block the conversation and give the user no way to interact. Submit first, confirm the job ID, then poll separately.
Step 2a: Submit the document (one Bash call)
DR_API_KEY=$(grep ^DEEPREAD_API_KEY .env | cut -d= -f2)
dr_submit=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST https://api.deepread.tech/v1/process \
-H "X-API-Key: $DR_API_KEY" \
-F "file=@document.pdf")
echo "$dr_submit"
Tell the user the job ID and that processing takes 2-3 minutes. Then move on to polling.
Step 2b: Poll for results (separate Bash call)
Prefer
run_in_background: truefor the poll loop so the conversation isn't blocked. If the user rejects a long-running poll, just do a single status check instead.
Guard against empty job ID. If
dr_job_idis empty or "null", stop immediately — don't loop.
Use
python3for parsing results, notjq. Job responses can be 200KB+ andjqmay choke with parse errors on large payloads. Always save to a temp file first.
DR_API_KEY=$(grep ^DEEPREAD_API_KEY .env | cut -d= -f2)
dr_job_id="THE_JOB_ID"
# Guard: bail if job ID is empty
if [ -z "$dr_job_id" ] || [ "$dr_job_id" = "null" ]; then
echo "ERROR: No job ID — submit may have failed. Check the submit response."
exit 1
fi
for dr_i in $(seq 1 40); do
sleep 5
dr_poll=$(curl -s "https://api.deepread.tech/v1/jobs/$dr_job_id" -H "X-API-Key: $DR_API_KEY")
dr_job_status=$(echo "$dr_poll" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','unknown'))")
echo "attempt=$dr_i status=$dr_job_status"
if [ "$dr_job_status" = "completed" ] || [ "$dr_job_status" = "failed" ]; then
echo "$dr_poll" > /tmp/deepread_result.json
python3 -c "
import json
with open('/tmp/deepread_result.json') as f:
data = json.load(f)
print(json.dumps({
'status': data.get('status'),
'preview_url': data.get('preview_url'),
'page_count': len(data.get('result', {}).get('pages', [])) if data.get('result') else 0,
'text_preview': (data.get('result', {}).get('text', '') or '')[:500],
}, indent=2))
"
break
fi
done
If the user says "check now" or the poll was rejected
Don't start a new poll loop — just do a single fetch:
DR_API_KEY=$(grep ^DEEPREAD_API_KEY .env | cut -d= -f2)
curl -s "https://api.deepread.tech/v1/jobs/JOB_ID" -H "X-API-Key: $DR_API_KEY" > /tmp/deepread_result.json
python3 -c "
import json
with open('/tmp/deepread_result.json') as f:
data = json.load(f)
print('Status:', data.get('status'))
if data.get('status') == 'completed':
print(json.dumps({
'preview_url': data.get('preview_url'),
'page_count': len(data.get('result', {}).get('pages', [])) if data.get('result') else 0,
'text_preview': (data.get('result', {}).get('text', '') or '')[:500],
}, indent=2))
elif data.get('status') == 'failed':
print('Error:', data.get('error'))
"
Supports PDF, PNG, JPG, JPEG. Max 15MB (free) / 50MB (paid).
Step 3: Extract Structured Data
Add a schema parameter with a JSON Schema. Field descriptions guide the AI — the better the description, the better the extraction.
Step 3a: Submit with schema (one Bash call)
DR_API_KEY=$(grep ^DEEPREAD_API_KEY .env | cut -d= -f2)
dr_submit=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST https://api.deepread.tech/v1/process \
-H "X-API-Key: $DR_API_KEY" \
-F "file=@invoice.pdf" \
-F 'schema={
"type": "object",
"properties": {
"vendor": {"type": "string", "description": "Company or vendor name on the invoice"},
"total": {"type": "number", "description": "Total amount due in dollars"},
"due_date": {"type": "string", "description": "Payment due date"}
}
}')
echo "$dr_submit"
Step 3b: Fetch structured results (separate Bash call)
Same split pattern as Step 2 — poll separately or do a single check when the user says the job is done:
DR_API_KEY=$(grep ^DEEPREAD_API_KEY .env | cut -d= -f2)
curl -s "https://api.deepread.tech/v1/jobs/JOB_ID" -H "X-API-Key: $DR_API_KEY" > /tmp/deepread_structured_result.json
python3 -c "
import json
with open('/tmp/deepread_structured_result.json') as f:
data = json.load(f)
print('Status:', data.get('status'))
if data.get('status') == 'completed':
print()
print('=== STRUCTURED DATA ===')
fields = data.get('result', {}).get('data', {})
if fields:
print(json.dumps(fields, indent=2))
else:
print('No structured data returned')
print()
meta = data.get('metadata', {})
print('=== METADATA ===')
print(json.dumps({k: meta[k] for k in ['page_count','pipeline','review_percentage','fields_requiring_review','total_fields'] if k in meta}, indent=2))
print()
print('Preview:', data.get('preview_url', 'N/A'))
elif data.get('status') == 'failed':
print('Error:', data.get('error'))
"
Each extracted field comes with quality metadata:
{
"vendor": {"value": "Acme Inc", "hil_flag": false, "found_on_page": 1},
"due_date": {"value": "2025-03-15", "hil_flag": true, "reason": "Multiple dates found", "found_on_page": 1}
}
hil_flag: false— extracted confidently, safe to auto-accepthil_flag: true— needs human review, checkreasonfor why
Step 4: Blueprints (Better Accuracy)
Blueprints are optimized schemas that improve accuracy by 20-30%. You give DeepRead sample documents + expected values, it enhances field descriptions automatically.
- Go to
https://www.deepread.tech/dashboard/optimizer - Upload 4+ sample docs + ground truth JSON
- DeepRead runs 3-5 optimization iterations
- Use the optimized blueprint:
curl -X POST https://api.deepread.tech/v1/process \
-H "X-API-Key: sk_live_YOUR_KEY" \
-F "file=@invoice.pdf" \
-F "blueprint_id=YOUR_BLUEPRINT_ID"
Use schema OR blueprint_id, not both.
Plans
| Plan | Pages/month | Max file | Per-doc limit | Price |
|---|---|---|---|---|
| Free | 2,000 | 15 MB | 50 pages | $0 |
| Pro | 50,000 | 50 MB | Unlimited | $99/mo |
| Scale | 1,000,000 | 50 MB | Unlimited | Custom |
What's Next
Use /api for the full reference — all endpoints, webhooks, error handling, blueprints API, and code examples in Python, JavaScript, and cURL.
Help the Developer
- No API key yet → run the device flow above (Step 1)
- Has API key → help send first request (Step 2)
- Wants structured data → help write a JSON Schema with good field descriptions (Step 3)
- Wants better accuracy → explain blueprints and optimizer (Step 4)
- Wants full integration → use
/apifor complete reference
More from deepread-tech/skills
api
Full DeepRead API reference. All endpoints, auth, request/response formats, blueprints, webhooks, error handling, and code examples.
9setup
Get started with DeepRead. Automatically obtains an API key via device authorization flow, then walks through first request, structured extraction, and blueprints.
8doc-sync
Detects when code changes have made documentation outdated and flags or updates the affected docs. Use after implementing features, changing APIs, or modifying architecture.
2enforce
Validates code changes against DeepRead's mandatory patterns and standards defined in AGENTS.md. Use this after writing or modifying code to catch violations before committing.
2prepare
Session opener. Analyzes a task description and creates a scoped plan with a checklist, affected files, and which skills to run. Use at the start of every coding session before writing any code.
2migrate
Helps create and manage Supabase database migrations. Use when adding or modifying database tables, columns, or constraints.
2