docling-serve-client 0.4.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,282 +6,584 @@
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 an invalid config, and a call with an
19
- invalid option throws a `TypeError` (the promise rejects) before it sends anything.
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.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ bun add docling-serve-client
22
+ # or
23
+ npm install docling-serve-client
24
+ ```
22
25
 
23
26
  ## Usage
24
27
 
25
28
  ```ts
29
+ import { readFile } from 'node:fs/promises';
26
30
  import { createDoclingClient } from 'docling-serve-client';
27
31
 
28
32
  const docling = createDoclingClient({ baseUrl: 'http://127.0.0.1:5001' });
29
33
 
30
- // Input is always a Uint8Array (Node Buffer is one): a PDF, or a PNG, JPEG, TIFF or WebP image
31
- const pdf = await readFile('paper.pdf'); // node:fs/promises
32
- // const pdf = await Bun.file('paper.pdf').bytes(); // Bun
33
- // const pdf = new Uint8Array(await (await fetch(pdfUrl)).arrayBuffer()); // any download
34
+ // Input is a Uint8Array (Node Buffer is an instance): PDF, PNG, JPEG, TIFF, or WebP
35
+ const pdf = await readFile('paper.pdf');
36
+ // const pdf = await Bun.file('paper.pdf').bytes(); // Bun
34
37
 
38
+ // 1. Convert to Markdown
35
39
  const md = await docling.convertMarkdown(pdf);
36
- 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
+ }
37
43
 
44
+ // 2. Convert to DoclingDocument JSON AST
38
45
  const doc = await docling.convertDocument(pdf);
39
46
  if (doc.ok) {
40
47
  for (const item of doc.data.document.texts) {
41
- 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
+ }
42
51
  }
43
52
  } else {
44
53
  console.error(doc.error.code, doc.error.message);
45
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
+ }
46
61
  ```
47
62
 
48
- ### Async
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 |
49
79
 
50
- `submitMarkdown()` and `submitDocument()` return a plain JSON job object. Store all of it and pass it back to
51
- `check()`. A Markdown job carries its image handling, so `check()` works the same from another process or after a
52
- restart. `check()` returns the result type that matches the job's `kind`.
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
98
+ ```
99
+
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.
101
+
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.
53
105
 
54
106
  ```ts
107
+ import type { DocumentJob } from 'docling-serve-client';
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:
55
116
  const job = await docling.submitDocument(pdf);
56
- if (!job.ok) return handle(job.error);
57
- await save(JSON.stringify(job.data)); // { kind: 'document', id }
117
+ if (!job.ok) {
118
+ throw new Error(`${job.error.code}: ${job.error.message}`);
119
+ }
120
+ await save(JSON.stringify(job.data)); // { kind: 'document', id, settings }
58
121
 
59
- // later, as often as you like:
60
- const state = await docling.check(JSON.parse(await load()));
122
+ // 3. Later (in this or another process): poll until complete
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):
132
+ const state = await docling.check(storedJob);
61
133
  if (state.ok) {
62
134
  switch (state.data.status) {
63
- case 'queued': /* state.data.position */ break;
64
- case 'running': break;
65
- case 'done': use(state.data.document); break;
135
+ case 'queued': console.log('Queue position:', state.data.position); break;
136
+ case 'running': console.log('Processing...'); break;
137
+ case 'done': console.log(state.data.document); break;
66
138
  }
139
+ } else {
140
+ // state.error.code: 'not_found' (expired or unknown ID), 'http', 'conversion_failed', etc.
141
+ console.error(state.error.code, state.error.message);
67
142
  }
68
143
  ```
69
144
 
70
- docling-serve deletes a result `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after its first fetch
71
- (`DOCLING_SERVE_SINGLE_USE_RESULTS=true`, the default). A `check()` whose result download failed (network error,
72
- `timeoutMs`, abort) can be repeated within that time; later `check()` returns `not_found` (`Result already fetched or
73
- expired`), so persist the `done` data. Start docling-serve with `DOCLING_SERVE_SINGLE_USE_RESULTS=false` to keep results
74
- until they expire. These are server settings; `convert*()` is not affected.
145
+ For Markdown jobs (`submitMarkdown`), `check()` returns `JobState<ConvertedMarkdown>` (`done` provides `markdown`, `images`, `warnings`, `versions`) and `waitForJob()` returns `ConvertedMarkdown`.
146
+
147
+ Job helper details:
75
148
 
76
- ### Options
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()`.
77
156
 
78
- Set docling options on the factory and override them per call, key by key; `ocrLang`, `pageRange` and
79
- `documentTimeout` are factory-only and reject per call. Nothing is silently ignored or converted:
80
- an unknown option or an invalid value throws a `TypeError` at the start of the call, before any request
81
- (`Option "x" is not supported`, `Invalid value for option "x"`; nested keys are named like `imageOptions.prefix`).
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.
158
+
159
+ ### Client configuration and options
160
+
161
+ Create a client instance with `createDoclingClient(config)`:
82
162
 
83
163
  ```ts
164
+ import { createDoclingClient } from 'docling-serve-client';
165
+
84
166
  const docling = createDoclingClient({
85
- baseUrl: 'http://127.0.0.1:5001',
86
- apiKey: '…', // only if docling-serve requires X-Api-Key
87
- timeoutMs: 120_000, // optional limit per request; on expiry the call resolves with `aborted`
88
- options: { ocrLang: ['de', 'en'] }, // docling defaults for every call
89
- 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
90
173
  });
91
-
92
- await docling.convertDocument(pdf, { ocr: false, images: 'placeholder', signal: AbortSignal.timeout(60_000) });
93
174
  ```
94
175
 
95
- docling options (factory and per call):
176
+ #### Factory configuration (`DoclingConfig`)
96
177
 
97
- | option | default | effect |
98
- |---|---|---|
99
- | `headingHierarchy` | `true` | heading levels by bookmarks, numbering and font; `false` = every heading level 1 |
100
- | `formulaEnrichment` | `true` | formulas as LaTeX (extra model; runs only on formulas); without it formula `text` is empty and the md shows `<!-- formula-not-decoded -->` |
101
- | `codeEnrichment` | `false` | code listings via extra model (slower) |
102
- | `chartExtraction` | `false` | charts as data (vision model, slower) |
103
- | `pipeline` | `'standard'` | `'vlm'` runs a vision-language model per page (slow) |
104
- | `pdfBackend` | `'docling_parse'` | text extractor; `'pypdfium2'` reads ligatures and word spaces better but misses page headers and footers |
105
- | `ocr` | `true` | OCR bitmap content of PDFs (scans, pictures); image input is always OCRed |
106
- | `forceOcr` | `false` | OCR whole PDF pages and drop their text layer, for PDFs with broken text; ignored for image input |
107
-
108
- Factory only (`options` of `createDoclingClient`; per call they throw `Option "x" is not supported`). Use a second
109
- client for other values:
110
-
111
- | option | default | effect |
112
- |---|---|---|
113
- | `ocrLang` | engine default | BCP-47 hints in order of preference, e.g. `['de', 'en-US']`; `[]` = engine default |
114
- | `pageRange` | all pages | `[from, to]`, 1-based, inclusive |
115
- | `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 |
116
186
 
117
- Per call only: `signal` (cancels the request), `images`, for Markdown `imageOptions`, for documents `pageImages`.
118
- `timeoutMs` is a factory setting: it limits every request except `GET /version` and is combined with `signal`.
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.
119
188
 
120
- Always on, not configurable: table structure with the accurate TableFormer model (`do_table_structure`,
121
- `table_mode: 'accurate'`), pictures and page images at 144 dpi (`images_scale=2`, exported as `PAGE_IMAGE_SCALE`)
122
- and, for Markdown, compact tables (`md_compact_tables`). The image scale does not affect OCR: docling OCRs at its own
123
- fixed 216 dpi.
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`).
192
+
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.
208
+
209
+ | Option | Type | Default | Description |
210
+ |---|---|---|---|
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.
124
235
 
125
236
  ### Input formats
126
237
 
127
- 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:
128
239
 
129
- | input | detected by | sent as |
240
+ | Format | Detection Signature | Sent As |
130
241
  |---|---|---|
131
242
  | PDF | `%PDF-` within the first 1024 bytes | `from_formats=pdf`, `document.pdf` |
132
- | 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` |
133
247
 
134
- An image is converted as one page: docling runs layout detection and OCR on it, and `ocrLang` applies. OCR is always
135
- on for images and never full-page: `ocr: false` would return no text, and `forceOcr: true` merges words (`stepsand`),
136
- 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
137
- comes back if the server has a working OCR engine. PDF-only options (`pdfBackend`, `pageRange`) have no effect on
138
- 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.
139
249
 
140
250
  ### Images in the DoclingDocument
141
251
 
142
- | `images` | effect |
252
+ | `images` | Effect |
143
253
  |---|---|
144
- | `'embedded'` (default) | `pictures[].image.uri` is a `data:image/png;base64,…` URI; pictures keep their position, captions and size |
145
- | `'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 |
146
256
 
147
- The base64 data stays inside the document. Pull it out when you need files, e.g. when rendering Markdown from the
148
- 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`.
149
258
 
150
259
  ### Page images
151
260
 
152
- `pageImages: true` (documents only, default `false`) asks docling to render every page (`include_page_images`). The
153
- client moves them out of the document: `document.pages[n].image` is always `null`, so the JSON stays small, and the
154
- 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`:
155
262
 
156
263
  ```ts
264
+ import { writeFile } from 'node:fs/promises';
265
+
157
266
  const res = await docling.convertDocument(pdf, { pageImages: true, images: 'placeholder' });
158
267
  if (res.ok) {
159
- 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
160
269
  for (const image of res.data.pageImages) {
161
- 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 }
162
271
  }
163
272
  }
164
273
  ```
165
274
 
166
- - `page` is the 1-based PDF page (absolute, also with `pageRange`); `dpi` is always 144.
167
- - `width` and `height` are pixels from the PNG header, else docling's `image.size`. Pixel size = page size in points
168
- × 2; an A4 page is about 1190 × 1684 px and 0.4 to 0.6 MB as PNG.
169
- - Page images are independent of `images`: `images: 'placeholder'` drops picture data and keeps page images.
170
- - Async: `submitDocument(pdf, { pageImages: true })` stores nothing extra in the job; `check()` returns the images.
171
- - A page image that is not a base64 data URI or has invalid or empty data is missing from `pageImages`, stays in
172
- `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`)
173
282
 
174
- The document keeps docling's bboxes as sent: PDF points, mostly with a bottom-left origin. `toPageImageBox` maps one
175
- onto its page image as `{ page, x, y, width, height }` in pixels with a top-left origin (unrounded), ready to store
176
- 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:
177
284
 
178
285
  ```ts
286
+ import { toPageImageBox } from 'docling-serve-client';
287
+
179
288
  for (const item of res.data.document.texts) {
180
289
  for (const prov of item.prov) {
181
- const box = toPageImageBox(res.data.document, prov); // undefined if the page is missing
290
+ const box = toPageImageBox(res.data.document, prov); // { page, x, y, width, height }
291
+ }
292
+ }
293
+
294
+ // Table cells provide their own bbox (with TOPLEFT origin) and belong to their table's page:
295
+ for (const table of res.data.document.tables) {
296
+ const pageNo = table.prov[0]?.page_no ?? 1;
297
+ for (const row of table.data.grid) {
298
+ for (const cell of row) {
299
+ if (cell.bbox) {
300
+ const box = toPageImageBox(res.data.document, { page_no: pageNo, bbox: cell.bbox });
301
+ }
302
+ }
182
303
  }
183
304
  }
184
- // table cells carry their own bbox (top-left origin) and the page of their table:
185
- const box = toPageImageBox(document, { page_no: table.prov[0].page_no, bbox: cell.bbox });
186
305
  ```
187
306
 
188
- 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.
308
+
309
+ ### DoclingDocument structure
310
+
311
+ `convertDocument()` returns the full JSON AST (`doc.data.document`) typed as `DoclingDocument`:
312
+
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.
322
+
323
+ #### Navigating the AST in reading order
189
324
 
190
- ### Images in the Markdown
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:
191
326
 
192
- | `images` | effect |
327
+ ```ts
328
+ import type { DoclingDocument } from 'docling-serve-client';
329
+
330
+ // Resolves a JSON pointer reference (e.g. '#/texts/0' or '#/body'):
331
+ function resolveRef(doc: DoclingDocument, ref: string) {
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])];
335
+ }
336
+
337
+ // Walks body items in logical reading order:
338
+ function* walkReadingOrder(
339
+ doc: DoclingDocument,
340
+ node: { children: { $ref: string }[] } = doc.body,
341
+ ): Generator<any> {
342
+ for (const child of node.children) {
343
+ const item = resolveRef(doc, child.$ref);
344
+ if (!item) continue;
345
+ if ('children' in item && Array.isArray(item.children) && item.children.length > 0) {
346
+ yield* walkReadingOrder(doc, item);
347
+ } else {
348
+ yield item;
349
+ }
350
+ }
351
+ }
352
+ ```
353
+
354
+ ### Images in Markdown
355
+
356
+ | `images` | Effect |
193
357
  |---|---|
194
- | `'placeholder'` (default) | `<!-- image -->` marks a picture, as docling writes it |
195
- | `'none'` | placeholders removed |
196
- | `'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>` |
197
361
 
198
- 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:
199
363
 
200
364
  ```ts
365
+ import { writeFile } from 'node:fs/promises';
366
+
201
367
  const res = await docling.convertMarkdown(pdf, {
202
368
  images: 'extract',
203
- imageOptions: { prefix: `images/${docId}-` }, // omitSmall: drop bullets, icons and small logos, default true
369
+ imageOptions: { prefix: `images/${docId}-` }, // omitSmall defaults to true
204
370
  });
205
371
  if (res.ok) {
206
- 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)
207
373
  for (const image of res.data.images) {
208
- 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 }
209
375
  }
210
376
  }
211
377
  ```
212
378
 
213
- - `n` starts at 1 and counts kept images only. `<ext>` comes from the MIME type: `png`, `jpg`, `gif`, `webp`, `svg`,
214
- `tiff`, `bmp`, otherwise `bin`.
215
- - `omitSmall` (default `true`) removes small pictures from the Markdown and does not return them. Small means: area
216
- below 15 000 px, longer side below 200 px, or shorter side below 24 px (`isSmallImage`). Only PNGs have a known
217
- size (`readPngSize`); other formats are always kept and report `width` and `height` as `0`.
218
- - A picture with invalid or empty base64 data is removed and reported as an `image_not_decoded` warning
219
- (`details.index`: 1-based position among all embedded pictures); the conversion still succeeds.
220
- - A picture docling sent without image data keeps its `<!-- image -->` placeholder, as in `'placeholder'` mode.
221
- - 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.
222
384
 
223
385
  ### Warnings
224
386
 
225
- `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:
226
388
 
227
- | code | `details` | meaning |
389
+ | Code | `details` | Description |
228
390
  |---|---|---|
229
- | `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` |
230
- | `image_not_decoded` | `{ index }` | Markdown with `images: 'extract'` only, see above |
231
- | `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 |
232
394
 
233
- Pages docling gave up on (e.g. `documentTimeout` exceeded) have no content. The only sign is a `server` warning per
234
- failed page.
395
+ Pages abandoned by the server (e.g. due to `documentTimeout`) contain no extracted content and trigger a `server` warning.
235
396
 
236
397
  ### Server versions
237
398
 
238
- `data.versions` holds the versions the server reports on `GET /version`, unchanged, e.g.
239
- `{ 'docling-serve': '1.36.0', docling: '2.135.0', 'docling-core': '2.101.1', … }`, or `null` if it reports none.
240
- Store it with each converted document: the same PDF can convert differently under another docling version, and
241
- `document.version` is the DoclingDocument schema version. Errors from a request the server answered (`http`,
242
- `not_found`, `conversion_failed`, `invalid_response`) carry the same `error.versions`. Each client asks for the
243
- versions once and keeps them; the request has its own 2 s limit, and when it fails `versions` is `null` and the next
244
- 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.
402
+
403
+ ### Conversion settings
404
+
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:
408
+
409
+ ```ts
410
+ const res = await docling.convertMarkdown(pdf, { ocrEngine: 'rapidocr' });
411
+ if (res.ok) {
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')
415
+ }
416
+ ```
417
+
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()`.
245
423
 
246
- ### Broken umlauts / special characters
424
+ ### Server capabilities
247
425
 
248
- If the output shows `fŸhrt`, `Nu§baum`, `…sterreich`, the PDF's text layer uses an encoding docling-parse misreads
249
- (seen with `/MacRomanEncoding` PDFs from Mac Word). Re-convert that document with `{ forceOcr: true }` (with `ocrLang: ['de']` on the factory).
250
- It is slower, so use it per document, not by default.
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:
427
+
428
+ ```ts
429
+ const caps = await docling.capabilities();
430
+ if (caps.ok) {
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
435
+ }
436
+ ```
437
+
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`.
442
+
443
+ ### Corrupt text encodings & special characters
444
+
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.
251
446
 
252
447
  ### Errors
253
448
 
254
- `error.code` is one of:
449
+ All client methods return a discriminated `Result<T>` union:
450
+ - `{ ok: true, data: T }` on success
451
+ - `{ ok: false, error: ConvertError }` on runtime failure
255
452
 
256
- | code | meaning |
453
+ #### Error structure
454
+
455
+ Every `ConvertError` contains:
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`)
461
+
462
+ | Code | Cause |
257
463
  |---|---|
258
- | `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 |
259
- | `network` | server unreachable (the message includes the cause, e.g. `ECONNREFUSED`) |
260
- | `aborted` | your `signal` fired (incl. `AbortSignal.timeout()`) or `timeoutMs` expired |
261
- | `http` | non-2xx response (`error.status` set), e.g. auth, sync wait exceeded |
262
- | `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 |
263
- | `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 |
264
- | `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) |
471
+
472
+ #### Error narrowing in TypeScript
473
+
474
+ TypeScript automatically narrows `error.details` based on `error.code`:
475
+
476
+ ```ts
477
+ if (!res.ok) {
478
+ switch (res.error.code) {
479
+ case 'conversion_failed':
480
+ console.error('Conversion failed:', res.error.message);
481
+ console.error('Server errors:', res.error.details.errors);
482
+ console.error('Task failure:', res.error.details.failure);
483
+ break;
484
+ case 'http':
485
+ console.error(`HTTP error ${res.error.status}:`, res.error.message);
486
+ break;
487
+ case 'invalid_input':
488
+ console.error('Validation failed before request:', res.error.message);
489
+ break;
490
+ default:
491
+ console.error(res.error.code, res.error.message);
492
+ }
493
+ }
494
+ ```
265
495
 
266
- `conversion_failed` adds `error.details` as `{ errors, failure }`: `errors` lists docling's error items as
267
- `{ message, details }` (component, module, category, page); `failure` is docling's task classification
268
- `{ category, phase, retryable }` for async jobs, or `null`.
496
+ #### Runtime errors vs programming errors
269
497
 
270
- ## Development
498
+ | Category | Causes | Handling |
499
+ |---|---|---|
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 |
271
502
 
272
- ### Running docling-serve (CUDA)
503
+ ### Exported types, utilities & constants
273
504
 
274
- ```bash
275
- # Install or reinstall docling-serve with CUDA 12.6
276
- uv tool install --reinstall --torch-backend cu126 docling-serve
505
+ In addition to `createDoclingClient`, the package exports helper functions, constants, and TypeScript types:
277
506
 
278
- # Start the server on port 5001
279
- docling-serve run --port 5001
507
+ ```ts
508
+ import {
509
+ createDoclingClient,
510
+ toPageImageBox,
511
+ isSmallImage,
512
+ readPngSize,
513
+ PAGE_IMAGE_SCALE,
514
+ type CallOptions,
515
+ type CapabilityLimits,
516
+ type CapabilityPreset,
517
+ type CapabilityStage,
518
+ type ClientOptions,
519
+ type ConversionFailure,
520
+ type ConvertError,
521
+ type ConvertedDocument,
522
+ type ConvertedMarkdown,
523
+ type ConvertInput,
524
+ type DoclingClient,
525
+ type DoclingConfig,
526
+ type DoclingDocument,
527
+ type DoclingOptions,
528
+ type DoclingSettings,
529
+ type DocumentImageMode,
530
+ type DocumentJob,
531
+ type DocumentOptions,
532
+ type DocumentWarning,
533
+ type ErrorCode,
534
+ type ExtractedImage,
535
+ type ImageOptions,
536
+ type ImageSize,
537
+ type ImageWarning,
538
+ type Job,
539
+ type JobRef,
540
+ type JobState,
541
+ type MarkdownImageMode,
542
+ type MarkdownJob,
543
+ type MarkdownJobOutput,
544
+ type MarkdownOptions,
545
+ type MarkdownWarning,
546
+ type OcrEngine,
547
+ type PageImage,
548
+ type PageImageBox,
549
+ type PageImageWarning,
550
+ type PdfBackend,
551
+ type PdfInput,
552
+ type Pipeline,
553
+ type PollOptions,
554
+ type Result,
555
+ type ServerCapabilities,
556
+ type ServerDetails,
557
+ type ServerError,
558
+ type ServerVersions,
559
+ type ServerWarning,
560
+ type TaskFailure,
561
+ type WithSettings,
562
+ } from 'docling-serve-client';
280
563
  ```
281
564
 
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).
569
+
570
+ ## Running docling-serve
571
+
572
+ The client connects to a running [docling-serve](https://github.com/docling-project/docling-serve) instance:
573
+
282
574
  ```bash
283
- bun install
284
- bun runtest check -p tsconfig.test.json # lint fixes, typecheck, lint, unit tests against a mock docling server
285
- bun run test:e2e # end-to-end tests against docling-serve on :5001; fail if it is down
286
- bun run check # lint, tests, build, smoke tests on the built package, package check
575
+ # Install docling-serve (default / CPU)
576
+ uv tool install docling-serve
577
+
578
+ # Or with CUDA (check your driver with nvidia-smi; use matching PyTorch tag, e.g. cu124, cu126)
579
+ uv tool install --torch-backend <cuda-tag> docling-serve
580
+
581
+ # Or via Docker (CPU)
582
+ docker run -p 5001:5001 quay.io/ds4sd/docling-serve
583
+
584
+ # Or via Docker (NVIDIA GPU / CUDA)
585
+ docker run --gpus all -p 5001:5001 quay.io/ds4sd/docling-serve
586
+
587
+ # Start the local server (listens on http://127.0.0.1:5001 by default)
588
+ docling-serve run
287
589
  ```