@proveanything/smartlinks 2.0.40 → 2.0.42

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.
@@ -167,10 +167,10 @@ Declare the minimum you need.
167
167
  | `sl:<resource>:read` / `sl:<resource>:write` | SDK access to that resource, e.g. `sl:products:read`, `sl:records:write`, `sl:attestations:write`, `sl:contacts:write`. |
168
168
  | `network` | `ctx.fetch` to any host. |
169
169
  | `network:<host>` | `ctx.fetch` to that host only (repeat for several). Prefer this over blanket `network`. |
170
- | `secrets:<ref>` | `ctx.secrets.get('<ref>')` for that one secret ref. |
170
+ | `secrets:<name>` | `ctx.secrets.get('<name>')` for that one secret (see [Secrets](#secrets--api-keys-per-collection)). |
171
171
 
172
- If you don't declare `network`, `ctx.fetch` is absent. If you don't declare a `secrets:<ref>`,
173
- `ctx.secrets.get('<ref>')` returns `null`.
172
+ If you don't declare `network`, `ctx.fetch` is absent. Reading a secret you didn't declare with
173
+ `secrets:<name>` throws.
174
174
 
175
175
  ---
176
176
 
@@ -186,8 +186,8 @@ interface ServerFunctionContext {
186
186
  * data — URLs, flags, counters, arrays), `sl.products`, `sl.attestations`. */
187
187
  sl: SmartLinks
188
188
 
189
- /** Capability-gated secrets (need `secrets:<ref>`). `get(ref)` resolves the collection's OWN
190
- * secret first, then the app-level one; `app(ref)` reads the app-level secret only. */
189
+ /** Capability-gated secrets (need `secrets:<name>`). `get(name)` reads this app's own secret on the
190
+ * collection first, then the app-level one; `app(name)` reads the app-level secret only. */
191
191
  secrets: { get(ref: string): Promise<string | null>; app(ref: string): Promise<string | null> }
192
192
 
193
193
  /** Who invoked this function. */
@@ -324,6 +324,40 @@ name (a first-party builtin still wins). Always prefer an appId.
324
324
 
325
325
  ---
326
326
 
327
+ ## Inputs — data handed to your function
328
+
329
+ Declare what your function needs and the platform fetches it **before** the run: read it from `ctx.inputs` instead of calling `ctx.sl` first. One less round trip — and for functions running in the isolated runner, one less network hop.
330
+
331
+ ```jsonc
332
+ // app.manifest.json → functions.definitions[]
333
+ {
334
+ "name": "onProductChanged",
335
+ "trigger": { "type": "event", "eventTypes": ["product.updated"] },
336
+ "capabilities": ["sl:products:read", "sl:data:read"],
337
+ "inputs": { "appConfig": true } // entity is on by default for event triggers
338
+ }
339
+ ```
340
+
341
+ ```js
342
+ export async function onProductChanged(ctx, event) {
343
+ const product = ctx.inputs.entity // the product that changed
344
+ const { interactionIds } = ctx.inputs.appConfig || {} // this app's config for the collection
345
+ }
346
+ ```
347
+
348
+ | Input | What you get | Needs | Triggers |
349
+ |---|---|---|---|
350
+ | `entity` | The record the event is about: the product, your app's record, or the interaction event. **On by default**; `"entity": false` opts out. | read on that resource (`sl:products:read`, `sl:records:read`) | event |
351
+ | `collection` | The collection's public record | — | all |
352
+ | `appConfig` | Your app's config for this collection (where setup like `interactionIds` lives) | `sl:data:read` | all |
353
+ | `product` | A product named by the request: `{ "from": "body.productId" }`, `"query.<field>"` or `"path"` | `sl:products:read` | http |
354
+
355
+ - **Same rules as `ctx.sl`.** Inputs are fetched with your function's authority and capabilities, scoped to the collection it runs in — a product from another collection is never fetched.
356
+ - **Never an error.** Not found, not allowed or larger than 256 KB → that input is `null`. Check before you use it.
357
+ - **Current state.** Event inputs are fetched when your function runs, so you see the record as it is now; `event` still carries the ids.
358
+ - **Checked at publish.** An unknown input, a bad `from`, or a missing capability fails validation.
359
+ - **Testing:** pass `inputs` to the test context — `createFunctionTestContext({ def, inputs: { entity: { id: 'p1', name: 'Kettle' } } })`.
360
+
327
361
  ## Exposing a function to the AI agent
328
362
 
329
363
  An `http` function can be offered to the AI agent as a **callable tool**, alongside the built-in
@@ -527,6 +561,102 @@ other secrets, or any other collection.
527
561
 
528
562
  ---
529
563
 
564
+ ## Secrets — API keys per collection
565
+
566
+ A function that calls another system needs that system's key, and each collection (each customer)
567
+ has its own. Your app's **admin screen** saves it; your **functions** read it by the same name.
568
+
569
+ **1. Declare it** on every function that reads it:
570
+
571
+ ```jsonc
572
+ { "name": "pullOrders", "trigger": { "type": "cron", "schedule": "0,30 * * * *" },
573
+ "capabilities": ["network:api.example-shop.com", "secrets:shop-api-key"] }
574
+ ```
575
+
576
+ **2. Save it from your admin screen**, signed in as a collection admin:
577
+
578
+ ```ts
579
+ await SL.secrets.put(collectionId, 'shop-api-key', apiKeyFromTheForm) // create or replace
580
+ const { secrets } = await SL.secrets.listOwn(collectionId) // names + masked hints, e.g. "••••1234"
581
+ await SL.secrets.removeOwn(collectionId, 'shop-api-key')
582
+ ```
583
+
584
+ The app comes from the SDK's app context (`initializeApi({ appId })`), or pass `{ appId }` as the
585
+ last argument. Secrets are write-only: nothing ever returns the value, so show the hint ("saved,
586
+ ends 1234") and offer "replace", not "edit". Build this into your own settings screen; the platform
587
+ doesn't prompt for it at install.
588
+
589
+ **3. Read it in the function:**
590
+
591
+ ```ts
592
+ const key = await ctx.secrets.get('shop-api-key') // null until an admin has saved it
593
+ if (!key) { ctx.log('shop not connected yet'); return { skipped: 'not connected' } }
594
+ ```
595
+
596
+ **Each app's secrets are its own.** `shop-api-key` saved by your app and `shop-api-key` saved by
597
+ another app on the same collection are two different secrets; your functions only ever read yours.
598
+
599
+ `ctx.secrets.get(name)` looks in this order:
600
+ 1. your app's secret on this collection (saved with `SL.secrets.put`);
601
+ 2. *SmartLinks' own apps only:* a collection-wide secret of that name. Third-party apps never read these;
602
+ 3. your app's shared, app-level secret (the same for every collection), if Forge has one for it.
603
+
604
+ Don't use `SL.secrets.set` for this. It creates a collection secret with a **generated** ref
605
+ (`sec_…`) for integration flows, which a function can't name in its manifest.
606
+
607
+ ---
608
+
609
+ ## Scheduled functions (cron)
610
+
611
+ A `cron` function runs on a schedule, on **every collection that has the app installed**, with
612
+ `collection` authority (there's no caller). It's the shape for polling another system ("pull new
613
+ orders every 15 minutes"), nightly reconciles, digests and clean-ups.
614
+
615
+ ```jsonc
616
+ {
617
+ "name": "pullOrders",
618
+ "trigger": { "type": "cron", "schedule": "*/15 * * * *" },
619
+ "capabilities": ["sl:records:write", "network:api.example-shop.com", "secrets:shop-api-key"]
620
+ },
621
+ {
622
+ "name": "morningDigest",
623
+ "trigger": { "type": "cron", "schedule": "0 9 * * MON-FRI", "timezone": "Europe/London" },
624
+ "capabilities": ["sl:records:read", "network:hooks.slack.com", "secrets:slack-webhook"]
625
+ }
626
+ ```
627
+
628
+ - **`schedule`** — standard 5-field cron: minute, hour, day-of-month, month, day-of-week. Lists,
629
+ ranges and steps (`0,30`, `9-17`, `*/15`), month and day names (`JAN`, `MON-FRI`; Sunday is 0 or 7),
630
+ and `@hourly` / `@daily` / `@weekly` / `@monthly` / `@yearly`. When both day-of-month and
631
+ day-of-week are restricted, a day matching either fires (as in cron).
632
+ - **`timezone`** *(optional)* — an IANA zone like `"Europe/London"`; the schedule follows its
633
+ summer time. Default UTC.
634
+ - **At most every 5 minutes.** A faster schedule, a bad expression or an unknown zone fails the
635
+ release (`SCHEDULE_INVALID`), so it never silently doesn't run.
636
+
637
+ **What runs:** each installation runs the current release of the channel it's installed on, so a
638
+ test collection with the app on `dev` runs your dev build on its schedule. Disabled installs and
639
+ expired dev installs don't run.
640
+
641
+ **The event** your handler receives:
642
+
643
+ ```ts
644
+ { type: 'cron', schedule: '*/15 * * * *', timezone: null, scheduledAt: '2026-10-06T10:15:00.000Z' }
645
+ ```
646
+
647
+ `scheduledAt` is the minute the run was due, which is not always the moment it starts: runs queue,
648
+ and a late one still carries its own minute. Use it, not `Date.now()`, as the end of the window
649
+ you're syncing.
650
+
651
+ **Write it to be safe to run twice.** Each (collection, function, minute) is enqueued once, but a
652
+ run that fails part-way can be retried, and a slow run can still be going when the next one starts.
653
+ Keep a cursor (last `scheduledAt` or the other system's own cursor) in the app's records or config,
654
+ and upsert by the other system's ID instead of inserting blindly.
655
+
656
+ Each run is logged like any other function run, and a failure shows in the collection's Errors view.
657
+
658
+ ---
659
+
530
660
  ## Invoking an http function
531
661
 
532
662
  An `http` function is called by POSTing to the app-scoped functions endpoint on the
package/dist/http.d.ts CHANGED
@@ -71,6 +71,11 @@ export declare function initializeApi(options: {
71
71
  awaitAuth?: boolean;
72
72
  /** How long (ms) to wait for the async token before letting requests proceed anyway. Default 8000. */
73
73
  awaitAuthTimeoutMs?: number;
74
+ /**
75
+ * proxyMode only: how long (ms) a request waits for the parent host's reply before failing with a
76
+ * 504 SmartlinksApiError (errorCode PROXY_TIMEOUT). Default 90000. 0 = wait indefinitely.
77
+ */
78
+ proxyTimeoutMs?: number;
74
79
  iframeAutoResize?: boolean;
75
80
  logger?: Logger;
76
81
  /**
package/dist/http.js CHANGED
@@ -31,6 +31,10 @@ let baseURL = null;
31
31
  let apiKey = undefined;
32
32
  let bearerToken = undefined;
33
33
  let proxyMode = false;
34
+ // How long a request through the parent (proxyMode) waits for the host's reply. Without a limit, a host
35
+ // that never answers (no responder, a dropped message) leaves the call pending forever: no network
36
+ // request in the frame, no error. 90 s covers the longest platform call (a 60 s function deadline).
37
+ let proxyTimeoutMs = 90000;
34
38
  let ngrokSkipBrowserWarning = false;
35
39
  let extraHeadersGlobal = {};
36
40
  /**
@@ -522,6 +526,8 @@ export function initializeApi(options) {
522
526
  });
523
527
  }
524
528
  proxyMode = !!options.proxyMode;
529
+ if (options.proxyTimeoutMs !== undefined)
530
+ proxyTimeoutMs = Math.max(0, Number(options.proxyTimeoutMs) || 0);
525
531
  // Auto-enable ngrok skip header if domain contains .ngrok.io and user did not explicitly set the flag.
526
532
  // Infer ngrok usage from common domains (.ngrok.io or .ngrok-free.dev)
527
533
  const inferredNgrok = /(\.ngrok\.io|\.ngrok-free\.dev)(\b|\/)/i.test(baseURL);
@@ -1082,11 +1088,30 @@ async function proxyRequest(method, path, body, headers, options) {
1082
1088
  };
1083
1089
  logDebug('[smartlinks] proxy:postMessage', { id, method, path, headers: headers ? redactHeaders(headers) : undefined, hasBody: !!body });
1084
1090
  return new Promise((resolve, reject) => {
1085
- proxyPending[id] = { resolve, reject };
1091
+ proxyPending[id] = withProxyTimeout(id, `${method} ${path}`, resolve, reject);
1086
1092
  window.parent.postMessage(msg, "*");
1087
- // Optionally: add a timeout here to reject if no response
1088
1093
  });
1089
1094
  }
1095
+ /**
1096
+ * Wrap a pending proxy request's resolve/reject with the proxy timeout (proxyTimeoutMs): if the host
1097
+ * hasn't answered by then, the request fails with a 504 PROXY_TIMEOUT instead of hanging forever.
1098
+ */
1099
+ function withProxyTimeout(id, what, resolve, reject) {
1100
+ if (!(proxyTimeoutMs > 0))
1101
+ return { resolve, reject };
1102
+ const timer = setTimeout(() => {
1103
+ if (!proxyPending[id])
1104
+ return;
1105
+ delete proxyPending[id];
1106
+ const message = `The host page did not answer ${what} within ${proxyTimeoutMs >= 1000 ? `${Math.round(proxyTimeoutMs / 1000)}s` : `${proxyTimeoutMs}ms`} (proxyMode). The parent may not be handling SmartLinks proxy requests.`;
1107
+ logDebug('[smartlinks] proxy:timeout', { id, what });
1108
+ reject(new SmartlinksApiError(message, 504, { code: 504, errorCode: 'PROXY_TIMEOUT', message }));
1109
+ }, proxyTimeoutMs);
1110
+ return {
1111
+ resolve: (data) => { clearTimeout(timer); resolve(data); },
1112
+ reject: (err) => { clearTimeout(timer); reject(err); },
1113
+ };
1114
+ }
1090
1115
  /**
1091
1116
  * Upload a FormData payload via proxy with progress events using chunked postMessage.
1092
1117
  * Parent is expected to implement the counterpart protocol.
@@ -1794,8 +1819,7 @@ export async function sendCustomProxyMessage(request, params) {
1794
1819
  };
1795
1820
  logDebug('[smartlinks] proxy:custom postMessage', { id, request, params: safeBodyPreview(params) });
1796
1821
  return new Promise((resolve, reject) => {
1797
- proxyPending[id] = { resolve, reject };
1822
+ proxyPending[id] = withProxyTimeout(id, `custom request ${request}`, resolve, reject);
1798
1823
  window.parent.postMessage(msg, "*");
1799
- // Optionally: add a timeout here to reject if no response
1800
1824
  });
1801
1825
  }
package/dist/openapi.yaml CHANGED
@@ -19086,6 +19086,8 @@ components:
19086
19086
  type: string
19087
19087
  schedule:
19088
19088
  type: string
19089
+ timezone:
19090
+ type: string
19089
19091
  route:
19090
19092
  type: string
19091
19093
  methods:
@@ -19124,6 +19126,8 @@ components:
19124
19126
  enum:
19125
19127
  - collection
19126
19128
  - global
19129
+ inputs:
19130
+ $ref: "#/components/schemas/AppFunctionInputs"
19127
19131
  apiVersion:
19128
19132
  type: string
19129
19133
  handler:
@@ -19233,6 +19237,8 @@ components:
19233
19237
  fetch:
19234
19238
  type: object
19235
19239
  additionalProperties: true
19240
+ inputs:
19241
+ $ref: "#/components/schemas/ServerFunctionInputs"
19236
19242
  required:
19237
19243
  - collectionId
19238
19244
  - appId
@@ -19240,6 +19246,34 @@ components:
19240
19246
  - secrets
19241
19247
  - caller
19242
19248
  - fetch
19249
+ - inputs
19250
+ AppFunctionInputs:
19251
+ type: object
19252
+ properties:
19253
+ entity:
19254
+ type: boolean
19255
+ collection:
19256
+ type: boolean
19257
+ appConfig:
19258
+ type: boolean
19259
+ product:
19260
+ type: object
19261
+ additionalProperties: true
19262
+ ServerFunctionInputs:
19263
+ type: object
19264
+ properties:
19265
+ entity:
19266
+ type: object
19267
+ additionalProperties: true
19268
+ collection:
19269
+ type: object
19270
+ additionalProperties: true
19271
+ appConfig:
19272
+ type: object
19273
+ additionalProperties: true
19274
+ product:
19275
+ type: object
19276
+ additionalProperties: true
19243
19277
  ServerFunctionHttpEvent:
19244
19278
  type: object
19245
19279
  properties:
@@ -30749,6 +30783,11 @@ components:
30749
30783
  type: string
30750
30784
  host:
30751
30785
  type: string
30786
+ AppSecretOptions:
30787
+ type: object
30788
+ properties:
30789
+ appId:
30790
+ type: string
30752
30791
  AllocateSequenceInput:
30753
30792
  type: object
30754
30793
  properties:
@@ -1,4 +1,4 @@
1
- import type { AppFunctionDef, ServerFunctionContext } from '../types/appManifest.js';
1
+ import type { AppFunctionDef, ServerFunctionContext, ServerFunctionInputs } from '../types/appManifest.js';
2
2
  export declare class CapabilityError extends Error {
3
3
  capability: string;
4
4
  code: string;
@@ -62,6 +62,8 @@ export interface CreateFunctionTestContextOptions {
62
62
  sl?: TestSlImpl;
63
63
  /** Backing fetch (defaults to global fetch). Still gated by the `network` capability. */
64
64
  fetch?: typeof fetch;
65
+ /** What the platform would have prefetched as `ctx.inputs` (e.g. `{ entity: product }`). Default `{}`. */
66
+ inputs?: ServerFunctionInputs;
65
67
  }
66
68
  export interface FunctionTestContext extends ServerFunctionContext {
67
69
  /** Captured log lines (also written via ctx.log). */
@@ -176,6 +176,7 @@ export function createFunctionTestContext(opts) {
176
176
  log: (message, data) => {
177
177
  logs.push(Object.assign({ at: new Date().toISOString(), message: String(message) }, (data ? { data } : {})));
178
178
  },
179
+ inputs: Object.freeze(Object.assign({}, (opts.inputs || {}))),
179
180
  logs,
180
181
  };
181
182
  }
@@ -213,8 +213,13 @@ export interface AppFunctionTrigger {
213
213
  type: AppFunctionTriggerType;
214
214
  /** `event`: event types this function subscribes to, e.g. `['interaction.submitted:comp-entry']`. */
215
215
  eventTypes?: string[];
216
- /** `cron`: standard 5-field crontab expression, evaluated in UTC. */
216
+ /**
217
+ * `cron`: standard 5-field crontab expression (`"0,30 * * * *"`, `"0 9 * * MON-FRI"`, or `@hourly` /
218
+ * `@daily` / `@weekly` / `@monthly`), evaluated in UTC unless `timezone` is set. At most every 5 minutes.
219
+ */
217
220
  schedule?: string;
221
+ /** `cron`: IANA time zone the schedule is read in, e.g. `"Europe/London"` (follows summer time). Default UTC. */
222
+ timezone?: string;
218
223
  /** `http`: URL path segment the function is exposed at (defaults to the function `name`). */
219
224
  route?: string;
220
225
  /** `http`: accepted HTTP methods (defaults to `['POST']`). */
@@ -268,6 +273,14 @@ export interface AppFunctionDef {
268
273
  * Per-call `{ scope }` and the `appData.global` / `appData.collection` handles override this.
269
274
  */
270
275
  dataScope?: 'collection' | 'global';
276
+ /**
277
+ * Data the platform fetches BEFORE the run and hands over as `ctx.inputs` — no round trip from your
278
+ * code (for isolated functions, no network hop). Same authority and capabilities as the equivalent
279
+ * `ctx.sl` read; a missing, denied or oversized (> 256 KB) input arrives as `null`, never an error.
280
+ * Event-trigger functions get `entity` (the record the event is about) by default; `entity: false`
281
+ * opts out. See server-functions.md → "Inputs".
282
+ */
283
+ inputs?: AppFunctionInputs;
271
284
  /** SDK/manifest API version this function targets (pinned for runtime compatibility). */
272
285
  apiVersion?: string;
273
286
  /** Exported handler name in the functions bundle. Defaults to `name`. */
@@ -403,6 +416,29 @@ export interface ServerFunctionContext {
403
416
  fetch: typeof fetch;
404
417
  /** Structured logging captured into run telemetry. */
405
418
  log: (message: string, data?: Record<string, any>) => void;
419
+ /** Data prefetched for this run, per the definition's `inputs` (read-only; `{}` when none). */
420
+ inputs: Readonly<ServerFunctionInputs>;
421
+ }
422
+ /** What a function asks to have prefetched (`AppFunctionDef.inputs`). */
423
+ export interface AppFunctionInputs {
424
+ /** Event triggers: the record the event is about — product (needs `sl:products:read`), app record of
425
+ * this app (`sl:records:read`), or the interaction event itself. On by default; `false` opts out. */
426
+ entity?: boolean;
427
+ /** The collection's public record. */
428
+ collection?: boolean;
429
+ /** This app's config for the collection — e.g. its `interactionIds`. Needs `sl:data:read`. */
430
+ appConfig?: boolean;
431
+ /** Http triggers: a product named by the request. Needs `sl:products:read`. */
432
+ product?: {
433
+ from: `body.${string}` | `query.${string}` | 'path';
434
+ };
435
+ }
436
+ /** `ctx.inputs` — what was prefetched for this run (keys you declared; `null` when unavailable). */
437
+ export interface ServerFunctionInputs {
438
+ entity?: Record<string, any> | null;
439
+ collection?: Record<string, any> | null;
440
+ appConfig?: Record<string, any> | null;
441
+ product?: Record<string, any> | null;
406
442
  }
407
443
  /** The HTTP request handed to an `http`-trigger function as `event`. */
408
444
  export interface ServerFunctionHttpEvent<TBody = any> {
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.40 | Generated: 2026-10-05T14:51:09.158Z
3
+ Version: 2.0.42 | Generated: 2026-10-06T15:17:04.562Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -2523,6 +2523,7 @@ interface AppFunctionTrigger {
2523
2523
  type: AppFunctionTriggerType;
2524
2524
  eventTypes?: string[];
2525
2525
  schedule?: string;
2526
+ timezone?: string;
2526
2527
  route?: string;
2527
2528
  methods?: Array<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'>;
2528
2529
  }
@@ -2539,6 +2540,7 @@ interface AppFunctionDef {
2539
2540
  elevated?: boolean;
2540
2541
  capabilities?: string[];
2541
2542
  dataScope?: 'collection' | 'global';
2543
+ inputs?: AppFunctionInputs;
2542
2544
  apiVersion?: string;
2543
2545
  handler?: string;
2544
2546
  agent?: AppFunctionAgentExposure;
@@ -2622,6 +2624,27 @@ interface ServerFunctionContext {
2622
2624
  caller: ServerFunctionCaller;
2623
2625
  fetch: typeof fetch;
2624
2626
  log: (message: string, data?: Record<string, any>) => void;
2627
+ inputs: Readonly<ServerFunctionInputs>;
2628
+ }
2629
+ ```
2630
+
2631
+ **AppFunctionInputs** (interface)
2632
+ ```typescript
2633
+ interface AppFunctionInputs {
2634
+ entity?: boolean;
2635
+ collection?: boolean;
2636
+ appConfig?: boolean;
2637
+ product?: { from: `body.${string}` | `query.${string}` | 'path' };
2638
+ }
2639
+ ```
2640
+
2641
+ **ServerFunctionInputs** (interface)
2642
+ ```typescript
2643
+ interface ServerFunctionInputs {
2644
+ entity?: Record<string, any> | null;
2645
+ collection?: Record<string, any> | null;
2646
+ appConfig?: Record<string, any> | null;
2647
+ product?: Record<string, any> | null;
2625
2648
  }
2626
2649
  ```
2627
2650
 
@@ -9697,6 +9720,15 @@ interface FunctionSiteUrlOptions {
9697
9720
 
9698
9721
  **FunctionCallResult** = `any`
9699
9722
 
9723
+ ### secrets (api)
9724
+
9725
+ **AppSecretOptions** (interface)
9726
+ ```typescript
9727
+ interface AppSecretOptions {
9728
+ appId?: string
9729
+ }
9730
+ ```
9731
+
9700
9732
  ### sequence (api)
9701
9733
 
9702
9734
  **AllocateSequenceInput** (interface)
@@ -12026,11 +12058,20 @@ Fetch + extract a web page: clean markdown, page metadata, and any schema.org JS
12026
12058
 
12027
12059
  ### secrets
12028
12060
 
12061
+ **put**(collectionId: string, name: string, value: string, opts: AppSecretOptions = {}) → `Promise<SetSecretResult>`
12062
+ Save this app's secret `name` on the collection (create or replace) — what the app's server functions read with `ctx.secrets.get(name)` (declare `secrets:<name>` in the manifest). Call it from your admin screen as a collection admin. Write-only: returns `{ ref: name, hint }`, never the value. PUT /app/:appId/secrets/:name
12063
+
12064
+ **listOwn**(collectionId: string, opts: AppSecretOptions = {}) → `Promise<SecretList>`
12065
+ This app's secrets on the collection: names + masked hints (never values). GET /app/:appId/secrets
12066
+
12067
+ **removeOwn**(collectionId: string, name: string, opts: AppSecretOptions = {}) → `Promise<`
12068
+ Delete this app's secret `name`. DELETE /app/:appId/secrets/:name
12069
+
12029
12070
  **list**(collectionId: string, query: ListSecretsQuery = {}) → `Promise<SecretList>`
12030
12071
  List secrets as refs + masked hints + metadata (never values). GET /secrets
12031
12072
 
12032
12073
  **set**(collectionId: string, input: SetSecretInput) → `Promise<SetSecretResult>`
12033
- Create a secret. POST /secrets → { ref, hint }. Store the ref on a flow.
12074
+ Create a collection secret with a GENERATED ref. POST /secrets → { ref, hint }. Store the ref on a flow. For a secret your app's server functions read by name, use `put` instead.
12034
12075
 
12035
12076
  **get**(collectionId: string, ref: string) → `Promise<SecretMeta>`
12036
12077
  Metadata for one secret (never the value). GET /secrets/:ref