@apostra/sdk 0.39.0 → 0.41.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.
@@ -52,8 +52,8 @@ export declare function exchangeCliLoginCode(options: Options, discovery: CliLog
52
52
  }): Promise<CliLoginTokens>;
53
53
  /**
54
54
  * Return a usable login for `baseUrl`, refreshing it if the access token is
55
- * about to expire. After taking the lock the file is read again: if another
56
- * process refreshed first, its token is used and no request is made.
55
+ * about to expire. After taking the lock the credential store is read again:
56
+ * if another process refreshed first, its token is used and no request is made.
57
57
  */
58
58
  export declare function refreshCliLogin(baseUrl: string, options?: Options): Promise<CliLoginEntry>;
59
59
  /**
package/dist/cli-login.js CHANGED
@@ -25,6 +25,7 @@ function node() {
25
25
  fs: get('node:fs'),
26
26
  os: get('node:os'),
27
27
  path: get('node:path'),
28
+ module: get('node:module'),
28
29
  };
29
30
  }
30
31
  const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -64,27 +65,39 @@ function parseEntry(value) {
64
65
  if (!isRecord(value))
65
66
  return undefined;
66
67
  const text = ['issuer', 'resource', 'clientId', 'accessToken', 'refreshToken'];
68
+ const expiresAt = typeof value.expiresAt === 'string' &&
69
+ !Number.isNaN(Date.parse(value.expiresAt))
70
+ ? new Date(value.expiresAt).toISOString()
71
+ : undefined;
67
72
  if (!text.every((k) => typeof value[k] === 'string' && value[k]) ||
68
- typeof value.expiresAt !== 'string' ||
69
- Number.isNaN(Date.parse(value.expiresAt)) ||
73
+ !expiresAt ||
70
74
  typeof value.accountId !== 'string' ||
71
75
  !Array.isArray(value.scopes) ||
72
76
  !value.scopes.every((s) => typeof s === 'string'))
73
77
  return undefined;
74
- return value;
78
+ // Credential stores round-trip text. Canonicalise at their boundary so a
79
+ // value with sub-millisecond precision cannot oscillate between clients.
80
+ return { ...value, expiresAt };
75
81
  }
76
- function readAll() {
82
+ function readFileStore() {
77
83
  const { fs } = node();
78
84
  try {
79
85
  const file = JSON.parse(fs.readFileSync(credentialsPath(), 'utf8'));
80
- return isRecord(file) && isRecord(file.logins) ? file.logins : {};
86
+ if (!isRecord(file) || !isRecord(file.logins))
87
+ return { logins: {}, authoritative: new Set() };
88
+ return {
89
+ logins: file.logins,
90
+ authoritative: new Set(Array.isArray(file.authoritativeFileLogins)
91
+ ? file.authoritativeFileLogins.filter((resource) => typeof resource === 'string')
92
+ : []),
93
+ };
81
94
  }
82
95
  catch {
83
- return {};
96
+ return { logins: {}, authoritative: new Set() };
84
97
  }
85
98
  }
86
99
  /** Atomic (temp file + rename), directory 0700, file 0600. */
87
- function writeAll(logins) {
100
+ function writeFileLogins(logins, authoritative = new Set()) {
88
101
  const { fs, path } = node();
89
102
  const file = credentialsPath();
90
103
  const dir = path.dirname(file);
@@ -97,15 +110,179 @@ function writeAll(logins) {
97
110
  const temp = `${file}.${proc().pid}.${Math.random().toString(36).slice(2)}.tmp`;
98
111
  const fd = fs.openSync(temp, 'wx', 0o600);
99
112
  try {
100
- fs.writeSync(fd, `${JSON.stringify({ version: 1, logins }, null, 2)}\n`);
113
+ fs.writeSync(fd, `${JSON.stringify({
114
+ version: 1,
115
+ logins,
116
+ ...(authoritative.size > 0
117
+ ? {
118
+ authoritativeFileLogins: [...authoritative]
119
+ .filter((resource) => resource in logins)
120
+ .sort(),
121
+ }
122
+ : {}),
123
+ }, null, 2)}\n`);
101
124
  }
102
125
  finally {
103
126
  fs.closeSync(fd);
104
127
  }
105
128
  fs.renameSync(temp, file);
106
129
  }
130
+ const keyringService = 'Apostra CLI login';
131
+ /**
132
+ * Resolve the native binding only when this Node-only feature is used. A static
133
+ * import would make the browser SDK bundle a native module.
134
+ */
135
+ function keyringEntry(resource) {
136
+ const { module } = node();
137
+ const required = module.createRequire(import.meta.url)('@napi-rs/keyring');
138
+ if (!isRecord(required) || typeof required.Entry !== 'function')
139
+ throw new Error('Apostra credential-store binding is unavailable');
140
+ const Entry = required.Entry;
141
+ // Do not accept keyutils on Linux. It is ephemeral and is not the requested
142
+ // Secret Service store, so an inaccessible service deliberately uses the
143
+ // documented 0600-file fallback instead.
144
+ return new Entry(keyringService, resource, proc().platform === 'linux'
145
+ ? { linux: { store: 'secret-service' } }
146
+ : undefined);
147
+ }
148
+ function readSecureLogin(resource) {
149
+ try {
150
+ const password = keyringEntry(resource).getPassword();
151
+ if (!password)
152
+ return { available: true };
153
+ return { available: true, entry: parseEntry(JSON.parse(password)) };
154
+ }
155
+ catch {
156
+ return { available: false };
157
+ }
158
+ }
159
+ function writeSecureLogin(resource, entry) {
160
+ try {
161
+ keyringEntry(resource).setPassword(JSON.stringify(entry));
162
+ return true;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
168
+ function deleteSecureLogin(resource) {
169
+ try {
170
+ return keyringEntry(resource).deletePassword();
171
+ }
172
+ catch {
173
+ return undefined;
174
+ }
175
+ }
176
+ function warnFileFallback() {
177
+ const { fs, path } = node();
178
+ const dir = path.dirname(credentialsPath());
179
+ const marker = path.join(dir, '.file-store-warning');
180
+ try {
181
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
182
+ fs.chmodSync(dir, 0o700);
183
+ const fd = fs.openSync(marker, 'wx', 0o600);
184
+ fs.closeSync(fd);
185
+ }
186
+ catch (error) {
187
+ if (errorCode(error) === 'EEXIST')
188
+ return;
189
+ // If the marker cannot be written, warn for this process rather than
190
+ // silently accepting plaintext storage.
191
+ }
192
+ proc().emitWarning?.(`Apostra could not access your OS credential store. Login tokens are stored in ${credentialsPath()} with 0600 permissions. Use Keychain, Credential Manager, or Secret Service when available.`);
193
+ }
194
+ /**
195
+ * Move every legacy entry while holding the normal refresh lock. Reads that
196
+ * arrive first use a short synchronous instance of the same lock; contenders
197
+ * keep reading the legacy file until the migration finishes.
198
+ */
199
+ function migrateFileLogins(lockHeld = false) {
200
+ const { fs, path } = node();
201
+ const fileStore = readFileStore();
202
+ const fileLogins = fileStore.logins;
203
+ const entries = Object.entries(fileLogins).flatMap(([resource, value]) => {
204
+ const entry = parseEntry(value);
205
+ return entry ? [[resource, entry]] : [];
206
+ });
207
+ if (entries.length === 0)
208
+ return false;
209
+ const lock = `${credentialsPath()}.lock`;
210
+ let owner;
211
+ if (!lockHeld) {
212
+ fs.mkdirSync(path.dirname(lock), { recursive: true, mode: 0o700 });
213
+ owner = `migration:${proc().pid}:${globalThis.crypto.randomUUID()}`;
214
+ try {
215
+ const fd = fs.openSync(lock, 'wx', 0o600);
216
+ try {
217
+ fs.writeSync(fd, owner);
218
+ }
219
+ finally {
220
+ fs.closeSync(fd);
221
+ }
222
+ }
223
+ catch {
224
+ return false;
225
+ }
226
+ }
227
+ try {
228
+ const remaining = { ...fileLogins };
229
+ const remainingAuthoritative = new Set(fileStore.authoritative);
230
+ for (const [resource, entry] of entries) {
231
+ // A keyring value is authoritative: preserving it avoids overwriting a
232
+ // token another process has already refreshed during an interrupted
233
+ // earlier migration, unless the protected fallback marks a replacement
234
+ // as authoritative after a failed secure-store rotation.
235
+ const stored = readSecureLogin(resource);
236
+ if (!fileStore.authoritative.has(resource) && stored.entry) {
237
+ delete remaining[resource];
238
+ remainingAuthoritative.delete(resource);
239
+ continue;
240
+ }
241
+ if (!stored.available || !writeSecureLogin(resource, entry)) {
242
+ // Retain only entries that still need migration. Otherwise a
243
+ // successfully migrated authoritative login could shadow its next
244
+ // secure-store refresh while another login remains in the file.
245
+ writeFileLogins(remaining, remainingAuthoritative);
246
+ return false;
247
+ }
248
+ delete remaining[resource];
249
+ remainingAuthoritative.delete(resource);
250
+ }
251
+ writeFileLogins(remaining, remainingAuthoritative);
252
+ return true;
253
+ }
254
+ finally {
255
+ if (owner) {
256
+ try {
257
+ if (fs.readFileSync(lock, 'utf8') === owner)
258
+ fs.rmSync(lock, { force: true });
259
+ }
260
+ catch {
261
+ // The regular lock protocol protects against removing another owner.
262
+ }
263
+ }
264
+ }
265
+ }
107
266
  export function readCliLogin(baseUrl) {
108
- return parseEntry(readAll()[normalizeBaseUrl(baseUrl)]);
267
+ const resource = normalizeBaseUrl(baseUrl);
268
+ const fileStore = readFileStore();
269
+ const fileEntry = parseEntry(fileStore.logins[resource]);
270
+ // A refresh token can rotate before a credential store reports an update
271
+ // failure. This marker makes its 0600 fallback authoritative over the now
272
+ // invalid secure-store value until a later read can migrate it safely.
273
+ if (fileStore.authoritative.has(resource) && fileEntry)
274
+ return fileEntry;
275
+ const secure = readSecureLogin(resource);
276
+ if (secure.entry)
277
+ return secure.entry;
278
+ const fileLogins = fileStore.logins;
279
+ if (Object.keys(fileLogins).length === 0)
280
+ return undefined;
281
+ if (migrateFileLogins())
282
+ return readSecureLogin(resource).entry;
283
+ if (!secure.available)
284
+ warnFileFallback();
285
+ return fileEntry;
109
286
  }
110
287
  /** Like `readCliLogin`, but never throws: for the SDK's default credential chain. */
111
288
  export function storedCliLogin(baseUrl) {
@@ -117,6 +294,43 @@ export function storedCliLogin(baseUrl) {
117
294
  }
118
295
  }
119
296
  export const isFresh = (entry) => Date.parse(entry.expiresAt) - FRESH_MARGIN_MS > Date.now();
297
+ /**
298
+ * Remove a lock whose heartbeat stopped, without ever removing a live one.
299
+ * Only the holder of the takeover guard checks and removes. Checking again
300
+ * under the guard means a lock another process took after the first check is
301
+ * fresh and is left alone. A guard a crashed process left behind goes stale
302
+ * like a lock does. Returns whether this call held the guard, so the caller
303
+ * retries at once; otherwise it waits like any other contender.
304
+ */
305
+ function breakStaleLock(fs, lock) {
306
+ const guard = `${lock}.takeover`;
307
+ try {
308
+ fs.closeSync(fs.openSync(guard, 'wx', 0o600));
309
+ }
310
+ catch (error) {
311
+ if (errorCode(error) !== 'EEXIST')
312
+ throw error;
313
+ try {
314
+ if (Date.now() - fs.statSync(guard).mtimeMs > LOCK_STALE_MS)
315
+ fs.rmSync(guard, { force: true });
316
+ }
317
+ catch {
318
+ // The guard went away between the two calls; the caller retries.
319
+ }
320
+ return false;
321
+ }
322
+ try {
323
+ if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS)
324
+ fs.rmSync(lock, { force: true });
325
+ }
326
+ catch {
327
+ // The lock went away; the caller retries.
328
+ }
329
+ finally {
330
+ fs.rmSync(guard, { force: true });
331
+ }
332
+ return true;
333
+ }
120
334
  /**
121
335
  * Serialise every read-modify-write of the store, across processes. Refresh
122
336
  * tokens rotate, so two processes must never present the same one outside the
@@ -154,11 +368,9 @@ export async function withCliLoginLock(fn) {
154
368
  if (errorCode(error) !== 'EEXIST')
155
369
  throw error;
156
370
  try {
157
- const stale = ownerOf();
158
371
  if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) {
159
- if (ownerOf() === stale)
160
- fs.rmSync(lock, { force: true });
161
- continue;
372
+ if (breakStaleLock(fs, lock))
373
+ continue;
162
374
  }
163
375
  }
164
376
  catch {
@@ -190,7 +402,25 @@ export async function withCliLoginLock(fn) {
190
402
  }
191
403
  /** Save a login. Call inside `withCliLoginLock`. */
192
404
  export function writeCliLogin(baseUrl, entry) {
193
- writeAll({ ...readAll(), [normalizeBaseUrl(baseUrl)]: entry });
405
+ const resource = normalizeBaseUrl(baseUrl);
406
+ const normalised = parseEntry(entry);
407
+ if (!normalised)
408
+ throw new TypeError('Apostra login is invalid');
409
+ // Callers hold the cross-process lock, so the legacy file cannot overwrite
410
+ // a rotated credential while it is being migrated.
411
+ migrateFileLogins(true);
412
+ const existing = readSecureLogin(resource);
413
+ if (writeSecureLogin(resource, normalised))
414
+ return;
415
+ warnFileFallback();
416
+ const fileStore = readFileStore();
417
+ // A refresh token may rotate while the OS credential store disappears. Its
418
+ // next read could reveal the older credential, so the protected fallback is
419
+ // authoritative both when we saw an old entry and when availability is
420
+ // unknown.
421
+ if (!existing.available || existing.entry)
422
+ fileStore.authoritative.add(resource);
423
+ writeFileLogins({ ...fileStore.logins, [resource]: normalised }, fileStore.authoritative);
194
424
  }
195
425
  async function send(fetcher, url, init = {}) {
196
426
  return fetcher(url, {
@@ -287,8 +517,8 @@ export function exchangeCliLoginCode(options, discovery, resource, grant) {
287
517
  }
288
518
  /**
289
519
  * Return a usable login for `baseUrl`, refreshing it if the access token is
290
- * about to expire. After taking the lock the file is read again: if another
291
- * process refreshed first, its token is used and no request is made.
520
+ * about to expire. After taking the lock the credential store is read again:
521
+ * if another process refreshed first, its token is used and no request is made.
292
522
  */
293
523
  export async function refreshCliLogin(baseUrl, options = {}) {
294
524
  const key = normalizeBaseUrl(baseUrl);
@@ -350,8 +580,14 @@ export async function logoutCliLogin(baseUrl, { fetch: f = globalThis.fetch } =
350
580
  catch {
351
581
  revoked = false;
352
582
  }
353
- const { [key]: _removed, ...rest } = readAll();
354
- writeAll(rest);
583
+ deleteSecureLogin(key);
584
+ // A prior fallback login can still be present even when the credential
585
+ // store has no entry (or is available again). Remove it unconditionally:
586
+ // otherwise its refresh token could be migrated back after logout.
587
+ const fileStore = readFileStore();
588
+ const { [key]: _removed, ...rest } = fileStore.logins;
589
+ fileStore.authoritative.delete(key);
590
+ writeFileLogins(rest, fileStore.authoritative);
355
591
  return { found: true, revoked };
356
592
  });
357
593
  }
@@ -43,12 +43,12 @@ export type GetSellerReportingReceiptsError = Wire.GetSellerReportingReceiptsErr
43
43
  export type GetStatusInput = Wire.GetStatusData['body'];
44
44
  export type GetStatusResult = Wire.GetStatusResponses[200]['data'];
45
45
  export type GetStatusError = Wire.GetStatusErrors[keyof Wire.GetStatusErrors];
46
+ export type LogEventInput = OptionalWireField<Wire.LogEventData['body'], "idempotencyKey">;
47
+ export type LogEventResult = Wire.LogEventResponses[200]['data'];
48
+ export type LogEventError = Wire.LogEventErrors[keyof Wire.LogEventErrors];
46
49
  export type OpenAdvertisersPageInput = Wire.OpenAdvertisersPageData['body'];
47
50
  export type OpenAdvertisersPageResult = Wire.OpenAdvertisersPageResponses[200]['data'];
48
51
  export type OpenAdvertisersPageError = Wire.OpenAdvertisersPageErrors[keyof Wire.OpenAdvertisersPageErrors];
49
- export type OpenAgentCertificationPageInput = Wire.OpenAgentCertificationPageData['body'];
50
- export type OpenAgentCertificationPageResult = Wire.OpenAgentCertificationPageResponses[200]['data'];
51
- export type OpenAgentCertificationPageError = Wire.OpenAgentCertificationPageErrors[keyof Wire.OpenAgentCertificationPageErrors];
52
52
  export type OpenAgentPageInput = Wire.OpenAgentPageData['body'];
53
53
  export type OpenAgentPageResult = Wire.OpenAgentPageResponses[200]['data'];
54
54
  export type OpenAgentPageError = Wire.OpenAgentPageErrors[keyof Wire.OpenAgentPageErrors];
@@ -276,14 +276,14 @@ export interface OperationTypes {
276
276
  input: GetStatusInput;
277
277
  result: GetStatusResult;
278
278
  };
279
+ "log_event": {
280
+ input: LogEventInput;
281
+ result: LogEventResult;
282
+ };
279
283
  "open_advertisers_page": {
280
284
  input: OpenAdvertisersPageInput;
281
285
  result: OpenAdvertisersPageResult;
282
286
  };
283
- "open_agent_certification_page": {
284
- input: OpenAgentCertificationPageInput;
285
- result: OpenAgentCertificationPageResult;
286
- };
287
287
  "open_agent_page": {
288
288
  input: OpenAgentPageInput;
289
289
  result: OpenAgentPageResult;
@@ -616,14 +616,23 @@ export declare class Apostra extends Transport {
616
616
  /** Show a seller what reporting we received for one buyer media buy, whether each receipt was accepted, and the current computed daily totals. Opens the Media Buy Timeline Glance. Returns the data together with response headers and request ID. */
617
617
  getSellerReportingReceiptsWithResponse(input: GetSellerReportingReceiptsInput, options?: RequestOptions): Promise<ResponseDetails<GetSellerReportingReceiptsResult>>;
618
618
  /**
619
- * Current account, state, blockers, exact fixes, and reachable accounts. For seller demand, use it to tell whether readiness stops requests before source calls; read before diagnosing source health, empty responses, or demand. A Developer account adds each agent's stage, blocker, and next call.
619
+ * Current account, state, blockers, exact fixes, accounts to pick from. For seller demand, tells if readiness stops requests before source calls; read before diagnosing source health, empty responses, or demand. A Developer account adds each agent's stage, blocker, next call.
620
620
  *
621
621
  * @example
622
622
  * await client.getStatus(input)
623
623
  */
624
624
  getStatus(input?: GetStatusInput, options?: RequestOptions): Promise<GetStatusResult>;
625
- /** Current account, state, blockers, exact fixes, and reachable accounts. For seller demand, use it to tell whether readiness stops requests before source calls; read before diagnosing source health, empty responses, or demand. A Developer account adds each agent's stage, blocker, and next call. Returns the data together with response headers and request ID. */
625
+ /** Current account, state, blockers, exact fixes, accounts to pick from. For seller demand, tells if readiness stops requests before source calls; read before diagnosing source health, empty responses, or demand. A Developer account adds each agent's stage, blocker, next call. Returns the data together with response headers and request ID. */
626
626
  getStatusWithResponse(input?: GetStatusInput, options?: RequestOptions): Promise<ResponseDetails<GetStatusResult>>;
627
+ /**
628
+ * Send up to 10,000 AdCP conversion events (log_event) to an event source the advertiser registered with save_event_source. Events must also pass the v2 rules: no raw email or phone in user_match, each event_type + event_id once per call. One invalid event refuses the whole call.
629
+ *
630
+ * @example
631
+ * await client.logEvent(input, { idempotencyKey: 'stable-key' })
632
+ */
633
+ logEvent(input: LogEventInput, options: WriteRequestOptions): Promise<LogEventResult>;
634
+ /** Send up to 10,000 AdCP conversion events (log_event) to an event source the advertiser registered with save_event_source. Events must also pass the v2 rules: no raw email or phone in user_match, each event_type + event_id once per call. One invalid event refuses the whole call. Returns the data together with response headers and request ID. */
635
+ logEventWithResponse(input: LogEventInput, options: WriteRequestOptions): Promise<ResponseDetails<LogEventResult>>;
627
636
  /**
628
637
  * Open the Advertisers Page: every advertiser with its campaign and draft counts and one next action each. Use it for "show my advertisers" or "where do I start"; the Page self-fetches. To add or set up an advertiser, pass intent: "add". For a text answer use search(kind: "advertiser").
629
638
  *
@@ -633,15 +642,6 @@ export declare class Apostra extends Transport {
633
642
  openAdvertisersPage(input: OpenAdvertisersPageInput, options?: RequestOptions): Promise<OpenAdvertisersPageResult>;
634
643
  /** Open the Advertisers Page: every advertiser with its campaign and draft counts and one next action each. Use it for "show my advertisers" or "where do I start"; the Page self-fetches. To add or set up an advertiser, pass intent: "add". For a text answer use search(kind: "advertiser"). Returns the data together with response headers and request ID. */
635
644
  openAdvertisersPageWithResponse(input: OpenAdvertisersPageInput, options?: RequestOptions): Promise<ResponseDetails<OpenAdvertisersPageResult>>;
636
- /**
637
- * Open one agent’s Test & certify Page: live certification per use case, recent test runs, AdCP 4.0 readiness and capability declarations. Read-only.
638
- *
639
- * @example
640
- * await client.openAgentCertificationPage(input)
641
- */
642
- openAgentCertificationPage(input: OpenAgentCertificationPageInput, options?: RequestOptions): Promise<OpenAgentCertificationPageResult>;
643
- /** Open one agent’s Test & certify Page: live certification per use case, recent test runs, AdCP 4.0 readiness and capability declarations. Read-only. Returns the data together with response headers and request ID. */
644
- openAgentCertificationPageWithResponse(input: OpenAgentCertificationPageInput, options?: RequestOptions): Promise<ResponseDetails<OpenAgentCertificationPageResult>>;
645
645
  /**
646
646
  * Open one agent owned by this Developer account.
647
647
  *
@@ -724,13 +724,13 @@ export declare class Apostra extends Transport {
724
724
  /** Open the buyer Creative Engines Page. Optionally focus an engine, connection, or its secure setup control; the buyer starts setup in the Page, and this does not authorize a provider or start generation. Returns the data together with response headers and request ID. */
725
725
  openCreativeEnginesPageWithResponse(input: OpenCreativeEnginesPageInput, options?: RequestOptions): Promise<ResponseDetails<OpenCreativeEnginesPageResult>>;
726
726
  /**
727
- * Open Creative Library. Use search for a plain object answer. Composer needs campaignId; assembly needs no Creative Engine. To save a draft, use exactly one format: a canonical format such as image, hosted video, or hosted audio, or a seller format.
727
+ * Open Creative Library. Use search for a plain object answer. The composer can start from an advertiser or a campaign and does not need a Creative Engine. To save a draft, use exactly one format: a canonical format such as image, hosted video, or hosted audio, or a seller format.
728
728
  *
729
729
  * @example
730
730
  * await client.openCreativeLibrary(input, { idempotencyKey: 'stable-key' })
731
731
  */
732
732
  openCreativeLibrary(input: OpenCreativeLibraryInput, options: WriteRequestOptions): Promise<OpenCreativeLibraryResult>;
733
- /** Open Creative Library. Use search for a plain object answer. Composer needs campaignId; assembly needs no Creative Engine. To save a draft, use exactly one format: a canonical format such as image, hosted video, or hosted audio, or a seller format. Returns the data together with response headers and request ID. */
733
+ /** Open Creative Library. Use search for a plain object answer. The composer can start from an advertiser or a campaign and does not need a Creative Engine. To save a draft, use exactly one format: a canonical format such as image, hosted video, or hosted audio, or a seller format. Returns the data together with response headers and request ID. */
734
734
  openCreativeLibraryWithResponse(input: OpenCreativeLibraryInput, options: WriteRequestOptions): Promise<ResponseDetails<OpenCreativeLibraryResult>>;
735
735
  /**
736
736
  * Open the Developer home for this Developer account: its agents, their stages and what to do next.
@@ -796,13 +796,13 @@ export declare class Apostra extends Transport {
796
796
  /** Show a saved Creative Session’s variants (find via search/get kind creative_session). Not for generating new variants. Returns the data together with response headers and request ID. */
797
797
  openVariantGalleryWithResponse(input: OpenVariantGalleryInput, options?: RequestOptions): Promise<ResponseDetails<OpenVariantGalleryResult>>;
798
798
  /**
799
- * Rechecks one external sales-agent source with no-spend get_products and, if it publishes one, a catalog refresh. Returns each check, findings still open, evidence IDs and readiness; no source configuration or media buy.
799
+ * Rechecks one external sales-agent source with no-spend get_products and, if it publishes one, a catalog refresh. Returns each check, findings still open, evidence IDs and readiness; no source configuration or media buy. scope accounts re-lists the source's accounts instead.
800
800
  *
801
801
  * @example
802
802
  * await client.refreshInventorySourceHealth(input, { idempotencyKey: 'stable-key' })
803
803
  */
804
804
  refreshInventorySourceHealth(input: RefreshInventorySourceHealthInput, options: WriteRequestOptions): Promise<RefreshInventorySourceHealthResult>;
805
- /** Rechecks one external sales-agent source with no-spend get_products and, if it publishes one, a catalog refresh. Returns each check, findings still open, evidence IDs and readiness; no source configuration or media buy. Returns the data together with response headers and request ID. */
805
+ /** Rechecks one external sales-agent source with no-spend get_products and, if it publishes one, a catalog refresh. Returns each check, findings still open, evidence IDs and readiness; no source configuration or media buy. scope accounts re-lists the source's accounts instead. Returns the data together with response headers and request ID. */
806
806
  refreshInventorySourceHealthWithResponse(input: RefreshInventorySourceHealthInput, options: WriteRequestOptions): Promise<ResponseDetails<RefreshInventorySourceHealthResult>>;
807
807
  /**
808
808
  * Request a Buyer child under the selected Organization. Nothing is created until an Organization administrator approves it on our site; the result is approval_required with a link to show the person. Repeat with the same idempotencyKey and name for the outcome.
@@ -895,13 +895,13 @@ export declare class Apostra extends Transport {
895
895
  /** Confirm a Sales Agent product mode, register a Sales or Creative Agent, or update an owned Agent: rename it or allow certification buys. To connect from a URL, send agentUrl with dryRun: true, show what was read, then send it without dryRun. A connected agent is not yet verified as theirs. Returns the data together with response headers and request ID. */
896
896
  saveAgentWithResponse(input: SaveAgentInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveAgentResult>>;
897
897
  /**
898
- * File or update a typed ask in the Apostra Support workflow; returns askId. Supply is buyer inventory. Call immediately with type support when a person explicitly asks for a human; do not diagnose first.
898
+ * File or update a typed ask in the Apostra Support workflow; returns askId. kind create files a new ask; kind update sends only id and requester. Supply is buyer inventory. Call immediately with type support when a person explicitly asks for a human; do not diagnose first.
899
899
  *
900
900
  * @example
901
901
  * await client.saveAsk(input, { idempotencyKey: 'stable-key' })
902
902
  */
903
903
  saveAsk(input: SaveAskInput, options: WriteRequestOptions): Promise<SaveAskResult>;
904
- /** File or update a typed ask in the Apostra Support workflow; returns askId. Supply is buyer inventory. Call immediately with type support when a person explicitly asks for a human; do not diagnose first. Returns the data together with response headers and request ID. */
904
+ /** File or update a typed ask in the Apostra Support workflow; returns askId. kind create files a new ask; kind update sends only id and requester. Supply is buyer inventory. Call immediately with type support when a person explicitly asks for a human; do not diagnose first. Returns the data together with response headers and request ID. */
905
905
  saveAskWithResponse(input: SaveAskInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveAskResult>>;
906
906
  /**
907
907
  * Sync first-party CRM audiences for a buyer advertiser. Each audiences[] item may add, remove, or delete members. Returns an operationId; use get(kind: audience, advertiserId: "...") to read match status after it settles.
@@ -913,13 +913,13 @@ export declare class Apostra extends Transport {
913
913
  /** Sync first-party CRM audiences for a buyer advertiser. Each audiences[] item may add, remove, or delete members. Returns an operationId; use get(kind: audience, advertiserId: "...") to read match status after it settles. Returns the data together with response headers and request ID. */
914
914
  saveAudienceWithResponse(input: SaveAudienceInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveAudienceResult>>;
915
915
  /**
916
- * Accept Terms, set the payment terms sellers are asked for, or accept a plan, answer its offer and change its renewal. Cards stay off MCP.
916
+ * Accept Terms, set the payment terms sellers are asked for, or accept a plan, answer its offer and change its renewal. Accepting a plan needs an administrator to approve it on our site. Cards stay off MCP.
917
917
  *
918
918
  * @example
919
919
  * await client.saveBilling(input, { idempotencyKey: 'stable-key' })
920
920
  */
921
921
  saveBilling(input: SaveBillingInput, options: WriteRequestOptions): Promise<SaveBillingResult>;
922
- /** Accept Terms, set the payment terms sellers are asked for, or accept a plan, answer its offer and change its renewal. Cards stay off MCP. Returns the data together with response headers and request ID. */
922
+ /** Accept Terms, set the payment terms sellers are asked for, or accept a plan, answer its offer and change its renewal. Accepting a plan needs an administrator to approve it on our site. Cards stay off MCP. Returns the data together with response headers and request ID. */
923
923
  saveBillingWithResponse(input: SaveBillingInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveBillingResult>>;
924
924
  /**
925
925
  * Write seller AI Business Rules. Policy content needs an account admin and both policy fields. Approval auto needs acknowledgeNoHumanReview.
@@ -976,22 +976,22 @@ export declare class Apostra extends Transport {
976
976
  /** Manage seller or creative-engine authorization, accounts and advertiser mappings. Seller-only: reporting, selection, billing, policy, activation. Returns the data together with response headers and request ID. */
977
977
  saveConnectionWithResponse(input: SaveConnectionInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveConnectionResult>>;
978
978
  /**
979
- * Declare publisher domains this Seller sells and its claimed properties. `domains` replaces the complete set; `add`, `remove`, `declareProperties`, and `removeProperties` change named items only. Read authorization; never assume it.
979
+ * Declare publisher domains this Seller sells and its claimed properties. `domains` replaces the complete set; `add`, `remove`, `declareProperties`, `removeProperties`, and `refresh` act on named items only. Read authorization; never assume it.
980
980
  *
981
981
  * @example
982
982
  * await client.saveCoverage(input, { idempotencyKey: 'stable-key' })
983
983
  */
984
984
  saveCoverage(input: SaveCoverageInput, options: WriteRequestOptions): Promise<SaveCoverageResult>;
985
- /** Declare publisher domains this Seller sells and its claimed properties. `domains` replaces the complete set; `add`, `remove`, `declareProperties`, and `removeProperties` change named items only. Read authorization; never assume it. Returns the data together with response headers and request ID. */
985
+ /** Declare publisher domains this Seller sells and its claimed properties. `domains` replaces the complete set; `add`, `remove`, `declareProperties`, `removeProperties`, and `refresh` act on named items only. Read authorization; never assume it. Returns the data together with response headers and request ID. */
986
986
  saveCoverageWithResponse(input: SaveCoverageInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveCoverageResult>>;
987
987
  /**
988
- * advertiserId/campaignId:name,message;assets/landing_page_url/social/sourceAssetRef/sourceAssets;Supplied content/assets only;generate new media (radio spots):save_creative_session;exactly one format selector:formatKind/formatParams,creativeFormatId/formatOptionRef;libraryRole:Buyer Account only.
988
+ * Save content you supply: advertiserId/campaignId, name, message, assets (landing_page_url, social, sourceAssetRef/sourceAssets). New media like radio spots: use save_creative_session. One format selector: formatKind+formatParams or creativeFormatId+formatOptionRef. libraryRole: Buyer Account only.
989
989
  *
990
990
  * @example
991
991
  * await client.saveCreative(input, { idempotencyKey: 'stable-key' })
992
992
  */
993
993
  saveCreative(input: SaveCreativeInput, options: WriteRequestOptions): Promise<SaveCreativeResult>;
994
- /** advertiserId/campaignId:name,message;assets/landing_page_url/social/sourceAssetRef/sourceAssets;Supplied content/assets only;generate new media (radio spots):save_creative_session;exactly one format selector:formatKind/formatParams,creativeFormatId/formatOptionRef;libraryRole:Buyer Account only. Returns the data together with response headers and request ID. */
994
+ /** Save content you supply: advertiserId/campaignId, name, message, assets (landing_page_url, social, sourceAssetRef/sourceAssets). New media like radio spots: use save_creative_session. One format selector: formatKind+formatParams or creativeFormatId+formatOptionRef. libraryRole: Buyer Account only. Returns the data together with response headers and request ID. */
995
995
  saveCreativeWithResponse(input: SaveCreativeInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveCreativeResult>>;
996
996
  /**
997
997
  * Manage collections. Create with one owner and name: campaignId or advertiserId. Only advertiser mutations of an existing collection need expectedUpdatedAt; campaign writes and creates do not. isArchived archives or restores advertiser collections.
@@ -1039,22 +1039,22 @@ export declare class Apostra extends Transport {
1039
1039
  /** Create, change, archive, or restore up to 50 advertiser conversion event sources (pixels, server feeds). Each entry is keyed by eventSourceId and succeeds or fails alone. Setup is not proof events flow: check health with get, then use eventSourceId in an optimization goal. Returns the data together with response headers and request ID. */
1040
1040
  saveEventSourceWithResponse(input: SaveEventSourceInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveEventSourceResult>>;
1041
1041
  /**
1042
- * Create or update where a seller's inventory comes from. Pass `id` to change a source or omit it to add one. Pass only `agentId` or `adServer` to open the form that connects an agent or an ad server. Credentials are never passed here; a source that needs one returns the page that collects it.
1042
+ * Create or update where a seller's inventory comes from. Pass `id` to change a source or omit it to add one. Add a sales agent with only `agentId`, never its address, or an ad server with only `adServer`; each opens a form. Credentials are never passed here; a source that needs one returns its form.
1043
1043
  *
1044
1044
  * @example
1045
1045
  * await client.saveInventorySource(input, { idempotencyKey: 'stable-key' })
1046
1046
  */
1047
1047
  saveInventorySource(input: SaveInventorySourceInput, options: WriteRequestOptions): Promise<SaveInventorySourceResult>;
1048
- /** Create or update where a seller's inventory comes from. Pass `id` to change a source or omit it to add one. Pass only `agentId` or `adServer` to open the form that connects an agent or an ad server. Credentials are never passed here; a source that needs one returns the page that collects it. Returns the data together with response headers and request ID. */
1048
+ /** Create or update where a seller's inventory comes from. Pass `id` to change a source or omit it to add one. Add a sales agent with only `agentId`, never its address, or an ad server with only `adServer`; each opens a form. Credentials are never passed here; a source that needs one returns its form. Returns the data together with response headers and request ID. */
1049
1049
  saveInventorySourceWithResponse(input: SaveInventorySourceInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveInventorySourceResult>>;
1050
1050
  /**
1051
- * Manage seller lessons in a seller account context; use switch_account first. propose requires lesson; approve and retire require lessonId; preview requires lessonId and brief. Preserve seller words from a private Session note and ask if kind or scope is unclear.
1051
+ * Manage seller lessons in a seller account; send the accountId of that account. propose requires lesson; approve and retire require lessonId; preview requires lessonId and brief. Preserve seller words from a private Session note and ask if kind or scope is unclear.
1052
1052
  *
1053
1053
  * @example
1054
1054
  * await client.saveLesson(input, { idempotencyKey: 'stable-key' })
1055
1055
  */
1056
1056
  saveLesson(input: SaveLessonInput, options: WriteRequestOptions): Promise<SaveLessonResult>;
1057
- /** Manage seller lessons in a seller account context; use switch_account first. propose requires lesson; approve and retire require lessonId; preview requires lessonId and brief. Preserve seller words from a private Session note and ask if kind or scope is unclear. Returns the data together with response headers and request ID. */
1057
+ /** Manage seller lessons in a seller account; send the accountId of that account. propose requires lesson; approve and retire require lessonId; preview requires lessonId and brief. Preserve seller words from a private Session note and ask if kind or scope is unclear. Returns the data together with response headers and request ID. */
1058
1058
  saveLessonWithResponse(input: SaveLessonInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveLessonResult>>;
1059
1059
  /**
1060
1060
  * Open a seller library request or close it with seller Material.
@@ -1075,13 +1075,13 @@ export declare class Apostra extends Transport {
1075
1075
  /** Create or update seller Materials. Seller beta: preview, delete, Undo deletion, or Include again for one imported Library document. Delete archives it, excludes its source until Include again, and purges it after 30 days. https://docs.apostra.com/v3/delete-from-apostra Returns the data together with response headers and request ID. */
1076
1076
  saveMaterialWithResponse(input: SaveMaterialInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveMaterialResult>>;
1077
1077
  /**
1078
- * Create DRAFT buy; no spend until confirmed. Meta: shared campaign=seller_optimized; ad sets=fixed; ask if unclear. Shared: omit buy/product budgets. Pause/resume: mediaBuyId + isPaused. Proposal: flight + total budget; omit products. channelGroupId: required if grouped; invalid ungrouped/updates.
1078
+ * Create DRAFT buy; no spend until confirmed. Meta: shared campaign=seller_optimized; ad sets=fixed; ask if unclear. Shared: omit product budgets. Pause/resume: mediaBuyId + isPaused. Proposal: flight + total budget; omit products. channelGroupId: required if grouped; invalid ungrouped/updates.
1079
1079
  *
1080
1080
  * @example
1081
1081
  * await client.saveMediaBuy(input, { idempotencyKey: 'stable-key' })
1082
1082
  */
1083
1083
  saveMediaBuy(input: SaveMediaBuyInput, options: WriteRequestOptions): Promise<SaveMediaBuyResult>;
1084
- /** Create DRAFT buy; no spend until confirmed. Meta: shared campaign=seller_optimized; ad sets=fixed; ask if unclear. Shared: omit buy/product budgets. Pause/resume: mediaBuyId + isPaused. Proposal: flight + total budget; omit products. channelGroupId: required if grouped; invalid ungrouped/updates. Returns the data together with response headers and request ID. */
1084
+ /** Create DRAFT buy; no spend until confirmed. Meta: shared campaign=seller_optimized; ad sets=fixed; ask if unclear. Shared: omit product budgets. Pause/resume: mediaBuyId + isPaused. Proposal: flight + total budget; omit products. channelGroupId: required if grouped; invalid ungrouped/updates. Returns the data together with response headers and request ID. */
1085
1085
  saveMediaBuyWithResponse(input: SaveMediaBuyInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveMediaBuyResult>>;
1086
1086
  /**
1087
1087
  * Deprecated compatibility tool ("listing" is the modern term). It writes only the legacy businessProfile and does not update the canonical buyer-visible listing. New clients must read with `get({ kind: "seller", include: ["listing"] })` and write with `save_seller`.
@@ -1138,13 +1138,13 @@ export declare class Apostra extends Transport {
1138
1138
  /** Change one RFP turn: release it, request or cancel a proposal file, append feedback, or record its outcome. One of these per call; revise, endorse and attach a response with save_rfp. Returns the data together with response headers and request ID. */
1139
1139
  saveRfpTurnWithResponse(input: SaveRfpTurnInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveRfpTurnResult>>;
1140
1140
  /**
1141
- * Save Seller identity, capabilities, Apostra listing, buyer-visible listing (mediaKit is a deprecated alias), Distribution's OpenAI challenge token, an admission, or the intake hold. Scope3 eligibility and Market Maker entitlements are read-only here. Read Distribution with get(kind:"distribution").
1141
+ * Save Seller identity, capabilities, buyer-visible listing (mediaKit is a deprecated alias), Distribution's OpenAI challenge token, an admission, the intake hold, or the demo's expiry and sample sources. Scope3 eligibility and Market Maker entitlements are read-only here. Read Distribution with get.
1142
1142
  *
1143
1143
  * @example
1144
1144
  * await client.saveSeller(input, { idempotencyKey: 'stable-key' })
1145
1145
  */
1146
1146
  saveSeller(input: SaveSellerInput, options: WriteRequestOptions): Promise<SaveSellerResult>;
1147
- /** Save Seller identity, capabilities, Apostra listing, buyer-visible listing (mediaKit is a deprecated alias), Distribution's OpenAI challenge token, an admission, or the intake hold. Scope3 eligibility and Market Maker entitlements are read-only here. Read Distribution with get(kind:"distribution"). Returns the data together with response headers and request ID. */
1147
+ /** Save Seller identity, capabilities, buyer-visible listing (mediaKit is a deprecated alias), Distribution's OpenAI challenge token, an admission, the intake hold, or the demo's expiry and sample sources. Scope3 eligibility and Market Maker entitlements are read-only here. Read Distribution with get. Returns the data together with response headers and request ID. */
1148
1148
  saveSellerWithResponse(input: SaveSellerInput, options: WriteRequestOptions): Promise<ResponseDetails<SaveSellerResult>>;
1149
1149
  /**
1150
1150
  * Create, replace, or archive a signal. Managed writes need a complete draft; read before updates. Without sourceId, writes the seller catalog.