Referência da API
URL base: https://api.hotdoc.io. Os corpos de requisição e resposta são JSON; os nomes dos campos estão em camelCase. Exceções: POST /v1/jobs/upload (resposta) e o corpo da requisição do webhook — ambos usam snake_case.
Endpoints de processamento
| Método | Endpoint | Finalidade |
|---|---|---|
POST | /v1/jobs | criar um job |
GET | /v1/jobs/{id} | status do job e lista de arquivos (sem os resultados de reconhecimento) |
GET | /v1/jobs/{id}/result | resultado completo: texto reconhecido e respostas do modelo por arquivo |
GET | /v1/jobs | listar os jobs da conta (paginação: pageSize, pageToken, filtro statusEq) |
POST | /v1/jobs/upload | enviar um único arquivo (multipart/form-data) |
POST /v1/jobs — criar um job
Corpo da requisição:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
sourceUrls | string[] | sim | URLs dos arquivos a processar |
prompts | string[] | não | instruções para o modelo (apenas o primeiro prompt é executado); sem prompts, o LLM não é chamado |
neural | object | sim | configuração do modelo (veja “Conectando um modelo”) |
ocr | object | sim | configuração do provedor de OCR (BYOK); sempre obrigatória (veja “Configuração de OCR”) |
title | string | não | nome arbitrário do job |
metadata | map<string,string> | não | pares chave-valor de string arbitrários |
webhookUrl | string | não | endpoint http/https absoluto a notificar na conclusão do job (veja “Webhooks”) |
webhookSecret | string | não | segredo HMAC opcional para assinar as requisições de webhook; aceito apenas como entrada, nunca retornado nas respostas |
idempotencyKey | string | não | chave do cliente para repetições seguras de criação; máximo de 255 caracteres (veja “Idempotência”) |
neural e ocr são sempre obrigatórios, mesmo quando prompts está vazio. Com prompts vazio, o modelo não é chamado: cada arquivo é marcado como JOB_LLM_STATUS_SKIPPED com skipReason=no_prompt e, em caso de OCR bem-sucedido, o job termina como JOB_STATUS_COMPLETE.
A resposta é o job criado com status JOB_STATUS_NEW.
Configuração de OCR
ocr seleciona o backend de reconhecimento (OCR) por requisição (BYOK):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
ocr.provider | enum | sim | um de NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI |
ocr.model | string | sim | o id do modelo do provedor, por exemplo mistral-ocr-latest (Mistral) ou o modelo de visão do provedor escolhido |
ocr.providerKey | string | sim | chave BYOK para o provedor de OCR; apenas como entrada, nunca retornada |
O Mistral é o backend de OCR típico (mistral-ocr-latest); os demais provedores executam
OCR via vision-chat. A chave é armazenada criptografada e excluída junto com o job.
Qual modelo usar? Compare inteligência, preço por token e provedores no comparativo de LLM e escolha o modelo ideal.
Idempotência
Envie idempotencyKey para tornar POST /v1/jobs seguro para repetição. Uma repetição com a
mesma chave e parâmetros de requisição idênticos retorna o job original (nenhum
duplicado é criado). A mesma chave com parâmetros diferentes é rejeitada com
400 "idempotency key reused with different request parameters". A chave tem no
máximo 255 caracteres; gere um novo UUID por criação lógica.
GET /v1/jobs/{id}/result — resultado
Retorna { "result": { "job": …, "ocr": [...], "llm": [...] } }. Exemplo (abreviado):
{
"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 na resposta não inclui apiKey; o objeto Job não tem campo ocr — a chave de OCR nunca é retornada. ocr[].content é o texto reconhecido no formato ocr[].outputFormat; o formato depende do tipo do arquivo: PDF e HTML → html, .xml → xml, .txt → plain, todo o resto (incluindo imagens e arquivos de office) → markdown. llm[].content é o texto da resposta do modelo exatamente como está (seu prompt determina a estrutura; não há validação no lado do servidor). No exemplo acima, campos vazios/zerados ("", {}, 0) são exibidos por completude — o protojson os omite, então eles podem estar ausentes em uma resposta real.
Campos de ocr[]:
| Campo | Tipo | Descrição |
|---|---|---|
jobId / file | string | id do job / URL do arquivo |
status | enum | JOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
content | string | texto reconhecido no formato outputFormat |
outputFormat | string | formato de content: markdown / html / xml / plain. Direciona o chunking consciente da estrutura no estágio de LLM |
model | string | método/mecanismo de OCR (por exemplo, pdf_fitz) |
request / rawData | string | debug: a requisição de OCR e a resposta bruta |
error | string | erro do estágio (vazio em caso de sucesso) |
duration | string | duração, ns (um número como string — o protojson retorna int64 como string) |
created / updated | string | RFC3339 |
Campos de llm[]:
| Campo | Tipo | Descrição |
|---|---|---|
jobId / file | string | id do job / URL do arquivo |
promptIndex | int | índice do prompt (atualmente sempre 0) |
chunkIndex / chunkTotal | int | número do chunk / total de chunks (se o texto foi dividido) |
status | enum | JOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED |
skipReason | string | quando SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content |
content | string | resposta do modelo (texto como está) |
model | string | a string de modelo da requisição |
request / rawData | string | debug |
error | string | erro do estágio (por exemplo, provider error (category=…, status=400)) |
duration | string | duração, ns (um número como string — o protojson retorna int64 como string) |
created / updated | string | RFC3339 |
Nota. Uma especificação legível por máquina (OpenAPI/Swagger) ainda não é gerada; esta referência é mantida manualmente. A OpenAPI está planejada como uma tarefa separada.
POST /v1/jobs/upload — enviar um arquivo
Um endpoint HTTP autônomo (não é grpc-gateway). Aceita um arquivo via multipart/form-data.
Resposta (snake_case — uma exceção ao camelCase geral):
{"url": "https://…/files/…/document.pdf", "name": "document.pdf", "size_bytes": 204800}
Use a url retornada em sourceUrls ao criar um job.
O corpo de erro deste endpoint é {"code": <int>, "message": "…"}, sem o array details. O limite de tamanho do arquivo é definido pela config (grpc.maxRecvMsgBytes; 20 MiB no deployment atual), e não por um padrão de código.
Versionamento
O caminho /v1 é estável. Mudanças incompatíveis com versões anteriores são lançadas sob um novo caminho (/v2). Mudanças aditivas (novos campos opcionais e endpoints) não quebram a compatibilidade e são anunciadas no Changelog.