Changelog

A public log of API changes. Additive, backward-compatible changes (new optional fields); breaking changes are flagged explicitly — existing integrations keep working unchanged.

2026-07-28 — Renamed to ChunkChef

The product is now ChunkChef. Three changes are visible on the wire; the rest is internal.

  • Breaking — webhook signature headers renamed. X-Hotdoc-Timestamp and X-Hotdoc-Signature are now X-Chunkchef-Timestamp and X-Chunkchef-Signature. The signing scheme is unchanged; only the header names differ. Update your verifier to read the new names.
  • Breaking — API key prefix changed. New keys are issued as chunkchef_<id>.<secret> instead of hotdoc_<id>.<secret>. Existing keys stop authenticating; mint a replacement in the dashboard.
  • Breaking — the typed error discriminator changed. Inside Status.details, @type now reads type.googleapis.com/chunkchef.v1.billing.QuotaExceededDetail. If you match on that string, update it.

Endpoints, request and response shapes, and the domains hotdoc.io / api.hotdoc.io are unchanged.

2026-07-28 — OpenAPI specification

  • Machine-readable spec published at /openapi.yaml — OpenAPI 3.0.3, generated from the service’s protobuf definitions, covering the five public Jobs operations (POST /v1/jobs/upload, POST /v1/jobs, GET /v1/jobs/{id}, GET /v1/jobs/{id}/result, GET /v1/jobs). Point any OpenAPI-compatible generator at it to build a client; this hand-written reference stays as the prose documentation.
  • Behaviour change — GET /v1/jobs page size is now bounded. pageSize defaults to 50 and is capped at 200. A call that omitted pageSize previously returned the entire job history in one response. If you relied on that, page through with pageToken.
  • Behaviour change — a pageToken that no longer resolves returns 400. Such a token previously restarted the listing from the first page with no signal, which could hand you rows you had already seen. Treat the 400 as “the cursor no longer applies, restart with pageToken unset”.
  • Fixed — paging through GET /v1/jobs no longer skips or repeats jobs. The cursor anchored on a different column than the one the list was ordered by, so a multi-page listing could drop some jobs and return others twice.

2026-07-24 — K-consensus confidence

  • New consensus field (optional) on POST /v1/jobs — opt-in, requires responseSchema (rejected with 400 otherwise). consensus.mode picks the run count: CONSENSUS_MODE_TWO_RUNS (2, an instability probe), CONSENSUS_MODE_THREE_RUNS (3, recommended), or CONSENSUS_MODE_FIVE_RUNS (5, maximum scrutiny). hotdoc runs each chunk k times on your key and votes field-by-field (plurality) on the answer.
  • New llm[].consensus result fieldk, runsVotable, minAgreement, incomplete, and disagreements[] (competing variants[] with run counts and whether each included the vote’s winner). Lets you see which fields the model was stable on and which it wavered on, at k× the token cost on your key. See “Consensus runs” in the Job API docs.

2026-07-15 — Chunk merge

  • New merge field (optional) on POST /v1/jobs — opt-in, server-side merging of per-chunk model answers into one whole-document result. merge.scope picks per-file (MERGE_SCOPE_FILE, default) or per-job (MERGE_SCOPE_JOB) merging; merge.conflictPolicy picks how disagreeing field values are resolved (MERGE_CONFLICT_POLICY_FIRST_NON_NULL, default, or MERGE_CONFLICT_POLICY_MAJORITY); merge.dedupeBy opts into cross-file content dedup by JSON field names (JSON + responseSchema only; empty keeps the safe technical-only default).
  • New merged[] result field — one entry per file (or per job at scope=job) with the combined content, schemaValid (when responseSchema is set), conflicts[], dedupeRemovedTechnical/dedupeRemovedContent counters, and mergeIncomplete/incompleteChunks when a chunk couldn’t be safely merged. The existing llm[] per-chunk rows are unchanged and remain available. This closes the standing “the service still does not merge chunks” note from 2026-06-24 — merging is now available, opt-in, and off by default. See “Merging chunked results” in the Job API docs.

2026-07-14 — Structured output (JSON Schema)

  • New responseSchema field (optional) on POST /v1/jobs — a self-contained JSON Schema (raw string; internal #/$defs only, ≤128 KiB, depth ≤64, ≤10000 nodes) that hotdoc validates the model’s JSON answer against. For OpenAI/Grok it must be strict-subset-compatible (every property in required, optionality via type: [..., "null"], additionalProperties: false); a schema written to that subset works unchanged across all providers. An invalid schema is rejected with 400 at job creation.
  • New llm[].schemaValid / llm[].schemaErrors result fields — reported only for single-chunk files. A non-conforming answer is still returned in content, but the job finishes as JOB_STATUS_PARTIAL instead of JOB_STATUS_COMPLETE. Providers hotdoc emulates structured outputs for (DeepSeek, Xiaomi) get one automatic repair retry before scoring.

2026-07-09 — Extraction mode

  • New extractionMode field (optional) on POST /v1/jobsEXTRACTION_MODE_HYBRID (Optimized Hybrid Extraction, the default) lets the converter decide text-extract vs OCR per file; EXTRACTION_MODE_OCR_ALWAYS forces neural OCR on every file. Plus a new account-level default, configurable in the dashboard under Settings, used whenever a job omits extractionMode.

2026-07-08 — Public playground

  • /playground on the site — try hotdoc without an account: upload a file, run a preset prompt, see the result in the browser.
  • New anonymous demo endpoints, /v1/demo/*: upload a file, create a demo job, poll its result, and claim it onto a real account after signup. Sessions are tracked by a cookie (no login) and capped at 3 runs per session plus a shared daily budget across all anonymous visitors; results are truncated. This is additive — the authenticated /v1/jobs API is unchanged.

2026-06-30 — Multi-provider OCR (BYOK)

Breaking. The OCR stage now takes ocr.{provider, model, providerKey} (was ocr.mistralApiKey). Choose any supported provider — Mistral is the default (mistral-ocr-latest). model is required. New OCR error markers (provider_auth_failed, provider_key_required, rate_limited, ocr_timeout, ocr_backend_*, too_many_pages) replace mistral_key_required.

2026-06-26 — Idempotency keys

POST /v1/jobs accepts idempotencyKey. Retrying with the same key and identical parameters returns the original job; the same key with different parameters returns 400.

2026-06-24 — job completion webhooks

  • New webhookUrl and webhookSecret fields (both optional) on job creation. webhookUrl is an absolute http/https endpoint; the service POSTs it once with a JSON body (job_id, account_id, status, finished_at) when the job reaches a terminal status. webhookSecret is an HMAC secret: when set, each request includes X-Hotdoc-Timestamp and X-Hotdoc-Signature headers (sha256=hex(hmac_sha256(secret, "<ts>.<body>"))). Both fields are accepted as input only and never appear in responses. Retry policy: up to 6 attempts with delays 1 m / 5 m / 15 m / 30 m / 30 m. See “Webhooks” for details.

2026-06-24 — structure-aware chunking and configurable chunk size

  • Structure-aware text chunking. Long recognized text is now cut along the boundaries of its format’s structure (Markdown / HTML / XML / plain): tables are not torn mid-row (a table’s header is repeated in each chunk), and section-heading context is preserved. The previous behavior (chunks as separate llm[] rows keyed by chunkIndex / chunkTotal) is unchanged — the service still does not merge chunks.
  • New neural.chunkBudgetTokens field (optional) on job creation — per-chunk token budget for your model’s context window; range 160002000000, defaults to the service’s conservative value. The response echoes the actual effective budget.
  • New ocr[].outputFormat field in the job result — the format of the recognized text (markdown / html / xml / plain).