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/v1

Die 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 lesen
  • documents.write — Dokumente anlegen und Felder korrigieren
  • workflows.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 Dokumente
  • name — optionaler Dokumentname; ohne Angabe wird der Dateiname verwendet
  • template_id — optionale Vorlagen-ID, damit die Felder Ihre eigenen Namen tragen
  • async — 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ültig
  • 401 — kein Schlüssel, oder der Schlüssel ist widerrufen oder abgelaufen
  • 403 — dem Schlüssel fehlt das Recht für diesen Endpunkt
  • 404 — kein Dokument mit dieser id in diesem Arbeitsbereich
  • 429 — Minutenlimit erreicht – warten und erneut versuchen
  • 500 — auf unserer Seite ist etwas fehlgeschlagen; die Antwort sagt was