Entwickler
Formalini API
Dokumente und ihre erkannten Felder lesen, Dokumente aus eigenen Systemen einliefern, Werte korrigieren und signierte Webhooks bei Änderungen empfangen. Jedes Beispiel gibt es in curl, JavaScript, Python, PHP und C#.
Basis-URL
Alle Endpunkte liegen unter dieser Basis-URL. Gesendet und empfangen wird JSON.
https://api.formalini.com/api/public/v1Die kurze Adresse api.formalini.com ist aktiv; es ist die oben gezeigte.
Authentifizierung
Erstelle in der App unter Integrationen einen API-Schlüssel und sende ihn bei jeder Anfrage als Bearer-Token. Ein Schlüssel gehört zu einem Arbeitsbereich und sieht nur dessen Daten.
documents.read— Dokumente und ihre Felder lesendocuments.write— Dokumente anlegen und Felder korrigierenworkflows.run— Ereignisse bei Bedarf an deine Webhooks senden
Jeder Schlüssel hat ein Limit pro Minute (Standard 60). Antworten enthalten X-RateLimit-Limit und X-RateLimit-Remaining; darüber kommt 429 mit Retry-After.
Dokumente auflisten
GET /documents?limit=25&status=approved — documents.read
Neueste zuerst. limit nimmt 1–100 (Standard 25), status filtert nach queued, processing, review, approved, exported oder failed.
curl -X GET "https://api.formalini.com/api/public/v1/documents?limit=25&status=approved" \
-H "Authorization: Bearer pf_live_your_key"{
"documents": [
{
"id": "b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90",
"name": "Job sheet 4821.jpg",
"status": "approved",
"average_confidence": 0.94,
"source": "upload",
"created_at": "2026-09-11T09:12:04.000Z",
"fields": [
{ "name": "engineer", "value": "M. Byrne", "type": "text",
"confidence": 0.97, "page": 1, "valid": true }
]
}
]
}Korrigierte Werte ersetzen die erste Lesung, value ist also immer der zu nutzende Wert.
Ein Dokument abrufen
GET /documents/{id} — documents.read
Liefert das Dokument mit Kategorie, Lieferant, Betrag, Datum und allen Feldern.
curl -X GET "https://api.formalini.com/api/public/v1/documents/b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90" \
-H "Authorization: Bearer pf_live_your_key"Datei hochladen und Daten erhalten
POST /documents/upload — documents.write
Senden Sie ein Seitenbild und erhalten Sie die strukturierten Daten in derselben Antwort. Das Dokument erscheint wie jeder andere Scan im Arbeitsbereich, verbraucht ein KI-Guthaben pro Seite und löst Ihre Webhooks aus.
file— das Seitenbild; wiederholen Sie das Feld je Seite (bis zu 25) für mehrseitige Dokumentename— optionaler Dokumentname; ohne Angabe wird der Dateiname verwendettemplate_id— optionale Vorlagen-ID, damit die Felder Ihre eigenen Namen tragenasync— true setzen, um sofort ein 202 zu erhalten und GET /documents/{id} abzufragen
curl -X POST "https://api.formalini.com/api/public/v1/documents/upload" \
-H "Authorization: Bearer pf_live_your_key" \
-F "file=@invoice-page-1.jpg" \
-F "file=@invoice-page-2.jpg" \
-F "name=Invoice 2026-0041"{
"document": {
"id": "8c2a1f76-4d33-4a1e-8f10-72b9e5c4a118",
"name": "Invoice 2026-0041",
"status": "review",
"average_confidence": 0.93,
"vendor": "Northbridge Supplies",
"amount": 1249.5,
"currency": "EUR",
"document_date": "2026-09-14",
"pages": 2,
"credits_spent": 2,
"fields": [
{ "name": "invoice_number", "value": "2026-0041", "type": "string",
"confidence": 0.98, "page": 1, "valid": true }
]
}
}JPEG, PNG, WebP und HEIC werden akzeptiert, bis 100 MB pro Seite. Das Lesen einer Seite dauert einige Sekunden: Wählen Sie ein großzügiges Timeout oder async=true. Ein 402 bedeutet, dass Guthaben, Speicher oder das Tageslimit erschöpft sind; die Antwort nennt den Grund.
PDFs werden hier noch nicht akzeptiert: Wandeln Sie jede Seite in JPEG oder PNG um und senden Sie sie als wiederholte file-Felder. In der App funktioniert der PDF-Upload weiterhin, da die Seiten im Browser gerendert werden.
Dokument anlegen
POST /documents — documents.write
Liefere ein Dokument ein, zu dem du schon Daten hast. name ist erforderlich; fields ist optional und fasst bis zu 400 Einträge. Mit Feldern landet es in der Prüfung, ohne in der Warteschlange.
curl -X POST "https://api.formalini.com/api/public/v1/documents" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice 2026-0041.pdf",
"category": "invoice",
"vendor": "Northbridge Supplies",
"amount": 1249.5,
"document_date": "2026-09-14",
"fields": [
{
"name": "invoice_number",
"value": "2026-0041",
"type": "text",
"confidence": 0.99,
"page": 1
},
{
"name": "total",
"value": "1249.50",
"type": "number",
"confidence": 0.96,
"page": 1
}
]
}'{
"document": {
"id": "8c2a…",
"name": "Invoice 2026-0041.pdf",
"status": "review",
"created_at": "2026-09-18T10:02:11.000Z",
"fields": 2
}
}Dokument aktualisieren
PATCH /documents/{id} — documents.write
Status ändern oder Feldwerte per Name korrigieren. Der Status approved, exported oder review löst zusätzlich den passenden Webhook aus.
curl -X PATCH "https://api.formalini.com/api/public/v1/documents/b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"status": "approved",
"fields": [
{
"name": "total",
"value": "1249.50"
}
]
}'{ "ok": true, "updated_fields": 1 }Webhooks auslösen
POST /workflows/dispatch — workflows.run
Sendet ein Ereignis an alle aktiven Webhooks, die es abonniert haben, um ein anderes System bei Bedarf zu starten. Erlaubt: document.processed, document.needs_review, document.approved, document.exported.
curl -X POST "https://api.formalini.com/api/public/v1/workflows/dispatch" \
-H "Authorization: Bearer pf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"event": "document.approved",
"document_id": "b0f1c8d2-5a71-4c0e-9f2a-1d4b6e8c3a90",
"payload": {
"target": "erp"
}
}'{ "ok": true, "event": "document.approved", "document_id": "b0f1…" }Webhooks
Lege in der App eine Webhook-URL an und wähle die Ereignisse. Jede Zustellung ist ein POST mit JSON-Body und Signatur-Header, signiert mit deinem Webhook-Secret.
POST https://your-app.example.com/hooks/formalini
Content-Type: application/json
X-Paperflow-Signature: sha256=<hex hmac of the raw body>
{ "event": "document.processed", "document_id": "b0f1…", "payload": { … } }Fehlgeschlagene Zustellungen werden bis zu fünfmal mit steigender Wartezeit wiederholt, und du kannst jede Zustellung in der App erneut senden.
Prüfe die Signatur gegen den unveränderten Body, bevor du einer Zustellung traust:
// Node.js
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const got = (header || "").replace(/^sha256=/, "");
return got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
# Python
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, (header or "").removeprefix("sha256="))
// PHP
$expected = hash_hmac('sha256', $rawBody, $secret);
$got = str_replace('sha256=', '', $_SERVER['HTTP_X_PAPERFLOW_SIGNATURE'] ?? '');
$ok = hash_equals($expected, $got);
// C#
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(rawBodyBytes)).ToLowerInvariant();
var ok = CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(header.Replace("sha256=", "")));Fehler
400— Body ist kein JSON, oder ein Pflichtwert fehlt bzw. ist ungültig401— kein Schlüssel, oder der Schlüssel ist widerrufen oder abgelaufen403— dem Schlüssel fehlt das Recht für diesen Endpunkt404— kein Dokument mit dieser id in diesem Arbeitsbereich429— Minutenlimit erreicht – warten und erneut versuchen500— auf unserer Seite ist etwas fehlgeschlagen; die Antwort sagt was