API-Referenz
Basis-URL: https://api.hotdoc.io. Anfrage- und Antwort-Bodies sind JSON; Feldnamen sind in camelCase. Ausnahmen: POST /v1/jobs/upload (Antwort) und der Webhook-Anfrage-Body — beide verwenden snake_case.
Verarbeitungs-Endpunkte
| Methode | Endpunkt | Zweck |
|---|---|---|
POST | /v1/jobs | Job erstellen |
GET | /v1/jobs/{id} | Job-Status und Dateiliste (ohne Erkennungsergebnisse) |
GET | /v1/jobs/{id}/result | vollständiges Ergebnis: erkannter Text und Modellantworten je Datei |
GET | /v1/jobs | Konto-Jobs auflisten (Paginierung: pageSize, pageToken, Filter statusEq) |
POST | /v1/jobs/upload | eine einzelne Datei hochladen (multipart/form-data) |
POST /v1/jobs — Job erstellen
Anfrage-Body:
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
sourceUrls | string[] | ja | zu verarbeitende Datei-URLs |
prompts | string[] | nein | Modellanweisungen (nur der erste Prompt wird ausgeführt); ohne Prompts wird das LLM nicht aufgerufen |
neural | object | ja | Modellkonfiguration (siehe „Modell anbinden”) |
ocr | object | ja | OCR-Provider-Konfiguration (BYOK); immer erforderlich (siehe „OCR-Konfiguration”) |
title | string | nein | beliebiger Job-Name |
metadata | map<string,string> | nein | beliebige String-Schlüssel-Wert-Paare |
webhookUrl | string | nein | absoluter http/https-Endpunkt, der bei Job-Abschluss benachrichtigt wird (siehe „Webhooks”) |
webhookSecret | string | nein | optionaler HMAC-Secret zum Signieren von Webhook-Anfragen; nur als Eingabe entgegengenommen, niemals in Antworten zurückgegeben |
idempotencyKey | string | nein | Client-Schlüssel für sichere Wiederholungen beim Erstellen; max. 255 Zeichen (siehe „Idempotenz”) |
neural und ocr sind immer erforderlich, auch wenn prompts leer ist. Bei leerem prompts wird das Modell nicht aufgerufen: Jede Datei wird mit JOB_LLM_STATUS_SKIPPED und skipReason=no_prompt markiert, und bei erfolgreicher OCR endet der Job als JOB_STATUS_COMPLETE.
Die Antwort ist der erstellte Job im Status JOB_STATUS_NEW.
OCR-Konfiguration
ocr wählt das Erkennungs-Backend (OCR) pro Anfrage (BYOK):
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
ocr.provider | enum | ja | einer von NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI |
ocr.model | string | ja | die Modell-ID des Providers, z. B. mistral-ocr-latest (Mistral) oder das Vision-Modell des gewählten Providers |
ocr.providerKey | string | ja | BYOK-Schlüssel für den OCR-Provider; nur als Eingabe, niemals zurückgegeben |
Mistral ist das typische OCR-Backend (mistral-ocr-latest); andere Provider führen
Vision-Chat-OCR aus. Der Schlüssel wird verschlüsselt gespeichert und zusammen mit dem Job gelöscht.
Welches Modell verwenden? Vergleichen Sie Intelligenz, Preis pro Token und Anbieter im LLM-Vergleich und wählen Sie das passende Modell.
Idempotenz
Senden Sie idempotencyKey, um POST /v1/jobs sicher wiederholbar zu machen. Eine Wiederholung mit
demselben Schlüssel und identischen Anfrageparametern gibt den ursprünglichen Job zurück (es wird kein
Duplikat erstellt). Derselbe Schlüssel mit anderen Parametern wird mit
400 "idempotency key reused with different request parameters" abgelehnt. Der Schlüssel ist
maximal 255 Zeichen lang; generieren Sie eine frische UUID pro logischer Erstellung.
GET /v1/jobs/{id}/result — Ergebnis
Gibt { "result": { "job": …, "ocr": [...], "llm": [...] } } zurück. Beispiel (gekürzt):
{
"result": {
"job": {
"id": "15b07304-…", "accountId": "…", "title": "Invoice #42",
"metadata": {}, "status": "JOB_STATUS_COMPLETE", "error": "",
"sourceUrls": ["https://example.com/invoice.pdf"],
"files": ["https://…/files/15b07304-…/invoice.pdf"],
"prompts": ["…"], "neural": { "type": "NEURAL_CLIENT_TYPE_XIAOMI", "model": "mimo-v2-flash", "chunkBudgetTokens": 240000 },
"created": "2026-06-17T16:57:49Z", "updated": "2026-06-17T16:58:05Z"
},
"ocr": [
{ "jobId": "15b07304-…", "file": "https://…/invoice.pdf",
"status": "JOB_OCR_STATUS_DONE", "content": "<p><b>…</b></p>",
"outputFormat": "html", "model": "pdf_fitz", "error": "", "duration": "149508116",
"created": "…", "updated": "…" }
],
"llm": [
{ "jobId": "15b07304-…", "file": "https://…/invoice.pdf",
"promptIndex": 0, "chunkIndex": 0, "chunkTotal": 1,
"status": "JOB_LLM_STATUS_DONE", "skipReason": "",
"model": "mimo-v2-flash", "content": "{\"total\": 15000}",
"error": "", "duration": "601925111", "created": "…", "updated": "…" }
]
}
}
neural in der Antwort enthält kein apiKey; das Job-Objekt hat kein ocr-Feld — der OCR-Schlüssel wird niemals zurückgegeben. ocr[].content ist der erkannte Text im Format ocr[].outputFormat; das Format hängt vom Dateityp ab: PDF und HTML → html, .xml → xml, .txt → plain, alles andere (einschließlich Bildern und Office-Dateien) → markdown. llm[].content ist der Antworttext des Modells unverändert (Ihr Prompt bestimmt die Struktur; es gibt keine serverseitige Validierung). Im obigen Beispiel werden leere/Null-Felder ("", {}, 0) der Vollständigkeit halber gezeigt — protojson lässt sie weg, sodass sie in einer echten Antwort fehlen können.
ocr[]-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
jobId / file | string | Job-ID / Datei-URL |
status | enum | JOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
content | string | erkannter Text im Format outputFormat |
outputFormat | string | Format von content: markdown / html / xml / plain. Steuert das strukturbewusste Chunking in der LLM-Stufe |
model | string | OCR-Methode/-Engine (z. B. pdf_fitz) |
request / rawData | string | Debug: die OCR-Anfrage und die Rohantwort |
error | string | Stufenfehler (leer bei Erfolg) |
duration | string | Dauer, ns (eine Zahl als String — protojson gibt int64 als String zurück) |
created / updated | string | RFC3339 |
llm[]-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
jobId / file | string | Job-ID / Datei-URL |
promptIndex | int | Prompt-Index (aktuell immer 0) |
chunkIndex / chunkTotal | int | Chunk-Nummer / Gesamtzahl der Chunks (falls der Text aufgeteilt wurde) |
status | enum | JOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
skipReason | string | bei SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content |
content | string | Modellantwort (Text unverändert) |
model | string | der model-String aus der Anfrage |
request / rawData | string | Debug |
error | string | Stufenfehler (z. B. provider error (category=…, status=400)) |
duration | string | Dauer, ns (eine Zahl als String — protojson gibt int64 als String zurück) |
created / updated | string | RFC3339 |
Hinweis. Eine maschinenlesbare Spezifikation (OpenAPI/Swagger) wird noch nicht generiert; diese Referenz wird von Hand gepflegt. OpenAPI ist als separate Aufgabe geplant.
POST /v1/jobs/upload — Datei hochladen
Ein eigenständiger HTTP-Endpunkt (kein grpc-gateway). Nimmt eine Datei über multipart/form-data entgegen.
Antwort (snake_case — eine Ausnahme zum allgemeinen camelCase):
{"url": "https://…/files/…/document.pdf", "name": "document.pdf", "size_bytes": 204800}
Verwenden Sie die zurückgegebene url beim Erstellen eines Jobs in sourceUrls.
Der Fehler-Body dieses Endpunkts ist {"code": <int>, "message": "…"}, ohne details-Array. Das Dateigrößenlimit wird per Konfiguration gesetzt (grpc.maxRecvMsgBytes; 20 MiB im aktuellen Deployment), nicht durch einen Code-Standardwert.
Versionierung
Der /v1-Pfad ist stabil. Rückwärtsinkompatible Änderungen erscheinen unter einem neuen Pfad (/v2). Additive Änderungen (neue optionale Felder und Endpunkte) brechen die Kompatibilität nicht und werden im Changelog angekündigt.