Référence API
URL de base : https://api.hotdoc.io. Les corps de requête et de réponse sont en JSON ; les noms de champs sont en camelCase. Exceptions : POST /v1/jobs/upload (réponse) et le corps de la requête webhook — tous deux utilisent snake_case.
Endpoints de traitement
| Méthode | Endpoint | Objet |
|---|---|---|
POST | /v1/jobs | créer un job |
GET | /v1/jobs/{id} | statut du job et liste des fichiers (sans les résultats de reconnaissance) |
GET | /v1/jobs/{id}/result | résultat complet : texte reconnu et réponses du modèle par fichier |
GET | /v1/jobs | lister les jobs du compte (pagination : pageSize, pageToken, filtre statusEq) |
POST | /v1/jobs/upload | envoyer un seul fichier (multipart/form-data) |
POST /v1/jobs — créer un job
Corps de la requête :
| Champ | Type | Requis | Description |
|---|---|---|---|
sourceUrls | string[] | oui | URL des fichiers à traiter |
prompts | string[] | non | instructions du modèle (seul le premier prompt s’exécute) ; sans prompts, le LLM n’est pas appelé |
neural | object | oui | configuration du modèle (voir « Connexion d’un modèle ») |
ocr | object | oui | configuration du fournisseur OCR (BYOK) ; toujours requise (voir « Configuration OCR ») |
title | string | non | nom de job arbitraire |
metadata | map<string,string> | non | paires clé-valeur de chaînes arbitraires |
webhookUrl | string | non | endpoint absolu http/https à notifier à la fin du job (voir « Webhooks ») |
webhookSecret | string | non | secret HMAC facultatif pour signer les requêtes webhook ; accepté uniquement en entrée, jamais renvoyé dans les réponses |
idempotencyKey | string | non | clé client pour des réessais de création sûrs ; max 255 caractères (voir « Idempotence ») |
neural et ocr sont toujours requis, même lorsque prompts est vide. Avec un prompts vide, le modèle n’est pas appelé : chaque fichier est marqué JOB_LLM_STATUS_SKIPPED avec skipReason=no_prompt, et en cas d’OCR réussi le job se termine avec le statut JOB_STATUS_COMPLETE.
La réponse est le job créé au statut JOB_STATUS_NEW.
Configuration OCR
ocr sélectionne le backend de reconnaissance (OCR) par requête (BYOK) :
| Champ | Type | Requis | Description |
|---|---|---|---|
ocr.provider | enum | oui | l’un de NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI |
ocr.model | string | oui | l’identifiant du modèle du fournisseur, par ex. mistral-ocr-latest (Mistral) ou le modèle vision du fournisseur choisi |
ocr.providerKey | string | oui | clé BYOK pour le fournisseur OCR ; en entrée uniquement, jamais renvoyée |
Mistral est le backend OCR typique (mistral-ocr-latest) ; les autres fournisseurs exécutent
un OCR de type vision-chat. La clé est stockée chiffrée et supprimée avec le job.
Quel modèle utiliser ? Comparez l’intelligence, le prix par token et les fournisseurs dans le comparatif LLM, puis choisissez le modèle adapté.
Idempotence
Envoyez idempotencyKey pour rendre POST /v1/jobs sûr à réessayer. Un nouvel envoi avec la
même clé et des paramètres de requête identiques renvoie le job d’origine (aucun
doublon n’est créé). La même clé avec des paramètres différents est rejetée avec
400 "idempotency key reused with different request parameters". La clé fait au
plus 255 caractères ; générez un UUID frais par création logique.
GET /v1/jobs/{id}/result — résultat
Renvoie { "result": { "job": …, "ocr": [...], "llm": [...] } }. Exemple (abrégé) :
{
"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 dans la réponse n’inclut pas apiKey ; l’objet Job n’a pas de champ ocr — la clé OCR n’est jamais renvoyée. ocr[].content est le texte reconnu au format ocr[].outputFormat ; le format dépend du type de fichier : PDF et HTML → html, .xml → xml, .txt → plain, tout le reste (y compris les images et les fichiers bureautiques) → markdown. llm[].content est le texte de la réponse du modèle tel quel (votre prompt en détermine la structure ; il n’y a aucune validation côté serveur). Dans l’exemple ci-dessus, les champs vides/à zéro ("", {}, 0) sont affichés par souci d’exhaustivité — protojson les omet, ils peuvent donc être absents d’une réponse réelle.
Champs ocr[] :
| Champ | Type | Description |
|---|---|---|
jobId / file | string | id du job / URL du fichier |
status | enum | JOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
content | string | texte reconnu au format outputFormat |
outputFormat | string | format de content : markdown / html / xml / plain. Détermine le découpage conscient de la structure à l’étape LLM |
model | string | méthode/moteur OCR (par ex. pdf_fitz) |
request / rawData | string | débogage : la requête OCR et la réponse brute |
error | string | erreur de l’étape (vide en cas de succès) |
duration | string | durée, ns (un nombre sous forme de chaîne — protojson renvoie int64 sous forme de chaîne) |
created / updated | string | RFC3339 |
Champs llm[] :
| Champ | Type | Description |
|---|---|---|
jobId / file | string | id du job / URL du fichier |
promptIndex | int | index du prompt (actuellement toujours 0) |
chunkIndex / chunkTotal | int | numéro du chunk / nombre total de chunks (si le texte a été découpé) |
status | enum | JOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
skipReason | string | lorsque SKIPPED : no_prompt / ocr_failed / no_ocr_content / empty_ocr_content |
content | string | réponse du modèle (texte tel quel) |
model | string | la chaîne model issue de la requête |
request / rawData | string | débogage |
error | string | erreur de l’étape (par ex. provider error (category=…, status=400)) |
duration | string | durée, ns (un nombre sous forme de chaîne — protojson renvoie int64 sous forme de chaîne) |
created / updated | string | RFC3339 |
Remarque. Une spécification lisible par machine (OpenAPI/Swagger) n’est pas encore générée ; cette référence est maintenue à la main. OpenAPI est prévu comme une tâche distincte.
POST /v1/jobs/upload — envoi de fichier
Un endpoint HTTP autonome (pas grpc-gateway). Accepte un fichier via multipart/form-data.
Réponse (snake_case — une exception au camelCase général) :
{"url": "https://…/files/…/document.pdf", "name": "document.pdf", "size_bytes": 204800}
Utilisez l’url renvoyée dans sourceUrls lors de la création d’un job.
Le corps d’erreur de cet endpoint est {"code": <int>, "message": "…"}, sans tableau details. La limite de taille du fichier est définie par la configuration (grpc.maxRecvMsgBytes ; 20 MiB dans le déploiement actuel), et non par une valeur par défaut du code.
Versionnage
Le chemin /v1 est stable. Les changements incompatibles sont livrés sous un nouveau chemin (/v2). Les changements additifs (nouveaux champs et endpoints facultatifs) ne rompent pas la compatibilité et sont annoncés dans le Changelog.