The best ad management interface is your editor.

Your AI Already Knows TypeScript. Full autocomplete, branded type safety, AI-native workflows — and a git-based review process your whole team already understands.

TypeScript
import { google, daily, exact, phrase,
  headlines, descriptions, rsa, url } from '@upspawn/ads'

google.search('Brand - Acme', {
  budget: daily(40),
  //           ^ number (dollars, not micros)
  bidding: 'maximize-clicks',
  //        ^ "maximize-clicks" | "maximize-conversions" | "target-cpa"
})
  .group('Core Keywords', {
    keywords: [
      ...exact('acme'),       // [acme]
      ...phrase('acme app'),  // "acme app"
    ],
    ad: rsa(
      headlines('Acme — AI Workflow Automation', 'Automate Your Workflows'),
      //  ^ Headline — max 30 chars, enforced at build time
      descriptions('Build automations in minutes. No code required.'),
      url('https://acme.dev'),
    ),
  })

Ad platforms have GUIs. GUIs can't be linted, reviewed, or committed to git.

Your AI assistant can suggest code, catch type errors, and generate entire campaign structures — but it can't click through a browser UI.

When campaigns live in TypeScript, every tool in your development environment works. Your editor, your linter, your AI assistant, your CI pipeline.

No plugin required. No MCP server to configure. Your AI already speaks TypeScript.

What becomes possible

Full autocomplete and type checking

Every property, every option, every helper is fully typed. Your editor knows what `bidding` accepts, how many headlines an RSA allows, and what the valid match types are — before you run a single command.

TypeScript
// TypeScript catches this before it reaches the API:

google.search('Acme Brand', {
  bidding: 'maximize-revenue',
  //        ^^^^^^^^^^^^^^^^^ Error: not assignable to
  //        '"maximize-clicks" | "maximize-conversions" | "target-cpa"'
})

headlines('This headline is way too long to fit in a Google ad')
//         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         Error: Headline exceeds 30 characters

Branded types catch errors at compile time

Headlines, descriptions, and keywords are branded string types. You can't accidentally pass a description where a headline is expected. Constraints are enforced at construction — not at API call time.

TypeScript
// Can't mix up Headline and Description:
const ad = rsa(
  headlines(['Short headline']),
  descriptions(['My description text']),
  url('https://acme.dev'),
)

rsa(
  headlines([ad.descriptions[0]]),
  //         ^^^^^^^^^^^^^^^^^^^ Error: Argument of type 'Description'
  //         is not assignable to parameter of type 'Headline'
)

AI tools work natively — no plugin needed

Because campaigns are TypeScript, any AI coding assistant can read, write, and reason about them. Ask your assistant to generate a campaign, add keywords, or rewrite headlines — it already knows the types.

TypeScript
// Ask your AI: "Add a competitor ad group targeting Notion and Linear"

.group('Competitor Keywords', {
  keywords: [
    ...phrase('notion alternative'),
    ...phrase('linear alternative'),
    ...exact('acme vs notion'),
  ],
  ad: rsa(
    headlines('Better Than Notion for Teams', 'The Automation Tool Notion Lacks'),
    descriptions('Automate workflows Notion can't touch. Try free.'),
    url('https://acme.dev/compare'),
  ),
})

Capabilities

Full TypeScript autocomplete

Every builder method, every option, every helper is typed. Your editor knows what's valid before you run anything.

Branded type validation

Headlines, descriptions, and keywords are distinct branded types. Constraint violations are compile-time errors.

AI code tools work natively

Copilot, Cursor, Claude — any AI assistant can read and write campaign TypeScript. No MCP server, no plugin, no configuration.

Git-based review workflow

Campaign changes go through the same pull request and code review process as your application code.

No MCP or plugin required

The SDK is just TypeScript. AI tools that understand TypeScript understand your campaigns out of the box.

Deterministic, auditable control

Every change is a diff in git. No mystery, no surprises — just a clear record of what changed, when, and why.