Guide
Kamy Ingest
POST a PDF, get structured JSON plus a public verify URL that proves the extraction matches the source. Built for AI agents that need to read documents from the outside world and act on them.
Quickstart
One POST /api/v1/extract with a template id and a source PDF (URL or base64). Auth via Bearer API key — same key you use for render and sign. Required scope: extract.
curl -X POST https://kamy.dev/api/v1/extract \
-H "Authorization: Bearer $KAMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "invoice",
"source": { "url": "https://example.com/invoice.pdf" }
}'Templates (v0)
Five predefined templates ship in v0. Custom JSON schemas land in v1; until then, pick the closest fit or use generic_form for anything that doesn't match.
invoice— Vendor bills with line items, totals, tax, due date, payment terms.receipt— POS and expense receipts with itemised purchases, totals, payment method.contract— Legal agreements: parties, key terms, dates, governing law, signatures.id_document— Passports, national IDs, driver's licences, residence permits.generic_form— Any labelled form: extracts every field/value pair plus form metadata.
Response shape
201 on success. The extraction field always matches the schema for the requested template. The verify_url is public and works forever.
{
"id": "8f3a5f2c-3b5d-4ea3-8b2e-4f6e7d8a9c01",
"template": "invoice",
"extraction": {
"invoice_number": "INV-2026-0042",
"issued_at": "2026-05-15",
"due_at": "2026-06-14",
"vendor": {
"name": "ACME Corp",
"address": "1 Market St, San Francisco CA",
"tax_id": "US-XX-XXXXXXX"
},
"customer": { "name": "Beta Industries" },
"line_items": [
{ "description": "Pro plan — May 2026", "quantity": 1, "unit_price": 99, "amount": 99 }
],
"subtotal": { "amount": 99, "currency": "USD" },
"tax": { "amount": 8.25, "currency": "USD" },
"total": { "amount": 107.25, "currency": "USD" },
"payment_terms": "Net 30"
},
"field_confidence": { "total.amount": 0.97, "payment_terms": 0.55 },
"low_confidence_fields": [{ "field": "payment_terms", "confidence": 0.55 }],
"needs_review": true,
"verify_url": "https://kamy.dev/extract/8f3a5f2c-...",
"source_sha256": "abc123...",
"extraction_sha256": "def456...",
"model": "claude-sonnet-4-6",
"signed_at": "2026-06-10T12:00:00Z",
"duration_ms": 8400
}Verifying the chain
Every extraction comes with an Ed25519 signature over the canonical concatenation of source SHA-256, extraction SHA-256, model id, and signing time. Anyone can re-verify offline with the source PDF, the extraction JSON, and Kamy's public key at /.well-known/kamy-extract-public-key.txt.
#!/usr/bin/env bash
# Verify a Kamy Ingest extraction locally. Inputs: source.pdf,
# extraction.json (downloaded from /extract/{id}/report), the
# signature, and Kamy's public key.
SOURCE_SHA=$(shasum -a 256 source.pdf | awk '{print $1}')
EXTRACTION_SHA=$(jq -cS . extraction.json | tr -d '\n' | shasum -a 256 | awk '{print $1}')
SIGNED_AT="2026-06-10T12:00:00Z"
MODEL="claude-sonnet-4-6"
SIG=$(jq -r '.signature_ed25519' report.json)
# Canonical input the signature was computed over
INPUT="$SOURCE_SHA|$EXTRACTION_SHA|$MODEL|$SIGNED_AT"
# Verify with Kamy's public key
echo -n "$INPUT" | openssl pkeyutl -verify \
-pubin -inkey kamy-public-key.pem \
-sigfile <(echo "$SIG" | base64 -d) \
&& echo "verified" || echo "FAILED"Download the full chain (including signature, canonical_input, and verify steps) at /extract/{id}/report.
Limits & pricing
- Max source size: 25 MB.
- Max pages: bounded by the Sonnet vision model — practical ceiling ~50 pages.
- Rate limit: 60 extractions / hour / user (sliding window).
- Free tier: 50 extractions / month, no credit card.
- Paid tiers share quota with renders / signatures — see pricing.
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid Bearer key. |
| 403 | FORBIDDEN | Key lacks the extract scope. |
| 413 | VALIDATION_ERROR | Source PDF exceeds 25 MB. |
| 422 | VALIDATION_ERROR | Source URL fetch failed, or the document is empty / unreadable. |
| 429 | RATE_LIMIT | 60/hour exceeded. Honour Retry-After. |
| 502 | INTERNAL_ERROR | Model returned no tool_use block or output failed schema validation. |