API quickstart
One endpoint in, typed fields out: POST /api/v1/extract returns every field as
{value, type, confidence, p_null} plus a document-level needs_review flag.
Three steps below — the curl example runs as-is once you have a key.
1 · Get an API key
1Free tier — instant key
1,000 extractions per UTC calendar month, no card:
curl -X POST https://extract.redsugartree.com/api/v1/signup \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
Returns {"api_key":"eex_free_…","plan":"free","quota":1000}. Save it — the key is shown
only once. Lost it? POST /api/v1/reveal-key with the same email gets it back (3 requests/hour/IP).
2Paid tier — buy, then reveal
Checkout on the pricing page ($9 Starter / 10K, $29 Pro / 100K). After payment confirmation, fetch your key with the purchase email:
curl -X POST https://extract.redsugartree.com/api/v1/reveal-key \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
keys@extract.redsugartree.com right after payment. If it never lands (spam folder, typo'd
address), don't wait — the reveal-key command above gets the same key with your purchase
email (3 requests/hour/IP).2 · Call the extract endpoint
3Send a document, get typed fields
Auth: Authorization: Bearer <key> (or X-API-Key: <key>).
Exactly one of text / image_b64 / file_url in input.
This example ships with the repo — copy, swap the key, run:
curl -X POST https://extract.redsugartree.com/api/v1/extract \
-H "Authorization: Bearer eex_free_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"doc_type":"invoice","input":{"text":"INVOICE INV-2291\nVendor: Acme Ltd\nInvoice date: 2026-11-01\nDue date: 2026-11-30\nCurrency: USD\nLine items:\n- Design retainer (Nov 2026): 1000.00\n- Extra revisions: 80.00\nTax: 0.00\nTotal due: 1080.00"}}'
200 response (real field names, real contract):
{
"doc_type": "invoice",
"data": {
"total": { "value": 1080.00, "type": "number", "confidence": 0.98, "p_null": 0.01 },
"tax": { "value": 0.00, "type": "number", "confidence": 0.95, "p_null": 0.04 },
"currency": { "value": "USD", "type": "text", "confidence": 0.99, "p_null": 0.00 },
"vendor": { "value": "Acme Ltd", "type": "text", "confidence": 0.99, "p_null": 0.00 },
"invoice_date":{ "value": "2026-11-01", "type": "date", "confidence": 0.97, "p_null": 0.02 },
"due_date": { "value": "2026-11-30", "type": "date", "confidence": 0.96, "p_null": 0.03 },
"line_items": { "value": [ { "description": "Design retainer (Nov 2026)", "amount": 1000.00 },
{ "description": "Extra revisions", "amount": 80.00 } ],
"type": "list", "confidence": 0.94, "p_null": 0.05 }
},
"needs_review": false,
"truncated": false,
"usage": { "tokens_in": 655, "cost_usd": 0.0000655 },
"generated_by": "ai"
}
Request reference
| Body field | Type | Notes |
|---|---|---|
doc_type | string | invoice | receipt | contract | ticket | custom (required) |
input | object | Exactly one of text (string), image_b64 (base64 image), file_url (http/https, 10s fetch; PDF only with a text layer) |
schema | object | Required only for doc_type=custom: {"fields":[{"name":"…","type":"number","required":true,"hint":"…"}]}. Ignored for built-in types. |
options.confidence_threshold | number 0–1 | Default 0.7. A field below it (or with p_null > 0.3) sets needs_review=true. |
options.return_raw | boolean | Default false. When true, the raw model output is attached as raw. |
Input rules
- Pick exactly one input kind:
text(any plain string),image_b64(PNG / JPEG / WebP, base64), orfile_url(http/https URL — plain text, images, and PDFs with a text layer). - Request body max 2 MB — larger →
422 input_too_large. textlonger than 50,000 chars is truncated and flagged"truncated": true(never an error).file_urlmust be http/https; fetched server-side with a 10s timeout. Images (jpg/png/webp) go through vision; PDF is supported only when it has an extractable text layer — the text is extracted server-side and run through the text path. Scanned / image-only PDFs →422 unsupported_input(extract the text client-side and send it astext).- Missing fields are returned as
value: nullwith a highp_null— absence is information, not an error. - Invoice totals are reconciled against
line_itemsin our code (1% tolerance); a mismatch only setsneeds_review, never fails the request.
Rate limits
10 requests/second sustained per key with a burst of 20. Over the limit → 429 rate_limited
with a Retry-After header. Limits are enforced per key; different keys never affect each other.
A free-tier edge firewall rule (Bot Fight Mode + WAF rate limiting on /api/*) backs this up.
Error codes
Every error uses the same body: {"error":{"code":"…","message":"…"}}.
| HTTP | code | When |
|---|---|---|
| 401 | invalid_api_key | Missing, invalid or revoked key. The response never reveals which. |
| 402 | quota_exceeded | Monthly quota reached, or subscription not active (error.reason = subscription_inactive; paid responses carry X-Subscription-Status). |
| 422 | invalid_request + sub-codes | See table below. |
| 429 | rate_limited | Per-key rate limit exceeded (Retry-After header set). |
| 500 | internal_error | Unexpected server error (no stack traces leaked). |
| 502 | upstream_unavailable | Extraction model failed after 2 retries — retry later, quota untouched. |
422 sub-codes
| code | Cause |
|---|---|
invalid_request | Bad JSON, unknown doc_type, more than one (or zero) input keys, bad options. |
input_too_large | Body > 2 MB. |
missing_schema | doc_type=custom without a schema. |
invalid_field_type | Custom field type outside text/number/date/datetime/list/boolean. |
too_many_fields / invalid_field_name / duplicate_field_name / invalid_schema | Custom schema shape violations (≤30 fields; names match [a-zA-Z_][a-zA-Z0-9_]*). |
invalid_confidence_threshold | options.confidence_threshold outside 0–1. |
invalid_file_url | Non-http(s) URL, fetch failure or timeout (>10s). |
unsupported_input | Unsupported content type from file_url, or a PDF without an extractable text layer. |
Built-in schemas
invoice 7 fields
| field | type | req |
|---|---|---|
total | number | yes |
tax | number | |
currency | text | yes |
vendor | text | yes |
invoice_date | date | |
due_date | date | |
line_items | list |
contract 6 fields
| field | type | req |
|---|---|---|
party_a | text | yes |
party_b | text | yes |
effective_date | date | |
term_months | number | |
value | number | |
renewal_date | date |
receipt 5 fields
| field | type | req |
|---|---|---|
total | number | yes |
tax | number | |
merchant | text | yes |
date | date | yes |
category | text |
ticket 5 fields
| field | type | req |
|---|---|---|
requester | text | |
priority | text | |
created_at | datetime | |
sla_due | datetime | |
summary | text | yes |
text, number, date,
datetime, list, boolean) — up to 30 fields, names matching
[a-zA-Z_][a-zA-Z0-9_]*. Send doc_type:"custom" plus your schema.Custom schema example
doc_type=custom with a two-field schema — the response contains exactly the fields you declare:
curl -X POST https://extract.redsugartree.com/api/v1/extract \
-H "Authorization: Bearer eex_free_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"doc_type": "custom",
"schema": {
"fields": [
{ "name": "po_number", "type": "text", "required": true, "hint": "Purchase order number" },
{ "name": "amount_due", "type": "number", "required": true, "hint": "Total amount due" }
]
},
"input": { "text": "PO-4471\nVendor: Acme Ltd\nAmount due: 2400.00 USD" }
}'
Response shape (same contract as the built-ins, only your two fields):
{
"doc_type": "custom",
"data": {
"po_number": { "value": "PO-4471", "type": "text", "confidence": 0.97, "p_null": 0.02 },
"amount_due": { "value": 2400.00, "type": "number", "confidence": 0.95, "p_null": 0.03 }
},
"needs_review": false,
"truncated": false,
"usage": { "tokens_in": 128, "cost_usd": 0.0000128 },
"generated_by": "ai"
}
Pricing
| Plan | Price | Extractions | Quota window |
|---|---|---|---|
| Free | $0 | 1,000 / month | UTC calendar month |
| Starter | $9 / month | 10,000 / month | Subscription period (resets on each renewal) |
| Pro | $29 / month | 100,000 / month | Subscription period (resets on each renewal) |
Only successful 200 responses consume quota. 4xx/5xx never do.
Running out → 402 quota_exceeded, never a silent downgrade. All tiers include
confidence + p_null on every field.
All endpoints
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/v1/extract | API key | Extract typed fields from a document |
POST /api/v1/signup | — | Issue a free key for an email (5 req/hour/IP) |
POST /api/v1/reveal-key | — | Retrieve the key for an email (3 req/hour/IP) |
Questions? support@redsugartree.com · Back to the homepage