Eén endpoint.
Eén header.
Eén bestand.
Alles hieronder is geverifieerd tegen de draaiende API. Basis-URL https://api.optii.eu
Authenticatie
Stuur uw sleutel in de X-API-Key-header. Sleutels beginnen met optii_sk_ en worden één keer getoond, bij het aanmaken. Sleutels worden gehasht opgeslagen; een verloren sleutel wordt geroteerd, niet hersteld.
Elk antwoord draagt een X-Request-ID-header. Vermeld die wanneer u ons schrijft.
curl https://api.optii.eu/ocr/extract \ -H "X-API-Key: optii_sk_…" \ -F "file=@document.pdf"
POST/ocr/extract
Multipart-upload van één bestand. Geeft elke regel terug met zijn bounding box en betrouwbaarheid, in leesvolgorde, pagina na pagina.
Queryparameters
- structured
- false (standaard) voor regels, true voor Belgische documentvelden (volgende sectie).
- format
- v2 (standaard). legacy geeft de platte factuurvorm van de klanten van 2025 terug; gebruik het niet voor nieuwe code.
Antwoord 200
{
"success": true,
"text": "DUMONT & FILS SPRL\nRue de la Loi 42 · 1000 Bruxelles\nTVA BE0202239951\n…",
"lines": [
{ "text": "DUMONT & FILS SPRL", "confidence": 0.996, "bbox": [[50.0, 50.0], [304.0, 50.0], [304.0, 84.0], [50.0, 84.0]] },
{ "text": "Rue de la Loi 42 · 1000 Bruxelles", "confidence": 0.991, "bbox": [[56.0, 82.0], [286.0, 82.0], [286.0, 100.0], [56.0, 100.0]] },
{ "text": "TVA BE0202239951", "confidence": 0.993, "bbox": [[56.0, 106.0], [198.0, 106.0], [198.0, 124.0], [56.0, 124.0]] }
],
"confidence": 0.991,
"source": "ocr",
"quality": { "blur": 812.4, "long_side_px": 2339, "low_confidence_ratio": 0.0, "warnings": [] },
"file_hash": "a3b2c1d4e5f6",
"processing_time_ms": 342
} Velden
- text
- de volledige tekst, regels gescheiden door regeleinden
- lines[]
- één object per regel: text, confidence (0 tot 1), bbox als vier hoekpunten [[x, y], …] in paginapixels, met de klok mee vanaf linksboven
- confidence
- gemiddelde regelbetrouwbaarheid
- source
- text, ocr of hybrid, zie Bron en kwaliteit
- quality
- blur, long_side_px, low_confidence_ratio, warnings[]
- file_hash
- SHA-256 van het bestand, eerste tekens; hetzelfde bestand geeft altijd dezelfde waarde
- processing_time_ms
- servertijd in milliseconden, upload niet meegerekend
POST/ocr/extract?structured=true
Dezelfde oproep met ?structured=true. De motor herkent het documenttype uit de tekst (invoice, credit_note, debit_note, receipt, anders unknown), extraheert de Belgische velden en voert de controles uit. Als de classifier zeker is, draagt document_type zijn label in de plaats.
Een veld dat niet op de pagina staat, is null. Bedragen zijn getallen met een punt, datums blijven zoals gedrukt (dd/mm/yyyy op het meeste Belgische papier).
{
"document_type": "invoice",
"source": "ocr",
"quality": { "blur": 812.4, "long_side_px": 2339, "low_confidence_ratio": 0.0, "warnings": [] },
"supplier_name": "DUMONT & FILS SPRL",
"supplier_vat": "BE0202239951",
"supplier_iban": "BE68539007547034",
"document_number": "2024-0847",
"document_date": "15/03/2024",
"due_date": "14/04/2024",
"structured_communication": "+++123/4567/89002+++",
"currency": "EUR",
"line_items": [
{ "description": "Conseil juridique — mars 2024", "quantity": 10, "unit_price": 125.0, "line_total": 1250.0, "vat_rate": 21.0 }
],
"vat_subtotals": [ { "rate": 21.0, "base_amount": 1250.0, "vat_amount": 262.5 } ],
"total_excl_vat": 1250.0,
"total_vat": 262.5,
"total_incl_vat": 1512.5,
"validations": [
{ "check": "vat_checkdigit", "passed": true, "message": "" },
{ "check": "iban_mod97", "passed": true, "message": "" },
{ "check": "structured_comm_mod97", "passed": true, "message": "" },
{ "check": "totals_sum", "passed": true, "message": "" }
],
"confidence": 0.91,
"processing_time_ms": 342
} Controles
- vat_checkdigit
- de controlecijfers van het Belgische btw-nummer (97 min de eerste acht cijfers mod 97)
- iban_mod97
- de IBAN herschikt mod 97 is gelijk aan 1
- structured_comm_mod97
- de controlecijfers van de gestructureerde mededeling +++xxx/xxxx/xxxxx+++
- totals_sum · vat_subtotals_sum · vat_bases_sum
- total_excl_vat + total_vat = total_incl_vat op één cent na
- line_items_sum
- de lijnen tellen op tot het totaal excl. btw wanneer aanwezig
POST/ocr/classify
Multipart-upload van één bestand. De tekst wordt gelezen (tekstlaag of OCR) en door een XLM-RoBERTa-model geclassificeerd in een van 32 Belgische documenttypes.
Queryparameters
- top_k
- aantal voorspellingen dat terugkomt, 1 tot 20, standaard 5.
Antwoord 200
{
"success": true,
"document_type": "acc_purchase_invoice",
"confidence": 0.9361,
"confident": true,
"predictions": [
{ "label": "acc_purchase_invoice", "confidence": 0.9361, "rank": 1 },
{ "label": "acc_sales_invoice", "confidence": 0.0298, "rank": 2 },
{ "label": "acc_credit_note", "confidence": 0.0112, "rank": 3 }
],
"processing_time_ms": 388
} - document_type
- het label als confident true is, anders unknown
- confident
- true als de hoogste kans op of boven de drempel ligt en het label geen fallback is
- predictions[]
- label, confidence, rank, beste eerst
Labels zijn snake_case met een familieprefix, bijvoorbeeld acc_purchase_invoice of acc_sales_invoice. De volledige lijst staat op het dashboard.
Bron en kwaliteit
Elk extract-antwoord zegt waar de tekst vandaan komt.
- text
- de PDF had een bruikbare tekstlaag; de tekens zijn exact, elke regel heeft confidence 1
- ocr
- elke pagina ging door de motor
- hybrid
- sommige pagina’s hadden een laag, andere niet
quality quality wordt berekend op de pagina’s die door OCR gingen:
- blur
- variantie van de Laplaciaan op een grijswaardenbeeld verkleind tot 1500 px; lager is waziger
- long_side_px
- de kleinste lange zijde van een pagina in pixels
- low_confidence_ratio
- aandeel regels onder 0.80 betrouwbaarheid
- warnings[]
- een of meer van low_resolution (onder 1500 px), blurry (onder 400), low_confidence (ratio boven 0.15)
Als warnings niet leeg is, toon de gebruiker de pagina en vraag een nieuwe scan. De cijfers kunnen nog kloppen; u kunt het niet weten.
Idempotentie
Stuur een Idempotency-Key-header (elke string tot 255 tekens, een UUID is prima). Gedurende 24 uur geeft dezelfde sleutel met hetzelfde bestand en dezelfde parameters het opgeslagen antwoord terug, zonder tweede lezing en zonder tweede telling. Dezelfde sleutel met een ander bestand antwoordt 409 idempotency_conflict.
De vingerafdruk is de SHA-256 van het bestand plus de parameters, dus een herhaalde multipart-upload matcht ook als de boundary verschilt.
Rate limits en quota
Limieten gelden per plan en per sleutel. De tabel is de live configuratie.
| Plan | Verzoeken / min | Max. bestand | Sleutels | Maandquotum |
|---|---|---|---|---|
| Free | 10 | 5 MB | 1 | 500 |
| Pro | 60 | 20 MB | 5 | 10.000 |
| Enterprise | 300 | 20 MB | 100 | onbeperkt |
Boven de rate limit: 429 rate_limited met Retry-After. Boven het quotum: 429 met een bericht dat het quotum noemt. Het publieke demo-endpoint laat 10 verzoeken per minuut per IP toe en 5 MB.
Fouten
Elke fout is JSON met twee velden: error, een snake_case-code, en message, een zin voor mensen. Parse het bericht nooit.
HTTP/1.1 413 Payload Too Large
X-Request-ID: 6f1c0a2b9d3e
Content-Type: application/json
{ "error": "file_too_large", "message": "File too large (max 5MB)" } | Status | error | Wanneer | Wat te doen |
|---|---|---|---|
| 400 | unsupported_file_type | extensie niet in pdf png jpg jpeg tiff tif bmp webp | converteer het bestand |
| 401 | unauthorized | ontbrekende of onbekende sleutel | controleer de sleutel in het dashboard |
| 403 | forbidden | het plan laat het niet toe (sleutellimiet, grootte) | zie prijzen |
| 409 | idempotency_conflict | zelfde Idempotency-Key, ander bestand | gebruik een nieuwe sleutel |
| 413 | file_too_large | boven het max. bestand van het plan | comprimeer of splits |
| 422 | validation_failed | geen file-veld, slechte queryparameter | lees de sectie over het verzoek |
| 429 | rate_limited | rate limit of maandquotum | wacht op Retry-After, of upgrade |
| 500 | processing_failed | de motor kon het bestand niet lezen | probeer één keer opnieuw, schrijf ons dan met de request id |
| 503 | model_unavailable | de motor laadt na een herstart | probeer over enkele seconden opnieuw |
| 503 | db_unavailable | gebruiksdatabase down; lezen werkt nog | probeer opnieuw |
Formaten en limieten
Invoer: PDF (tekst of gescand, meerdere pagina’s), PNG, JPG, TIFF, BMP, WebP. Max. 20 MB met een sleutel, 5 MB op de demo. Gelezen talen: Frans, Nederlands, Duits, Engels; de motor kan 50 schriften aan, maar de veldextractie en de classifier zijn Belgisch.
Uitvoer: JSON, UTF-8. De regelvolgorde is de leesvolgorde per pagina. Kaders zijn veelhoeken in de pixelruimte van de gerenderde pagina.
Snippets
Geen SDK te installeren. Drie snippets die werken zoals ze zijn.
curl -X POST "https://api.optii.eu/ocr/extract?structured=true" \ -H "X-API-Key: optii_sk_…" \ -H "Idempotency-Key: $(uuidgen)" \ -F "file=@facture-2024-0847.pdf"
import requests, uuid
with open("facture-2024-0847.pdf", "rb") as f:
r = requests.post(
"https://api.optii.eu/ocr/extract",
params={"structured": "true"},
headers={"X-API-Key": "optii_sk_…", "Idempotency-Key": str(uuid.uuid4())},
files={"file": f},
timeout=90,
)
r.raise_for_status()
doc = r.json()
print(doc["supplier_vat"], doc["total_incl_vat"], doc["validations"])import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("facture-2024-0847.pdf")]), "facture-2024-0847.pdf");
const res = await fetch("https://api.optii.eu/ocr/extract?structured=true", {
method: "POST",
headers: { "X-API-Key": "optii_sk_…", "Idempotency-Key": crypto.randomUUID() },
body: form,
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error}`);
const doc = await res.json();
console.log(doc.supplier_vat, doc.total_incl_vat, doc.validations); OpenAPI
Het machineleesbare contract staat op https://api.optii.eu/openapi.json. Het is het gecureerde toolmanifest dat het chut.app-platform gebruikt; de dashboardroutes staan er niet in.