docling-serve-client 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,19 +6,14 @@
6
6
 
7
7
  # docling-serve-client
8
8
 
9
- Typed HTTP client for a running [docling-serve](https://github.com/docling-project/docling-serve): **PDF or image
10
- bytes in, docling's Markdown or DoclingDocument JSON out**.
11
-
12
- - Two outputs: `convertMarkdown` returns docling's Markdown unchanged except for image handling; `convertDocument`
13
- returns the DoclingDocument (the JSON AST with labels, reading order, pages and bounding boxes) as docling sends it.
14
- - No cleanup, no heuristics on text. Repairs and analysis belong in the code that uses this client. The Markdown is
15
- docling's raw output, e.g. with `&` entities, soft hyphens and dot leaders left in.
16
- - In-memory only: no file I/O, no streaming.
17
- - **Calls never throw on runtime failures.** Every call resolves to `{ ok: true, data } | { ok: false, error }`.
18
- Two exceptions are programming errors: the factory throws a `TypeError` on invalid config, and any call with an
19
- invalid option rejects with a `TypeError` before sending a request.
20
- - Sync (`convert*`) and async (`submit*` + `check`). No built-in polling, queueing or rate limiting.
21
- - Zero dependencies. ESM, works in Node ≥ 22.12 (also via `require()`) and Bun.
9
+ Typed HTTP client for a running [docling-serve](https://github.com/docling-project/docling-serve): **PDF or image bytes in, docling's Markdown or DoclingDocument JSON out**.
10
+
11
+ - **Two outputs**: `convertMarkdown` returns docling's Markdown unchanged (with configurable image handling); `convertDocument` returns the full DoclingDocument JSON AST (labels, reading order, page metadata, bounding boxes).
12
+ - **No text heuristics**: raw docling output is preserved exactly as returned by the server (e.g. HTML entities, soft hyphens, dot leaders).
13
+ - **In-memory only**: no file I/O or streaming dependencies.
14
+ - **Never throws on runtime failures**: all client calls resolve to `{ ok: true, data } | { ok: false, error }`. Programming errors throw a `TypeError` (synchronously on the factory for invalid configuration; rejected on methods before sending requests for invalid options).
15
+ - **Three execution modes**: single requests (`convert*`), background jobs (`submit*` + `check`), and automated polling helpers (`run*`, `waitForJob`). See [Single request or job?](#single-request-or-job).
16
+ - **Zero dependencies**: pure ESM, runs on Node ≥ 22.12 (including `require()`) and Bun.
22
17
 
23
18
  ## Installation
24
19
 
@@ -36,204 +31,267 @@ import { createDoclingClient } from 'docling-serve-client';
36
31
 
37
32
  const docling = createDoclingClient({ baseUrl: 'http://127.0.0.1:5001' });
38
33
 
39
- // Input is always a Uint8Array (Node Buffer is one): a PDF, or a PNG, JPEG, TIFF or WebP image
34
+ // Input is a Uint8Array (Node Buffer is an instance): PDF, PNG, JPEG, TIFF, or WebP
40
35
  const pdf = await readFile('paper.pdf');
41
- // const pdf = await Bun.file('paper.pdf').bytes(); // Bun
42
- // const pdf = new Uint8Array(await (await fetch(pdfUrl)).arrayBuffer()); // any download
36
+ // const pdf = await Bun.file('paper.pdf').bytes(); // Bun
43
37
 
38
+ // 1. Convert to Markdown
44
39
  const md = await docling.convertMarkdown(pdf);
45
- if (md.ok) console.log(md.data.markdown, md.data.warnings);
40
+ if (md.ok) {
41
+ console.log(md.data.markdown, md.data.warnings);
42
+ }
46
43
 
44
+ // 2. Convert to DoclingDocument JSON AST
47
45
  const doc = await docling.convertDocument(pdf);
48
46
  if (doc.ok) {
49
47
  for (const item of doc.data.document.texts) {
50
- if (item.label === 'section_header') console.log(item.level, item.text, item.prov[0]?.page_no);
48
+ if (item.label === 'section_header') {
49
+ console.log(item.level, item.text, item.prov[0]?.page_no);
50
+ }
51
51
  }
52
52
  } else {
53
53
  console.error(doc.error.code, doc.error.message);
54
54
  }
55
+
56
+ // 3. Automated job helper (identical data, typically faster for small inputs)
57
+ const fast = await docling.runDocument(pdf);
58
+ if (fast.ok) {
59
+ console.log(fast.data.document.texts.length, fast.data.settings);
60
+ }
61
+ ```
62
+
63
+ ### Single request or job?
64
+
65
+ docling-serve exposes two endpoint families, covered by three client patterns:
66
+
67
+ | Client Calls | docling-serve Endpoints | Execution Model |
68
+ |---|---|---|
69
+ | Single request: `convertMarkdown`, `convertDocument` | Sync: `POST /v1/convert/file` | Holds a single HTTP connection open until conversion completes |
70
+ | Background job: `submitMarkdown`, `submitDocument`, `check` | Async: `POST /v1/convert/file/async`, `GET /v1/status/poll/{id}`, `GET /v1/result/{id}` | Returns a job ID immediately; caller persists job and polls with `check()` |
71
+ | Job helper: `runMarkdown`, `runDocument`, `waitForJob` | Async (as above) | Submits (or takes a stored job) and polls every `pollMs` (default 10 ms) until complete |
72
+
73
+ Under the hood, docling-serve processes single requests as background jobs polled at intervals of `DOCLING_SERVE_SYNC_POLL_INTERVAL` seconds (default 2). A sync request only finishes on a tick: a 0.5 s conversion waits up to 2 s, and a 2.1 s conversion waits 4 s. Measured on a one-page PDF (docling-serve 1.38.2, 5 calls each after warm-up):
74
+
75
+ | Call | Duration per call |
76
+ |---|---|
77
+ | `convertDocument`, `convertMarkdown` | 2006 to 2033 ms |
78
+ | `runDocument`, `runMarkdown` | 221 to 420 ms |
79
+
80
+ Which to use:
81
+
82
+ - **Small inputs or latency-sensitive tasks** (single images, OCR crops, one-page PDFs): use `runMarkdown` or `runDocument`. They resolve to the same `data` as `convert*` and act as drop-in replacements.
83
+ - **Long documents**: any call works (latency overhead is at most one tick). Single requests fail with HTTP 504 after `DOCLING_SERVE_MAX_SYNC_WAIT` (120 s), so use a job or job helper for long conversions.
84
+ - **Cross-process workflows, queue progress, or recovery**: use `submit*`, persist the job object, and poll with `check()` or `waitForJob()`.
85
+ - **Single HTTP request without polling**: use `convert*`.
86
+
87
+ Relevant docling-serve environment variables (read from the server environment or `.env` in the server root):
88
+
89
+ | Variable | Default | Description |
90
+ |---|---|---|
91
+ | `DOCLING_SERVE_SYNC_POLL_INTERVAL` | `2` | Seconds between status checks on the sync endpoint (integer; `1` halves single-request wait; fractions like `0.5` fail server startup) |
92
+ | `DOCLING_SERVE_MAX_SYNC_WAIT` | `120` | Seconds a single request waits before returning HTTP 504 (the server task continues running) |
93
+ | `DOCLING_SERVE_SINGLE_USE_RESULTS` | `true` | Purges task results `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds after first result fetch (see [Jobs](#jobs)) |
94
+ | `DOCLING_SERVE_RESULT_REMOVAL_DELAY` | `300` | Seconds from first result fetch until the task is deleted |
95
+
96
+ ```bash
97
+ DOCLING_SERVE_SYNC_POLL_INTERVAL=1 docling-serve run --host 127.0.0.1 --port 5001
55
98
  ```
56
99
 
57
- ### Async
100
+ In Node, `fetch` aborts if response headers take longer than 300 s (`network` error due to undici's default `headersTimeout`), so single requests fail after 300 s even if `DOCLING_SERVE_MAX_SYNC_WAIT` is set higher. Jobs are not affected.
58
101
 
59
- `submitMarkdown()` and `submitDocument()` return a plain JSON job object. Store all of it and pass it back to
60
- `check()`. A Markdown job carries its image handling, so `check()` works the same from another process or after a
61
- restart. `check()` returns the result type that matches the job's `kind`. The job also carries the
62
- [conversion settings](#conversion-settings) of its submission; `check()` does not need them, so a stored job may omit
63
- `settings`, and the `done` state does not repeat them.
102
+ ### Jobs
103
+
104
+ `submitMarkdown()` and `submitDocument()` return a plain JSON job object. Persist it and pass it to `check()` or `waitForJob()`. A Markdown job preserves its image handling settings across processes and restarts, returning the result type matching the job's `kind`. Jobs also store the [conversion settings](#conversion-settings) sent at submission; `check()` and `waitForJob()` do not require `settings` and omit them from their result.
64
105
 
65
106
  ```ts
66
107
  import type { DocumentJob } from 'docling-serve-client';
67
108
 
109
+ // 1. One awaited helper call: submit, poll every 10 ms, return result (same data as convertDocument)
110
+ const res = await docling.runDocument(pdf, { images: 'placeholder' });
111
+ if (res.ok) {
112
+ console.log(res.data.document, res.data.settings);
113
+ }
114
+
115
+ // 2. Or submit and store the job handle:
68
116
  const job = await docling.submitDocument(pdf);
69
- if (!job.ok) return handle(job.error);
117
+ if (!job.ok) {
118
+ throw new Error(`${job.error.code}: ${job.error.message}`);
119
+ }
70
120
  await save(JSON.stringify(job.data)); // { kind: 'document', id, settings }
71
121
 
72
- // later, as often as you like (e.g. in a polling loop or another worker):
122
+ // 3. Later (in this or another process): poll until complete
73
123
  const storedJob: DocumentJob = JSON.parse(await load());
124
+ const done = await docling.waitForJob(storedJob, { pollMs: 100 }); // Result<ConvertedDocument>
125
+ if (done.ok) {
126
+ console.log(done.data.document);
127
+ } else {
128
+ console.error(done.error.code, done.error.message);
129
+ }
130
+
131
+ // 4. Or check status on demand (e.g. for progress bars):
74
132
  const state = await docling.check(storedJob);
75
133
  if (state.ok) {
76
134
  switch (state.data.status) {
77
135
  case 'queued': console.log('Queue position:', state.data.position); break;
78
136
  case 'running': console.log('Processing...'); break;
79
- case 'done': use(state.data.document); break;
137
+ case 'done': console.log(state.data.document); break;
80
138
  }
81
139
  } else {
82
- // state.error.code: 'not_found' (e.g. expired or unknown id), 'http', 'conversion_failed', etc.
140
+ // state.error.code: 'not_found' (expired or unknown ID), 'http', 'conversion_failed', etc.
83
141
  console.error(state.error.code, state.error.message);
84
142
  }
85
-
86
- // In-process polling loop until done:
87
- while (true) {
88
- const poll = await docling.check(storedJob);
89
- if (!poll.ok) {
90
- console.error('Job error:', poll.error.code, poll.error.message);
91
- break;
92
- }
93
- if (poll.data.status === 'done') {
94
- use(poll.data.document);
95
- break;
96
- }
97
- await new Promise((resolve) => setTimeout(resolve, 1000));
98
- }
99
143
  ```
100
144
 
101
- For Markdown jobs (`submitMarkdown`), `check()` returns `JobState<ConvertedMarkdown>` whose `done` state provides
102
- `markdown`, `images`, `warnings`, and `versions`.
145
+ For Markdown jobs (`submitMarkdown`), `check()` returns `JobState<ConvertedMarkdown>` (`done` provides `markdown`, `images`, `warnings`, `versions`) and `waitForJob()` returns `ConvertedMarkdown`.
146
+
147
+ Job helper details:
103
148
 
104
- docling-serve deletes a result `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after its first fetch
105
- (`DOCLING_SERVE_SINGLE_USE_RESULTS=true`, the default). A `check()` whose result download failed (network error,
106
- `timeoutMs`, abort) can be repeated within that time; later `check()` returns `not_found` (`Result already fetched or
107
- expired`), so persist the `done` data. Start docling-serve with `DOCLING_SERVE_SINGLE_USE_RESULTS=false` to keep results
108
- until they expire. These are server settings; `convert*()` is not affected.
149
+ - `runMarkdown()` / `runDocument()` = `submit*()` + `waitForJob()`. They accept `convert*` options plus `pollMs` and resolve to `WithSettings<...>` (matching `convert*`).
150
+ - `waitForJob()` resolves to `check()`'s `done` data without `status` (`ConvertedMarkdown | ConvertedDocument` when called with an unnarrowed `JobRef`).
151
+ - The first status poll runs immediately; `pollMs` delays only after `queued` or `running` states, keeping at most one request in flight. Finished jobs return in a single check.
152
+ - The first encountered error terminates the call without retries (same error codes as `submit*` and `check()`).
153
+ - `signal` covers submission, status polls, and waits. Factory `timeoutMs` limits individual HTTP requests. Use `signal: AbortSignal.timeout(ms)` to bound total execution time.
154
+ - Aborting a call (or encountering an HTTP 504 on `convert*`) cancels only client-side execution; docling-serve has no task cancellation endpoint, so the server task continues.
155
+ - `run*` does not return the job handle. To retain job IDs across restarts or retry result downloads, use `submit*` with `waitForJob()`.
109
156
 
110
- ### Options
157
+ When `DOCLING_SERVE_SINGLE_USE_RESULTS=true` (the default), docling-serve purges a task `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after its result is first fetched. If a download in `check()` or `waitForJob()` fails (e.g. network interruption or abort), it can be retried within this window. Once purged, calls return `not_found` (`Unknown job id`). To retain completed tasks on the server, configure `DOCLING_SERVE_SINGLE_USE_RESULTS=false`. Single requests (`convert*`) are unaffected.
111
158
 
112
- Set docling options on the factory and override them per call, key by key; `ocrLang`, `pageRange` and
113
- `documentTimeout` are factory-only and reject per call. Nothing is silently ignored or converted:
114
- an unknown option or an invalid value throws a `TypeError` at the start of the call, before any request
115
- (`Option "x" is not supported`, `Invalid value for option "x"`; nested keys are named like `imageOptions.prefix`).
159
+ ### Client configuration and options
160
+
161
+ Create a client instance with `createDoclingClient(config)`:
116
162
 
117
163
  ```ts
164
+ import { createDoclingClient } from 'docling-serve-client';
165
+
118
166
  const docling = createDoclingClient({
119
- baseUrl: 'http://127.0.0.1:5001',
120
- apiKey: '…', // only if docling-serve requires X-Api-Key
121
- timeoutMs: 120_000, // optional limit per request; on expiry the call resolves with `aborted`
122
- options: { ocrLang: ['de', 'en'] }, // docling defaults for every call
123
- fetch: myFetch, // optional custom fetch
167
+ baseUrl: 'http://127.0.0.1:5001', // base URL (may include a path prefix); trailing slash stripped; no query, fragment, or auth
168
+ apiKey: 'my-secret-key', // optional X-Api-Key header
169
+ timeoutMs: 120_000, // optional per-request timeout in ms (resolves to aborted)
170
+ pollMs: 10, // default polling interval in ms for job helpers (default 10)
171
+ options: { ocrLang: ['de', 'en'] }, // default docling options applied to every call
172
+ fetch: customFetch, // optional custom fetch implementation
124
173
  });
125
-
126
- await docling.convertDocument(pdf, { ocr: false, images: 'placeholder', signal: AbortSignal.timeout(60_000) });
127
174
  ```
128
175
 
129
- docling options (factory and per call):
176
+ #### Factory configuration (`DoclingConfig`)
130
177
 
131
- | option | default | effect |
132
- |---|---|---|
133
- | `headingHierarchy` | `true` | heading levels by bookmarks, numbering and font; `false` = every heading level 1 |
134
- | `formulaEnrichment` | `true` | formulas as LaTeX (extra model; runs only on formulas); without it formula `text` is empty and the md shows `<!-- formula-not-decoded -->` |
135
- | `codeEnrichment` | `false` | code listings via extra model (slower) |
136
- | `chartExtraction` | `false` | charts as data (vision model, slower) |
137
- | `pipeline` | `'standard'` | `'vlm'` runs a vision-language model per page (slow) |
138
- | `pdfBackend` | `'docling_parse'` | text extractor; `'pypdfium2'` reads ligatures and word spaces better but misses page headers and footers |
139
- | `ocr` | `true` | OCR bitmap content of PDFs (scans, pictures); image input is always OCRed |
140
- | `forceOcr` | `false` | OCR whole PDF pages and drop their text layer, for PDFs with broken text; ignored for image input |
141
- | `ocrEngine` | `'auto'` | OCR engine preset (`ocr_preset`): `'auto'`, `'rapidocr'`, `'easyocr'`, `'tesseract'`, `'tesserocr'`, `'ocrmac'` or any other non-empty preset id; the server rejects an id it does not allow with `http` 422, see [server capabilities](#server-capabilities) |
142
-
143
- Factory only (`options` of `createDoclingClient`; per call they reject with `Option "x" is not supported`). Use a second
144
- client for other values:
145
-
146
- | option | default | effect |
147
- |---|---|---|
148
- | `ocrLang` | engine default | BCP-47 hints in order of preference, e.g. `['de', 'en-US']`; `[]` = engine default |
149
- | `pageRange` | all pages | `[from, to]`, 1-based, inclusive |
150
- | `documentTimeout` | server default | server-side limit in seconds |
178
+ | Option | Type | Default | Description |
179
+ |---|---|---|---|
180
+ | `baseUrl` | `string` | *required* | docling-serve base URL (e.g. `'http://127.0.0.1:5001'`). May include a path prefix; trailing slash stripped; no query, fragment, or credentials |
181
+ | `apiKey` | `string` | none | Sent in `X-Api-Key` request header |
182
+ | `timeoutMs` | `number` | none | Per-request timeout in milliseconds (integer `1..2147483647`; resolves to `aborted` on expiry; excludes `/version`) |
183
+ | `pollMs` | `number` | `10` | Default polling interval in ms for job helpers (`run*`, `waitForJob`) (integer `1..2147483647`) |
184
+ | `options` | `ClientOptions` | none | Default conversion options applied to every call |
185
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation for proxies, mocking, or custom dispatchers |
186
+
187
+ The default `pollMs` of 10 ms sends up to 100 status requests per second per active job. On a local docling-serve instance with the default in-memory engine, status lookups are in-memory checks. For remote servers, queue-backed workers (RQ or Ray), or high-concurrency environments, configure 100 to 500 ms. The client performs no internal request queueing or rate limiting.
188
+
189
+ #### Conversion options (`DoclingOptions`)
190
+
191
+ Configurable on the factory as defaults and overridable per call. Invalid option keys or values throw a `TypeError` synchronously on the factory, or reject with a `TypeError` on client calls before sending a request (`Option "x" is not supported`, `Invalid value for option "x"`; nested keys are named like `imageOptions.prefix`).
151
192
 
152
- Per call only (not allowed in factory `options`, rejected with `Option "x" is not supported`):
193
+ | Option | Type | Default | Description |
194
+ |---|---|---|---|
195
+ | `headingHierarchy` | `boolean` | `true` | Infers heading levels from bookmarks, numbering, and font styles. When `false`, all headings are level 1 |
196
+ | `formulaEnrichment` | `boolean` | `true` | Extracts math formulas as LaTeX via dedicated model. When `false`, formula text is empty and Markdown shows `<!-- formula-not-decoded -->` |
197
+ | `codeEnrichment` | `boolean` | `false` | Extracts code listings via dedicated model (slower) |
198
+ | `chartExtraction` | `boolean` | `false` | Extracts charts and plots as structured data via vision model (slower) |
199
+ | `pipeline` | `'standard' \| 'vlm'` | `'standard'` | Processing pipeline. `'vlm'` runs a vision-language model per page (slow) |
200
+ | `pdfBackend` | `'docling_parse' \| 'pypdfium2'` | `'docling_parse'` | Text extraction engine. `'pypdfium2'` handles ligatures and word spaces better but omits page headers/footers |
201
+ | `ocr` | `boolean` | `true` | Runs OCR on scanned pages and raster graphics. Image input always runs OCR |
202
+ | `forceOcr` | `boolean` | `false` | Forces full-page OCR across all pages and drops the PDF text layer (for PDFs with corrupt text encodings). Enables OCR even with `ocr: false`. Slower than standard OCR, so prefer using it per document. Ignored for image input |
203
+ | `ocrEngine` | `OcrEngine` | `'auto'` | OCR engine preset (`ocr_preset`), e.g. `'auto'`, `'rapidocr'`, `'easyocr'`, `'tesseract'`, `'tesserocr'`, `'ocrmac'`, or any non-empty preset ID allowed by the server. An unallowed preset is rejected by the server with `http` status 422 (see [Server capabilities](#server-capabilities)) |
204
+
205
+ #### Factory-only options
206
+
207
+ Configured in `options` of `createDoclingClient`. Passing these per-call rejects with a `TypeError: Option "x" is not supported`. Use a separate client instance if different values are required.
153
208
 
154
- | option | calls | default | effect |
209
+ | Option | Type | Default | Description |
155
210
  |---|---|---|---|
156
- | `images` | `convertMarkdown`, `submitMarkdown` | `'placeholder'` | `'placeholder'`, `'none'`, or `'extract'` |
157
- | `imageOptions` | `convertMarkdown`, `submitMarkdown` | none | `{ prefix: string, omitSmall?: boolean }`; required with `images: 'extract'`, not allowed otherwise |
158
- | `images` | `convertDocument`, `submitDocument` | `'embedded'` | `'embedded'` or `'placeholder'` |
159
- | `pageImages` | `convertDocument`, `submitDocument` | `false` | renders pages as images, decoded in `pageImages` |
160
- | `signal` | all calls (`convert*`, `submit*`, `check`, `capabilities`) | none | `AbortSignal` to cancel the request |
161
-
162
- `timeoutMs` is a factory setting: it limits every request except `GET /version` and is combined with `signal`.
163
-
164
- Always on, not configurable: table structure with the accurate TableFormer model (`do_table_structure`,
165
- `table_mode: 'accurate'`, `table_structure_preset: 'tableformer_v1_accurate'`, `table_cell_matching`), pictures and
166
- page images at 144 dpi (`images_scale=2`, exported as `PAGE_IMAGE_SCALE`) and, for Markdown, compact tables
167
- (`md_compact_tables`) without page break placeholders (`md_page_break_placeholder`). The image scale does not affect
168
- OCR: docling OCRs at its own fixed 216 dpi. Model presets are pinned: `granite_docling` for `pipeline: 'vlm'`,
169
- `granite_vision_v4` for `chartExtraction`, `codeformulav2` for formula and code enrichment. Picture classification
170
- and description stay off.
211
+ | `ocrLang` | `string[]` | engine default | BCP-47 language priority codes, e.g. `['de', 'en-US']`. `[]` uses the engine default |
212
+ | `pageRange` | `[number, number]` | all pages | Inclusive 1-based page range `[from, to]` |
213
+ | `documentTimeout` | `number` | server default | Server-side conversion timeout in seconds |
214
+
215
+ #### Per-call options
216
+
217
+ Options passed to individual conversion calls. These cannot be set in factory `options` (the factory throws a synchronous `TypeError: Option "x" is not supported`).
218
+
219
+ | Option | Applicable Calls | Type | Default | Description |
220
+ |---|---|---|---|---|
221
+ | `images` | Markdown calls | `'placeholder' \| 'none' \| 'extract'` | `'placeholder'` | Markdown image handling mode (see [Images in Markdown](#images-in-markdown)) |
222
+ | `imageOptions` | Markdown calls | `{ prefix: string, omitSmall?: boolean }` | none | Link prefix and size filtering. Required with `images: 'extract'`; rejected otherwise |
223
+ | `images` | Document calls | `'embedded' \| 'placeholder'` | `'embedded'` | AST picture image mode (see [Images in the DoclingDocument](#images-in-the-doclingdocument)) |
224
+ | `pageImages` | Document calls | `boolean` | `false` | Renders pages as images, decoded into `data.pageImages` (see [Page images](#page-images)) |
225
+ | `signal` | All calls | `AbortSignal` | none | Cancels the request or overall operation on abort |
226
+ | `pollMs` | `runMarkdown`, `runDocument`, `waitForJob` | `number` | factory `pollMs` (`10`) | Polling interval in milliseconds (integer `1..2147483647`). Rejected by `convert*`, `submit*`, and `check` |
227
+
228
+ #### Pinned server settings
229
+
230
+ The client enforces explicit server settings for consistent output:
231
+ - **Table structure**: Accurate TableFormer model is always enabled (`do_table_structure: true`, `table_mode: 'accurate'`, `table_structure_preset: 'tableformer_v1_accurate'`, `table_cell_matching: true`).
232
+ - **Resolution**: Pictures and page images render at 144 DPI (`images_scale: 2`, exported as `PAGE_IMAGE_SCALE`). Note: OCR runs independently at docling's internal 216 DPI.
233
+ - **Markdown format**: Compact tables are enabled (`md_compact_tables: true`) without page break placeholders (`md_page_break_placeholder: ''`). For document output, both parameters are omitted (`null`).
234
+ - **Model presets**: Pinned to `granite_docling` for `pipeline: 'vlm'`, `granite_vision_v4` for `chartExtraction`, and `codeformulav2` for code and formula enrichment. Picture classification and description remain disabled.
171
235
 
172
236
  ### Input formats
173
237
 
174
- The format is detected from the first bytes; anything else resolves to `invalid_input` without a request.
238
+ Input is supplied as a `Uint8Array` (Node `Buffer` instances are compatible). File formats are detected directly from header magic bytes; unsupported formats immediately resolve to `invalid_input` without sending an HTTP request:
175
239
 
176
- | input | detected by | sent as |
240
+ | Format | Detection Signature | Sent As |
177
241
  |---|---|---|
178
242
  | PDF | `%PDF-` within the first 1024 bytes | `from_formats=pdf`, `document.pdf` |
179
- | PNG, JPEG, TIFF, WebP | file signature at byte 0 | `from_formats=image`, `document.png` / `.jpg` / `.tiff` / `.webp` |
243
+ | PNG | `89 50 4E 47 0D 0A 1A 0A` at byte 0 | `from_formats=image`, `document.png` |
244
+ | JPEG | `FF D8 FF` at byte 0 | `from_formats=image`, `document.jpg` |
245
+ | TIFF | Little-endian `49 49 2A 00` or big-endian `4D 4D 00 2A` at byte 0 | `from_formats=image`, `document.tiff` |
246
+ | WebP | `RIFF` at byte 0 and `WEBP` at byte 8 | `from_formats=image`, `document.webp` |
180
247
 
181
- An image is converted as one page: docling runs layout detection and OCR on it, and `ocrLang` applies. OCR is always
182
- on for images and never full-page: `ocr: false` would return no text, and `forceOcr: true` merges words (`stepsand`),
183
- so both are ignored for image input. Use it e.g. to re-read a crop of a page whose text layer is broken. Text only
184
- comes back if the server has a working OCR engine. PDF-only options (`pdfBackend`, `pageRange`) have no effect on
185
- images.
248
+ Image inputs are converted as single-page documents. Docling runs layout detection and OCR on the image (`ocrLang` applies). Because images contain no embedded text layer, text extraction requires a working OCR engine on the server. OCR is always active for images: `ocr: false` and `forceOcr: true` are ignored (forcing full-page OCR on images merges words like `stepsand`). PDF-specific options (`pdfBackend`, `pageRange`) do not affect images.
186
249
 
187
250
  ### Images in the DoclingDocument
188
251
 
189
- | `images` | effect |
252
+ | `images` | Effect |
190
253
  |---|---|
191
- | `'embedded'` (default) | `pictures[].image.uri` is a `data:image/png;base64,…` URI; pictures keep their position, captions and size |
192
- | `'placeholder'` | `pictures[].image` is `null`; the picture items stay, the document is much smaller |
254
+ | `'embedded'` (default) | `pictures[].image.uri` contains a `data:image/png;base64,…` URI. Pictures retain bounding boxes, captions, and dimensions |
255
+ | `'placeholder'` | `pictures[].image` is `null`. Picture entries, bounding boxes, and captions remain intact; document payload is significantly smaller |
193
256
 
194
- The base64 data stays inside the document. Pull it out when you need files, e.g. when rendering Markdown from the
195
- document. `isSmallImage(picture.image.size)` applies the same size filter as `omitSmall` (see below).
257
+ Base64 image data remains inside the document JSON. `isSmallImage(picture.image.size)` can be used to filter icons and decorative elements using the same heuristic as `omitSmall`.
196
258
 
197
259
  ### Page images
198
260
 
199
- `pageImages: true` (documents only, default `false`) asks docling to render every page (`include_page_images`). The
200
- client moves them out of the document: `document.pages[n].image` is always `null`, so the JSON stays small, and the
201
- images come back decoded in `data.pageImages`, in page order:
261
+ Setting `pageImages: true` (document conversions only; default `false`) requests page renderings from docling (`include_page_images`). The client strips embedded base64 data from `document.pages[n].image` (setting it to `null`) and returns decoded raw PNG images in `data.pageImages`:
202
262
 
203
263
  ```ts
204
264
  import { writeFile } from 'node:fs/promises';
205
265
 
206
266
  const res = await docling.convertDocument(pdf, { pageImages: true, images: 'placeholder' });
207
267
  if (res.ok) {
208
- await writeFile(`out/${docId}.json`, JSON.stringify(res.data.document)); // no page image data inside
268
+ await writeFile(`out/${docId}.json`, JSON.stringify(res.data.document)); // Small JSON without embedded page images
209
269
  for (const image of res.data.pageImages) {
210
- await writeFile(`out/${docId}-p${image.page}.png`, image.bytes); // { page, mimeType, width, height, dpi, bytes }
270
+ await writeFile(`out/${docId}-p${image.page}.png`, image.bytes); // { page, mimeType, width, height, dpi, bytes }
211
271
  }
212
272
  }
213
273
  ```
214
274
 
215
- - `page` is the 1-based PDF page (absolute, also with `pageRange`); `dpi` is always 144.
216
- - `width` and `height` are pixels from the PNG header, else docling's `image.size`. Pixel size = page size in points
217
- × 2; an A4 page is about 1190 × 1684 px and 0.4 to 0.6 MB as PNG.
218
- - Page images are independent of `images`: `images: 'placeholder'` drops picture data and keeps page images.
219
- - Async: `submitDocument(pdf, { pageImages: true })` stores nothing extra in the job; `check()` returns the images.
220
- - A page image that is not a base64 data URI or has invalid or empty data is missing from `pageImages`, stays in
221
- `document.pages[].image` as docling sent it, and is reported as a `page_image_not_decoded` warning (`details.page`).
275
+ - `page`: 1-based page number (absolute, preserved across `pageRange`); `dpi` is the resolution reported by docling (typically 144), or `0` if not reported.
276
+ - `width` and `height`: Pixel dimensions parsed from the PNG header (or docling's `image.size`). Scaled at 2 px per PDF point (an A4 page is ~1190 × 1684 px, ~0.4–0.6 MB PNG).
277
+ - Independent of `images`: `{ images: 'placeholder', pageImages: true }` drops figure raster data while returning page images.
278
+ - Job persistence: `submitDocument(pdf, { pageImages: true })` requires no extra storage; `check()` and `waitForJob()` return decoded page images.
279
+ - Any page image whose URI is not a base64 data URI, or contains invalid or empty data, is omitted from `pageImages`, retained in `document.pages[].image` as docling sent it, and reported in `data.warnings` with code `page_image_not_decoded` (`details.page`).
280
+
281
+ #### Mapping bounding boxes to page images (`toPageImageBox`)
222
282
 
223
- The document keeps docling's bboxes as sent: PDF points, mostly with a bottom-left origin. `toPageImageBox` maps one
224
- onto its page image as `{ page, x, y, width, height }` in pixels with a top-left origin (unrounded), ready to store
225
- next to the page image file or to crop with:
283
+ Docling documents store coordinates in PDF points, typically with a `BOTTOMLEFT` origin. The helper `toPageImageBox` maps any provenance bounding box to top-left pixel coordinates matching rendered page images:
226
284
 
227
285
  ```ts
228
286
  import { toPageImageBox } from 'docling-serve-client';
229
287
 
230
288
  for (const item of res.data.document.texts) {
231
289
  for (const prov of item.prov) {
232
- const box = toPageImageBox(res.data.document, prov); // undefined if coordinates or page missing
290
+ const box = toPageImageBox(res.data.document, prov); // { page, x, y, width, height }
233
291
  }
234
292
  }
235
293
 
236
- // table cells carry their own bbox (top-left origin) and belong to the page of their table:
294
+ // Table cells provide their own bbox (with TOPLEFT origin) and belong to their table's page:
237
295
  for (const table of res.data.document.tables) {
238
296
  const pageNo = table.prov[0]?.page_no ?? 1;
239
297
  for (const row of table.data.grid) {
@@ -246,36 +304,37 @@ for (const table of res.data.document.tables) {
246
304
  }
247
305
  ```
248
306
 
249
- The boxes fit page images of any conversion by this client, also when `pageImages` was off.
307
+ `toPageImageBox` calculates pixel coordinates using `PAGE_IMAGE_SCALE` (`2`). It works on any conversion result, even when `pageImages: false` was used. Returns `undefined` if page dimensions or coordinates are missing or invalid.
250
308
 
251
309
  ### DoclingDocument structure
252
310
 
253
311
  `convertDocument()` returns the full JSON AST (`doc.data.document`) typed as `DoclingDocument`:
254
312
 
255
- - `texts`: array of text blocks (`label`, `text`, `orig`, `prov: [{ page_no, bbox, charspan }]`). Common labels include `'paragraph'`, `'section_header'`, `'title'`, `'list_item'`, `'code'`, `'formula'`, `'footnote'`, `'page_header'`, `'page_footer'`, `'caption'`. Section headers include heading `level`. List items include `enumerated` and `marker`. Code listings include `code_language`. Formulas contain LaTeX in `text` with `formulaEnrichment`. Note on `prov.charspan`: `[start, end)` span is in Python Unicode code points; slice in JS using `Array.from(item.text).slice(start, end).join('')`, not `item.text.slice(start, end)`.
256
- - `tables`: array of tables (`prov`, `data: { grid: TableCell[][], num_rows, num_cols }`). Cells contain `text`, `row_span`, `col_span`, `bbox`, `column_header`, and `row_header`.
257
- - `pictures`: array of figures with `captions`, `prov`, `image` (`ImageRef` with base64 data URI when `images: 'embedded'`, or `null` when `images: 'placeholder'`), and `annotations` (e.g. chart data when `chartExtraction` was enabled, classifications, descriptions).
258
- - `groups`: structural groupings (`(ListGroup | InlineGroup | GroupItem)[]`) such as lists, inline formatting runs, chapters, or form sections.
259
- - `key_value_items`: key-value regions extracted from forms or documents (`graph: { cells: GraphCell[], links: GraphLink[] }`).
260
- - `form_items`: form regions and form fields (`graph: { cells: GraphCell[], links: GraphLink[] }`).
261
- - `pages`: record of page metadata keyed by 1-based page number string (`"1"`, `"2"`, ...) with `size: { width, height }` in PDF points.
262
- - `body`: root group of content in reading order. Walk `body.children` (`{ $ref: string }` JSON pointers, e.g. `'#/texts/0'`) depth-first to follow the logical document flow.
263
- - `furniture`: root group of header, footer and page number items outside the body flow.
313
+ - `texts`: Array of text blocks (`label`, `text`, `orig`, `prov: [{ page_no, bbox, charspan }]`). Common labels: `'paragraph'`, `'section_header'`, `'title'`, `'list_item'`, `'code'`, `'formula'`, `'footnote'`, `'page_header'`, `'page_footer'`, `'caption'`. Section headers include heading `level`. List items include `enumerated` and `marker`. Code listings include `code_language`. Formulas contain LaTeX in `text` when `formulaEnrichment` is enabled. Note on `prov.charspan`: `[start, end)` is in Python Unicode code points; slice in JavaScript using `Array.from(item.text).slice(start, end).join('')` instead of `item.text.slice(start, end)` to handle surrogate pairs.
314
+ - `tables`: Array of tables (`prov`, `data: { grid: TableCell[][], num_rows, num_cols }`). Cells provide `text`, `row_span`, `col_span`, `bbox`, `column_header`, and `row_header`.
315
+ - `pictures`: Array of figures with `captions`, `prov`, `image` (`ImageRef` with base64 data URI when `images: 'embedded'`, or `null` when `images: 'placeholder'`), and `annotations` (e.g. chart data when `chartExtraction` was enabled, classifications, descriptions).
316
+ - `groups`: Structural groupings (`(ListGroup | InlineGroup | GroupItem)[]`) such as lists, inline formatting runs, chapters, or form sections.
317
+ - `key_value_items`: Key-value regions extracted from forms or documents (`graph: { cells: GraphCell[], links: GraphLink[] }`).
318
+ - `form_items`: Form regions and fields (`graph: { cells: GraphCell[], links: GraphLink[] }`).
319
+ - `pages`: Record of page metadata keyed by 1-based page number string (`"1"`, `"2"`, ...) with `size: { width, height }` in PDF points.
320
+ - `body`: Root container of content in logical reading order. Walk `body.children` (`{ $ref: string }` JSON pointers, e.g. `'#/texts/0'`) depth-first to traverse the document flow.
321
+ - `furniture`: Root container of headers, footers, and page numbers outside the body flow.
264
322
 
265
323
  #### Navigating the AST in reading order
266
324
 
267
- The root content in reading order is stored in `body.children`. Each child is a JSON pointer reference (e.g. `#/texts/0`, `#/tables/1`, `#/groups/2`). You can resolve references and walk the reading order like this:
325
+ Root document flow is organized under `body.children`. Each child contains a JSON pointer reference (e.g. `#/texts/0`, `#/tables/1`, `#/groups/2`). References can be resolved and traversed like this:
268
326
 
269
327
  ```ts
270
328
  import type { DoclingDocument } from 'docling-serve-client';
271
329
 
272
- // Resolving a JSON pointer reference (e.g. '#/texts/0'):
330
+ // Resolves a JSON pointer reference (e.g. '#/texts/0' or '#/body'):
273
331
  function resolveRef(doc: DoclingDocument, ref: string) {
274
- const [, collection, idx] = ref.split('/');
275
- return (doc as Record<string, any>)[collection]?.[Number(idx)];
332
+ const parts = ref.replace(/^#\//, '').split('/');
333
+ if (parts.length === 1) return (doc as Record<string, any>)[parts[0]!];
334
+ return (doc as Record<string, any>)[parts[0]!]?.[Number(parts[1])];
276
335
  }
277
336
 
278
- // Walking body items in logical reading order:
337
+ // Walks body items in logical reading order:
279
338
  function* walkReadingOrder(
280
339
  doc: DoclingDocument,
281
340
  node: { children: { $ref: string }[] } = doc.body,
@@ -292,159 +351,141 @@ function* walkReadingOrder(
292
351
  }
293
352
  ```
294
353
 
295
- ### Images in the Markdown
354
+ ### Images in Markdown
296
355
 
297
- | `images` | effect |
356
+ | `images` | Effect |
298
357
  |---|---|
299
- | `'placeholder'` (default) | `<!-- image -->` marks a picture, as docling writes it |
300
- | `'none'` | placeholders removed |
301
- | `'extract'` | pictures come back as bytes in `data.images`, linked as `<prefix><n>.<ext>` |
358
+ | `'placeholder'` (default) | `<!-- image -->` comment marks figure locations as produced by docling |
359
+ | `'none'` | Removes image comments |
360
+ | `'extract'` | Extracts images as binary buffers in `data.images` and updates Markdown links to `<prefix><n>.<ext>` |
302
361
 
303
- With `'extract'`, `imageOptions` is required on each call, because the prefix names one document's images:
362
+ When `images: 'extract'` is selected, `imageOptions` is required:
304
363
 
305
364
  ```ts
306
365
  import { writeFile } from 'node:fs/promises';
307
366
 
308
367
  const res = await docling.convertMarkdown(pdf, {
309
368
  images: 'extract',
310
- imageOptions: { prefix: `images/${docId}-` }, // omitSmall: drop bullets, icons and small logos, default true
369
+ imageOptions: { prefix: `images/${docId}-` }, // omitSmall defaults to true
311
370
  });
312
371
  if (res.ok) {
313
- await writeFile(`out/${docId}.md`, res.data.markdown); // ![Image](images/1234-1.png)
372
+ await writeFile(`out/${docId}.md`, res.data.markdown); // e.g. ![Image](images/doc123-1.png)
314
373
  for (const image of res.data.images) {
315
- await writeFile(`out/${image.path}`, image.bytes); // { path, mimeType, bytes, width, height }
374
+ await writeFile(`out/${image.path}`, image.bytes); // { path, mimeType, bytes, width, height }
316
375
  }
317
376
  }
318
377
  ```
319
378
 
320
- - `n` starts at 1 and counts kept images only. `<ext>` comes from the MIME type: `png`, `jpg`, `gif`, `webp`, `svg`,
321
- `tiff`, `bmp`, otherwise `bin`.
322
- - `omitSmall` (default `true`) removes small pictures from the Markdown and does not return them. Small means: area
323
- below 15 000 px, longer side below 200 px, or shorter side below 24 px (`isSmallImage`). Only PNGs have a known
324
- size (`readPngSize`); other formats are always kept and report `width` and `height` as `0`.
325
- - A picture with invalid or empty base64 data is removed and reported as an `image_not_decoded` warning
326
- (`details.index`: 1-based position among all embedded pictures); the conversion still succeeds.
327
- - A picture docling sent without image data keeps its `<!-- image -->` placeholder, as in `'placeholder'` mode.
328
- - Removing a picture or placeholder keeps the blank line that separates the paragraphs around it.
379
+ - Image numbering `n` starts at 1 and counts only retained images. Extension `<ext>` is derived from MIME type (`png`, `jpg`, `gif`, `webp`, `svg`, `tiff`, `bmp`, or `bin`).
380
+ - `omitSmall` (default `true`): Drops small icons, bullets, and decorative lines. Small images meet any of: area < 15,000 px, long side < 200 px, or short side < 24 px (`isSmallImage`). Only PNG images have known dimensions (`readPngSize`); non-PNG formats are retained with `width` and `height` set to `0`.
381
+ - Corrupt or empty base64 image data is removed and recorded in `data.warnings` with code `image_not_decoded` (`details.index` is the 1-based index among embedded images).
382
+ - Figures emitted by docling without image data retain their `<!-- image -->` placeholder comment.
383
+ - Removing placeholders or small images preserves adjacent paragraph line spacing.
329
384
 
330
385
  ### Warnings
331
386
 
332
- `data.warnings` lists non-fatal notes. The conversion still succeeded; decide yourself whether a warning matters.
387
+ `data.warnings` contains non-fatal issues encountered during an otherwise successful conversion:
333
388
 
334
- | code | `details` | meaning |
389
+ | Code | `details` | Description |
335
390
  |---|---|---|
336
- | `server` | `{ component, module, category, page }` | problem reported by docling-serve (e.g. a single page failed, `document timeout exceeded`); `page` is the 1-based PDF page, or `null` |
337
- | `image_not_decoded` | `{ index }` | Markdown with `images: 'extract'` only, see above |
338
- | `page_image_not_decoded` | `{ page }` | documents with `pageImages: true` only, see above |
391
+ | `server` | `{ component, module, category, page }` | Warning reported by docling-serve (e.g. single page failure or `document timeout exceeded`). `page` is the 1-based page number or `null` |
392
+ | `image_not_decoded` | `{ index }` | Undecodable embedded image in Markdown (`images: 'extract'`). `index` is 1-based |
393
+ | `page_image_not_decoded` | `{ page }` | Undecodable page image in Document output (`pageImages: true`). `page` is 1-based |
339
394
 
340
- Pages docling gave up on (e.g. `documentTimeout` exceeded) have no content. The only sign is a `server` warning per
341
- failed page.
395
+ Pages abandoned by the server (e.g. due to `documentTimeout`) contain no extracted content and trigger a `server` warning.
342
396
 
343
397
  ### Server versions
344
398
 
345
- `data.versions` holds the versions the server reports on `GET /version`, unchanged, e.g.
346
- `{ 'docling-serve': '1.36.0', docling: '2.135.0', 'docling-core': '2.101.1', … }`, or `null` if it reports none.
347
- Store it with each converted document: the same PDF can convert differently under another docling version, and
348
- `document.version` is the DoclingDocument schema version. Errors from a request the server answered (`http`,
349
- `not_found`, `conversion_failed`, `invalid_response`) carry the same `error.versions`. Each client asks for the
350
- versions once and keeps them; the request has its own 2 s limit, and when it fails `versions` is `null` and the next
351
- result asks again. Create a new client after a server update.
399
+ `data.versions` contains version metadata reported by `GET /version` (e.g. `{ 'docling-serve': '1.38.2', docling: '2.135.0', 'docling-core': '2.101.1', ... }`), or `null` if unreachable.
400
+
401
+ Storing versions alongside converted documents aids reproducibility, as conversion output can vary across docling releases (`document.version` indicates the DoclingDocument schema version, whereas `data.versions` reflects server runtime packages). Errors from requests answered by the server (`http`, `not_found`, `conversion_failed`, `invalid_response`) also populate `error.versions`. The client caches versions across requests; if a version check fails (2 s timeout), `versions` is `null` and retried on the next request. Create a new client instance after upgrading docling-serve.
352
402
 
353
403
  ### Conversion settings
354
404
 
355
- `convert*()` results (`data.settings`) and `submit*()` jobs (`job.settings`) carry the form fields the request sent,
356
- as a typed `DoclingSettings` object. The client sends an explicit value for every field it controls, also where it
357
- equals docling-serve's default, so the settings do not depend on the server's defaults. Store them with the versions
358
- to know how a document was converted.
405
+ Results from `convert*()` and `run*()` (`data.settings`) and job objects from `submit*()` (`job.settings`) include a typed `DoclingSettings` record of all form parameters sent to docling-serve.
406
+
407
+ The client sends explicit values for every setting it controls, avoiding reliance on server defaults:
359
408
 
360
409
  ```ts
361
410
  const res = await docling.convertMarkdown(pdf, { ocrEngine: 'rapidocr' });
362
411
  if (res.ok) {
363
- res.data.settings.ocrPreset; // 'rapidocr'
364
- res.data.settings.tableStructurePreset; // 'tableformer_v1_accurate'
365
- res.data.settings.vlmPipelinePreset; // null: pipeline is 'standard'
412
+ console.log(res.data.settings.ocrPreset); // 'rapidocr'
413
+ console.log(res.data.settings.tableStructurePreset); // 'tableformer_v1_accurate'
414
+ console.log(res.data.settings.vlmPipelinePreset); // null (pipeline is 'standard')
366
415
  }
367
416
  ```
368
417
 
369
- - Each key is the camelCase name of a docling-serve form field (`ocrPreset` = `ocr_preset`).
370
- - `null` means the field was not sent: Markdown-only fields for document output, the preset of a disabled stage,
371
- unset `pageRange` and `documentTimeout`. An empty `ocrLang` was not sent either (engine default).
372
- - Image input records what was sent: `doOcr: true` and `forceOcr: false`, whatever `ocr` and `forceOcr` say.
373
- - Server configuration that has no request field (e.g. the layout model) is not included; see
374
- `stages.layout` of the [server capabilities](#server-capabilities).
418
+ - Keys represent camelCase mappings of server form parameters (`ocrPreset` = `ocr_preset`).
419
+ - `null` indicates the parameter was omitted: Markdown-specific fields for JSON output, presets of inactive pipeline stages, or unset factory options (`pageRange`, `documentTimeout`). An empty `ocrLang` is omitted (server engine default).
420
+ - For image input, `doOcr: true` and `forceOcr: false` are recorded regardless of option flags.
421
+ - For PDF input with `forceOcr: true`, `doOcr: true` is recorded even when `ocr: false`.
422
+ - Server configuration outside request fields (e.g. default layout model) is not included; inspect `stages.layout` via `docling.capabilities()`.
375
423
 
376
424
  ### Server capabilities
377
425
 
378
- `docling.capabilities()` asks `GET /v1/capabilities` for the presets, limits and features of the server. It is for
379
- diagnostics, e.g. to list the allowed `ocrEngine` ids; conversions never call it. It takes only `{ signal }`.
426
+ `docling.capabilities({ signal? })` queries `GET /v1/capabilities` to inspect supported presets, stages, and limits. Used for diagnostics (e.g. checking available OCR engines); conversion calls do not invoke it automatically:
380
427
 
381
428
  ```ts
382
429
  const caps = await docling.capabilities();
383
430
  if (caps.ok) {
384
- caps.data.stages.ocr?.presets.map((preset) => preset.id); // ['auto', 'easyocr', 'rapidocr', 'tesseract', …]
385
- caps.data.stages.table_structure?.default; // 'tableformer_v1_accurate'
386
- caps.data.limits.maxImagesScale; // 2, or null when unlimited
387
- caps.data.features.apiKeyRequired; // false
431
+ console.log(caps.data.stages.ocr?.presets.map((p) => p.id)); // ['auto', 'easyocr', 'rapidocr', 'tesseract', ...]
432
+ console.log(caps.data.stages.table_structure?.default); // 'tableformer_v1_accurate'
433
+ console.log(caps.data.limits.maxImagesScale); // 2 or null
434
+ console.log(caps.data.features.apiKeyRequired); // false
388
435
  }
389
436
  ```
390
437
 
391
- - The server's snake_case keys come back in camelCase; `stages` keys stay as the server sends them (`ocr`, `layout`,
392
- `table_structure`, `vlm_pipeline`, `chart_extraction`, …). Each stage has `option` (its form field), `default` and
393
- `presets` (`{ id, name, description, source }`).
394
- - Missing or mistyped fields become `[]`, `''`, `null` or `false`, so a newer or older payload does not fail the call.
395
- - An allowed preset is not always usable: e.g. `ocrmac` only runs on macOS.
396
- - An older server without the endpoint answers `http` with `status: 404`.
438
+ - Snake_case server keys are converted to camelCase, while `stages` keys retain their server names (`ocr`, `layout`, `table_structure`, `vlm_pipeline`, `chart_extraction`, etc.). Each stage provides its form parameter name (`option`), server default (`default`), and allowed presets (`presets: { id, name, description, source }[]`).
439
+ - Missing or mistyped payload properties fall back to safe empty defaults (`[]`, `''`, `null`, `false`).
440
+ - Capabilities do not guarantee runtime availability (e.g. `ocrmac` requires macOS).
441
+ - Older server versions lacking the endpoint return an `http` error with status `404`.
397
442
 
398
- ### Broken umlauts / special characters
443
+ ### Corrupt text encodings & special characters
399
444
 
400
- If the output shows `fŸhrt`, `Nu§baum`, `…sterreich`, the PDF's text layer uses an encoding docling-parse misreads
401
- (seen with `/MacRomanEncoding` PDFs from Mac Word). Re-convert that document with `{ forceOcr: true }` (with `ocrLang: ['de']` on the factory).
402
- It is slower, so use it per document, not by default.
445
+ If output displays corrupted umlauts or symbols (e.g. `fŸhrt`, `Nu§baum`, `…sterreich`), the PDF text layer uses an encoding incompatible with docling's parser (common in Mac Word documents with `/MacRomanEncoding`). Re-convert with `{ forceOcr: true }` (and configure `ocrLang` on the factory) to bypass the corrupt text layer and run full-page OCR.
403
446
 
404
447
  ### Errors
405
448
 
406
- All client calls resolve to a `Result<T>` union:
449
+ All client methods return a discriminated `Result<T>` union:
407
450
  - `{ ok: true, data: T }` on success
408
451
  - `{ ok: false, error: ConvertError }` on runtime failure
409
452
 
410
- #### Error fields
453
+ #### Error structure
411
454
 
412
455
  Every `ConvertError` contains:
413
- - `code`: one of the `ErrorCode` values below
414
- - `message`: human-readable explanation of the failure
415
- - `status`: optional HTTP status code (present when the server answered, e.g. `400`, `404`, `422`, `500`)
416
- - `versions`: server versions captured from `GET /version` if the server answered, or `null`
417
- - `details`: `{ errors: ServerError[]; failure: TaskFailure | null }` (present only when `code === 'conversion_failed'`)
456
+ - `code`: Error classification (see table below)
457
+ - `message`: Diagnostic description
458
+ - `status`: Optional HTTP status code when answered by the server (e.g. `400`, `404`, `422`, `500`)
459
+ - `versions`: Server versions captured via `GET /version`, or `null`
460
+ - `details`: `{ errors: ServerError[]; failure: TaskFailure | null }` (present only when `code === 'conversion_failed'`). `errors` lists docling error items with `{ message, details: { component, module, category, page } }`; `failure` categorizes task failure as `{ category, phase, retryable }` (or `null`)
418
461
 
419
- | code | meaning |
462
+ | Code | Cause |
420
463
  |---|---|
421
- | `invalid_input` | not a non-empty PDF, PNG, JPEG, TIFF or WebP `Uint8Array`, a truncated PDF (cross-reference offset past the end of the file), or not a job object from `submit*()`, or input that could not be prepared for upload (e.g. out of memory); no request sent |
422
- | `network` | server unreachable (the message includes the cause, e.g. `ECONNREFUSED`) |
423
- | `aborted` | your `signal` fired (incl. `AbortSignal.timeout()`) or `timeoutMs` expired |
424
- | `http` | non-2xx response (`error.status` set), e.g. auth, sync wait exceeded |
425
- | `not_found` | `check()`: `Unknown job id` (e.g. after a server restart), or `Result already fetched or expired`; the server's own message follows in parentheses |
426
- | `conversion_failed` | the server could not convert the document, also a `partial_success` (or a success with error items) that has no Markdown or DoclingDocument |
427
- | `invalid_response` | unexpected response body (no status, no Markdown or DoclingDocument without server details, a non-JSON body (quoted in the message), an unusable task id, or a body too large to read) |
464
+ | `invalid_input` | Input is not a non-empty `Uint8Array`, contains an unrecognized file signature, has a truncated PDF cross-reference table, is an invalid job object, or failed buffer preparation before upload. No request sent |
465
+ | `network` | Server unreachable or connection refused |
466
+ | `aborted` | Abort signal fired (including `AbortSignal.timeout()`), or factory `timeoutMs` expired |
467
+ | `http` | Non-2xx HTTP response, or single-request timeout (HTTP 504 after `DOCLING_SERVE_MAX_SYNC_WAIT`) |
468
+ | `not_found` | `check()` or `waitForJob()`: unknown or expired job ID (`Unknown job id` after server restart or expiration of 300 s result delay), or task purged between poll and fetch (`Result already fetched or expired`). Server message follows in parentheses |
469
+ | `conversion_failed` | Server failed to convert the document (or returned partial success without output content). Details provided in `error.details` |
470
+ | `invalid_response` | Unexpected response payload (unparseable JSON, missing task ID, missing output data, or body exceeding readable bounds) |
428
471
 
429
- `conversion_failed` adds `error.details` as `{ errors, failure }`: `errors` lists docling's error items as
430
- `{ message, details }` (component, module, category, page); `failure` is docling's task classification
431
- `{ category, phase, retryable }` for async jobs, or `null`.
472
+ #### Error narrowing in TypeScript
432
473
 
433
- TypeScript narrows `error` on `error.code`:
474
+ TypeScript automatically narrows `error.details` based on `error.code`:
434
475
 
435
476
  ```ts
436
477
  if (!res.ok) {
437
478
  switch (res.error.code) {
438
479
  case 'conversion_failed':
439
480
  console.error('Conversion failed:', res.error.message);
440
- console.error('Server error items:', res.error.details.errors);
481
+ console.error('Server errors:', res.error.details.errors);
441
482
  console.error('Task failure:', res.error.details.failure);
442
483
  break;
443
484
  case 'http':
444
485
  console.error(`HTTP error ${res.error.status}:`, res.error.message);
445
486
  break;
446
487
  case 'invalid_input':
447
- console.error('Input rejected before request:', res.error.message);
488
+ console.error('Validation failed before request:', res.error.message);
448
489
  break;
449
490
  default:
450
491
  console.error(res.error.code, res.error.message);
@@ -452,12 +493,12 @@ if (!res.ok) {
452
493
  }
453
494
  ```
454
495
 
455
- #### Programming errors vs runtime errors
496
+ #### Runtime errors vs programming errors
456
497
 
457
- | Error Category | Trigger | Handling |
498
+ | Category | Causes | Handling |
458
499
  |---|---|---|
459
- | **Runtime failure** | Server unreachable, invalid input file bytes, server HTTP 4xx/5xx, model/conversion failure, abort signal or request timeout | Resolves to `{ ok: false, error: ConvertError }` (never throws) |
460
- | **Programming error** | Invalid client config (malformed URL, invalid types), unsupported or mistyped call options, incompatible options (such as `imageOptions` without `images: 'extract'`) | Throws `TypeError` synchronously on factory; rejects with `TypeError` on async client calls before sending request |
500
+ | **Runtime failure** | Server down, invalid file bytes, HTTP 4xx/5xx, model crash, request timeout, abort signal | Resolves to `{ ok: false, error: ConvertError }` (never throws) |
501
+ | **Programming error** | Malformed base URL, invalid config types, unsupported call options, mutually exclusive options (e.g. `imageOptions` without `images: 'extract'`) | Factory throws `TypeError` synchronously; methods reject with `TypeError` before sending a request |
461
502
 
462
503
  ### Exported types, utilities & constants
463
504
 
@@ -470,45 +511,65 @@ import {
470
511
  isSmallImage,
471
512
  readPngSize,
472
513
  PAGE_IMAGE_SCALE,
473
- type DoclingClient,
474
- type DoclingConfig,
475
- type ClientOptions,
476
- type DoclingOptions,
477
- type MarkdownOptions,
478
- type DocumentOptions,
479
514
  type CallOptions,
480
- type Result,
515
+ type CapabilityLimits,
516
+ type CapabilityPreset,
517
+ type CapabilityStage,
518
+ type ClientOptions,
519
+ type ConversionFailure,
481
520
  type ConvertError,
482
- type ErrorCode,
483
- type ConvertedMarkdown,
484
521
  type ConvertedDocument,
522
+ type ConvertedMarkdown,
523
+ type ConvertInput,
524
+ type DoclingClient,
525
+ type DoclingConfig,
485
526
  type DoclingDocument,
527
+ type DoclingOptions,
528
+ type DoclingSettings,
529
+ type DocumentImageMode,
486
530
  type DocumentJob,
487
- type MarkdownJob,
531
+ type DocumentOptions,
532
+ type DocumentWarning,
533
+ type ErrorCode,
534
+ type ExtractedImage,
535
+ type ImageOptions,
536
+ type ImageSize,
537
+ type ImageWarning,
488
538
  type Job,
489
539
  type JobRef,
490
540
  type JobState,
491
- type DoclingSettings,
492
- type ServerCapabilities,
493
- type ExtractedImage,
541
+ type MarkdownImageMode,
542
+ type MarkdownJob,
543
+ type MarkdownJobOutput,
544
+ type MarkdownOptions,
545
+ type MarkdownWarning,
546
+ type OcrEngine,
494
547
  type PageImage,
495
548
  type PageImageBox,
496
- type ImageSize,
497
- type MarkdownWarning,
498
- type DocumentWarning,
549
+ type PageImageWarning,
550
+ type PdfBackend,
551
+ type PdfInput,
552
+ type Pipeline,
553
+ type PollOptions,
554
+ type Result,
555
+ type ServerCapabilities,
556
+ type ServerDetails,
499
557
  type ServerError,
558
+ type ServerVersions,
559
+ type ServerWarning,
500
560
  type TaskFailure,
561
+ type WithSettings,
501
562
  } from 'docling-serve-client';
502
563
  ```
503
564
 
504
- - `toPageImageBox(document, prov)`: maps a provenance item `{ page_no, bbox }` to `{ page, x, y, width, height }` in pixels with a top-left origin; returns `undefined` if the page is missing or coordinates are invalid.
505
- - `isSmallImage(size)`: returns `true` if pixel dimensions match the `omitSmall` filter (area < 15,000 px, long side < 200 px, or short side < 24 px).
506
- - `readPngSize(bytes)`: parses `{ width, height }` directly from a PNG's IHDR header; returns `undefined` if not a valid PNG.
507
- - `PAGE_IMAGE_SCALE`: constant `2` (144 dpi resolution used by docling page images and bounding box coordinates).
565
+ - `toPageImageBox(document, prov)`: Maps a provenance entry `{ page_no, bbox }` to `{ page, x, y, width, height }` in top-left pixel coordinates. Returns `undefined` if page dimensions or coordinates are missing or invalid.
566
+ - `isSmallImage(size)`: Returns `true` if pixel dimensions match the `omitSmall` filter (area < 15,000 px, long side < 200 px, or short side < 24 px).
567
+ - `readPngSize(bytes)`: Extracts `{ width, height }` directly from a PNG's IHDR chunk. Returns `undefined` for invalid or non-PNG data.
568
+ - `PAGE_IMAGE_SCALE`: Constant `2` (144 DPI resolution used by docling page images and coordinate scaling).
508
569
 
509
570
  ## Running docling-serve
510
571
 
511
- The client connects to a running [docling-serve](https://github.com/docling-project/docling-serve) server:
572
+ The client connects to a running [docling-serve](https://github.com/docling-project/docling-serve) instance:
512
573
 
513
574
  ```bash
514
575
  # Install docling-serve (default / CPU)