@synterai/sdk-js
TypeScript SDK for the Synter advertising API — manage campaigns across Google Ads, Meta, LinkedIn, Microsoft Ads, Reddit, TikTok, and X from your own code, with full types and IDE autocomplete.
0.1.0— early release. Live on npm and installable, but pre-1.0: the surface may change before a stable1.0. Pin a version in production.
What this actually talks to
Every method in this SDK calls the same live, production endpoint that powers Synter's MCP server (@synterai/mcp-server) and the synter CLI: POST https://syntermedia.ai/api/v1/tools/run. There is no separate "SDK backend" — if a method works here, it works because the exact same call already works for every MCP client (Claude, Cursor, Codex, ChatGPT) today.
See ../sdk-shared/SPEC.md and ../sdk-shared/catalog.json for the full transport contract this SDK — and every sibling-language SDK — implements.
Install
npm install @synterai/sdk-js
Authentication
Get an API key at syntermedia.ai/developer. Keys look like syn_ followed by 32 base64url characters.
Server-side only. Your
SYNTER_API_KEYis a secret that can spend money and modify your ad accounts. Use this SDK from a backend — a Node service, a Next.js API route or Server Action, an edge/serverless function. Never instantiateSynterin client-side code (React components, browser bundles, mobile apps); the key would ship to every visitor. For a React/SPA frontend, call your own backend, and have the backend call Synter.
import { Synter } from '@synterai/sdk-js';
const synter = new Synter(process.env.SYNTER_API_KEY!);
// or, with options:
const synter = new Synter({
apiKey: process.env.SYNTER_API_KEY!,
timeout: 30_000, // ms, default 30s
maxRetries: 3, // default 3
});
Quickstart
import { Synter } from '@synterai/sdk-js';
const synter = new Synter(process.env.SYNTER_API_KEY!);
// List campaigns (defaults to Google if no platform is given)
const campaigns = await synter.campaigns.list({ status: 'ENABLED', limit: 10 });
// Create a Google Search campaign
await synter.campaigns.createSearch({
campaign_name: 'Q4 Launch',
daily_budget: 50,
keywords: ['running shoes'],
headlines: ['Fast. Light. Yours.', 'New Season, New PR', 'Free Shipping Today'],
descriptions: ['Premium running shoes built for speed.', 'Order today, ships free.'],
final_url: 'https://example.com/shoes',
});
// Pull performance metrics
const perf = await synter.analytics.getPerformance({ date_range: 'LAST_30_DAYS' });
// Generate an AI creative
const image = await synter.creative.generateImage({ prompt: 'a running shoe on a cloud, product photography' });
// Escape hatch: call any of the 140+ backend scripts by name
await synter.execute('google_ads_list_audiences', { status: 'ENABLED' }, 'google');
API surface
Methods are grouped by category, matching the tool catalog:
| Namespace | Methods |
|---|---|
synter.campaigns |
list, createSearch, createDisplay, createPmax, pause, updateBudget |
synter.analytics |
getPerformance, getDailySpend |
synter.keywords |
add, addNegative |
synter.conversions |
create, list, diagnoseTracking |
synter.creative |
generateImage, generateVideo |
synter.meta |
createCampaign |
synter.linkedin |
createCampaign |
synter.reddit |
createCampaign |
synter.audiences |
stageArtifact, sync, manage |
synter (top level) |
listAdAccounts, uploadImage, listLandingPages, execute |
Every method takes a fully-typed input object (required fields are non-optional in TypeScript) and returns Promise<Record<string, unknown>> — the backend scripts return script-specific JSON that isn't worth over-narrowing today.
synter.execute(scriptName, args, platform?) is the universal escape hatch: it can call any of the 140+ backend scripts beyond the typed methods above, converting an idiomatic {flagName: value} map into the CLI-flag wire format internally.
Errors
import { SynterError, SynterValidationError } from '@synterai/sdk-js';
try {
await synter.campaigns.createSearch({ /* missing required fields */ } as any);
} catch (err) {
if (err instanceof SynterValidationError) {
// Caught client-side, before any network call — e.g. a missing required field.
} else if (err instanceof SynterError) {
// The API rejected the call: err.status, err.message, err.code, err.details
}
}
The client automatically retries up to 3 times (exponential backoff, 1s base / 10s cap) on 429 responses (honoring Retry-After) and on network/timeout errors. Other 4xx/5xx responses are not retried.
Known issues (intentionally preserved, not "fixed" here)
These mirror real, current production behavior of the backend scripts. The SDK calls the backend exactly as it works today rather than silently diverging — see ../sdk-shared/SPEC.md's "Known issues" section and catalog.json's knownIssues:
campaigns.pause()andcampaigns.updateBudget()accept aplatformfield (their schemas advertise all 7 platforms), but the live dispatch always usesplatform=googleregardless of what's passed.uploadImage()accepts an optionalplatform(google/meta/linkedin), but live dispatch always hardcodesplatform=google.campaigns.createDisplay()andcampaigns.createPmax()use different flag names for the same concept —--landscape-image/--square-imagevs.--landscape-image-url/--square-image-url.
These are flagged to Joel as product bugs to fix upstream (in catalog.json first, then every SDK), not something to patch ad hoc in one language.
Amp Toolbox
synter.amp is a separate, AI-powered toolbox for Amp integration (campaign generation, creative variants, budget optimization, etc.) — see AMP_TOOLBOX_README.md. It predates and is independent of the catalog-driven transport rewrite above.
Development
npm run build # tsup, dual ESM/CJS + .d.ts
npm run typecheck # tsc --noEmit
npx vitest run # test suite (mocked fetch, no real network/API key required)
License
MIT