@proveanything/smartlinks 2.0.66 → 2.0.67

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/dist/ai-tools.js CHANGED
@@ -62,7 +62,7 @@ export const BUILTIN_AI_TOOLS = [
62
62
  { name: 'image.transform', group: 'media', capabilities: ['media:image'],
63
63
  description: 'Transform an image: resize/crop/rotate/flip/grayscale/tint/format-convert/compress → hosted URL.' },
64
64
  { name: 'pdf.create', group: 'media', capabilities: ['media:pdf'],
65
- description: 'Render HTML to a PDF and return a hosted URL.' },
65
+ description: 'Render HTML (or one fragment per page) to a PDF, 1:1: exact page sizes (width/height/unit) for labels, many pages per call, text and SVG kept vector, no scripts. Returns a public URL, an expiring private link, or base64; identical inputs reuse the cached render.' },
66
66
  { name: 'pdf.fill', group: 'media', capabilities: ['media:pdf'],
67
67
  description: 'Fill an AcroForm PDF\'s fields from a { field: value } map → hosted URL.' },
68
68
  { name: 'pdf.merge', group: 'media', capabilities: ['media:pdf'],
@@ -6,9 +6,9 @@ import type { TagIndexEntry, CreateTagRequest, CreateTagResponse, BatchCreateTag
6
6
  * etc.) and products, proofs, or any polymorphic object in the platform.
7
7
  *
8
8
  * ### Two-tier architecture
9
- * - **Per-org shard** (`tags` table) — full tag data; used for all collection-scoped
9
+ * - **Per-org shard** (`tags` table) — full tag data; used for all collection-scoped
10
10
  * queries.
11
- * - **Shared shard** (`tag_index` table) — `tagId → collectionId` routing only; used
11
+ * - **Shared shard** (`tag_index` table) — `tagId → collectionId` routing only; used
12
12
  * when the collection is not yet known.
13
13
  *
14
14
  * 99% of callers will know the collection and should use collection-scoped endpoints
@@ -49,7 +49,7 @@ export declare namespace tags {
49
49
  *
50
50
  * Tags with `productId` but no `proofId` automatically get serial numbers.
51
51
  * Serial number generation is grouped by `(productId, variantId, batchId)` for
52
- * efficiency. Partial success is possible — check `results` for individual outcomes.
52
+ * efficiency. Partial success is possible — check `results` for individual outcomes.
53
53
  *
54
54
  * @param collectionId - Collection context
55
55
  * @param data - Batch payload; `force` applies to all tags in the batch
@@ -78,7 +78,7 @@ export declare namespace tags {
78
78
  /**
79
79
  * Update a tag (admin).
80
80
  *
81
- * Partial update — only provided fields are changed. `metadata` is
81
+ * Partial update — only provided fields are changed. `metadata` is
82
82
  * deep-merged with the existing value. Pass `refType: null, refId: null`
83
83
  * to clear the polymorphic ref.
84
84
  *
@@ -130,7 +130,7 @@ export declare namespace tags {
130
130
  */
131
131
  function list(collectionId: string, params?: ListTagsRequest): Promise<ListTagsResponse>;
132
132
  /**
133
- * Reverse lookup — find all tags linked to a given object (admin).
133
+ * Reverse lookup — find all tags linked to a given object (admin).
134
134
  *
135
135
  * Uses a compound index on `(orgId, refType, refId)` on the per-org shard.
136
136
  * No embed support on the admin side.
@@ -149,7 +149,7 @@ export declare namespace tags {
149
149
  */
150
150
  function byRef(collectionId: string, params: ReverseTagLookupParams): Promise<ReverseTagLookupResponse>;
151
151
  /**
152
- * Global tag resolve — returns `{ tagId, collectionId }` only.
152
+ * Global tag resolve — returns `{ tagId, collectionId }` only.
153
153
  *
154
154
  * Use this **only** when you have a raw `tagId` and do not yet know which
155
155
  * collection it belongs to. Queries the shared `tag_index` shard.
@@ -159,7 +159,7 @@ export declare namespace tags {
159
159
  * > Use the collection-scoped `publicByRef` instead.
160
160
  *
161
161
  * @param tagId - Physical tag identifier
162
- * @returns `{ tagId, collectionId }` — routing info only, no full tag data
162
+ * @returns `{ tagId, collectionId }` — routing info only, no full tag data
163
163
  *
164
164
  * @example
165
165
  * ```typescript
@@ -179,7 +179,7 @@ export declare namespace tags {
179
179
  * `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref`
180
180
  *
181
181
  * Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'`
182
- * (`'collection'` is not supported — the collection is already known from the URL).
182
+ * (`'collection'` is not supported — the collection is already known from the URL).
183
183
  *
184
184
  * @param collectionId - Collection context
185
185
  * @param tagId - Physical tag identifier
package/dist/api/tags.js CHANGED
@@ -7,9 +7,9 @@ import { request, post, put, del } from "../http.js";
7
7
  * etc.) and products, proofs, or any polymorphic object in the platform.
8
8
  *
9
9
  * ### Two-tier architecture
10
- * - **Per-org shard** (`tags` table) — full tag data; used for all collection-scoped
10
+ * - **Per-org shard** (`tags` table) — full tag data; used for all collection-scoped
11
11
  * queries.
12
- * - **Shared shard** (`tag_index` table) — `tagId → collectionId` routing only; used
12
+ * - **Shared shard** (`tag_index` table) — `tagId → collectionId` routing only; used
13
13
  * when the collection is not yet known.
14
14
  *
15
15
  * 99% of callers will know the collection and should use collection-scoped endpoints
@@ -58,7 +58,7 @@ export var tags;
58
58
  *
59
59
  * Tags with `productId` but no `proofId` automatically get serial numbers.
60
60
  * Serial number generation is grouped by `(productId, variantId, batchId)` for
61
- * efficiency. Partial success is possible — check `results` for individual outcomes.
61
+ * efficiency. Partial success is possible — check `results` for individual outcomes.
62
62
  *
63
63
  * @param collectionId - Collection context
64
64
  * @param data - Batch payload; `force` applies to all tags in the batch
@@ -95,7 +95,7 @@ export var tags;
95
95
  /**
96
96
  * Update a tag (admin).
97
97
  *
98
- * Partial update — only provided fields are changed. `metadata` is
98
+ * Partial update — only provided fields are changed. `metadata` is
99
99
  * deep-merged with the existing value. Pass `refType: null, refId: null`
100
100
  * to clear the polymorphic ref.
101
101
  *
@@ -175,7 +175,7 @@ export var tags;
175
175
  }
176
176
  tags.list = list;
177
177
  /**
178
- * Reverse lookup — find all tags linked to a given object (admin).
178
+ * Reverse lookup — find all tags linked to a given object (admin).
179
179
  *
180
180
  * Uses a compound index on `(orgId, refType, refId)` on the per-org shard.
181
181
  * No embed support on the admin side.
@@ -201,10 +201,10 @@ export var tags;
201
201
  }
202
202
  tags.byRef = byRef;
203
203
  // ============================================================================
204
- // Public Endpoints — global resolve (collection unknown)
204
+ // Public Endpoints — global resolve (collection unknown)
205
205
  // ============================================================================
206
206
  /**
207
- * Global tag resolve — returns `{ tagId, collectionId }` only.
207
+ * Global tag resolve — returns `{ tagId, collectionId }` only.
208
208
  *
209
209
  * Use this **only** when you have a raw `tagId` and do not yet know which
210
210
  * collection it belongs to. Queries the shared `tag_index` shard.
@@ -214,7 +214,7 @@ export var tags;
214
214
  * > Use the collection-scoped `publicByRef` instead.
215
215
  *
216
216
  * @param tagId - Physical tag identifier
217
- * @returns `{ tagId, collectionId }` — routing info only, no full tag data
217
+ * @returns `{ tagId, collectionId }` — routing info only, no full tag data
218
218
  *
219
219
  * @example
220
220
  * ```typescript
@@ -233,7 +233,7 @@ export var tags;
233
233
  }
234
234
  tags.resolveTag = resolveTag;
235
235
  // ============================================================================
236
- // Public Endpoints — collection-scoped
236
+ // Public Endpoints — collection-scoped
237
237
  // ============================================================================
238
238
  /**
239
239
  * Single tag lookup with optional embedded data (public).
@@ -241,7 +241,7 @@ export var tags;
241
241
  * `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref`
242
242
  *
243
243
  * Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'`
244
- * (`'collection'` is not supported — the collection is already known from the URL).
244
+ * (`'collection'` is not supported — the collection is already known from the URL).
245
245
  *
246
246
  * @param collectionId - Collection context
247
247
  * @param tagId - Physical tag identifier
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.66 | Generated: 2026-10-14T17:03:31.949Z
3
+ Version: 2.0.67 | Generated: 2026-10-15T17:52:10.614Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1600,10 +1600,35 @@ interface PdfClip {
1600
1600
  **PdfCreateArgs** (interface)
1601
1601
  ```typescript
1602
1602
  interface PdfCreateArgs {
1603
- html: string
1603
+ html?: string
1604
+ pages?: string[]
1605
+ head?: string
1606
+ width?: number
1607
+ height?: number
1608
+ unit?: 'mm' | 'cm' | 'in' | 'pt' | 'px'
1604
1609
  format?: string
1605
1610
  landscape?: boolean
1606
1611
  margin?: string
1612
+ return?: 'url' | 'signedUrl' | 'base64'
1613
+ expiresIn?: number
1614
+ filename?: string
1615
+ cache?: boolean
1616
+ }
1617
+ ```
1618
+
1619
+ **PdfCreateResult** (interface)
1620
+ ```typescript
1621
+ interface PdfCreateResult {
1622
+ url?: string
1623
+ hostedUrl?: string
1624
+ base64?: string
1625
+ expiresAt?: string
1626
+ contentType: 'application/pdf'
1627
+ size: number
1628
+ pageCount: number
1629
+ cached: boolean
1630
+ renderer?: string
1631
+ warnings?: string[]
1607
1632
  }
1608
1633
  ```
1609
1634
 
@@ -2786,6 +2811,7 @@ interface ServerFunctionSl {
2786
2811
  forCaller(): Promise<{ contactId: string } | null>;
2787
2812
  };
2788
2813
  interactions: { record(event: ServerFunctionInteractionEvent): Promise<{ eventId: string }> };
2814
+ tools: { run<T = any>(name: string, args?: Record<string, any>): Promise<T> };
2789
2815
  products: any;
2790
2816
  attestations: any;
2791
2817
  appData: AppDataHandle & { global: AppDataHandle; collection: AppDataHandle };
@@ -12815,7 +12841,7 @@ Create a single tag mapping (admin). If `productId` is set without `proofId`, a
12815
12841
 
12816
12842
  **createBatch**(collectionId: string,
12817
12843
  data: BatchCreateTagRequest) → `Promise<BatchCreateResult>`
12818
- Batch-create tags (admin). Tags with `productId` but no `proofId` automatically get serial numbers. Serial number generation is grouped by `(productId, variantId, batchId)` for efficiency. Partial success is possible — check `results` for individual outcomes. ```typescript const result = await tags.createBatch('coll_123', { tags: [ { tagId: 'NFC-001', productId: 'prod_456', batchId: 'batch_2026_01' }, { tagId: 'NFC-002', productId: 'prod_456', batchId: 'batch_2026_01' }, ], }) console.log(`Created: ${result.summary.created}, Conflicts: ${result.summary.conflicts}`) ```
12844
+ Batch-create tags (admin). Tags with `productId` but no `proofId` automatically get serial numbers. Serial number generation is grouped by `(productId, variantId, batchId)` for efficiency. Partial success is possible — check `results` for individual outcomes. ```typescript const result = await tags.createBatch('coll_123', { tags: [ { tagId: 'NFC-001', productId: 'prod_456', batchId: 'batch_2026_01' }, { tagId: 'NFC-002', productId: 'prod_456', batchId: 'batch_2026_01' }, ], }) console.log(`Created: ${result.summary.created}, Conflicts: ${result.summary.conflicts}`) ```
12819
12845
 
12820
12846
  **get**(collectionId: string,
12821
12847
  tagId: string) → `Promise<GetTagResponse>`
@@ -12824,7 +12850,7 @@ Get a single tag by `tagId` (admin).
12824
12850
  **update**(collectionId: string,
12825
12851
  tagId: string,
12826
12852
  data: UpdateTagRequest) → `Promise<UpdateTagResponse>`
12827
- Update a tag (admin). Partial update — only provided fields are changed. `metadata` is deep-merged with the existing value. Pass `refType: null, refId: null` to clear the polymorphic ref. ```typescript const updated = await tags.update('coll_123', 'NFC-001', { variantId: 'var_premium', metadata: { notes: 'Updated to premium variant' }, }) // Clear polymorphic ref await tags.update('coll_123', 'NFC-001', { refType: null, refId: null }) ```
12853
+ Update a tag (admin). Partial update — only provided fields are changed. `metadata` is deep-merged with the existing value. Pass `refType: null, refId: null` to clear the polymorphic ref. ```typescript const updated = await tags.update('coll_123', 'NFC-001', { variantId: 'var_premium', metadata: { notes: 'Updated to premium variant' }, }) // Clear polymorphic ref await tags.update('coll_123', 'NFC-001', { refType: null, refId: null }) ```
12828
12854
 
12829
12855
  **remove**(collectionId: string,
12830
12856
  tagId: string) → `Promise<DeleteTagResponse>`
@@ -12836,15 +12862,15 @@ List tags with optional filters and pagination (admin). ```typescript // All tag
12836
12862
 
12837
12863
  **byRef**(collectionId: string,
12838
12864
  params: ReverseTagLookupParams) → `Promise<ReverseTagLookupResponse>`
12839
- Reverse lookup — find all tags linked to a given object (admin). Uses a compound index on `(orgId, refType, refId)` on the per-org shard. No embed support on the admin side. ```typescript const { tags: linked } = await tags.byRef('coll_123', { refType: 'container', refId: 'container-uuid', }) ```
12865
+ Reverse lookup — find all tags linked to a given object (admin). Uses a compound index on `(orgId, refType, refId)` on the per-org shard. No embed support on the admin side. ```typescript const { tags: linked } = await tags.byRef('coll_123', { refType: 'container', refId: 'container-uuid', }) ```
12840
12866
 
12841
12867
  **resolveTag**(tagId: string) → `Promise<TagIndexEntry>`
12842
- Global tag resolve — returns `{ tagId, collectionId }` only. Use this **only** when you have a raw `tagId` and do not yet know which collection it belongs to. Queries the shared `tag_index` shard. Once `collectionId` is resolved, call `publicGetByCollection` for full data. > The global `/public/tags/by-ref` endpoint has been removed. > Use the collection-scoped `publicByRef` instead. ```typescript // Step 1: resolve collection const { collectionId } = await tags.resolveTag('NFC-001') // Step 2: full lookup with embedded data const { tag, embedded } = await tags.publicGetByCollection( collectionId, 'NFC-001', 'product,proof' ) ```
12868
+ Global tag resolve — returns `{ tagId, collectionId }` only. Use this **only** when you have a raw `tagId` and do not yet know which collection it belongs to. Queries the shared `tag_index` shard. Once `collectionId` is resolved, call `publicGetByCollection` for full data. > The global `/public/tags/by-ref` endpoint has been removed. > Use the collection-scoped `publicByRef` instead. ```typescript // Step 1: resolve collection const { collectionId } = await tags.resolveTag('NFC-001') // Step 2: full lookup with embedded data const { tag, embedded } = await tags.publicGetByCollection( collectionId, 'NFC-001', 'product,proof' ) ```
12843
12869
 
12844
12870
  **publicGetByCollection**(collectionId: string,
12845
12871
  tagId: string,
12846
12872
  embed?: string) → `Promise<PublicGetTagResponse>`
12847
- Single tag lookup with optional embedded data (public). `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref` Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'` (`'collection'` is not supported — the collection is already known from the URL). ```typescript const { tag, embedded } = await tags.publicGetByCollection( 'coll_123', 'NFC-001', 'product,proof' ) const product = embedded.products?.[tag.productId!] const proof = embedded.proofs?.[tag.proofId!] ```
12873
+ Single tag lookup with optional embedded data (public). `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref` Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'` (`'collection'` is not supported — the collection is already known from the URL). ```typescript const { tag, embedded } = await tags.publicGetByCollection( 'coll_123', 'NFC-001', 'product,proof' ) const product = embedded.products?.[tag.productId!] const proof = embedded.proofs?.[tag.proofId!] ```
12848
12874
 
12849
12875
  **lookupTags**(collectionId: string,
12850
12876
  data: LookupTagsRequest) → `Promise<TagLookupResponse>`
@@ -29,7 +29,7 @@ needs to call it — a description and an input schema:
29
29
  ```jsonc
30
30
  {
31
31
  "functions": {
32
- "files": { "js": { "umd": "dist/functions.umd.js" } },
32
+ "files": { "js": { "umd": "functions.umd.js" } },
33
33
  "definitions": [
34
34
  {
35
35
  "name": "createFaq",
package/dist/docs/ai.md CHANGED
@@ -323,7 +323,7 @@ in `input`.
323
323
  | `image.fromReference` | Image-to-image: generate guided by reference image(s). |
324
324
  | `image.searchStock` | Search real stock photos (Unsplash). |
325
325
  | `image.transform` | Resize / crop / rotate / grayscale / format-convert / compress → hosted URL. |
326
- | `pdf.create` | Render HTML → PDF → hosted URL. |
326
+ | `pdf.create` | Render HTML (or one fragment per page) → PDF, 1:1: exact `width`/`height`/`unit` for labels, `pages[]` for many pages in one render, no scripts, only SmartLinks/Google Fonts/data: resources. `return`: `url` (public), `signedUrl` (expiring), `base64`. Cached by input. |
327
327
  | `pdf.fill` | Fill an AcroForm PDF's fields (`{ field: value }`) → hosted URL. |
328
328
  | `pdf.merge` | Merge several PDFs into one, in order → hosted URL. |
329
329
  | `pdf.inspect` | Cheap, no-AI introspection: page count/sizes, which pages have a real text layer, raster present, and a routing hint (`text` vs `vision`). |
@@ -402,9 +402,11 @@ if (!ready.result.report.compliant) showFindings(ready.result.report.findings)
402
402
  - **A review UI that flags guessed fields** → `pdf.extract` with `includeConfidence` (per-field
403
403
  confidence + source) and `includeBoxes` (per-field bbox on text-native pages) in `fieldsMeta`.
404
404
  - **Produce a PDF** → `pdf.create` (HTML → PDF), `pdf.fill` (populate an AcroForm's fields),
405
- `pdf.merge` (combine several). These return a hosted `hostedUrl` (`pdf.create` also returns it as
406
- `url`). `pdf.create` loads remote `<img>` URLs before rendering and honours `page-break-inside`; pass
407
- `margin: "0"` when your HTML sets its own body margin (default page margin is 18mm/14mm).
405
+ `pdf.merge` (combine several). `pdf.create` takes an exact page size (`width`, `height`, `unit`) or a
406
+ `format`, `html` or `pages: [html, …]` (one per page, sharing `head`), and returns `url` (public),
407
+ a `signedUrl` that expires, or `base64` (`return`). JavaScript doesn't run, and images, fonts and
408
+ stylesheets load only from SmartLinks hosts, Google Fonts or `data:` URIs. From a server function:
409
+ `ctx.sl.tools.run('pdf.create', …)` with the `tools:pdf.create` capability (server-functions.md).
408
410
 
409
411
  Every tool is also directly callable without the model loop via `ai.tools.run(collectionId, name,
410
412
  args)` — e.g. render a page or extract fields straight from a UI, no agent round-trip:
@@ -428,7 +428,7 @@ this is just the manifest shape.
428
428
 
429
429
  | Field | Type | Description |
430
430
  |-------|------|-------------|
431
- | `files.js.umd` | string | UMD bundle path (e.g. `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
431
+ | `files.js.umd` | string | UMD bundle path, relative to the published build like the other bundles (e.g. `functions.umd.js`, built to `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
432
432
  | `definitions[]` | object[] | One entry per function |
433
433
  | `definitions[].name` | string | Function name; also the handler export name (override with `handler`) |
434
434
  | `definitions[].trigger` | object | `{ type: "http"|"event"|"cron", … }` |
@@ -66,7 +66,7 @@ deterministically. A connector's `shape` and `serialize` must be deterministic t
66
66
  "aiGuide": "connector-ai-guide.md"
67
67
  },
68
68
  "functions": {
69
- "files": { "js": { "umd": "dist/functions.umd.js" } },
69
+ "files": { "js": { "umd": "functions.umd.js" } },
70
70
  "definitions": [
71
71
  { "name": "testConnection", "trigger": { "type": "http", "method": "POST" },
72
72
  "visibility": "admin", "authority": "collection",
@@ -33,7 +33,7 @@ in a bundle alongside your widgets/containers:
33
33
  ```jsonc
34
34
  {
35
35
  "functions": {
36
- "files": { "js": { "umd": "dist/functions.umd.js" } },
36
+ "files": { "js": { "umd": "functions.umd.js" } },
37
37
  "definitions": [
38
38
  {
39
39
  "name": "submitCompetitionEntry",
@@ -98,9 +98,12 @@ export default defineConfig({
98
98
  }
99
99
  ```
100
100
 
101
- That emits `dist/functions.umd.js` (matching `functions.files.js.umd`), whose named exports the
102
- platform pairs with your manifest `definitions`. Then **deploy** it — for dev,
103
- `smartlinks-publish` uploads `dist/` and registers in one step (see
101
+ That emits `dist/functions.umd.js`, whose named exports the platform pairs with your manifest
102
+ `definitions`. In the manifest, `functions.files.js.umd` is **`functions.umd.js`** — relative to the
103
+ published build (the contents of `dist/`), like the widget and container files; not
104
+ `dist/functions.umd.js`. Publishing refuses a release whose functions bundle isn't there.
105
+
106
+ Then **deploy** it — for dev, `smartlinks-publish` uploads `dist/` and registers in one step (see
104
107
  [Deploying & registering](deploying-apps.md)).
105
108
 
106
109
  ---
@@ -169,6 +172,7 @@ Declare the minimum you need.
169
172
  | `network:<host>` | `ctx.fetch` to that host only (repeat for several). Prefer this over blanket `network`. |
170
173
  | `network:*.<domain>` | `ctx.fetch` to any subdomain of `<domain>` (not `<domain>` itself), for a customer's own endpoint at a provider, e.g. `network:*.servicebus.windows.net`. `<domain>` needs at least two labels (`*.com` is not honoured). |
171
174
  | `secrets:<name>` | `ctx.secrets.get('<name>')` for that one secret (see [Secrets](#secrets--api-keys-per-collection)). |
175
+ | `tools:<name>` / `tools:<family>.*` | `ctx.sl.tools.run('<name>', args)` for a platform tool, e.g. `tools:pdf.create`, or `tools:pdf.*` for every PDF tool (see [Platform tools](#platform-tools--pdfs-labels-barcodes)). |
172
176
 
173
177
  If you don't declare `network`, `ctx.fetch` is absent. Reading a secret you didn't declare with
174
178
  `secrets:<name>` throws.
@@ -606,6 +610,58 @@ await ctx.sl.interactions.record({
606
610
 
607
611
  ---
608
612
 
613
+ ## Platform tools — PDFs, labels, barcodes
614
+
615
+ `ctx.sl.tools.run(name, args)` runs one of the platform's deterministic tools from your function: no
616
+ model, no agent loop. Declare each tool as a capability: `tools:pdf.create`, or `tools:pdf.*` for the
617
+ whole family. Open to functions: `pdf.create`, `pdf.merge`, `pdf.fill`, `pdf.inspect`, `pdf.render`,
618
+ `pdf.decodeBarcodes`, `pdf.inspectGraphics`, `pdf.preflight`, `pdf.printReady`, `pdf.edit`,
619
+ `image.transform` (arguments as in [ai.md](ai.md)).
620
+
621
+ - **Identity:** the call runs as the function's collection, whatever the caller. An anonymous caller
622
+ holding only your own key gets the same result; the work is attributed and billed to the collection.
623
+ - **Rate:** per collection (10 calls a second by default); bursts wait rather than fail.
624
+ - **Failure:** throws an `Error` with `code` (`bad_args`, `too_large`, `too_many_pages`,
625
+ `resource_timeout`, `timeout`, `busy`, `TOOL_NOT_AVAILABLE`, or a capability denial).
626
+
627
+ **Labels with `pdf.create`.** Exact page sizes, many pages in one render, and bytes straight back:
628
+
629
+ ```ts
630
+ // capabilities: ["tools:pdf.create", "tools:pdf.decodeBarcodes"]
631
+ export async function printLabel(ctx, event) {
632
+ const { gtin, lot, expiry, qty = 1 } = event.body
633
+ const barcode = bwipjs.toSVG({ bcid: 'gs1-128', text: `(01)${gtin}(10)${lot}(17)${expiry}` }) // inline SVG
634
+ const pdf = await ctx.sl.tools.run('pdf.create', {
635
+ width: 100, height: 50, unit: 'mm', // exact; output is 1:1
636
+ head: '<style>.l{position:absolute;font:3mm Arial}</style>',
637
+ pages: Array.from({ length: qty }, () => `<div class="l" style="left:4mm;top:3mm">Lot ${lot}</div>
638
+ <div style="position:absolute;left:4mm;top:10mm;width:92mm">${barcode}</div>`),
639
+ return: 'base64', // or 'signedUrl' + expiresIn
640
+ })
641
+ return { pdf: pdf.base64, pages: pdf.pageCount }
642
+ }
643
+ ```
644
+
645
+ - **Page size:** `width` + `height` (+ `unit`: mm, cm, in, pt, px) is exact to the point. Otherwise
646
+ `format` ("A4", "Letter"), or the document's own CSS `@page { size }` when neither is given.
647
+ - **Pages:** `pages: [html, …]` lays one fragment per page; with an exact size and no margin each page
648
+ box is that size (overflow hidden), so a long value can't push onto the next label. Up to 2000 pages
649
+ and 10 MB of HTML per render.
650
+ - **Output:** 1:1 at scale 1 with backgrounds; text stays text and SVG stays vector (render barcodes as
651
+ inline SVG, e.g. bwip-js `toSVG`, never as a canvas bitmap). Colour is RGB; for CMYK pass the result
652
+ through `pdf.printReady`.
653
+ - **What loads:** JavaScript does not run. Images, fonts and stylesheets load only from SmartLinks hosts
654
+ (our CDN), Google Fonts or inline `data:` URIs; anything else is skipped and listed in `warnings`.
655
+ Embed brand fonts as `data:` URIs in `@font-face`, or host them on the SmartLinks CDN.
656
+ - **Delivery:** `return: 'base64'` (nothing stored), `'signedUrl'` (private link that expires after
657
+ `expiresIn` seconds, default 900, max 7 days — use this for anything with lot, expiry or serial
658
+ data), or `'url'` (public and permanent — artwork and documents meant to be shared).
659
+ - **Cache:** the same inputs return the earlier render without rendering again (`cached: true`).
660
+ - **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
661
+ confirm the digits before answering.
662
+
663
+ ---
664
+
609
665
  ## Worked example — a public competition entry
610
666
 
611
667
  ```ts
package/dist/openapi.yaml CHANGED
@@ -8339,7 +8339,7 @@ paths:
8339
8339
  get:
8340
8340
  tags:
8341
8341
  - tags
8342
- summary: Reverse lookup — find all tags linked to a given object (admin).
8342
+ summary: Reverse lookup — find all tags linked to a given object (admin).
8343
8343
  operationId: tags_byRef
8344
8344
  security:
8345
8345
  - bearerAuth: []
@@ -14108,7 +14108,7 @@ paths:
14108
14108
  get:
14109
14109
  tags:
14110
14110
  - tags
14111
- summary: "Global tag resolve — returns `{ tagId, collectionId }` only."
14111
+ summary: "Global tag resolve — returns `{ tagId, collectionId }` only."
14112
14112
  operationId: tags_resolveTag
14113
14113
  security: []
14114
14114
  parameters:
@@ -17499,14 +17499,74 @@ components:
17499
17499
  properties:
17500
17500
  html:
17501
17501
  type: string
17502
+ pages:
17503
+ type: array
17504
+ items:
17505
+ type: string
17506
+ head:
17507
+ type: string
17508
+ width:
17509
+ type: number
17510
+ height:
17511
+ type: number
17512
+ unit:
17513
+ type: string
17514
+ enum:
17515
+ - mm
17516
+ - cm
17517
+ - in
17518
+ - pt
17519
+ - px
17502
17520
  format:
17503
17521
  type: string
17504
17522
  landscape:
17505
17523
  type: boolean
17506
17524
  margin:
17507
17525
  type: string
17526
+ return:
17527
+ type: string
17528
+ enum:
17529
+ - url
17530
+ - signedUrl
17531
+ - base64
17532
+ expiresIn:
17533
+ type: number
17534
+ filename:
17535
+ type: string
17536
+ cache:
17537
+ type: boolean
17538
+ PdfCreateResult:
17539
+ type: object
17540
+ properties:
17541
+ url:
17542
+ type: string
17543
+ hostedUrl:
17544
+ type: string
17545
+ base64:
17546
+ type: string
17547
+ expiresAt:
17548
+ type: string
17549
+ contentType:
17550
+ type: string
17551
+ enum:
17552
+ - application/pdf
17553
+ size:
17554
+ type: number
17555
+ pageCount:
17556
+ type: number
17557
+ cached:
17558
+ type: boolean
17559
+ renderer:
17560
+ type: string
17561
+ warnings:
17562
+ type: array
17563
+ items:
17564
+ type: string
17508
17565
  required:
17509
- - html
17566
+ - contentType
17567
+ - size
17568
+ - pageCount
17569
+ - cached
17510
17570
  PdfFillArgs:
17511
17571
  type: object
17512
17572
  properties:
@@ -19657,6 +19717,9 @@ components:
19657
19717
  interactions:
19658
19718
  type: object
19659
19719
  additionalProperties: true
19720
+ tools:
19721
+ type: object
19722
+ additionalProperties: true
19660
19723
  products: {}
19661
19724
  attestations: {}
19662
19725
  appData:
@@ -19667,6 +19730,7 @@ components:
19667
19730
  - tags
19668
19731
  - contacts
19669
19732
  - interactions
19733
+ - tools
19670
19734
  - products
19671
19735
  - attestations
19672
19736
  - appData
@@ -945,11 +945,49 @@ export interface PdfClip {
945
945
  y1: number;
946
946
  }
947
947
  export interface PdfCreateArgs {
948
- html: string;
948
+ /** One HTML document (inline CSS). Give `html` or `pages`. */
949
+ html?: string;
950
+ /** One HTML fragment per page, laid out one per page (sharing `head`). With width/height and no margin each page box is exactly that size, overflow hidden. 500 labels = one call. */
951
+ pages?: string[];
952
+ /** With `pages`: shared <head> content (<style>, @font-face). */
953
+ head?: string;
954
+ /** Exact page size (labels, rolls, sheets) — exact to the point. Give both. */
955
+ width?: number;
956
+ height?: number;
957
+ /** Unit for width/height (default 'mm'). */
958
+ unit?: 'mm' | 'cm' | 'in' | 'pt' | 'px';
959
+ /** Named page size ("A4", "Letter") when not giving width/height. With neither, the document's CSS @page size is used (else A4). */
949
960
  format?: string;
950
961
  landscape?: boolean;
951
- /** Page margin for all sides, e.g. "0" or "12mm" (default 18mm/14mm). Use "0" when the HTML sets its own margins. */
962
+ /** Page margin for all sides, e.g. "0" or "12mm". Default 0 with width/height or pages; 18mm/14mm for named formats. */
952
963
  margin?: string;
964
+ /** 'url' (public, permanent; default), 'signedUrl' (private link that expires), 'base64' (bytes in the response, nothing stored). */
965
+ return?: 'url' | 'signedUrl' | 'base64';
966
+ /** signedUrl lifetime in seconds: 30 to 604800 (7 days); default 900. */
967
+ expiresIn?: number;
968
+ /** File name for signedUrl downloads (default document.pdf). */
969
+ filename?: string;
970
+ /** Reuse an identical earlier render (default true). */
971
+ cache?: boolean;
972
+ }
973
+ export interface PdfCreateResult {
974
+ /** return 'url' or 'signedUrl'. */
975
+ url?: string;
976
+ /** return 'url': same as url. */
977
+ hostedUrl?: string;
978
+ /** return 'base64'. */
979
+ base64?: string;
980
+ /** return 'signedUrl': when the link stops working. */
981
+ expiresAt?: string;
982
+ contentType: 'application/pdf';
983
+ size: number;
984
+ pageCount: number;
985
+ /** An identical earlier render was reused (no new render). */
986
+ cached: boolean;
987
+ /** The renderer, e.g. "Chrome/141.0…". */
988
+ renderer?: string;
989
+ /** Resources that were not loaded (only images, fonts and stylesheets from SmartLinks hosts, Google Fonts or data: URIs load). */
990
+ warnings?: string[];
953
991
  }
954
992
  export interface PdfFillArgs {
955
993
  url: string;
@@ -457,6 +457,16 @@ export interface ServerFunctionSl {
457
457
  eventId: string;
458
458
  }>;
459
459
  };
460
+ /**
461
+ * Platform tools (`tools:<name>` or `tools:<family>.*`, e.g. `tools:pdf.create`, `tools:pdf.*`):
462
+ * pdf.create, pdf.merge, pdf.fill, pdf.inspect, pdf.render, pdf.decodeBarcodes, pdf.inspectGraphics,
463
+ * pdf.preflight, pdf.printReady, pdf.edit, image.transform. Runs as THIS collection whatever the caller
464
+ * (authorised by the declaration, billed to the collection), rate-limited per collection. Returns the
465
+ * tool's result; a failure throws an Error with `code` (e.g. bad_args, busy, too_many_pages).
466
+ */
467
+ tools: {
468
+ run<T = any>(name: string, args?: Record<string, any>): Promise<T>;
469
+ };
460
470
  /** Products, scoped by caller authority + declared capability. */
461
471
  products: any;
462
472
  /** Attestations, scoped by caller authority + declared capability. */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.66 | Generated: 2026-10-14T17:03:31.949Z
3
+ Version: 2.0.67 | Generated: 2026-10-15T17:52:10.614Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1600,10 +1600,35 @@ interface PdfClip {
1600
1600
  **PdfCreateArgs** (interface)
1601
1601
  ```typescript
1602
1602
  interface PdfCreateArgs {
1603
- html: string
1603
+ html?: string
1604
+ pages?: string[]
1605
+ head?: string
1606
+ width?: number
1607
+ height?: number
1608
+ unit?: 'mm' | 'cm' | 'in' | 'pt' | 'px'
1604
1609
  format?: string
1605
1610
  landscape?: boolean
1606
1611
  margin?: string
1612
+ return?: 'url' | 'signedUrl' | 'base64'
1613
+ expiresIn?: number
1614
+ filename?: string
1615
+ cache?: boolean
1616
+ }
1617
+ ```
1618
+
1619
+ **PdfCreateResult** (interface)
1620
+ ```typescript
1621
+ interface PdfCreateResult {
1622
+ url?: string
1623
+ hostedUrl?: string
1624
+ base64?: string
1625
+ expiresAt?: string
1626
+ contentType: 'application/pdf'
1627
+ size: number
1628
+ pageCount: number
1629
+ cached: boolean
1630
+ renderer?: string
1631
+ warnings?: string[]
1607
1632
  }
1608
1633
  ```
1609
1634
 
@@ -2786,6 +2811,7 @@ interface ServerFunctionSl {
2786
2811
  forCaller(): Promise<{ contactId: string } | null>;
2787
2812
  };
2788
2813
  interactions: { record(event: ServerFunctionInteractionEvent): Promise<{ eventId: string }> };
2814
+ tools: { run<T = any>(name: string, args?: Record<string, any>): Promise<T> };
2789
2815
  products: any;
2790
2816
  attestations: any;
2791
2817
  appData: AppDataHandle & { global: AppDataHandle; collection: AppDataHandle };
@@ -12815,7 +12841,7 @@ Create a single tag mapping (admin). If `productId` is set without `proofId`, a
12815
12841
 
12816
12842
  **createBatch**(collectionId: string,
12817
12843
  data: BatchCreateTagRequest) → `Promise<BatchCreateResult>`
12818
- Batch-create tags (admin). Tags with `productId` but no `proofId` automatically get serial numbers. Serial number generation is grouped by `(productId, variantId, batchId)` for efficiency. Partial success is possible — check `results` for individual outcomes. ```typescript const result = await tags.createBatch('coll_123', { tags: [ { tagId: 'NFC-001', productId: 'prod_456', batchId: 'batch_2026_01' }, { tagId: 'NFC-002', productId: 'prod_456', batchId: 'batch_2026_01' }, ], }) console.log(`Created: ${result.summary.created}, Conflicts: ${result.summary.conflicts}`) ```
12844
+ Batch-create tags (admin). Tags with `productId` but no `proofId` automatically get serial numbers. Serial number generation is grouped by `(productId, variantId, batchId)` for efficiency. Partial success is possible — check `results` for individual outcomes. ```typescript const result = await tags.createBatch('coll_123', { tags: [ { tagId: 'NFC-001', productId: 'prod_456', batchId: 'batch_2026_01' }, { tagId: 'NFC-002', productId: 'prod_456', batchId: 'batch_2026_01' }, ], }) console.log(`Created: ${result.summary.created}, Conflicts: ${result.summary.conflicts}`) ```
12819
12845
 
12820
12846
  **get**(collectionId: string,
12821
12847
  tagId: string) → `Promise<GetTagResponse>`
@@ -12824,7 +12850,7 @@ Get a single tag by `tagId` (admin).
12824
12850
  **update**(collectionId: string,
12825
12851
  tagId: string,
12826
12852
  data: UpdateTagRequest) → `Promise<UpdateTagResponse>`
12827
- Update a tag (admin). Partial update — only provided fields are changed. `metadata` is deep-merged with the existing value. Pass `refType: null, refId: null` to clear the polymorphic ref. ```typescript const updated = await tags.update('coll_123', 'NFC-001', { variantId: 'var_premium', metadata: { notes: 'Updated to premium variant' }, }) // Clear polymorphic ref await tags.update('coll_123', 'NFC-001', { refType: null, refId: null }) ```
12853
+ Update a tag (admin). Partial update — only provided fields are changed. `metadata` is deep-merged with the existing value. Pass `refType: null, refId: null` to clear the polymorphic ref. ```typescript const updated = await tags.update('coll_123', 'NFC-001', { variantId: 'var_premium', metadata: { notes: 'Updated to premium variant' }, }) // Clear polymorphic ref await tags.update('coll_123', 'NFC-001', { refType: null, refId: null }) ```
12828
12854
 
12829
12855
  **remove**(collectionId: string,
12830
12856
  tagId: string) → `Promise<DeleteTagResponse>`
@@ -12836,15 +12862,15 @@ List tags with optional filters and pagination (admin). ```typescript // All tag
12836
12862
 
12837
12863
  **byRef**(collectionId: string,
12838
12864
  params: ReverseTagLookupParams) → `Promise<ReverseTagLookupResponse>`
12839
- Reverse lookup — find all tags linked to a given object (admin). Uses a compound index on `(orgId, refType, refId)` on the per-org shard. No embed support on the admin side. ```typescript const { tags: linked } = await tags.byRef('coll_123', { refType: 'container', refId: 'container-uuid', }) ```
12865
+ Reverse lookup — find all tags linked to a given object (admin). Uses a compound index on `(orgId, refType, refId)` on the per-org shard. No embed support on the admin side. ```typescript const { tags: linked } = await tags.byRef('coll_123', { refType: 'container', refId: 'container-uuid', }) ```
12840
12866
 
12841
12867
  **resolveTag**(tagId: string) → `Promise<TagIndexEntry>`
12842
- Global tag resolve — returns `{ tagId, collectionId }` only. Use this **only** when you have a raw `tagId` and do not yet know which collection it belongs to. Queries the shared `tag_index` shard. Once `collectionId` is resolved, call `publicGetByCollection` for full data. > The global `/public/tags/by-ref` endpoint has been removed. > Use the collection-scoped `publicByRef` instead. ```typescript // Step 1: resolve collection const { collectionId } = await tags.resolveTag('NFC-001') // Step 2: full lookup with embedded data const { tag, embedded } = await tags.publicGetByCollection( collectionId, 'NFC-001', 'product,proof' ) ```
12868
+ Global tag resolve — returns `{ tagId, collectionId }` only. Use this **only** when you have a raw `tagId` and do not yet know which collection it belongs to. Queries the shared `tag_index` shard. Once `collectionId` is resolved, call `publicGetByCollection` for full data. > The global `/public/tags/by-ref` endpoint has been removed. > Use the collection-scoped `publicByRef` instead. ```typescript // Step 1: resolve collection const { collectionId } = await tags.resolveTag('NFC-001') // Step 2: full lookup with embedded data const { tag, embedded } = await tags.publicGetByCollection( collectionId, 'NFC-001', 'product,proof' ) ```
12843
12869
 
12844
12870
  **publicGetByCollection**(collectionId: string,
12845
12871
  tagId: string,
12846
12872
  embed?: string) → `Promise<PublicGetTagResponse>`
12847
- Single tag lookup with optional embedded data (public). `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref` Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'` (`'collection'` is not supported — the collection is already known from the URL). ```typescript const { tag, embedded } = await tags.publicGetByCollection( 'coll_123', 'NFC-001', 'product,proof' ) const product = embedded.products?.[tag.productId!] const proof = embedded.proofs?.[tag.proofId!] ```
12873
+ Single tag lookup with optional embedded data (public). `GET /public/collection/:collectionId/tags/:tagId?embed=product,proof,container,ref` Supported `embed` values: `'product'`, `'proof'`, `'container'`, `'ref'` (`'collection'` is not supported — the collection is already known from the URL). ```typescript const { tag, embedded } = await tags.publicGetByCollection( 'coll_123', 'NFC-001', 'product,proof' ) const product = embedded.products?.[tag.productId!] const proof = embedded.proofs?.[tag.proofId!] ```
12848
12874
 
12849
12875
  **lookupTags**(collectionId: string,
12850
12876
  data: LookupTagsRequest) → `Promise<TagLookupResponse>`
@@ -29,7 +29,7 @@ needs to call it — a description and an input schema:
29
29
  ```jsonc
30
30
  {
31
31
  "functions": {
32
- "files": { "js": { "umd": "dist/functions.umd.js" } },
32
+ "files": { "js": { "umd": "functions.umd.js" } },
33
33
  "definitions": [
34
34
  {
35
35
  "name": "createFaq",
package/docs/ai.md CHANGED
@@ -323,7 +323,7 @@ in `input`.
323
323
  | `image.fromReference` | Image-to-image: generate guided by reference image(s). |
324
324
  | `image.searchStock` | Search real stock photos (Unsplash). |
325
325
  | `image.transform` | Resize / crop / rotate / grayscale / format-convert / compress → hosted URL. |
326
- | `pdf.create` | Render HTML → PDF → hosted URL. |
326
+ | `pdf.create` | Render HTML (or one fragment per page) → PDF, 1:1: exact `width`/`height`/`unit` for labels, `pages[]` for many pages in one render, no scripts, only SmartLinks/Google Fonts/data: resources. `return`: `url` (public), `signedUrl` (expiring), `base64`. Cached by input. |
327
327
  | `pdf.fill` | Fill an AcroForm PDF's fields (`{ field: value }`) → hosted URL. |
328
328
  | `pdf.merge` | Merge several PDFs into one, in order → hosted URL. |
329
329
  | `pdf.inspect` | Cheap, no-AI introspection: page count/sizes, which pages have a real text layer, raster present, and a routing hint (`text` vs `vision`). |
@@ -402,9 +402,11 @@ if (!ready.result.report.compliant) showFindings(ready.result.report.findings)
402
402
  - **A review UI that flags guessed fields** → `pdf.extract` with `includeConfidence` (per-field
403
403
  confidence + source) and `includeBoxes` (per-field bbox on text-native pages) in `fieldsMeta`.
404
404
  - **Produce a PDF** → `pdf.create` (HTML → PDF), `pdf.fill` (populate an AcroForm's fields),
405
- `pdf.merge` (combine several). These return a hosted `hostedUrl` (`pdf.create` also returns it as
406
- `url`). `pdf.create` loads remote `<img>` URLs before rendering and honours `page-break-inside`; pass
407
- `margin: "0"` when your HTML sets its own body margin (default page margin is 18mm/14mm).
405
+ `pdf.merge` (combine several). `pdf.create` takes an exact page size (`width`, `height`, `unit`) or a
406
+ `format`, `html` or `pages: [html, …]` (one per page, sharing `head`), and returns `url` (public),
407
+ a `signedUrl` that expires, or `base64` (`return`). JavaScript doesn't run, and images, fonts and
408
+ stylesheets load only from SmartLinks hosts, Google Fonts or `data:` URIs. From a server function:
409
+ `ctx.sl.tools.run('pdf.create', …)` with the `tools:pdf.create` capability (server-functions.md).
408
410
 
409
411
  Every tool is also directly callable without the model loop via `ai.tools.run(collectionId, name,
410
412
  args)` — e.g. render a page or extract fields straight from a UI, no agent round-trip:
@@ -428,7 +428,7 @@ this is just the manifest shape.
428
428
 
429
429
  | Field | Type | Description |
430
430
  |-------|------|-------------|
431
- | `files.js.umd` | string | UMD bundle path (e.g. `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
431
+ | `files.js.umd` | string | UMD bundle path, relative to the published build like the other bundles (e.g. `functions.umd.js`, built to `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
432
432
  | `definitions[]` | object[] | One entry per function |
433
433
  | `definitions[].name` | string | Function name; also the handler export name (override with `handler`) |
434
434
  | `definitions[].trigger` | object | `{ type: "http"|"event"|"cron", … }` |
@@ -66,7 +66,7 @@ deterministically. A connector's `shape` and `serialize` must be deterministic t
66
66
  "aiGuide": "connector-ai-guide.md"
67
67
  },
68
68
  "functions": {
69
- "files": { "js": { "umd": "dist/functions.umd.js" } },
69
+ "files": { "js": { "umd": "functions.umd.js" } },
70
70
  "definitions": [
71
71
  { "name": "testConnection", "trigger": { "type": "http", "method": "POST" },
72
72
  "visibility": "admin", "authority": "collection",
@@ -33,7 +33,7 @@ in a bundle alongside your widgets/containers:
33
33
  ```jsonc
34
34
  {
35
35
  "functions": {
36
- "files": { "js": { "umd": "dist/functions.umd.js" } },
36
+ "files": { "js": { "umd": "functions.umd.js" } },
37
37
  "definitions": [
38
38
  {
39
39
  "name": "submitCompetitionEntry",
@@ -98,9 +98,12 @@ export default defineConfig({
98
98
  }
99
99
  ```
100
100
 
101
- That emits `dist/functions.umd.js` (matching `functions.files.js.umd`), whose named exports the
102
- platform pairs with your manifest `definitions`. Then **deploy** it — for dev,
103
- `smartlinks-publish` uploads `dist/` and registers in one step (see
101
+ That emits `dist/functions.umd.js`, whose named exports the platform pairs with your manifest
102
+ `definitions`. In the manifest, `functions.files.js.umd` is **`functions.umd.js`** — relative to the
103
+ published build (the contents of `dist/`), like the widget and container files; not
104
+ `dist/functions.umd.js`. Publishing refuses a release whose functions bundle isn't there.
105
+
106
+ Then **deploy** it — for dev, `smartlinks-publish` uploads `dist/` and registers in one step (see
104
107
  [Deploying & registering](deploying-apps.md)).
105
108
 
106
109
  ---
@@ -169,6 +172,7 @@ Declare the minimum you need.
169
172
  | `network:<host>` | `ctx.fetch` to that host only (repeat for several). Prefer this over blanket `network`. |
170
173
  | `network:*.<domain>` | `ctx.fetch` to any subdomain of `<domain>` (not `<domain>` itself), for a customer's own endpoint at a provider, e.g. `network:*.servicebus.windows.net`. `<domain>` needs at least two labels (`*.com` is not honoured). |
171
174
  | `secrets:<name>` | `ctx.secrets.get('<name>')` for that one secret (see [Secrets](#secrets--api-keys-per-collection)). |
175
+ | `tools:<name>` / `tools:<family>.*` | `ctx.sl.tools.run('<name>', args)` for a platform tool, e.g. `tools:pdf.create`, or `tools:pdf.*` for every PDF tool (see [Platform tools](#platform-tools--pdfs-labels-barcodes)). |
172
176
 
173
177
  If you don't declare `network`, `ctx.fetch` is absent. Reading a secret you didn't declare with
174
178
  `secrets:<name>` throws.
@@ -606,6 +610,58 @@ await ctx.sl.interactions.record({
606
610
 
607
611
  ---
608
612
 
613
+ ## Platform tools — PDFs, labels, barcodes
614
+
615
+ `ctx.sl.tools.run(name, args)` runs one of the platform's deterministic tools from your function: no
616
+ model, no agent loop. Declare each tool as a capability: `tools:pdf.create`, or `tools:pdf.*` for the
617
+ whole family. Open to functions: `pdf.create`, `pdf.merge`, `pdf.fill`, `pdf.inspect`, `pdf.render`,
618
+ `pdf.decodeBarcodes`, `pdf.inspectGraphics`, `pdf.preflight`, `pdf.printReady`, `pdf.edit`,
619
+ `image.transform` (arguments as in [ai.md](ai.md)).
620
+
621
+ - **Identity:** the call runs as the function's collection, whatever the caller. An anonymous caller
622
+ holding only your own key gets the same result; the work is attributed and billed to the collection.
623
+ - **Rate:** per collection (10 calls a second by default); bursts wait rather than fail.
624
+ - **Failure:** throws an `Error` with `code` (`bad_args`, `too_large`, `too_many_pages`,
625
+ `resource_timeout`, `timeout`, `busy`, `TOOL_NOT_AVAILABLE`, or a capability denial).
626
+
627
+ **Labels with `pdf.create`.** Exact page sizes, many pages in one render, and bytes straight back:
628
+
629
+ ```ts
630
+ // capabilities: ["tools:pdf.create", "tools:pdf.decodeBarcodes"]
631
+ export async function printLabel(ctx, event) {
632
+ const { gtin, lot, expiry, qty = 1 } = event.body
633
+ const barcode = bwipjs.toSVG({ bcid: 'gs1-128', text: `(01)${gtin}(10)${lot}(17)${expiry}` }) // inline SVG
634
+ const pdf = await ctx.sl.tools.run('pdf.create', {
635
+ width: 100, height: 50, unit: 'mm', // exact; output is 1:1
636
+ head: '<style>.l{position:absolute;font:3mm Arial}</style>',
637
+ pages: Array.from({ length: qty }, () => `<div class="l" style="left:4mm;top:3mm">Lot ${lot}</div>
638
+ <div style="position:absolute;left:4mm;top:10mm;width:92mm">${barcode}</div>`),
639
+ return: 'base64', // or 'signedUrl' + expiresIn
640
+ })
641
+ return { pdf: pdf.base64, pages: pdf.pageCount }
642
+ }
643
+ ```
644
+
645
+ - **Page size:** `width` + `height` (+ `unit`: mm, cm, in, pt, px) is exact to the point. Otherwise
646
+ `format` ("A4", "Letter"), or the document's own CSS `@page { size }` when neither is given.
647
+ - **Pages:** `pages: [html, …]` lays one fragment per page; with an exact size and no margin each page
648
+ box is that size (overflow hidden), so a long value can't push onto the next label. Up to 2000 pages
649
+ and 10 MB of HTML per render.
650
+ - **Output:** 1:1 at scale 1 with backgrounds; text stays text and SVG stays vector (render barcodes as
651
+ inline SVG, e.g. bwip-js `toSVG`, never as a canvas bitmap). Colour is RGB; for CMYK pass the result
652
+ through `pdf.printReady`.
653
+ - **What loads:** JavaScript does not run. Images, fonts and stylesheets load only from SmartLinks hosts
654
+ (our CDN), Google Fonts or inline `data:` URIs; anything else is skipped and listed in `warnings`.
655
+ Embed brand fonts as `data:` URIs in `@font-face`, or host them on the SmartLinks CDN.
656
+ - **Delivery:** `return: 'base64'` (nothing stored), `'signedUrl'` (private link that expires after
657
+ `expiresIn` seconds, default 900, max 7 days — use this for anything with lot, expiry or serial
658
+ data), or `'url'` (public and permanent — artwork and documents meant to be shared).
659
+ - **Cache:** the same inputs return the earlier render without rendering again (`cached: true`).
660
+ - **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
661
+ confirm the digits before answering.
662
+
663
+ ---
664
+
609
665
  ## Worked example — a public competition entry
610
666
 
611
667
  ```ts
package/openapi.yaml CHANGED
@@ -8339,7 +8339,7 @@ paths:
8339
8339
  get:
8340
8340
  tags:
8341
8341
  - tags
8342
- summary: Reverse lookup — find all tags linked to a given object (admin).
8342
+ summary: Reverse lookup — find all tags linked to a given object (admin).
8343
8343
  operationId: tags_byRef
8344
8344
  security:
8345
8345
  - bearerAuth: []
@@ -14108,7 +14108,7 @@ paths:
14108
14108
  get:
14109
14109
  tags:
14110
14110
  - tags
14111
- summary: "Global tag resolve — returns `{ tagId, collectionId }` only."
14111
+ summary: "Global tag resolve — returns `{ tagId, collectionId }` only."
14112
14112
  operationId: tags_resolveTag
14113
14113
  security: []
14114
14114
  parameters:
@@ -17499,14 +17499,74 @@ components:
17499
17499
  properties:
17500
17500
  html:
17501
17501
  type: string
17502
+ pages:
17503
+ type: array
17504
+ items:
17505
+ type: string
17506
+ head:
17507
+ type: string
17508
+ width:
17509
+ type: number
17510
+ height:
17511
+ type: number
17512
+ unit:
17513
+ type: string
17514
+ enum:
17515
+ - mm
17516
+ - cm
17517
+ - in
17518
+ - pt
17519
+ - px
17502
17520
  format:
17503
17521
  type: string
17504
17522
  landscape:
17505
17523
  type: boolean
17506
17524
  margin:
17507
17525
  type: string
17526
+ return:
17527
+ type: string
17528
+ enum:
17529
+ - url
17530
+ - signedUrl
17531
+ - base64
17532
+ expiresIn:
17533
+ type: number
17534
+ filename:
17535
+ type: string
17536
+ cache:
17537
+ type: boolean
17538
+ PdfCreateResult:
17539
+ type: object
17540
+ properties:
17541
+ url:
17542
+ type: string
17543
+ hostedUrl:
17544
+ type: string
17545
+ base64:
17546
+ type: string
17547
+ expiresAt:
17548
+ type: string
17549
+ contentType:
17550
+ type: string
17551
+ enum:
17552
+ - application/pdf
17553
+ size:
17554
+ type: number
17555
+ pageCount:
17556
+ type: number
17557
+ cached:
17558
+ type: boolean
17559
+ renderer:
17560
+ type: string
17561
+ warnings:
17562
+ type: array
17563
+ items:
17564
+ type: string
17508
17565
  required:
17509
- - html
17566
+ - contentType
17567
+ - size
17568
+ - pageCount
17569
+ - cached
17510
17570
  PdfFillArgs:
17511
17571
  type: object
17512
17572
  properties:
@@ -19657,6 +19717,9 @@ components:
19657
19717
  interactions:
19658
19718
  type: object
19659
19719
  additionalProperties: true
19720
+ tools:
19721
+ type: object
19722
+ additionalProperties: true
19660
19723
  products: {}
19661
19724
  attestations: {}
19662
19725
  appData:
@@ -19667,6 +19730,7 @@ components:
19667
19730
  - tags
19668
19731
  - contacts
19669
19732
  - interactions
19733
+ - tools
19670
19734
  - products
19671
19735
  - attestations
19672
19736
  - appData
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.66",
3
+ "version": "2.0.67",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",