Quick Start
Get up and running with Refractr in 60 seconds. You'll need an API key: create an account and generate one from your dashboard.
import requests
BASE_URL = "https://api.refractr.io"
API_KEY = "re_your_api_key_here"
payload = {
"document_text": "Invoice #2847\nDate: January 15, 2026\nTotal: EUR 1,249.00",
"template": {
"invoice_number": "__str__",
"date": "__date__",
"total_amount": "__float__",
"currency": "__str__"
}
}
response = requests.post(
f"{BASE_URL}/api/v1/extract/",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
timeout=30
)
result = response.json()
print(result["extracted_data"])
# {"invoice_number": "2847", "date": "2026-01-15",
# "total_amount": 1249.0, "currency": "EUR"}
curl -X POST https://api.refractr.io/api/v1/extract/ \
-H "Authorization: Bearer re_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"document_text": "Invoice #2847\nDate: January 15, 2026\nTotal: EUR 1,249.00",
"template": {
"invoice_number": "__str__",
"date": "__date__",
"total_amount": "__float__",
"currency": "__str__"
}
}'
const response = await fetch(
"https://api.refractr.io/api/v1/extract/",
{
method: "POST",
headers: {
"Authorization": "Bearer re_your_api_key_here",
"Content-Type": "application/json"
},
body: JSON.stringify({
document_text: "Invoice #2847\nDate: January 15, 2026\nTotal: EUR 1,249.00",
template: {
invoice_number: "__str__",
date: "__date__",
total_amount: "__float__",
currency: "__str__"
}
})
}
);
const result = await response.json();
console.log(result.extracted_data);
The API returns your extracted data structured exactly as you defined it. Because each field declares its type, the values are guaranteed to be that type. Note the normalization: "January 15, 2026" comes back as "2026-01-15", and "EUR 1,249.00" becomes the bare number 1249.0 with the currency in its own field. See Templates & Types.
Authentication
All API requests must include your API key in the Authorization header.
Authorization: Bearer re_your_api_key_here
Templates & Types
A template is a JSON object shaped like the answer you want. Each leaf value declares the expected type using a typed placeholder. The type is enforced during generation, so the output is guaranteed to be that JSON type or null, not "usually".
| Placeholder | Output is always | Notes |
|---|---|---|
"__str__" | string or null | verbatim text span from the document |
"__int__" | integer or null | bare number: 3, never "3" or "3 items" |
"__float__" | number or null | bare: 4779.5, never "EUR 4,779.50" |
"__bool__" | true/false or null | never "yes"/"no" |
"__date__" | "YYYY-MM-DD" or null | ISO 8601, regardless of the format in the document |
Missing values
null always means "not present in the document". Every typed field is nullable by design. A field is either a value of the declared type or null; never an empty string, a placeholder artifact, or malformed JSON. The model does not invent values for fields the document doesn't contain.
Arrays
| Template form | Meaning |
|---|---|
["__str__"] | array of strings; an empty answer is [], never [null] or [""] |
[{"name": "__str__", "amount": "__float__"}] | array of objects with typed inner fields |
[] | untyped; accepts any array |
Untyped fields
A bare null leaf is still legal and means "any JSON type". Typed placeholders are recommended for every field where you know the type. They make results predictable and remove the need for defensive parsing on your side.
__something__ are reserved for placeholders. A template containing one that isn't in the table above (e.g. "__string__") is rejected with a 400 that names the offending field.
Canonical value formats
Rule of thumb: representation is normalized, content is verbatim. Refractr converts date and number formats to the canonical forms below, but never summarizes or paraphrases a text span.
| Semantic type | Format | Example |
|---|---|---|
| Date | "YYYY-MM-DD" | "2026-03-14" |
| Monetary amount | bare float, . decimal, no symbol | 4779.5 |
| Currency | separate field, ISO 4217 | "USD" |
| Quantity / count | bare integer | 3 |
| Boolean | JSON true/false | true |
| Missing scalar | null | |
| Empty list | [] | |
| Names / free text | verbatim from document | "Dr. Emily Watson" |
Scope & Accuracy
Refractr is built for one thing: simple-field extraction, fast and cheap. The sweet spot is a template of up to ~10 concrete fields (IDs, names, dates, amounts, booleans, short verbatim spans), extracted at around 500ms per document.
Out of scope
Fields that require derived reasoning or aggregation are not what this API is for: summaries, key-point lists, "all X mentioned in the document" sweeps, sentiment. The model will attempt them, but accuracy is materially lower; by design, this is not the product. If you need both, split your pipeline: use Refractr for the concrete fields and a separate step for derived ones.
invoice_date beats d1), and declare types for every field you can.
Extract Data
The core endpoint. Submit a document and get back structured data matching your template.
Request Body
{
"document_text": "BREAKING — Nvidia soars 12% to ~$187 after crushing Q4 earnings. Revenue hit $22.1B vs $20.4B expected.",
"template": {
"company": "__str__",
"stock_move_pct": "__float__",
"revenue_billions": "__float__",
"currency": "__str__"
},
"wait": true
}
Response (Sync Mode)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "success",
"extracted_data": {
"company": "Nvidia",
"stock_move_pct": 12.0,
"revenue_billions": 22.1,
"currency": "USD"
},
"validation": {
"warnings": []
},
"metadata": {
"model": "refractr-v7",
"inference_ms": 340,
"credits_charged": 1
}
}
validation.warnings lists informational notes about your template, for example that an empty string was normalized to null for a field. They're useful while iterating on a template and safe to ignore in production.
Response (Async Mode)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"message": "Job submitted. Poll GET /api/v1/extract/{job_id}/ for results."
}
When wait: false, the API returns immediately with a job ID. Use the Poll Status endpoint to check when your result is ready.
503 rather than a hang. For spiky workloads or very large documents, prefer wait: false and poll.
requests.Session() instead of calling requests.post() directly; in Node.js, fetch and most HTTP clients reuse connections automatically. This is free latency — no code changes beyond creating the client once and reusing it.
Poll Status
Check the status of an async extraction job.
Response (Still Processing)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"message": "Job is still processing.",
"queue_depth": 2
}
status is queued until a worker picks the job up, then processing. queue_depth is the number of jobs currently waiting ahead in the queue.
Response (Complete)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "success",
"extracted_data": { ... },
"validation": { "warnings": [] },
"metadata": { ... }
}
Pricing & Limits
Extractions are billed in credits, pay-as-you-go. Pricing is flat: it does not depend on document or template size.
| Item | Cost |
|---|---|
| Successful extraction | 1 credit |
| Failed extraction (schema violation, timeout, worker error) | Free — no credit charged |
| Credit price | €1 = 125 credits (top up from €5) |
| Signup bonus (alpha) | 500 free extractions |
The metadata.credits_charged field in each response tells you exactly what a call cost (0 for a failed extraction).
Rate limits
Requests are throttled per API key, 60 requests per minute by default. Exceeding the limit returns 429 with a Retry-After header; back off and retry after that interval.