API Documentation

Extract structured data from unstructured text with Refractr's simple API.

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.

Python
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
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__"
    }
  }'
JavaScript
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
Tip: Keep your API key secret. If compromised, regenerate it immediately from your dashboard. Never commit it to version control.

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". That guarantee is about shape, not accuracy: you always get well-formed, correctly typed JSON, but the values inside it come from a model and can be wrong.

PlaceholderOutput is alwaysNotes
"__str__"string or nullverbatim text span from the document
"__int__"integer or nullbare number: 3, never "3" or "3 items"
"__float__"number or nullbare: 4779.5, never "EUR 4,779.50"
"__bool__"true/false or nullnever "yes"/"no"
"__date__""YYYY-MM-DD" or nullISO 8601, regardless of the format in the document

Missing values

null 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.

Accuracy. Typed placeholders guarantee the shape of the response, not the correctness of the values. Extraction is model-based, so validate values against your own rules before treating them as authoritative, particularly for fields you are not certain the document contains.

Arrays

Template formMeaning
["__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.

Reserved namespace: strings of the form __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 typeFormatExample
Date"YYYY-MM-DD""2026-03-14"
Monetary amountbare float, . decimal, no symbol4779.5
Currencyseparate field, ISO 4217"USD"
Quantity / countbare integer3
BooleanJSON true/falsetrue
Missing scalarnull
Empty list[]
Names / free textverbatim from document"Dr. Emily Watson"

Postman collection

Every endpoint, pre-filled with working examples. Import it, paste your API key once, and send.

Download the collection

Setting it up

  1. In Postman, choose Import and select the downloaded file.
  2. Open the collection's Variables tab and paste your key into the current value of api_key. Create a key from your dashboard.
  3. Send Extract (sync). It ships with a sample invoice and a typed template.

The key is applied to every request as Authorization: Bearer {{api_key}} through collection-level auth, so you only set it once. base_url is a variable too, if you ever need to point it elsewhere.

Async is wired up for you: the Extract (async) request saves the returned job_id into a collection variable, so Poll job result works straight afterwards without copying anything by hand.

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.

Getting the best results: keep templates small and concrete, use descriptive field names (invoice_date beats d1), and declare types for every field you can.

Reporting a bad extraction

Corrections from alpha users feed directly into model training. If an extraction comes back wrong, email support@refractr.io with the job_id from the response, the field in question, and either the correct value or "not in the document".

Both cases are useful, and the second is the one we can least easily determine on our own.

Extract Data

The core endpoint. Submit a document and get back structured data matching your template.

POST /api/v1/extract/

Request Body

Example
{
  "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
}
document_text required
The raw text to extract from. Max 50,000 characters.
template required
A JSON object defining the structure you want. Use typed placeholders (__str__, __int__, __float__, __bool__, __date__) as leaf values, arrays like ["__str__"] for lists, and nested objects for hierarchical data. Bare null declares an untyped field. See Templates & Types.
wait
Wait for the extraction to complete (default: true). Set to false for async mode and poll the result later.

Response (Sync Mode)

200 OK
{
  "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)

202 Accepted
{
  "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.

No compile step: a template you've never sent before costs no extra latency. The engine prewarms its grammar cache at startup, so a never-seen template and a warm one measure the same end to end (typically 180–350ms). There is no per-schema compile, warm-up, or deployment. Sync mode waits up to 30 seconds; the only slow path is a worker restart (~2.5 min, rare), which surfaces as a 503 rather than a hang. For spiky workloads or very large documents, prefer wait: false and poll.
Reuse connections: opening a fresh HTTPS connection costs an extra ~60-100ms in TLS handshake before your request even starts. If you make more than one call, keep the connection alive: in Python, use a requests.Session() instead of calling requests.post() directly; in Node.js, fetch and most HTTP clients reuse connections automatically. This is free latency, with no code changes beyond creating the client once and reusing it.

Poll Status

Check the status of an async extraction job.

GET /api/v1/extract/{job_id}/

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": { ... }
}
Best Practice: Start with 100ms delays between polls, then back off exponentially. Most extractions complete within 1–5 seconds.

Pricing & Limits

Extractions are billed in credits, pay-as-you-go. Pricing is flat: it does not depend on document or template size.

ItemCost
Successful extraction1 credit
Failed extraction (schema violation, timeout, worker error)Free, no credit charged
Credit price€1 = 125 credits (top up from €5)
Free daily allowance (alpha)100 free extractions per day

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.

Error Codes

400 Bad Request
Invalid request (e.g., malformed JSON, missing required fields, or an unknown placeholder like "__string__"; only __str__, __int__, __float__, __bool__, __date__ are valid). The error message names the offending field.
401 Unauthorized
Invalid or missing API key. Verify your key is correct and included in the Authorization header.
402 Payment Required
Insufficient API credits. Purchase more at /billing/.
429 Too Many Requests
Rate limited. Requests are throttled per API key (60/min by default). Back off and retry after the Retry-After interval.
503 Service Unavailable
GPU servers are temporarily unavailable. Retry in a few moments.
504 Gateway Timeout
Extraction took longer than 30 seconds. The job may still complete; poll GET /api/v1/extract/{job_id}/ to retrieve it. Use async mode (wait: false) for large documents.