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étodoEndpointFinalidade
POST/v1/jobscriar um job
GET/v1/jobs/{id}status do job e lista de arquivos (sem os resultados de reconhecimento)
GET/v1/jobs/{id}/resultresultado completo: texto reconhecido e respostas do modelo por arquivo
GET/v1/jobslistar os jobs da conta (paginação: pageSize, pageToken, filtro statusEq)
POST/v1/jobs/uploadenviar um único arquivo (multipart/form-data)

POST /v1/jobs — criar um job

Corpo da requisição:

CampoTipoObrigatórioDescrição
sourceUrlsstring[]simURLs dos arquivos a processar
promptsstring[]nãoinstruções para o modelo (apenas o primeiro prompt é executado); sem prompts, o LLM não é chamado
neuralobjectsimconfiguração do modelo (veja “Conectando um modelo”)
ocrobjectsimconfiguração do provedor de OCR (BYOK); sempre obrigatória (veja “Configuração de OCR”)
titlestringnãonome arbitrário do job
metadatamap<string,string>nãopares chave-valor de string arbitrários
webhookUrlstringnãoendpoint http/https absoluto a notificar na conclusão do job (veja “Webhooks”)
webhookSecretstringnãosegredo HMAC opcional para assinar as requisições de webhook; aceito apenas como entrada, nunca retornado nas respostas
idempotencyKeystringnãochave 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):

CampoTipoObrigatórioDescrição
ocr.providerenumsimum de NEURAL_CLIENT_TYPE_MISTRAL, _OPENAI, _CLAUDE, _DEEPSEEK, _GROK, _TOGETHER, _OPENROUTER, _XIAOMI
ocr.modelstringsimo id do modelo do provedor, por exemplo mistral-ocr-latest (Mistral) ou o modelo de visão do provedor escolhido
ocr.providerKeystringsimchave 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, .xmlxml, .txtplain, 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[]:

CampoTipoDescrição
jobId / filestringid do job / URL do arquivo
statusenumJOB_OCR_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
contentstringtexto reconhecido no formato outputFormat
outputFormatstringformato de content: markdown / html / xml / plain. Direciona o chunking consciente da estrutura no estágio de LLM
modelstringmétodo/mecanismo de OCR (por exemplo, pdf_fitz)
request / rawDatastringdebug: a requisição de OCR e a resposta bruta
errorstringerro do estágio (vazio em caso de sucesso)
durationstringduração, ns (um número como string — o protojson retorna int64 como string)
created / updatedstringRFC3339

Campos de llm[]:

CampoTipoDescrição
jobId / filestringid do job / URL do arquivo
promptIndexintíndice do prompt (atualmente sempre 0)
chunkIndex / chunkTotalintnúmero do chunk / total de chunks (se o texto foi dividido)
statusenumJOB_LLM_STATUS_PENDING / _SKIPPED / _DONE / _FAILED
skipReasonstringquando SKIPPED: no_prompt / ocr_failed / no_ocr_content / empty_ocr_content
contentstringresposta do modelo (texto como está)
modelstringa string de modelo da requisição
request / rawDatastringdebug
errorstringerro do estágio (por exemplo, provider error (category=…, status=400))
durationstringduração, ns (um número como string — o protojson retorna int64 como string)
created / updatedstringRFC3339

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.