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

StatusCodeWhen
401UNAUTHORIZEDMissing or invalid Bearer key.
403FORBIDDENKey lacks the extract scope.
413VALIDATION_ERRORSource PDF exceeds 25 MB.
422VALIDATION_ERRORSource URL fetch failed, or the document is empty / unreadable.
429RATE_LIMIT60/hour exceeded. Honour Retry-After.
502INTERNAL_ERRORModel returned no tool_use block or output failed schema validation.
Kamy Ingest — extract structured data from PDFs — Kamy