@synterai/sdk-js 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-10-02
4
+
5
+ Metadata: public repo link (Synter-Media-AI/synter-docs), synterai.com homepage, hello@synterai.com author, and the official-packages provenance line in the README.
6
+
7
+ Default production URLs and documentation links now use synterai.com. The artifact API uses api.synterai.com where applicable.
8
+
9
+ Explicit `baseUrl` and `artifactBaseUrl` options take precedence over `SYNTER_API_URL` and `SYNTER_ARTIFACT_API_URL` in Node.js. Browser clients can use the options without a `process` global.
10
+
11
+ The syntermedia.ai machine endpoints remain dual-served for previously published versions and explicit overrides.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript SDK for the [Synter](https://syntermedia.ai) advertising API — manage campaigns across 27 ad platforms (Google Ads, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, Amazon DSP, Amazon Ads, Pinterest, Snap, Spotify, OpenAI Ads, The Trade Desk, FreeWheel, DV360, CM360, Walmart Connect, Instacart Ads, Target Roundel, Criteo) from your own code, with full types and IDE autocomplete.
4
4
 
5
- > **`0.1.2` — live on npm.** Pre-1.0: the surface may change before a stable `1.0`. Pin a version in production. `0.1.2` fixes `analytics.getPerformance({ platform: "google" | "linkedin" })`, which in `0.1.0`/`0.1.1` sent the script *filename* (`pull_google_ads_data`) instead of the canonical script name (`pull_google_ads`); the backend rejected it with `400 UNKNOWN_TOOL` until it gained an alias on 2026-08-27.
5
+ > **`0.1.2` — live on npm.** Pre-1.0: the surface may change before a stable `1.0`. Pin a version in production. `0.1.2` fixes `analytics.getPerformance({ platform: "google" | "linkedin" })`, which in `0.1.0`/`0.1.1` sent the script *filename* (`pull_google_ads_data`) instead of the canonical script name (`pull_google_ads`); the backend rejected it with `400 UNKNOWN_TOOL` until it gained an alias on 2026-08-27 (synter-media PR #9684).
6
6
 
7
7
  ## What this actually talks to
8
8
 
@@ -64,6 +64,68 @@ const image = await synter.creative.generateImage({ prompt: 'a running shoe on a
64
64
  await synter.execute('google_ads_list_audiences', { status: 'ENABLED' }, 'google');
65
65
  ```
66
66
 
67
+ ## Post-Write Readback Verification (Agent Safety Pattern)
68
+
69
+ When coding agents or automated scripts modify campaign state (such as pausing overspending campaigns or halving daily budgets), **never trust an HTTP 200 `{success: true}` alone**. Always execute an explicit post-write readback to verify that the upstream ad platform actually persisted and confirmed the state change:
70
+
71
+ ```ts
72
+ import { Synter } from '@synterai/sdk-js';
73
+
74
+ const synter = new Synter(process.env.SYNTER_API_KEY!);
75
+
76
+ async function optimizeCampaignBudget(campaignId: string, targetCpa: number, isDryRun = true) {
77
+ // 1. Fetch 7-day performance
78
+ const perf = await synter.analytics.getPerformance({ platform: 'google', date_range: 'LAST_7_DAYS' });
79
+ const campaign = (perf.campaigns as any[])?.find(c => c.id === campaignId || c.campaign_id === campaignId);
80
+
81
+ if (!campaign) throw new Error(`Campaign ${campaignId} not found`);
82
+
83
+ const cost = Number(campaign.cost || 0);
84
+ const conversions = Number(campaign.conversions || 0);
85
+
86
+ // Handle missing or zero conversions explicitly — never guess a CPA
87
+ if (conversions <= 0) {
88
+ console.log(`[SKIP] Campaign ${campaignId}: 0 conversions recorded, CPA is undefined.`);
89
+ return;
90
+ }
91
+
92
+ const cpa = cost / conversions;
93
+ if (cpa <= targetCpa) {
94
+ console.log(`[OK] Campaign ${campaignId}: CPA $${cpa.toFixed(2)} <= threshold $${targetCpa}. No action needed.`);
95
+ return;
96
+ }
97
+
98
+ const currentBudget = Number(campaign.daily_budget || campaign.budget || 50);
99
+ const newBudget = Math.max(1, Math.round(currentBudget * 0.5));
100
+
101
+ console.log(`[ACTION] Campaign ${campaignId}: CPA $${cpa.toFixed(2)} > threshold $${targetCpa}. Target: PAUSE and cut budget to $${newBudget}/day.`);
102
+
103
+ if (isDryRun) {
104
+ console.log(`[DRY RUN] Would pause campaign ${campaignId} and reduce daily budget from $${currentBudget} to $${newBudget}.`);
105
+ return;
106
+ }
107
+
108
+ // 2. Execute mutations in apply mode
109
+ const pauseResult = await synter.campaigns.pause({ campaign_id: campaignId, platform: 'google' });
110
+ const budgetResult = await synter.campaigns.updateBudget({ campaign_id: campaignId, platform: 'google', daily_budget: newBudget });
111
+
112
+ // 3. MANDATORY POST-WRITE READBACK VERIFICATION
113
+ // Re-read campaign state from Google Ads to verify provider applied the mutations
114
+ const refreshed = await synter.campaigns.list({ platform: 'google' });
115
+ const verified = (refreshed.campaigns as any[])?.find(c => c.id === campaignId || c.campaign_id === campaignId);
116
+
117
+ if (!verified) {
118
+ throw new Error(`Readback verification failed: Campaign ${campaignId} not returned in refreshed list.`);
119
+ }
120
+
121
+ if (verified.status !== 'PAUSED') {
122
+ throw new Error(`Readback verification failed: Campaign ${campaignId} status is '${verified.status}', expected 'PAUSED'.`);
123
+ }
124
+
125
+ console.log(`[VERIFIED] Campaign ${campaignId} successfully paused and daily budget reduced to $${newBudget}. Confirmed on live platform.`);
126
+ }
127
+ ```
128
+
67
129
  ## API surface
68
130
 
69
131
  Methods are grouped by category, matching the tool catalog:
package/dist/index.d.mts CHANGED
@@ -153,6 +153,34 @@ declare class CampaignsResource {
153
153
  * dispatches with `platform=google` regardless of input.
154
154
  */
155
155
  updateBudget(input: UpdateCampaignBudgetInput): Promise<Record<string, unknown>>;
156
+ /**
157
+ * Update campaign budget with deterministic readback verification.
158
+ *
159
+ * Executes the budget update, waits for propagation, and reads back
160
+ * campaign status from the platform to verify the update succeeded on the wire.
161
+ */
162
+ updateBudgetAndVerify(input: UpdateCampaignBudgetInput & {
163
+ maxWaitMs?: number;
164
+ pollIntervalMs?: number;
165
+ }): Promise<{
166
+ mutation: Record<string, unknown>;
167
+ verified: boolean;
168
+ readback?: Record<string, unknown>;
169
+ }>;
170
+ /**
171
+ * Pause a campaign with deterministic readback verification.
172
+ *
173
+ * Executes the pause command, waits for propagation, and reads back
174
+ * campaign status from the platform to verify status === 'PAUSED' on the wire.
175
+ */
176
+ pauseAndVerify(input: PauseCampaignInput & {
177
+ maxWaitMs?: number;
178
+ pollIntervalMs?: number;
179
+ }): Promise<{
180
+ mutation: Record<string, unknown>;
181
+ verified: boolean;
182
+ readback?: Record<string, unknown>;
183
+ }>;
156
184
  }
157
185
 
158
186
  /**
@@ -217,7 +245,8 @@ declare class KeywordsResource {
217
245
 
218
246
  /**
219
247
  * `conversions` category — catalog.json tools: create_conversion,
220
- * list_conversions, diagnose_tracking.
248
+ * list_conversions, diagnose_tracking, get_pixel_health,
249
+ * list_event_deliveries, get_click_attribution.
221
250
  */
222
251
 
223
252
  interface CreateConversionInput {
@@ -232,6 +261,21 @@ interface DiagnoseTrackingInput {
232
261
  /** Website URL to check. */
233
262
  url: string;
234
263
  }
264
+ interface GetPixelHealthInput {
265
+ site_key?: string;
266
+ }
267
+ interface ListEventDeliveriesInput {
268
+ site_key?: string;
269
+ status?: 'PENDING' | 'SUCCESS' | 'RETRYING' | 'FAILED';
270
+ platform?: string;
271
+ limit?: number;
272
+ }
273
+ interface GetClickAttributionInput {
274
+ site_key?: string;
275
+ user_id?: number;
276
+ email?: string;
277
+ limit?: number;
278
+ }
235
279
  declare class ConversionsResource {
236
280
  private readonly client;
237
281
  constructor(client: Synter);
@@ -250,6 +294,9 @@ declare class ConversionsResource {
250
294
  * credentials (SPEC.md's "Platform resolution" rule 3).
251
295
  */
252
296
  diagnoseTracking(input: DiagnoseTrackingInput): Promise<Record<string, unknown>>;
297
+ getPixelHealth(input?: GetPixelHealthInput): Promise<Record<string, unknown>>;
298
+ listEventDeliveries(input?: ListEventDeliveriesInput): Promise<Record<string, unknown>>;
299
+ getClickAttribution(input?: GetClickAttributionInput): Promise<Record<string, unknown>>;
253
300
  }
254
301
 
255
302
  /**
@@ -419,7 +466,7 @@ declare class AudiencesResource {
419
466
  * identifiers inline when over ~500 entries.
420
467
  *
421
468
  * Bypasses the primary `tools/run` dispatcher: different host
422
- * (`https://api.syntermedia.ai`) and a different auth header
469
+ * (`https://api.synterai.com`) and a different auth header
423
470
  * (`X-Synter-Key`, not `Authorization: Bearer`) — see SPEC.md/catalog.json
424
471
  * `transport.special`.
425
472
  */
@@ -442,6 +489,127 @@ declare class AudiencesResource {
442
489
  manage(input: ManageAudienceInput): Promise<Record<string, unknown>>;
443
490
  }
444
491
 
492
+ /**
493
+ * `shield` category — Agent Shield spend limits, burst thresholds,
494
+ * circuit breakers, and kill-switch inspection.
495
+ */
496
+
497
+ interface InspectShieldInput {
498
+ platform?: AdPlatform;
499
+ }
500
+ interface AgentShieldPolicy {
501
+ policy_id: string;
502
+ name: string;
503
+ enforced: boolean;
504
+ max_daily_spend_usd: number;
505
+ max_hourly_spend_usd: number;
506
+ burst_threshold_multiplier: number;
507
+ circuit_breaker_active: boolean;
508
+ kill_switch_engaged: boolean;
509
+ }
510
+ declare class ShieldResource {
511
+ private readonly client;
512
+ constructor(client: Synter);
513
+ /**
514
+ * Inspect Agent Shield velocity limits, burst thresholds, circuit breakers,
515
+ * and kill-switch status before executing ad-spend mutations.
516
+ */
517
+ inspect(input?: InspectShieldInput): Promise<{
518
+ status: 'ACTIVE' | 'TRIPPED' | 'PAUSED';
519
+ policies: AgentShieldPolicy[];
520
+ kill_switch_engaged: boolean;
521
+ notice: string;
522
+ }>;
523
+ }
524
+
525
+ /**
526
+ * `angstrom` category — Synter Ångström attribution claim verification.
527
+ */
528
+
529
+ interface VerifyAngstromClaimInput {
530
+ platform?: AdPlatform;
531
+ claimed_conversions?: number;
532
+ claimed_revenue_usd?: number;
533
+ touchpoint_ids?: string[];
534
+ }
535
+ interface AngstromVerificationResult {
536
+ confidence_score: number;
537
+ verdict: 'VERIFIED' | 'DISPUTED' | 'UNVERIFIABLE';
538
+ incremental_lift_multiplier: number;
539
+ deterministic_touchpoints_verified: number;
540
+ rubric: {
541
+ single_touch_discount: number;
542
+ cross_network_dedup_factor: number;
543
+ latency_decay_score: number;
544
+ };
545
+ explanation: string;
546
+ }
547
+ declare class AngstromResource {
548
+ private readonly client;
549
+ constructor(client: Synter);
550
+ /**
551
+ * Verify attribution claims against Synter Ångström's deterministic confidence rubric.
552
+ */
553
+ verify(input?: VerifyAngstromClaimInput): Promise<AngstromVerificationResult>;
554
+ }
555
+
556
+ /**
557
+ * `skills` category — Core autonomous growth playbooks.
558
+ */
559
+
560
+ type GrowthSkillId = 'pmax-brand-cannibalization' | 'search-hygiene-shield' | 'marginal-cpa-budget-rebalancer' | 'meta-asc-dark-pool-auditor' | 'single-truth-attribution';
561
+ interface GrowthSkillPlaybook {
562
+ skill_id: GrowthSkillId;
563
+ name: string;
564
+ category: string;
565
+ description: string;
566
+ required_tools: string[];
567
+ execution_steps: string[];
568
+ safety_checks: string[];
569
+ expected_outcome: string;
570
+ }
571
+ declare class SkillsResource {
572
+ private readonly client;
573
+ constructor(client: Synter);
574
+ /**
575
+ * Load an autonomous growth runbook by ID.
576
+ */
577
+ get(skillId: GrowthSkillId): Promise<GrowthSkillPlaybook>;
578
+ /**
579
+ * List available autonomous growth runbook IDs.
580
+ */
581
+ list(): GrowthSkillId[];
582
+ }
583
+
584
+ /**
585
+ * `visibility` category — LLM search brand visibility and AEO share of voice.
586
+ */
587
+
588
+ interface GetVisibilityInput {
589
+ query?: string;
590
+ competitor_domains?: string[];
591
+ }
592
+ interface EngineVisibilityScore {
593
+ engine: 'chatgpt' | 'claude' | 'perplexity' | 'gemini';
594
+ share_of_voice_pct: number;
595
+ mention_count: number;
596
+ ranking_position_avg: number;
597
+ }
598
+ interface VisibilityReportResult {
599
+ brand: string;
600
+ overall_aeo_score: number;
601
+ engines: EngineVisibilityScore[];
602
+ recommendations: string[];
603
+ }
604
+ declare class VisibilityResource {
605
+ private readonly client;
606
+ constructor(client: Synter);
607
+ /**
608
+ * Read AI search brand visibility and AEO share of voice across ChatGPT, Claude, Perplexity, and Gemini.
609
+ */
610
+ get(input?: GetVisibilityInput): Promise<VisibilityReportResult>;
611
+ }
612
+
445
613
  /**
446
614
  * Amp Custom Advertising Toolbox
447
615
  *
@@ -961,19 +1129,22 @@ declare class SynterValidationError extends Error {
961
1129
  * Main Synter SDK client.
962
1130
  *
963
1131
  * Transport per SPEC.md: nearly every tool dispatches through
964
- * `POST /api/v1/tools/run` on `https://syntermedia.ai` with
1132
+ * `POST /api/v1/tools/run` on `https://synterai.com` with
965
1133
  * `{ script_name, args, platform? }`, auth via `Authorization: Bearer <key>`.
966
1134
  * Two tools bypass that dispatcher (`list_landing_pages`, `stage_audience_artifact`)
967
1135
  * — see `resources/audiences.ts` and `listLandingPages` below.
968
1136
  */
969
1137
 
970
1138
  /** Real, production base URL — see SPEC.md's "Transport" section. */
971
- declare const DEFAULT_BASE_URL = "https://syntermedia.ai";
1139
+ declare const DEFAULT_BASE_URL = "https://synterai.com";
1140
+ declare const DEFAULT_ARTIFACT_BASE_URL = "https://api.synterai.com";
972
1141
  interface SynterConfig {
973
- /** `syn_` + 32 base64url characters, issued at syntermedia.ai/developer. */
1142
+ /** `syn_` + 32 base64url characters, issued at synterai.com/developer. */
974
1143
  apiKey: string;
975
- /** Override the base URL (defaults to `https://syntermedia.ai`). Mostly for tests. */
1144
+ /** Primary API URL. Takes precedence over SYNTER_API_URL in Node.js. */
976
1145
  baseUrl?: string;
1146
+ /** Artifact API URL. Takes precedence over SYNTER_ARTIFACT_API_URL in Node.js. */
1147
+ artifactBaseUrl?: string;
977
1148
  /** Request timeout in ms. Default 30000 (30s), per SPEC.md. */
978
1149
  timeout?: number;
979
1150
  /** Max retry attempts for 429s and network/timeout errors. Default 3, per SPEC.md. */
@@ -998,6 +1169,7 @@ interface UploadImageInput {
998
1169
  declare class Synter {
999
1170
  private readonly apiKey;
1000
1171
  private readonly baseUrl;
1172
+ private readonly artifactBaseUrl;
1001
1173
  private readonly timeout;
1002
1174
  private readonly maxRetries;
1003
1175
  private readonly fetchImpl;
@@ -1010,6 +1182,10 @@ declare class Synter {
1010
1182
  readonly linkedin: LinkedInResource;
1011
1183
  readonly reddit: RedditResource;
1012
1184
  readonly audiences: AudiencesResource;
1185
+ readonly shield: ShieldResource;
1186
+ readonly angstrom: AngstromResource;
1187
+ readonly skills: SkillsResource;
1188
+ readonly visibility: VisibilityResource;
1013
1189
  /** AI-powered advertising toolbox for Amp integration — unchanged, out of scope for the transport rewrite. */
1014
1190
  readonly amp: AmpToolbox;
1015
1191
  constructor(config: SynterConfig | string, ampConfig?: AmpToolboxConfig);
@@ -1043,7 +1219,9 @@ declare class Synter {
1043
1219
  */
1044
1220
  listLandingPages(): Promise<Record<string, unknown>>;
1045
1221
  private authHeaders;
1046
- /** The raw API key, for the small number of tools that need a non-Bearer header (e.g. `stage_audience_artifact`). */
1222
+ /** Artifact host used by audience staging. */
1223
+ getArtifactBaseUrl(): string;
1224
+ /** The raw API key, for tools that need a non-Bearer header. */
1047
1225
  getApiKey(): string;
1048
1226
  /**
1049
1227
  * Low-level generic HTTP helper retained for `AmpToolbox` and any other
@@ -1255,4 +1433,4 @@ declare function normalizeConversionEvent(params: {
1255
1433
  metadata?: Record<string, any>;
1256
1434
  }): AnalyticsEvent;
1257
1435
 
1258
- export { type AdPlatform, type AddKeywordsInput, type AddNegativeKeywordsInput, AmpToolbox, type AmpToolboxConfig, type AnalyticsEvent, type AnalyticsIntegration, type AnalyticsIntegrationConfig, AnalyticsIntegrationManager, type AnalyticsPlatform, AnalyticsResource, AudiencesResource, type CampaignBriefInput, CampaignsResource, type ConversionPayload, ConversionsResource, type CreateConversionInput, type CreateDisplayCampaignInput, type CreateLinkedInCampaignInput, type CreateMetaCampaignInput, type CreatePmaxCampaignInput, type CreateRedditCampaignInput, type CreateSearchCampaignInput, CreativeResource, type CreativeVariant, DEFAULT_BASE_URL, type DateRange, type DiagnoseTrackingInput, type GenerateImageInput, type GenerateVideoInput, type GeneratedCampaign, type GetDailySpendInput, type GetPerformanceInput, KeywordsResource, LinkedInResource, type ListCampaignsInput, type ManageAudienceInput, MetaResource, type OptimizationRecommendation, type PauseCampaignInput, type Platform, type PlatformConnection, type PlatformStatus, RedditResource, type StageAudienceArtifactInput, type StageAudienceArtifactResult, type SyncAudienceInput, Synter, type SynterConfig, SynterError, SynterValidationError, type TrackingParams, type UTMParameters, type UTMTemplate, type UpdateCampaignBudgetInput, type UploadImageInput, buildConversionPayload, buildTrackingURL, buildUTMParameters, Synter as default, detectClickId, extractTrackingParams, extractUTMParameters, generateDefaultUTMTemplate, normalizeAdClickEvent, normalizeConversionEvent, normalizeEventName, validateClickId, validateUTMTemplate };
1436
+ export { type AdPlatform, type AddKeywordsInput, type AddNegativeKeywordsInput, type AgentShieldPolicy, AmpToolbox, type AmpToolboxConfig, type AnalyticsEvent, type AnalyticsIntegration, type AnalyticsIntegrationConfig, AnalyticsIntegrationManager, type AnalyticsPlatform, AnalyticsResource, AngstromResource, type AngstromVerificationResult, AudiencesResource, type CampaignBriefInput, CampaignsResource, type ConversionPayload, ConversionsResource, type CreateConversionInput, type CreateDisplayCampaignInput, type CreateLinkedInCampaignInput, type CreateMetaCampaignInput, type CreatePmaxCampaignInput, type CreateRedditCampaignInput, type CreateSearchCampaignInput, CreativeResource, type CreativeVariant, DEFAULT_ARTIFACT_BASE_URL, DEFAULT_BASE_URL, type DateRange, type DiagnoseTrackingInput, type EngineVisibilityScore, type GenerateImageInput, type GenerateVideoInput, type GeneratedCampaign, type GetDailySpendInput, type GetPerformanceInput, type GetVisibilityInput, type GrowthSkillId, type GrowthSkillPlaybook, type InspectShieldInput, KeywordsResource, LinkedInResource, type ListCampaignsInput, type ManageAudienceInput, MetaResource, type OptimizationRecommendation, type PauseCampaignInput, type Platform, type PlatformConnection, type PlatformStatus, RedditResource, ShieldResource, SkillsResource, type StageAudienceArtifactInput, type StageAudienceArtifactResult, type SyncAudienceInput, Synter, type SynterConfig, SynterError, SynterValidationError, type TrackingParams, type UTMParameters, type UTMTemplate, type UpdateCampaignBudgetInput, type UploadImageInput, type VerifyAngstromClaimInput, type VisibilityReportResult, VisibilityResource, buildConversionPayload, buildTrackingURL, buildUTMParameters, Synter as default, detectClickId, extractTrackingParams, extractUTMParameters, generateDefaultUTMTemplate, normalizeAdClickEvent, normalizeConversionEvent, normalizeEventName, validateClickId, validateUTMTemplate };
package/dist/index.d.ts CHANGED
@@ -153,6 +153,34 @@ declare class CampaignsResource {
153
153
  * dispatches with `platform=google` regardless of input.
154
154
  */
155
155
  updateBudget(input: UpdateCampaignBudgetInput): Promise<Record<string, unknown>>;
156
+ /**
157
+ * Update campaign budget with deterministic readback verification.
158
+ *
159
+ * Executes the budget update, waits for propagation, and reads back
160
+ * campaign status from the platform to verify the update succeeded on the wire.
161
+ */
162
+ updateBudgetAndVerify(input: UpdateCampaignBudgetInput & {
163
+ maxWaitMs?: number;
164
+ pollIntervalMs?: number;
165
+ }): Promise<{
166
+ mutation: Record<string, unknown>;
167
+ verified: boolean;
168
+ readback?: Record<string, unknown>;
169
+ }>;
170
+ /**
171
+ * Pause a campaign with deterministic readback verification.
172
+ *
173
+ * Executes the pause command, waits for propagation, and reads back
174
+ * campaign status from the platform to verify status === 'PAUSED' on the wire.
175
+ */
176
+ pauseAndVerify(input: PauseCampaignInput & {
177
+ maxWaitMs?: number;
178
+ pollIntervalMs?: number;
179
+ }): Promise<{
180
+ mutation: Record<string, unknown>;
181
+ verified: boolean;
182
+ readback?: Record<string, unknown>;
183
+ }>;
156
184
  }
157
185
 
158
186
  /**
@@ -217,7 +245,8 @@ declare class KeywordsResource {
217
245
 
218
246
  /**
219
247
  * `conversions` category — catalog.json tools: create_conversion,
220
- * list_conversions, diagnose_tracking.
248
+ * list_conversions, diagnose_tracking, get_pixel_health,
249
+ * list_event_deliveries, get_click_attribution.
221
250
  */
222
251
 
223
252
  interface CreateConversionInput {
@@ -232,6 +261,21 @@ interface DiagnoseTrackingInput {
232
261
  /** Website URL to check. */
233
262
  url: string;
234
263
  }
264
+ interface GetPixelHealthInput {
265
+ site_key?: string;
266
+ }
267
+ interface ListEventDeliveriesInput {
268
+ site_key?: string;
269
+ status?: 'PENDING' | 'SUCCESS' | 'RETRYING' | 'FAILED';
270
+ platform?: string;
271
+ limit?: number;
272
+ }
273
+ interface GetClickAttributionInput {
274
+ site_key?: string;
275
+ user_id?: number;
276
+ email?: string;
277
+ limit?: number;
278
+ }
235
279
  declare class ConversionsResource {
236
280
  private readonly client;
237
281
  constructor(client: Synter);
@@ -250,6 +294,9 @@ declare class ConversionsResource {
250
294
  * credentials (SPEC.md's "Platform resolution" rule 3).
251
295
  */
252
296
  diagnoseTracking(input: DiagnoseTrackingInput): Promise<Record<string, unknown>>;
297
+ getPixelHealth(input?: GetPixelHealthInput): Promise<Record<string, unknown>>;
298
+ listEventDeliveries(input?: ListEventDeliveriesInput): Promise<Record<string, unknown>>;
299
+ getClickAttribution(input?: GetClickAttributionInput): Promise<Record<string, unknown>>;
253
300
  }
254
301
 
255
302
  /**
@@ -419,7 +466,7 @@ declare class AudiencesResource {
419
466
  * identifiers inline when over ~500 entries.
420
467
  *
421
468
  * Bypasses the primary `tools/run` dispatcher: different host
422
- * (`https://api.syntermedia.ai`) and a different auth header
469
+ * (`https://api.synterai.com`) and a different auth header
423
470
  * (`X-Synter-Key`, not `Authorization: Bearer`) — see SPEC.md/catalog.json
424
471
  * `transport.special`.
425
472
  */
@@ -442,6 +489,127 @@ declare class AudiencesResource {
442
489
  manage(input: ManageAudienceInput): Promise<Record<string, unknown>>;
443
490
  }
444
491
 
492
+ /**
493
+ * `shield` category — Agent Shield spend limits, burst thresholds,
494
+ * circuit breakers, and kill-switch inspection.
495
+ */
496
+
497
+ interface InspectShieldInput {
498
+ platform?: AdPlatform;
499
+ }
500
+ interface AgentShieldPolicy {
501
+ policy_id: string;
502
+ name: string;
503
+ enforced: boolean;
504
+ max_daily_spend_usd: number;
505
+ max_hourly_spend_usd: number;
506
+ burst_threshold_multiplier: number;
507
+ circuit_breaker_active: boolean;
508
+ kill_switch_engaged: boolean;
509
+ }
510
+ declare class ShieldResource {
511
+ private readonly client;
512
+ constructor(client: Synter);
513
+ /**
514
+ * Inspect Agent Shield velocity limits, burst thresholds, circuit breakers,
515
+ * and kill-switch status before executing ad-spend mutations.
516
+ */
517
+ inspect(input?: InspectShieldInput): Promise<{
518
+ status: 'ACTIVE' | 'TRIPPED' | 'PAUSED';
519
+ policies: AgentShieldPolicy[];
520
+ kill_switch_engaged: boolean;
521
+ notice: string;
522
+ }>;
523
+ }
524
+
525
+ /**
526
+ * `angstrom` category — Synter Ångström attribution claim verification.
527
+ */
528
+
529
+ interface VerifyAngstromClaimInput {
530
+ platform?: AdPlatform;
531
+ claimed_conversions?: number;
532
+ claimed_revenue_usd?: number;
533
+ touchpoint_ids?: string[];
534
+ }
535
+ interface AngstromVerificationResult {
536
+ confidence_score: number;
537
+ verdict: 'VERIFIED' | 'DISPUTED' | 'UNVERIFIABLE';
538
+ incremental_lift_multiplier: number;
539
+ deterministic_touchpoints_verified: number;
540
+ rubric: {
541
+ single_touch_discount: number;
542
+ cross_network_dedup_factor: number;
543
+ latency_decay_score: number;
544
+ };
545
+ explanation: string;
546
+ }
547
+ declare class AngstromResource {
548
+ private readonly client;
549
+ constructor(client: Synter);
550
+ /**
551
+ * Verify attribution claims against Synter Ångström's deterministic confidence rubric.
552
+ */
553
+ verify(input?: VerifyAngstromClaimInput): Promise<AngstromVerificationResult>;
554
+ }
555
+
556
+ /**
557
+ * `skills` category — Core autonomous growth playbooks.
558
+ */
559
+
560
+ type GrowthSkillId = 'pmax-brand-cannibalization' | 'search-hygiene-shield' | 'marginal-cpa-budget-rebalancer' | 'meta-asc-dark-pool-auditor' | 'single-truth-attribution';
561
+ interface GrowthSkillPlaybook {
562
+ skill_id: GrowthSkillId;
563
+ name: string;
564
+ category: string;
565
+ description: string;
566
+ required_tools: string[];
567
+ execution_steps: string[];
568
+ safety_checks: string[];
569
+ expected_outcome: string;
570
+ }
571
+ declare class SkillsResource {
572
+ private readonly client;
573
+ constructor(client: Synter);
574
+ /**
575
+ * Load an autonomous growth runbook by ID.
576
+ */
577
+ get(skillId: GrowthSkillId): Promise<GrowthSkillPlaybook>;
578
+ /**
579
+ * List available autonomous growth runbook IDs.
580
+ */
581
+ list(): GrowthSkillId[];
582
+ }
583
+
584
+ /**
585
+ * `visibility` category — LLM search brand visibility and AEO share of voice.
586
+ */
587
+
588
+ interface GetVisibilityInput {
589
+ query?: string;
590
+ competitor_domains?: string[];
591
+ }
592
+ interface EngineVisibilityScore {
593
+ engine: 'chatgpt' | 'claude' | 'perplexity' | 'gemini';
594
+ share_of_voice_pct: number;
595
+ mention_count: number;
596
+ ranking_position_avg: number;
597
+ }
598
+ interface VisibilityReportResult {
599
+ brand: string;
600
+ overall_aeo_score: number;
601
+ engines: EngineVisibilityScore[];
602
+ recommendations: string[];
603
+ }
604
+ declare class VisibilityResource {
605
+ private readonly client;
606
+ constructor(client: Synter);
607
+ /**
608
+ * Read AI search brand visibility and AEO share of voice across ChatGPT, Claude, Perplexity, and Gemini.
609
+ */
610
+ get(input?: GetVisibilityInput): Promise<VisibilityReportResult>;
611
+ }
612
+
445
613
  /**
446
614
  * Amp Custom Advertising Toolbox
447
615
  *
@@ -961,19 +1129,22 @@ declare class SynterValidationError extends Error {
961
1129
  * Main Synter SDK client.
962
1130
  *
963
1131
  * Transport per SPEC.md: nearly every tool dispatches through
964
- * `POST /api/v1/tools/run` on `https://syntermedia.ai` with
1132
+ * `POST /api/v1/tools/run` on `https://synterai.com` with
965
1133
  * `{ script_name, args, platform? }`, auth via `Authorization: Bearer <key>`.
966
1134
  * Two tools bypass that dispatcher (`list_landing_pages`, `stage_audience_artifact`)
967
1135
  * — see `resources/audiences.ts` and `listLandingPages` below.
968
1136
  */
969
1137
 
970
1138
  /** Real, production base URL — see SPEC.md's "Transport" section. */
971
- declare const DEFAULT_BASE_URL = "https://syntermedia.ai";
1139
+ declare const DEFAULT_BASE_URL = "https://synterai.com";
1140
+ declare const DEFAULT_ARTIFACT_BASE_URL = "https://api.synterai.com";
972
1141
  interface SynterConfig {
973
- /** `syn_` + 32 base64url characters, issued at syntermedia.ai/developer. */
1142
+ /** `syn_` + 32 base64url characters, issued at synterai.com/developer. */
974
1143
  apiKey: string;
975
- /** Override the base URL (defaults to `https://syntermedia.ai`). Mostly for tests. */
1144
+ /** Primary API URL. Takes precedence over SYNTER_API_URL in Node.js. */
976
1145
  baseUrl?: string;
1146
+ /** Artifact API URL. Takes precedence over SYNTER_ARTIFACT_API_URL in Node.js. */
1147
+ artifactBaseUrl?: string;
977
1148
  /** Request timeout in ms. Default 30000 (30s), per SPEC.md. */
978
1149
  timeout?: number;
979
1150
  /** Max retry attempts for 429s and network/timeout errors. Default 3, per SPEC.md. */
@@ -998,6 +1169,7 @@ interface UploadImageInput {
998
1169
  declare class Synter {
999
1170
  private readonly apiKey;
1000
1171
  private readonly baseUrl;
1172
+ private readonly artifactBaseUrl;
1001
1173
  private readonly timeout;
1002
1174
  private readonly maxRetries;
1003
1175
  private readonly fetchImpl;
@@ -1010,6 +1182,10 @@ declare class Synter {
1010
1182
  readonly linkedin: LinkedInResource;
1011
1183
  readonly reddit: RedditResource;
1012
1184
  readonly audiences: AudiencesResource;
1185
+ readonly shield: ShieldResource;
1186
+ readonly angstrom: AngstromResource;
1187
+ readonly skills: SkillsResource;
1188
+ readonly visibility: VisibilityResource;
1013
1189
  /** AI-powered advertising toolbox for Amp integration — unchanged, out of scope for the transport rewrite. */
1014
1190
  readonly amp: AmpToolbox;
1015
1191
  constructor(config: SynterConfig | string, ampConfig?: AmpToolboxConfig);
@@ -1043,7 +1219,9 @@ declare class Synter {
1043
1219
  */
1044
1220
  listLandingPages(): Promise<Record<string, unknown>>;
1045
1221
  private authHeaders;
1046
- /** The raw API key, for the small number of tools that need a non-Bearer header (e.g. `stage_audience_artifact`). */
1222
+ /** Artifact host used by audience staging. */
1223
+ getArtifactBaseUrl(): string;
1224
+ /** The raw API key, for tools that need a non-Bearer header. */
1047
1225
  getApiKey(): string;
1048
1226
  /**
1049
1227
  * Low-level generic HTTP helper retained for `AmpToolbox` and any other
@@ -1255,4 +1433,4 @@ declare function normalizeConversionEvent(params: {
1255
1433
  metadata?: Record<string, any>;
1256
1434
  }): AnalyticsEvent;
1257
1435
 
1258
- export { type AdPlatform, type AddKeywordsInput, type AddNegativeKeywordsInput, AmpToolbox, type AmpToolboxConfig, type AnalyticsEvent, type AnalyticsIntegration, type AnalyticsIntegrationConfig, AnalyticsIntegrationManager, type AnalyticsPlatform, AnalyticsResource, AudiencesResource, type CampaignBriefInput, CampaignsResource, type ConversionPayload, ConversionsResource, type CreateConversionInput, type CreateDisplayCampaignInput, type CreateLinkedInCampaignInput, type CreateMetaCampaignInput, type CreatePmaxCampaignInput, type CreateRedditCampaignInput, type CreateSearchCampaignInput, CreativeResource, type CreativeVariant, DEFAULT_BASE_URL, type DateRange, type DiagnoseTrackingInput, type GenerateImageInput, type GenerateVideoInput, type GeneratedCampaign, type GetDailySpendInput, type GetPerformanceInput, KeywordsResource, LinkedInResource, type ListCampaignsInput, type ManageAudienceInput, MetaResource, type OptimizationRecommendation, type PauseCampaignInput, type Platform, type PlatformConnection, type PlatformStatus, RedditResource, type StageAudienceArtifactInput, type StageAudienceArtifactResult, type SyncAudienceInput, Synter, type SynterConfig, SynterError, SynterValidationError, type TrackingParams, type UTMParameters, type UTMTemplate, type UpdateCampaignBudgetInput, type UploadImageInput, buildConversionPayload, buildTrackingURL, buildUTMParameters, Synter as default, detectClickId, extractTrackingParams, extractUTMParameters, generateDefaultUTMTemplate, normalizeAdClickEvent, normalizeConversionEvent, normalizeEventName, validateClickId, validateUTMTemplate };
1436
+ export { type AdPlatform, type AddKeywordsInput, type AddNegativeKeywordsInput, type AgentShieldPolicy, AmpToolbox, type AmpToolboxConfig, type AnalyticsEvent, type AnalyticsIntegration, type AnalyticsIntegrationConfig, AnalyticsIntegrationManager, type AnalyticsPlatform, AnalyticsResource, AngstromResource, type AngstromVerificationResult, AudiencesResource, type CampaignBriefInput, CampaignsResource, type ConversionPayload, ConversionsResource, type CreateConversionInput, type CreateDisplayCampaignInput, type CreateLinkedInCampaignInput, type CreateMetaCampaignInput, type CreatePmaxCampaignInput, type CreateRedditCampaignInput, type CreateSearchCampaignInput, CreativeResource, type CreativeVariant, DEFAULT_ARTIFACT_BASE_URL, DEFAULT_BASE_URL, type DateRange, type DiagnoseTrackingInput, type EngineVisibilityScore, type GenerateImageInput, type GenerateVideoInput, type GeneratedCampaign, type GetDailySpendInput, type GetPerformanceInput, type GetVisibilityInput, type GrowthSkillId, type GrowthSkillPlaybook, type InspectShieldInput, KeywordsResource, LinkedInResource, type ListCampaignsInput, type ManageAudienceInput, MetaResource, type OptimizationRecommendation, type PauseCampaignInput, type Platform, type PlatformConnection, type PlatformStatus, RedditResource, ShieldResource, SkillsResource, type StageAudienceArtifactInput, type StageAudienceArtifactResult, type SyncAudienceInput, Synter, type SynterConfig, SynterError, SynterValidationError, type TrackingParams, type UTMParameters, type UTMTemplate, type UpdateCampaignBudgetInput, type UploadImageInput, type VerifyAngstromClaimInput, type VisibilityReportResult, VisibilityResource, buildConversionPayload, buildTrackingURL, buildUTMParameters, Synter as default, detectClickId, extractTrackingParams, extractUTMParameters, generateDefaultUTMTemplate, normalizeAdClickEvent, normalizeConversionEvent, normalizeEventName, validateClickId, validateUTMTemplate };
package/dist/index.js CHANGED
@@ -23,18 +23,23 @@ __export(index_exports, {
23
23
  AmpToolbox: () => AmpToolbox,
24
24
  AnalyticsIntegrationManager: () => AnalyticsIntegrationManager,
25
25
  AnalyticsResource: () => AnalyticsResource,
26
+ AngstromResource: () => AngstromResource,
26
27
  AudiencesResource: () => AudiencesResource,
27
28
  CampaignsResource: () => CampaignsResource,
28
29
  ConversionsResource: () => ConversionsResource,
29
30
  CreativeResource: () => CreativeResource,
31
+ DEFAULT_ARTIFACT_BASE_URL: () => DEFAULT_ARTIFACT_BASE_URL,
30
32
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
31
33
  KeywordsResource: () => KeywordsResource,
32
34
  LinkedInResource: () => LinkedInResource,
33
35
  MetaResource: () => MetaResource,
34
36
  RedditResource: () => RedditResource,
37
+ ShieldResource: () => ShieldResource,
38
+ SkillsResource: () => SkillsResource,
35
39
  Synter: () => Synter,
36
40
  SynterError: () => SynterError,
37
41
  SynterValidationError: () => SynterValidationError,
42
+ VisibilityResource: () => VisibilityResource,
38
43
  buildConversionPayload: () => buildConversionPayload,
39
44
  buildTrackingURL: () => buildTrackingURL,
40
45
  buildUTMParameters: () => buildUTMParameters,
@@ -202,7 +207,7 @@ var PAUSE_CAMPAIGN_ARG_MAPPING = [
202
207
  ];
203
208
  var UPDATE_BUDGET_ARG_MAPPING = [
204
209
  { field: "campaign_id", flag: "--campaign-id", type: "string", required: true },
205
- { field: "daily_budget", flag: "--budget", type: "number", required: true }
210
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true }
206
211
  ];
207
212
  var CampaignsResource = class {
208
213
  constructor(client) {
@@ -275,6 +280,72 @@ var CampaignsResource = class {
275
280
  const args = buildArgsFromMapping(UPDATE_BUDGET_ARG_MAPPING, input);
276
281
  return this.client.runTool("update_campaign_budget", args, "google");
277
282
  }
283
+ /**
284
+ * Update campaign budget with deterministic readback verification.
285
+ *
286
+ * Executes the budget update, waits for propagation, and reads back
287
+ * campaign status from the platform to verify the update succeeded on the wire.
288
+ */
289
+ async updateBudgetAndVerify(input) {
290
+ const mutation = await this.updateBudget(input);
291
+ const maxWaitMs = input.maxWaitMs ?? 5e3;
292
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
293
+ const startTime = Date.now();
294
+ let lastReadback;
295
+ while (Date.now() - startTime <= maxWaitMs) {
296
+ try {
297
+ const readback = await this.list({
298
+ platform: input.platform
299
+ });
300
+ lastReadback = readback;
301
+ const campaigns = readback.campaigns || readback.data || [];
302
+ const match = campaigns.find(
303
+ (c) => String(c.id || c.campaign_id) === String(input.campaign_id)
304
+ );
305
+ if (match) {
306
+ const budget = Number(match.daily_budget_usd || match.daily_budget || match.budget);
307
+ if (Math.abs(budget - input.daily_budget) < 0.01) {
308
+ return { mutation, verified: true, readback: match };
309
+ }
310
+ }
311
+ } catch {
312
+ }
313
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
314
+ }
315
+ return { mutation, verified: false, readback: lastReadback };
316
+ }
317
+ /**
318
+ * Pause a campaign with deterministic readback verification.
319
+ *
320
+ * Executes the pause command, waits for propagation, and reads back
321
+ * campaign status from the platform to verify status === 'PAUSED' on the wire.
322
+ */
323
+ async pauseAndVerify(input) {
324
+ const mutation = await this.pause(input);
325
+ const maxWaitMs = input.maxWaitMs ?? 5e3;
326
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
327
+ const startTime = Date.now();
328
+ let lastReadback;
329
+ while (Date.now() - startTime <= maxWaitMs) {
330
+ try {
331
+ const readback = await this.list({
332
+ platform: input.platform,
333
+ status: "PAUSED"
334
+ });
335
+ lastReadback = readback;
336
+ const campaigns = readback.campaigns || readback.data || [];
337
+ const match = campaigns.find(
338
+ (c) => String(c.id || c.campaign_id) === String(input.campaign_id)
339
+ );
340
+ if (match) {
341
+ return { mutation, verified: true, readback: match };
342
+ }
343
+ } catch {
344
+ }
345
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
346
+ }
347
+ return { mutation, verified: false, readback: lastReadback };
348
+ }
278
349
  };
279
350
 
280
351
  // src/resources/analytics.ts
@@ -368,6 +439,21 @@ var CREATE_CONVERSION_ARG_MAPPING = [
368
439
  var DIAGNOSE_TRACKING_ARG_MAPPING = [
369
440
  { field: "url", flag: "--url", type: "string", required: true }
370
441
  ];
442
+ var GET_PIXEL_HEALTH_ARG_MAPPING = [
443
+ { field: "site_key", flag: "--site-key", type: "string" }
444
+ ];
445
+ var LIST_EVENT_DELIVERIES_ARG_MAPPING = [
446
+ { field: "site_key", flag: "--site-key", type: "string" },
447
+ { field: "status", flag: "--status", type: "string" },
448
+ { field: "platform", flag: "--platform", type: "string" },
449
+ { field: "limit", flag: "--limit", type: "number" }
450
+ ];
451
+ var GET_CLICK_ATTRIBUTION_ARG_MAPPING = [
452
+ { field: "site_key", flag: "--site-key", type: "string" },
453
+ { field: "user_id", flag: "--user-id", type: "number" },
454
+ { field: "email", flag: "--email", type: "string" },
455
+ { field: "limit", flag: "--limit", type: "number" }
456
+ ];
371
457
  var ConversionsResource = class {
372
458
  constructor(client) {
373
459
  this.client = client;
@@ -393,7 +479,19 @@ var ConversionsResource = class {
393
479
  */
394
480
  async diagnoseTracking(input) {
395
481
  const args = buildArgsFromMapping(DIAGNOSE_TRACKING_ARG_MAPPING, input);
396
- return this.client.runTool("diagnose_conversion_tracking", args);
482
+ return this.client.runTool("diagnose_tracking", args);
483
+ }
484
+ async getPixelHealth(input = {}) {
485
+ const args = buildArgsFromMapping(GET_PIXEL_HEALTH_ARG_MAPPING, input);
486
+ return this.client.runTool("get_pixel_health", args);
487
+ }
488
+ async listEventDeliveries(input = {}) {
489
+ const args = buildArgsFromMapping(LIST_EVENT_DELIVERIES_ARG_MAPPING, input);
490
+ return this.client.runTool("list_event_deliveries", args);
491
+ }
492
+ async getClickAttribution(input = {}) {
493
+ const args = buildArgsFromMapping(GET_CLICK_ATTRIBUTION_ARG_MAPPING, input);
494
+ return this.client.runTool("get_click_attribution", args);
397
495
  }
398
496
  };
399
497
 
@@ -486,7 +584,6 @@ var RedditResource = class {
486
584
 
487
585
  // src/resources/audiences.ts
488
586
  var CONSENT_WARNING = "[@synterai/sdk-js] i_have_consent was not provided. Confirm lawful basis under GDPR/CCPA and the destination platform's terms for every identifier before staging or syncing PII, then pass `i_have_consent: true`. (Soft-launch notice \u2014 mirrors the backend's own behavior; this call is not blocked.)";
489
- var ARTIFACT_ENDPOINT = "https://api.syntermedia.ai/artifacts/audience-sync-input";
490
587
  var SYNC_AUDIENCE_SCRIPT_BY_PLATFORM = {
491
588
  google: "google_ads_create_customer_match",
492
589
  meta: "meta_ads_create_audience",
@@ -518,7 +615,7 @@ var AudiencesResource = class {
518
615
  * identifiers inline when over ~500 entries.
519
616
  *
520
617
  * Bypasses the primary `tools/run` dispatcher: different host
521
- * (`https://api.syntermedia.ai`) and a different auth header
618
+ * (`https://api.synterai.com`) and a different auth header
522
619
  * (`X-Synter-Key`, not `Authorization: Bearer`) — see SPEC.md/catalog.json
523
620
  * `transport.special`.
524
621
  */
@@ -533,7 +630,7 @@ var AudiencesResource = class {
533
630
  if (input.script_name !== void 0) payload.script_name = input.script_name;
534
631
  if (input.ttl_seconds !== void 0) payload.ttl_seconds = input.ttl_seconds;
535
632
  if (input.i_have_consent !== void 0) payload.i_have_consent = input.i_have_consent;
536
- return this.client.sendAbsolute(ARTIFACT_ENDPOINT, {
633
+ return this.client.sendAbsolute(`${this.client.getArtifactBaseUrl()}/artifacts/audience-sync-input`, {
537
634
  method: "POST",
538
635
  headers: {
539
636
  "X-Synter-Key": this.client.getApiKey(),
@@ -608,6 +705,91 @@ var AudiencesResource = class {
608
705
  }
609
706
  };
610
707
 
708
+ // src/resources/shield.ts
709
+ var ShieldResource = class {
710
+ constructor(client) {
711
+ this.client = client;
712
+ }
713
+ /**
714
+ * Inspect Agent Shield velocity limits, burst thresholds, circuit breakers,
715
+ * and kill-switch status before executing ad-spend mutations.
716
+ */
717
+ async inspect(input = {}) {
718
+ const args = [];
719
+ if (input.platform) {
720
+ args.push("--platform", input.platform);
721
+ }
722
+ const result = await this.client.runTool("inspect_agent_shield", args, input.platform);
723
+ return result;
724
+ }
725
+ };
726
+
727
+ // src/resources/angstrom.ts
728
+ var AngstromResource = class {
729
+ constructor(client) {
730
+ this.client = client;
731
+ }
732
+ /**
733
+ * Verify attribution claims against Synter Ångström's deterministic confidence rubric.
734
+ */
735
+ async verify(input = {}) {
736
+ const args = [];
737
+ if (input.platform) args.push("--platform", input.platform);
738
+ if (input.claimed_conversions !== void 0) args.push("--conversions", String(input.claimed_conversions));
739
+ if (input.claimed_revenue_usd !== void 0) args.push("--revenue", String(input.claimed_revenue_usd));
740
+ if (input.touchpoint_ids && input.touchpoint_ids.length > 0) {
741
+ args.push("--touchpoints", input.touchpoint_ids.join(","));
742
+ }
743
+ const result = await this.client.runTool("verify_angstrom_claim", args, input.platform);
744
+ return result;
745
+ }
746
+ };
747
+
748
+ // src/resources/skills.ts
749
+ var SkillsResource = class {
750
+ constructor(client) {
751
+ this.client = client;
752
+ }
753
+ /**
754
+ * Load an autonomous growth runbook by ID.
755
+ */
756
+ async get(skillId) {
757
+ const result = await this.client.runTool("get_skill", ["--skill-id", skillId]);
758
+ return result;
759
+ }
760
+ /**
761
+ * List available autonomous growth runbook IDs.
762
+ */
763
+ list() {
764
+ return [
765
+ "pmax-brand-cannibalization",
766
+ "search-hygiene-shield",
767
+ "marginal-cpa-budget-rebalancer",
768
+ "meta-asc-dark-pool-auditor",
769
+ "single-truth-attribution"
770
+ ];
771
+ }
772
+ };
773
+
774
+ // src/resources/visibility.ts
775
+ var VisibilityResource = class {
776
+ constructor(client) {
777
+ this.client = client;
778
+ }
779
+ /**
780
+ * Read AI search brand visibility and AEO share of voice across ChatGPT, Claude, Perplexity, and Gemini.
781
+ */
782
+ async get(input = {}) {
783
+ const args = [];
784
+ if (input.query) args.push("--query", input.query);
785
+ if (input.competitor_domains && input.competitor_domains.length > 0) {
786
+ args.push("--competitors", input.competitor_domains.join(","));
787
+ }
788
+ const result = await this.client.runTool("get_llm_visibility", args);
789
+ return result;
790
+ }
791
+ };
792
+
611
793
  // src/toolbox/amp-tools.ts
612
794
  var AmpToolbox = class {
613
795
  constructor(client, config = {}) {
@@ -841,7 +1023,8 @@ var AmpToolbox = class {
841
1023
  };
842
1024
 
843
1025
  // src/client.ts
844
- var DEFAULT_BASE_URL = "https://syntermedia.ai";
1026
+ var DEFAULT_BASE_URL = "https://synterai.com";
1027
+ var DEFAULT_ARTIFACT_BASE_URL = "https://api.synterai.com";
845
1028
  var DEFAULT_TIMEOUT_MS = 3e4;
846
1029
  var DEFAULT_MAX_RETRIES = 3;
847
1030
  var RETRY_BASE_MS = 1e3;
@@ -859,7 +1042,9 @@ var Synter = class {
859
1042
  throw new SynterValidationError("`apiKey` is required to construct a Synter client.", "apiKey");
860
1043
  }
861
1044
  this.apiKey = fullConfig.apiKey;
862
- this.baseUrl = fullConfig.baseUrl ?? DEFAULT_BASE_URL;
1045
+ const env = typeof process !== "undefined" ? process.env : void 0;
1046
+ this.baseUrl = fullConfig.baseUrl ?? (env?.SYNTER_API_URL || DEFAULT_BASE_URL);
1047
+ this.artifactBaseUrl = fullConfig.artifactBaseUrl ?? (env?.SYNTER_ARTIFACT_API_URL || DEFAULT_ARTIFACT_BASE_URL);
863
1048
  this.timeout = fullConfig.timeout ?? DEFAULT_TIMEOUT_MS;
864
1049
  this.maxRetries = fullConfig.maxRetries ?? DEFAULT_MAX_RETRIES;
865
1050
  this.fetchImpl = fullConfig.fetch ?? globalThis.fetch.bind(globalThis);
@@ -872,6 +1057,10 @@ var Synter = class {
872
1057
  this.linkedin = new LinkedInResource(this);
873
1058
  this.reddit = new RedditResource(this);
874
1059
  this.audiences = new AudiencesResource(this);
1060
+ this.shield = new ShieldResource(this);
1061
+ this.angstrom = new AngstromResource(this);
1062
+ this.skills = new SkillsResource(this);
1063
+ this.visibility = new VisibilityResource(this);
875
1064
  this.amp = new AmpToolbox(this, ampConfig);
876
1065
  }
877
1066
  // ---------------------------------------------------------------------
@@ -950,7 +1139,11 @@ var Synter = class {
950
1139
  "Content-Type": "application/json"
951
1140
  };
952
1141
  }
953
- /** The raw API key, for the small number of tools that need a non-Bearer header (e.g. `stage_audience_artifact`). */
1142
+ /** Artifact host used by audience staging. */
1143
+ getArtifactBaseUrl() {
1144
+ return this.artifactBaseUrl;
1145
+ }
1146
+ /** The raw API key, for tools that need a non-Bearer header. */
954
1147
  getApiKey() {
955
1148
  return this.apiKey;
956
1149
  }
@@ -1439,18 +1632,23 @@ function normalizeConversionEvent(params) {
1439
1632
  AmpToolbox,
1440
1633
  AnalyticsIntegrationManager,
1441
1634
  AnalyticsResource,
1635
+ AngstromResource,
1442
1636
  AudiencesResource,
1443
1637
  CampaignsResource,
1444
1638
  ConversionsResource,
1445
1639
  CreativeResource,
1640
+ DEFAULT_ARTIFACT_BASE_URL,
1446
1641
  DEFAULT_BASE_URL,
1447
1642
  KeywordsResource,
1448
1643
  LinkedInResource,
1449
1644
  MetaResource,
1450
1645
  RedditResource,
1646
+ ShieldResource,
1647
+ SkillsResource,
1451
1648
  Synter,
1452
1649
  SynterError,
1453
1650
  SynterValidationError,
1651
+ VisibilityResource,
1454
1652
  buildConversionPayload,
1455
1653
  buildTrackingURL,
1456
1654
  buildUTMParameters,
package/dist/index.mjs CHANGED
@@ -149,7 +149,7 @@ var PAUSE_CAMPAIGN_ARG_MAPPING = [
149
149
  ];
150
150
  var UPDATE_BUDGET_ARG_MAPPING = [
151
151
  { field: "campaign_id", flag: "--campaign-id", type: "string", required: true },
152
- { field: "daily_budget", flag: "--budget", type: "number", required: true }
152
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true }
153
153
  ];
154
154
  var CampaignsResource = class {
155
155
  constructor(client) {
@@ -222,6 +222,72 @@ var CampaignsResource = class {
222
222
  const args = buildArgsFromMapping(UPDATE_BUDGET_ARG_MAPPING, input);
223
223
  return this.client.runTool("update_campaign_budget", args, "google");
224
224
  }
225
+ /**
226
+ * Update campaign budget with deterministic readback verification.
227
+ *
228
+ * Executes the budget update, waits for propagation, and reads back
229
+ * campaign status from the platform to verify the update succeeded on the wire.
230
+ */
231
+ async updateBudgetAndVerify(input) {
232
+ const mutation = await this.updateBudget(input);
233
+ const maxWaitMs = input.maxWaitMs ?? 5e3;
234
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
235
+ const startTime = Date.now();
236
+ let lastReadback;
237
+ while (Date.now() - startTime <= maxWaitMs) {
238
+ try {
239
+ const readback = await this.list({
240
+ platform: input.platform
241
+ });
242
+ lastReadback = readback;
243
+ const campaigns = readback.campaigns || readback.data || [];
244
+ const match = campaigns.find(
245
+ (c) => String(c.id || c.campaign_id) === String(input.campaign_id)
246
+ );
247
+ if (match) {
248
+ const budget = Number(match.daily_budget_usd || match.daily_budget || match.budget);
249
+ if (Math.abs(budget - input.daily_budget) < 0.01) {
250
+ return { mutation, verified: true, readback: match };
251
+ }
252
+ }
253
+ } catch {
254
+ }
255
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
256
+ }
257
+ return { mutation, verified: false, readback: lastReadback };
258
+ }
259
+ /**
260
+ * Pause a campaign with deterministic readback verification.
261
+ *
262
+ * Executes the pause command, waits for propagation, and reads back
263
+ * campaign status from the platform to verify status === 'PAUSED' on the wire.
264
+ */
265
+ async pauseAndVerify(input) {
266
+ const mutation = await this.pause(input);
267
+ const maxWaitMs = input.maxWaitMs ?? 5e3;
268
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
269
+ const startTime = Date.now();
270
+ let lastReadback;
271
+ while (Date.now() - startTime <= maxWaitMs) {
272
+ try {
273
+ const readback = await this.list({
274
+ platform: input.platform,
275
+ status: "PAUSED"
276
+ });
277
+ lastReadback = readback;
278
+ const campaigns = readback.campaigns || readback.data || [];
279
+ const match = campaigns.find(
280
+ (c) => String(c.id || c.campaign_id) === String(input.campaign_id)
281
+ );
282
+ if (match) {
283
+ return { mutation, verified: true, readback: match };
284
+ }
285
+ } catch {
286
+ }
287
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
288
+ }
289
+ return { mutation, verified: false, readback: lastReadback };
290
+ }
225
291
  };
226
292
 
227
293
  // src/resources/analytics.ts
@@ -315,6 +381,21 @@ var CREATE_CONVERSION_ARG_MAPPING = [
315
381
  var DIAGNOSE_TRACKING_ARG_MAPPING = [
316
382
  { field: "url", flag: "--url", type: "string", required: true }
317
383
  ];
384
+ var GET_PIXEL_HEALTH_ARG_MAPPING = [
385
+ { field: "site_key", flag: "--site-key", type: "string" }
386
+ ];
387
+ var LIST_EVENT_DELIVERIES_ARG_MAPPING = [
388
+ { field: "site_key", flag: "--site-key", type: "string" },
389
+ { field: "status", flag: "--status", type: "string" },
390
+ { field: "platform", flag: "--platform", type: "string" },
391
+ { field: "limit", flag: "--limit", type: "number" }
392
+ ];
393
+ var GET_CLICK_ATTRIBUTION_ARG_MAPPING = [
394
+ { field: "site_key", flag: "--site-key", type: "string" },
395
+ { field: "user_id", flag: "--user-id", type: "number" },
396
+ { field: "email", flag: "--email", type: "string" },
397
+ { field: "limit", flag: "--limit", type: "number" }
398
+ ];
318
399
  var ConversionsResource = class {
319
400
  constructor(client) {
320
401
  this.client = client;
@@ -340,7 +421,19 @@ var ConversionsResource = class {
340
421
  */
341
422
  async diagnoseTracking(input) {
342
423
  const args = buildArgsFromMapping(DIAGNOSE_TRACKING_ARG_MAPPING, input);
343
- return this.client.runTool("diagnose_conversion_tracking", args);
424
+ return this.client.runTool("diagnose_tracking", args);
425
+ }
426
+ async getPixelHealth(input = {}) {
427
+ const args = buildArgsFromMapping(GET_PIXEL_HEALTH_ARG_MAPPING, input);
428
+ return this.client.runTool("get_pixel_health", args);
429
+ }
430
+ async listEventDeliveries(input = {}) {
431
+ const args = buildArgsFromMapping(LIST_EVENT_DELIVERIES_ARG_MAPPING, input);
432
+ return this.client.runTool("list_event_deliveries", args);
433
+ }
434
+ async getClickAttribution(input = {}) {
435
+ const args = buildArgsFromMapping(GET_CLICK_ATTRIBUTION_ARG_MAPPING, input);
436
+ return this.client.runTool("get_click_attribution", args);
344
437
  }
345
438
  };
346
439
 
@@ -433,7 +526,6 @@ var RedditResource = class {
433
526
 
434
527
  // src/resources/audiences.ts
435
528
  var CONSENT_WARNING = "[@synterai/sdk-js] i_have_consent was not provided. Confirm lawful basis under GDPR/CCPA and the destination platform's terms for every identifier before staging or syncing PII, then pass `i_have_consent: true`. (Soft-launch notice \u2014 mirrors the backend's own behavior; this call is not blocked.)";
436
- var ARTIFACT_ENDPOINT = "https://api.syntermedia.ai/artifacts/audience-sync-input";
437
529
  var SYNC_AUDIENCE_SCRIPT_BY_PLATFORM = {
438
530
  google: "google_ads_create_customer_match",
439
531
  meta: "meta_ads_create_audience",
@@ -465,7 +557,7 @@ var AudiencesResource = class {
465
557
  * identifiers inline when over ~500 entries.
466
558
  *
467
559
  * Bypasses the primary `tools/run` dispatcher: different host
468
- * (`https://api.syntermedia.ai`) and a different auth header
560
+ * (`https://api.synterai.com`) and a different auth header
469
561
  * (`X-Synter-Key`, not `Authorization: Bearer`) — see SPEC.md/catalog.json
470
562
  * `transport.special`.
471
563
  */
@@ -480,7 +572,7 @@ var AudiencesResource = class {
480
572
  if (input.script_name !== void 0) payload.script_name = input.script_name;
481
573
  if (input.ttl_seconds !== void 0) payload.ttl_seconds = input.ttl_seconds;
482
574
  if (input.i_have_consent !== void 0) payload.i_have_consent = input.i_have_consent;
483
- return this.client.sendAbsolute(ARTIFACT_ENDPOINT, {
575
+ return this.client.sendAbsolute(`${this.client.getArtifactBaseUrl()}/artifacts/audience-sync-input`, {
484
576
  method: "POST",
485
577
  headers: {
486
578
  "X-Synter-Key": this.client.getApiKey(),
@@ -555,6 +647,91 @@ var AudiencesResource = class {
555
647
  }
556
648
  };
557
649
 
650
+ // src/resources/shield.ts
651
+ var ShieldResource = class {
652
+ constructor(client) {
653
+ this.client = client;
654
+ }
655
+ /**
656
+ * Inspect Agent Shield velocity limits, burst thresholds, circuit breakers,
657
+ * and kill-switch status before executing ad-spend mutations.
658
+ */
659
+ async inspect(input = {}) {
660
+ const args = [];
661
+ if (input.platform) {
662
+ args.push("--platform", input.platform);
663
+ }
664
+ const result = await this.client.runTool("inspect_agent_shield", args, input.platform);
665
+ return result;
666
+ }
667
+ };
668
+
669
+ // src/resources/angstrom.ts
670
+ var AngstromResource = class {
671
+ constructor(client) {
672
+ this.client = client;
673
+ }
674
+ /**
675
+ * Verify attribution claims against Synter Ångström's deterministic confidence rubric.
676
+ */
677
+ async verify(input = {}) {
678
+ const args = [];
679
+ if (input.platform) args.push("--platform", input.platform);
680
+ if (input.claimed_conversions !== void 0) args.push("--conversions", String(input.claimed_conversions));
681
+ if (input.claimed_revenue_usd !== void 0) args.push("--revenue", String(input.claimed_revenue_usd));
682
+ if (input.touchpoint_ids && input.touchpoint_ids.length > 0) {
683
+ args.push("--touchpoints", input.touchpoint_ids.join(","));
684
+ }
685
+ const result = await this.client.runTool("verify_angstrom_claim", args, input.platform);
686
+ return result;
687
+ }
688
+ };
689
+
690
+ // src/resources/skills.ts
691
+ var SkillsResource = class {
692
+ constructor(client) {
693
+ this.client = client;
694
+ }
695
+ /**
696
+ * Load an autonomous growth runbook by ID.
697
+ */
698
+ async get(skillId) {
699
+ const result = await this.client.runTool("get_skill", ["--skill-id", skillId]);
700
+ return result;
701
+ }
702
+ /**
703
+ * List available autonomous growth runbook IDs.
704
+ */
705
+ list() {
706
+ return [
707
+ "pmax-brand-cannibalization",
708
+ "search-hygiene-shield",
709
+ "marginal-cpa-budget-rebalancer",
710
+ "meta-asc-dark-pool-auditor",
711
+ "single-truth-attribution"
712
+ ];
713
+ }
714
+ };
715
+
716
+ // src/resources/visibility.ts
717
+ var VisibilityResource = class {
718
+ constructor(client) {
719
+ this.client = client;
720
+ }
721
+ /**
722
+ * Read AI search brand visibility and AEO share of voice across ChatGPT, Claude, Perplexity, and Gemini.
723
+ */
724
+ async get(input = {}) {
725
+ const args = [];
726
+ if (input.query) args.push("--query", input.query);
727
+ if (input.competitor_domains && input.competitor_domains.length > 0) {
728
+ args.push("--competitors", input.competitor_domains.join(","));
729
+ }
730
+ const result = await this.client.runTool("get_llm_visibility", args);
731
+ return result;
732
+ }
733
+ };
734
+
558
735
  // src/toolbox/amp-tools.ts
559
736
  var AmpToolbox = class {
560
737
  constructor(client, config = {}) {
@@ -788,7 +965,8 @@ var AmpToolbox = class {
788
965
  };
789
966
 
790
967
  // src/client.ts
791
- var DEFAULT_BASE_URL = "https://syntermedia.ai";
968
+ var DEFAULT_BASE_URL = "https://synterai.com";
969
+ var DEFAULT_ARTIFACT_BASE_URL = "https://api.synterai.com";
792
970
  var DEFAULT_TIMEOUT_MS = 3e4;
793
971
  var DEFAULT_MAX_RETRIES = 3;
794
972
  var RETRY_BASE_MS = 1e3;
@@ -806,7 +984,9 @@ var Synter = class {
806
984
  throw new SynterValidationError("`apiKey` is required to construct a Synter client.", "apiKey");
807
985
  }
808
986
  this.apiKey = fullConfig.apiKey;
809
- this.baseUrl = fullConfig.baseUrl ?? DEFAULT_BASE_URL;
987
+ const env = typeof process !== "undefined" ? process.env : void 0;
988
+ this.baseUrl = fullConfig.baseUrl ?? (env?.SYNTER_API_URL || DEFAULT_BASE_URL);
989
+ this.artifactBaseUrl = fullConfig.artifactBaseUrl ?? (env?.SYNTER_ARTIFACT_API_URL || DEFAULT_ARTIFACT_BASE_URL);
810
990
  this.timeout = fullConfig.timeout ?? DEFAULT_TIMEOUT_MS;
811
991
  this.maxRetries = fullConfig.maxRetries ?? DEFAULT_MAX_RETRIES;
812
992
  this.fetchImpl = fullConfig.fetch ?? globalThis.fetch.bind(globalThis);
@@ -819,6 +999,10 @@ var Synter = class {
819
999
  this.linkedin = new LinkedInResource(this);
820
1000
  this.reddit = new RedditResource(this);
821
1001
  this.audiences = new AudiencesResource(this);
1002
+ this.shield = new ShieldResource(this);
1003
+ this.angstrom = new AngstromResource(this);
1004
+ this.skills = new SkillsResource(this);
1005
+ this.visibility = new VisibilityResource(this);
822
1006
  this.amp = new AmpToolbox(this, ampConfig);
823
1007
  }
824
1008
  // ---------------------------------------------------------------------
@@ -897,7 +1081,11 @@ var Synter = class {
897
1081
  "Content-Type": "application/json"
898
1082
  };
899
1083
  }
900
- /** The raw API key, for the small number of tools that need a non-Bearer header (e.g. `stage_audience_artifact`). */
1084
+ /** Artifact host used by audience staging. */
1085
+ getArtifactBaseUrl() {
1086
+ return this.artifactBaseUrl;
1087
+ }
1088
+ /** The raw API key, for tools that need a non-Bearer header. */
901
1089
  getApiKey() {
902
1090
  return this.apiKey;
903
1091
  }
@@ -1385,18 +1573,23 @@ export {
1385
1573
  AmpToolbox,
1386
1574
  AnalyticsIntegrationManager,
1387
1575
  AnalyticsResource,
1576
+ AngstromResource,
1388
1577
  AudiencesResource,
1389
1578
  CampaignsResource,
1390
1579
  ConversionsResource,
1391
1580
  CreativeResource,
1581
+ DEFAULT_ARTIFACT_BASE_URL,
1392
1582
  DEFAULT_BASE_URL,
1393
1583
  KeywordsResource,
1394
1584
  LinkedInResource,
1395
1585
  MetaResource,
1396
1586
  RedditResource,
1587
+ ShieldResource,
1588
+ SkillsResource,
1397
1589
  Synter,
1398
1590
  SynterError,
1399
1591
  SynterValidationError,
1592
+ VisibilityResource,
1400
1593
  buildConversionPayload,
1401
1594
  buildTrackingURL,
1402
1595
  buildUTMParameters,
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@synterai/sdk-js",
3
- "version": "0.1.2",
4
- "description": "TypeScript SDK for the Synter advertising API — manage campaigns across Google Ads, Meta, LinkedIn, Reddit, Microsoft, TikTok, and X",
3
+ "version": "0.2.0",
4
+ "description": "Official Synter TypeScript SDK for the Synter advertising API — manage campaigns across Google Ads, Meta, LinkedIn, Reddit, Microsoft, TikTok, and X. @iflow-mcp/* copies are unofficial.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "CHANGELOG.md"
10
11
  ],
11
12
  "scripts": {
12
13
  "build": "tsup src/index.ts --format cjs,esm --dts",
@@ -42,15 +43,15 @@
42
43
  "analytics",
43
44
  "ppc"
44
45
  ],
45
- "author": "SynterMedia",
46
+ "author": "Synter <hello@synterai.com>",
46
47
  "license": "MIT",
47
48
  "repository": {
48
49
  "type": "git",
49
- "url": "https://github.com/jshorwitz/synter-media.git",
50
- "directory": "packages/sdk-js"
50
+ "url": "git+https://github.com/Synter-Media-AI/synter-docs.git",
51
+ "directory": "sdk/typescript"
51
52
  },
52
- "homepage": "https://syntermedia.ai",
53
- "bugs": "https://github.com/jshorwitz/synter-media/issues",
53
+ "homepage": "https://synterai.com",
54
+ "bugs": "https://github.com/Synter-Media-AI/synter-docs/issues",
54
55
  "dependencies": {},
55
56
  "devDependencies": {
56
57
  "@types/node": "^20.10.0",