Zum Inhalt springen

Für Entwickler

PDF-Formulare per API ausfüllen

Die Ausfüllpilot-API erkennt die Felder in jedem PDF – ausfüllbar, nicht ausfüllbar oder gescannt – und füllt sie aus Anweisungen, Daten oder Dokumenten. Sie wird in der EU betrieben.

Was die API kann

Felder erkennen

POST /detect_form liefert ein JSON-Schema mit Position, Art und Namen jedes Feldes – auch für Scans und PDFs ohne Formularfelder.

Formulare ausfüllen

POST /edit füllt aus Anweisungen in natürlicher Sprache, aus beliebigem JSON (config.data) oder aus Dokumenten und liefert ein PDF plus filledValues.

Eigenes Schema

Mit einem gespeicherten Schema überspringen Sie die Erkennung; inputSchema ordnet Ihr eigenes Datenmodell den Feldern zu.

Asynchron und Webhooks

Lange Formulare über /edit_runs starten und per Webhook benachrichtigen lassen – signiert, mit Wiederholungen.

Sauberes Schreiben

Kästchen für einzelne Ziffern, Datumsformate, Ankreuzfelder, Tabellen, Unterschriften; standardmäßig fest eingebettet, auf Wunsch als bearbeitbare Felder. Digital signierte PDFs werden inkrementell gespeichert, damit die Signaturen erhalten bleiben.

Prüfliste

Unsichere oder gekürzte Werte stehen in output.review – ideal für eine menschliche Kontrolle vor dem Versand.

Schnellstart

Hochladen, ausfüllen, herunterladen. Setzen Sie FORMFILL_API_URL und einen Schlüssel aus dem Dashboard; Testschlüssel beginnen mit ff_test_.
cURLbash
export FORMFILL_API_URL="https://api.ausfuellpilot.de"
export FORMFILL_API_KEY="ff_test_…"

# 1. PDF hochladen
FILE_ID=$(curl -sS "$FORMFILL_API_URL/files/upload" \
  -H "Authorization: Bearer $FORMFILL_API_KEY" \
  -F file=@formular.pdf | jq -r .id)

# 2. Ausfüllen (synchron; für lange Formulare: POST /edit_runs und GET /edit_runs/:id)
curl -sS "$FORMFILL_API_URL/edit" \
  -H "Authorization: Bearer $FORMFILL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "file": { "id": "'"$FILE_ID"'" },
    "config": {
      "instructions": "Fülle das Formular für Erika Mustermann aus, geboren am 12.08.1990 in Musterstadt, wohnhaft Musterstraße 1, 12345 Musterstadt. Unterschreibe mit ihrem Namen und dem heutigen Datum.",
      "advancedOptions": { "flattenPdf": true }
    }
  }' > run.json

# 3. Ergebnis herunterladen (Link 15 Minuten gültig)
curl -sS -o ausgefuellt.pdf "$(jq -r .output.editedFile.presignedUrl run.json)"
TypeScript (Node 20+)ts
import { readFile, writeFile } from "node:fs/promises";

const API = process.env.FORMFILL_API_URL!;
const auth = { Authorization: `Bearer ${process.env.FORMFILL_API_KEY}` };

// 1. PDF hochladen
const form = new FormData();
form.append("file", new Blob([await readFile("formular.pdf")], { type: "application/pdf" }), "formular.pdf");
const file = await (await fetch(`${API}/files/upload`, { method: "POST", headers: auth, body: form })).json();

// 2. Ausfüllen
const res = await fetch(`${API}/edit`, {
  method: "POST",
  headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({
    file: { id: file.id },
    config: { instructions: "Fülle das Formular für Erika Mustermann aus, geboren am 12.08.1990 in Musterstadt, wohnhaft Musterstraße 1, 12345 Musterstadt. Unterschreibe mit ihrem Namen und dem heutigen Datum.", advancedOptions: { flattenPdf: true } },
  }),
});
const run = await res.json();
if (!res.ok) throw new Error(`${run.code}: ${run.message}`);
if (run.status === "FAILED") throw new Error(`${run.failureReason}: ${run.failureMessage}`);
console.log(run.output.filledValues, run.output.review);

// 3. Herunterladen (Link 15 Minuten gültig)
const pdf = await fetch(run.output.editedFile.presignedUrl);
await writeFile("ausgefuellt.pdf", Buffer.from(await pdf.arrayBuffer()));
Python (requests)py
import os, uuid, requests

API = os.environ["FORMFILL_API_URL"]
AUTH = {"Authorization": f"Bearer {os.environ['FORMFILL_API_KEY']}"}

# 1. PDF hochladen
with open("formular.pdf", "rb") as f:
    file = requests.post(f"{API}/files/upload", headers=AUTH,
                         files={"file": ("formular.pdf", f, "application/pdf")}, timeout=60).json()

# 2. Ausfüllen
res = requests.post(
    f"{API}/edit",
    headers={**AUTH, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "file": {"id": file["id"]},
        "config": {
            "instructions": "Fülle das Formular für Erika Mustermann aus, geboren am 12.08.1990 in Musterstadt, wohnhaft Musterstraße 1, 12345 Musterstadt. Unterschreibe mit ihrem Namen und dem heutigen Datum.",
            "advancedOptions": {"flattenPdf": True},
        },
    },
    timeout=300,
)
res.raise_for_status()
run = res.json()
if run["status"] == "FAILED":
    raise RuntimeError(f"{run['failureReason']}: {run['failureMessage']}")

# 3. Herunterladen (Link 15 Minuten gültig)
pdf = requests.get(run["output"]["editedFile"]["presignedUrl"], timeout=60)
with open("ausgefuellt.pdf", "wb") as f:
    f.write(pdf.content)

Ausführlich mit asynchronen Runs und Fehlerbehandlung: Schnellstart in der Dokumentation.

Webhooks mit Signatur

Webhooks richten Sie pro Organisation im Dashboard ein. Ereignisse:

  • edit_run.processed
  • edit_run.failed
  • form_detection_run.processed
  • form_detection_run.failed
  • batch.completed

Jede Zustellung trägt x-formfill-signature: t=<unix>,v1=<hex> – ein HMAC-SHA256 über "<t>.<body>" mit Ihrem Webhook-Secret – sowie x-formfill-event-id und x-formfill-event-type. Fehlgeschlagene Zustellungen werden mit wachsendem Abstand wiederholt.

Signatur prüfen – TypeScriptts
import crypto from "node:crypto";

/** Verify x-formfill-signature: t=<unix>,v1=<hex hmac_sha256(secret, "<t>.<body>")> */
export function verifyFormfillSignature(rawBody: string, header: string, secret: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2) as [string, string]));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
Signatur prüfen – Pythonpy
import hashlib, hmac, time

def verify_formfill_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Idempotenz, Limits und Schlüssel

Idempotency-Key
Senden Sie bei POST-Anfragen einen eindeutigen Idempotency-Key. Wiederholungen mit demselben Schlüssel innerhalb von 24 Stunden liefern die ursprüngliche Antwort – ohne doppelte Abrechnung.
Ratenbegrenzung
Bis zu 600 Anfragen pro Minute im Tarif API. Darüber antwortet die API mit 429 und Retry-After.
Testschlüssel
ff_test_…: kostenlos, bis zu 50 Seiten pro Tag, Ergebnisse mit Wasserzeichen „TEST“.
Live-Schlüssel
ff_live_…: Abrechnung pro Seite. Schlüssel werden nur als Hash gespeichert und einmal angezeigt.

Preise pro Seite

Monatlich nach Seiten abgerechnet, gestaffelt; die ersten 100 Seiten jeder Art sind kostenlos. Preise zzgl. USt.
Preisstufen der API
Seiten pro MonatAusfüllen inkl. ErkennungAusfüllen mit SchemaNur Erkennung
1 – 100kostenloskostenloskostenlos
101 – 10.0000,03 €0,01 €0,02 €
10.001 – 100.0000,025 €0,008 €0,015 €
ab 100.0010,02 €0,006 €0,01 €

Beispiel: 2.000 ausgefüllte Seiten im Monat kosten 57,00 €, 20.000 Seiten 547,00 € (jeweils zzgl. USt.). Mit mitgeliefertem Schema oder gespeicherter Vorlage entfällt die Felderkennung, dann gilt der günstigere Preis „Ausfüllen mit Schema“. Der Tarif Business enthält die API ebenfalls.

Alle Preise

Hosting und Datenschutz

  • Dateien und Ergebnisse werden in Frankfurt am Main gespeichert und nach der eingestellten Frist automatisch gelöscht; Download-Links gelten 15 Minuten.
  • KI-Anfragen laufen ausschließlich in EU-Rechenzentren (Microsoft Azure, EU-Region), an Modell-Endpunkte ohne Datenspeicherung und ohne Training – in jedem Tarif. Einzelheiten und Grenzen auf der Seite Sicherheit.
  • Auftragsverarbeitungsvertrag nach Art. 28 DSGVO für alle Geschäftskunden: AVV lesen.

Loslegen

Das erste Formular per API ausfüllen