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

MethodeEndpunktZweck
POST/v1/jobsJob erstellen
GET/v1/jobs/{id}Job-Status und Dateiliste (ohne Erkennungsergebnisse)
GET/v1/jobs/{id}/resultvollständiges Ergebnis: erkannter Text und Modellantworten je Datei
GET/v1/jobsKonto-Jobs auflisten (Paginierung: pageSize, pageToken, Filter statusEq)
POST/v1/jobs/uploadeine einzelne Datei hochladen (multipart/form-data)

POST /v1/jobs — Job erstellen

Anfrage-Body:

FeldTypErforderlichBeschreibung
sourceUrlsstring[]jazu verarbeitende Datei-URLs
promptsstring[]neinModellanweisungen (nur der erste Prompt wird ausgeführt); ohne Prompts wird das LLM nicht aufgerufen
neuralobjectjaModellkonfiguration (siehe „Modell anbinden”)
ocrobjectjaOCR-Provider-Konfiguration (BYOK); immer erforderlich (siehe „OCR-Konfiguration”)
titlestringneinbeliebiger Job-Name
metadatamap<string,string>neinbeliebige String-Schlüssel-Wert-Paare
webhookUrlstringneinabsoluter http/https-Endpunkt, der bei Job-Abschluss benachrichtigt wird (siehe „Webhooks”)
webhookSecretstringneinoptionaler HMAC-Secret zum Signieren von Webhook-Anfragen; nur als Eingabe entgegengenommen, niemals in Antworten zurückgegeben
idempotencyKeystringneinClient-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):

FeldTypErforderlichBeschreibung
ocr.providerenumjaeiner von NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI
ocr.modelstringjadie Modell-ID des Providers, z. B. mistral-ocr-latest (Mistral) oder das Vision-Modell des gewählten Providers
ocr.providerKeystringjaBYOK-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, .xmlxml, .txtplain, 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:

FeldTypBeschreibung
jobId / filestringJob-ID / Datei-URL
statusenumJOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
contentstringerkannter Text im Format outputFormat
outputFormatstringFormat von content: markdown / html / xml / plain. Steuert das strukturbewusste Chunking in der LLM-Stufe
modelstringOCR-Methode/-Engine (z. B. pdf_fitz)
request / rawDatastringDebug: die OCR-Anfrage und die Rohantwort
errorstringStufenfehler (leer bei Erfolg)
durationstringDauer, ns (eine Zahl als String — protojson gibt int64 als String zurück)
created / updatedstringRFC3339

llm[]-Felder:

FeldTypBeschreibung
jobId / filestringJob-ID / Datei-URL
promptIndexintPrompt-Index (aktuell immer 0)
chunkIndex / chunkTotalintChunk-Nummer / Gesamtzahl der Chunks (falls der Text aufgeteilt wurde)
statusenumJOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
skipReasonstringbei SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content
contentstringModellantwort (Text unverändert)
modelstringder model-String aus der Anfrage
request / rawDatastringDebug
errorstringStufenfehler (z. B. provider error (category=…, status=400))
durationstringDauer, ns (eine Zahl als String — protojson gibt int64 als String zurück)
created / updatedstringRFC3339

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.