Browse the docs

Waverunner MCP server

Waverunner is always-on advertising on a prepaid wallet. This MCP server is how Cursor, Claude, ChatGPT, and custom agents create an account, fund the wallet, launch campaigns, and read performance. Unauthenticated calls only see signup. After a bearer API key or Cursor OAuth, tools match REST API v1.

What connectors should know

Server name waverunner (title Waverunner). Transport: Streamable HTTP, POST https://waverunner.adwave.com/api/mcp. Clients must send Accept: application/json, text/event-stream. Discovery documents: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource (they name this product and point here).

  • Without a key: signup_start and signup_poll only. Email one-time code. The poll returns an API key once.
  • With a key or Cursor OAuth: businesses, campaigns, wallet checkout, ad review, tracking, Catalog Video, and Scale agency tools. Same org scope, validation, and rate limits as the REST API.
  • Cursor plugin: the Waverunner Cursor plugin teaches signup_start, named channels (platforms / inventorySurfaces), wallet checkout, and tracker install. Cursor can also connect with OAuth (client cursor-mcp). Marketplace listing is a separate publish step.
  • Money: integer cents on a prepaid wallet. New business accounts start with $60 starter credit. launch_campaign charges day one. A 402 includes nextTool: "create_wallet_checkout". Never send card numbers through MCP.

Quick start (two minutes)

  1. Sign up from the agent with signup_start / signup_poll (email one-time code). The poll returns an API key once. Cursor can also connect with OAuth (static public client cursor-mcp, PKCE S256).
  2. Or create an API key in the app: Settings → API keys. The key is shown once. Keys are scoped to the organization that was active when you created them; switch workspaces in the sidebar first if you need a key for another organization. There is no required key prefix.
  3. Add the server to your client: point it at https://waverunner.adwave.com/api/mcp with the key as a bearer token (configs below), or complete Cursor OAuth.
  4. Ask your agent something: try "List my Waverunner campaigns and their spend". The agent discovers tools from tools/list and the server instructions.
The MCP server wraps the same REST API v1 handlers in-process: same organization scoping, validation, and rate limits. Authenticated tools are listed below; each maps to a documented REST operation. Prefer specialized tools over the bulk org export when you only need one resource.

How to connect each client

Cursor: add to .cursor/mcp.json (or Settings → MCP). Any client that supports Streamable HTTP with custom headers uses this same shape:

{
  "mcpServers": {
    "waverunner": {
      "url": "https://waverunner.adwave.com/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

Claude Code: one CLI command:

claude mcp add --transport http waverunner https://waverunner.adwave.com/api/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

Grok and xAI: send the Settings API key as a static header. Do not complete Cursor OAuth (client cursor-mcp is for Cursor). xAI Remote MCP: authorization is the token placed on Authorization. Grok CLI:

grok mcp add --transport http waverunner https://waverunner.adwave.com/api/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

Claude Desktop and other stdio-only clients: bridge with mcp-remote in claude_desktop_config.json:

{
  "mcpServers": {
    "waverunner": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "https://waverunner.adwave.com/api/mcp",
        "--header", "Authorization: Bearer YOUR_API_KEY"
      ]
    }
  }
}

Custom agents: the official MCP TypeScript SDK (Python and other SDKs work the same way):

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(
  new StreamableHTTPClientTransport(new URL("https://waverunner.adwave.com/api/mcp"), {
    requestInit: {
      headers: { Authorization: `Bearer ${process.env.WAVERUNNER_API_KEY}` },
    },
  }),
);

const { tools } = await client.listTools();
const report = await client.callTool({
  name: "get_campaign_report",
  arguments: { campaignId: "018f..." },
});

The server is stateless: no sessions to create or clean up, and a dropped connection can simply reconnect. Treat the API key like a password. Anyone holding it can spend from your wallet.

Cursor OAuth

Cursor can connect without pasting a key. Waverunner advertises a static public client cursor-mcp (PKCE S256, token endpoint auth method none). The connector reads /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, then sends the human through /oauth/authorize and exchanges the code at /oauth/token. Other clients should use a bearer API key from signup or Settings.

Ad review webhook

Manual-approval orgs can poll list_ad_reviews or register a URL with set_ad_review_webhook. HTTPS is required except on localhost. The tool returns secret once; store it. get_ad_review_webhook never includes the secret. Each set call rotates it. clear_ad_review_webhook stops delivery.

We POST { "type": "ad_review.pending", "campaignId", "campaignName", "pending" } with x-webhook-id, x-webhook-timestamp, and x-webhook-signature (v1,<hex>). Verify HMAC-SHA256 of id.timestamp.rawBody (periods between the three values, exact JSON bytes) and reject timestamps more than 300 seconds off. A down endpoint does not undo the review email; poll list_ad_reviews if a delivery is missed. Same contract as API ad reviews.

Agency tools

An agency API key (Scale) can list, create, read, and update child clients, disable or restore a client, pull a child's campaign report, and download a cost CSV of media spend at cost. Prefer update_agency_client with operatingMode managed or self_serve (same packaged setter as the console). Client-scoped keys stay inside that client. There is no leads inbox tool. Fund the wallet with create_wallet_checkout (hosted Stripe URL; never send card numbers). offboard_agency_client pauses live campaigns and that client's keys; it does not delete history. restore_agency_client does not resume campaigns.

Signup tools (no API key)

Unauthenticated tools/list returns only these. Open verificationUri (/mcp-signup/verify?user_code=…). The human confirms the displayed code matches userCode, then enters the email one-time code. After signup_poll returns apiKey, reconnect with that bearer token to see the authenticated catalog.

WRITEsignup_start

Create a Waverunner account from an agent. Sends an email one-time code and returns deviceCode (keep secret), userCode to show the human, verificationUri to open (/mcp-signup/verify?user_code=...), and poll interval. Never returns an API key. Next: the human confirms userCode on that page, enters the email code, then you call signup_poll.

WRITEsignup_poll

Wait until the human finishes the email one-time code. Pass deviceCode from signup_start. status=pending: keep polling at interval seconds. status=consumed: apiKey is returned once (store it; reconnect with Authorization: Bearer). status=expired with reason device_expired or otp_expired: start over with signup_start. Includes starterCreditCents when the account is new.

Authenticated tools

Same org scope as REST API v1. Read tools send readOnlyHint; money and disable tools send destructiveHint where it applies. Grouped by resource:

Businesses

READlist_businesses

List your businesses with their analysis status. Poll after add_business until ready.

WRITEadd_business

Add a business by website URL and kick off analysis (scrape, profile, personas). Free. Agency parent keys cannot add a website (use a client workspace).

WRITEarchive_business

Archive a business that has no live or launching campaigns. Hidden from list_businesses; restore any time with unarchive_business.

WRITEunarchive_business

Restore an archived business to active use.

READget_business

Business detail: the extracted profile, restrictedContent (locks and attestation), and every persona on the business profile with its id and Persona Reach (targetingSeeds). Campaigns copy Reach after interest targeting is planned; persona seeds stay as written on the Creative profile.

WRITEupdate_business_restricted_content

Confirm system locks, attest restricted categories (or none), and set political disclosure fields. Cannot clear system locks; refused for restrictive upgrades while campaigns are launching, live, or paused.

WRITEcreate_persona

Create a persona for a business from free-text notes (enqueues enrichment; the worker builds the full persona). Free.

WRITEupdate_persona

Partial update of a persona: name, description, demographics, motivations, pain points, selected flag, and/or demographic pins and exclusions (catalog segment keys).

READget_business_ltv

Customer lifetime value for a business: average/quartile LTV and acquisition-month payback cohorts.

Creatives

READlist_creatives

List generated creatives for the business (newest first) with static variants, video ads, render status, and asset URLs. Paginated: default 20, max 100 per page; pass the returned nextCursor to fetch the next page.

READlist_ad_revisions

Media revision history for one static, video, or audio creative (prior deliverable URLs). Distinct from campaign serving version lineage.

WRITErestore_ad_revision

Restore a prior media revision to current (free). Live/paused campaign ads require a rationale; replaces DSP creatives when externalized.

WRITEgenerate_creatives

Charge and enqueue a library ad generation batch for a ready business (paid from the wallet; starter credit applies). Quote first with get_creation_quote.

READget_creation_quote

Pre-flight price for an ad generation batch. Pass campaignId for statics × interest lines; omit for library Launch-pack pricing.

Campaigns

READlist_campaigns

List campaigns with objective, status, and daily budget.

WRITEcreate_campaign

Create a draft campaign with one of seven outcomes (awareness, traffic, leads, calls, visits/foot traffic, bookings, or sales), optionally with targeted states, named platforms (google_ads, meta, reddit, google_search; a present platforms array is authoritative and does not re-add open inventory), inventorySurfaces (mobile-web, mobile-apps, tv), playbook (follow-journey | open-mix), followSpend (omitted stamps on; pass false to opt out), prospectSpend (omitted stamps on), winback (omitted stamps on), a landing URL, a geo-holdout for lift measurement, and audience boost. After interest targeting is planned, Autopilot builds a recommended creative plan (reuse ready creatives, generate only gaps across targeted personas; generationDeferred). Pass campaignAds: false for reuse-only. Metered: quote with campaignId once lines exist. Channels use Preparing → Starting → Live after launch.

READget_campaign

Full setup detail for one campaign: status, budget, flight dates, landing URL, targeted states, platforms, inventory surfaces, autopilotMode (auto | manual; manual = Guided), followSpend, prospectSpend, winback, playbook, beeswaxAudioEnabled, measureLift, audience boost, geo-holdout states, personas, budgetMix (Your split vs Autopilot), and the offer/instructions brief.

WRITEupdate_campaign_budget

Change a campaign's daily budget. No proration; the next daily charge uses the new value.

WRITEupdate_campaign_budget_mix

Set Your split (channel and persona shares of daily media) or return the mix to Autopilot. Returning to Autopilot requires a short reason. Shares must add up to 100%.

WRITErename_campaign

Rename a campaign in any status except mid-launch. Display-only; never affects serving or billing.

WRITEupdate_campaign_end_date

Set or clear the flight end date on a live or paused campaign (YYYY-MM-DD or null).

WRITEupdate_campaign_landing_url

Change the landing page on a live or paused campaign; refreshes serving ads to the new URL.

WRITEupdate_campaign_autopilot

Switch a campaign between Autopilot (auto) and Guided (manual). Returning to Autopilot requires a short reason.

WRITEupdate_campaign_setup

Edit draft campaign setup: US states (empty = national), named platforms (a present array is authoritative; google_ads, meta, reddit, google_search), live inventory tiles (inventorySurfaces: mobile-web, mobile-apps, tv), targeted personas (icpIds from get_business), spend legs (followSpend, prospectSpend, winback), playbook (follow-journey | open-mix), beeswaxAudioEnabled, geo-holdout (measureLift), and/or audience boost. Platforms, inventorySurfaces, spend-leg flags, and beeswaxAudioEnabled also apply live/paused (Audio is 422 audio_unavailable until that inventory is live). icpIds and playbook are refused once the campaign has launched. Switch Autopilot vs Guided with update_campaign_autopilot (campaign-wide). Send at least one field.

WRITElaunch_campaign

Launch a draft. Charges day one when ads are ready, or commits with awaitingCreatives while ads are still rendering. 402 includes nextTool create_wallet_checkout. 409 license_inactive when the agency license is not active.

WRITEcancel_campaign_launch

Cancel a launch that is still waiting on creatives (no charge yet). Returns the campaign to draft.

WRITEretry_campaign_launch

Retry a failed campaign: resets it to draft and re-runs the launch gate (may defer charge while ads render).

WRITEpause_campaign

Pause a live campaign. Future daily charges stop; today's charge stands.

WRITEresume_campaign

Resume a paused campaign. Re-charges today idempotently before serving restarts.

WRITEend_campaign

Permanently end a campaign. Stops all serving and charges; cannot be undone.

WRITEarchive_campaign

Archive a campaign that is not launching or live. Hidden from list_campaigns; restore any time with unarchive_campaign.

WRITEunarchive_campaign

Restore an archived campaign to your lists.

WRITEdelete_campaign

Permanently delete a never-run draft campaign. Generated ads stay in the Ad Library; campaigns that have run can only be archived.

READget_campaign_report

Full performance rollup: spend, conversions, revenue, CPA, ROAS, sessions (ad visits from this campaign's ads), per-platform breakdowns with honest channel fields (clickable vs TV views, assisted conversions as reporting-only union plus path breakdown, identity coverage, seed impact), and CPM breakout (all-in, audience list-rate fees, media remainder).

READget_campaign_analytics

Daily funnel timeseries (impressions → clicks → ad visits/sessions → conversions), device/region mix (observed phones/computers/TVs, not sold channel cards), where ads appeared as publishers (TV channels, web/apps, Google networks, Meta platforms, Reddit communities), per-persona and per-segment performance, per-day spend story. Channel cards stay Mobile web / TV / Google / Meta; deviceMix is a breakdown.

READget_campaign_lift

Lift scorecard: geo-holdout causal lift, cross-channel overlap, and Autopilot effectiveness (acted vs observe-only), each with a 95% CI and significance flag. Includes holdoutInconclusive when the holdout read is still directional.

READget_campaign_customers

Customers we introduced (first-touch-new identities with no prior graph presence before ad exposure) plus new vs returning conversions and cost per new / introduced customer.

READget_campaign_ads

Every ad in the campaign Ads set (not the business creative library) with status, persona, and lifetime performance.

WRITEset_ad_serving

Pause or resume one campaign ad across every platform. On live or paused campaigns, pass reasonCode (Autopilot override). Use resume_optimizer_pause for Autopilot pauses and resume_user_pause for manual pauses. Refuses to pause the last serving ad on this interest segment line.

Audiences

WRITEset_audience_boost

Turn audience boost on or off (draft, live, or paused). Turning it on requires an imported customer list for that business.

READlist_audiences

Website audiences (observe-first): first-party visitor segments with member counts, platform audiences (campaign reach, synced lists, lookalikes), customer sources for Audience boost, plus Customize targeting guides (interest and demographic catalog picks Autopilot always includes on the next launch). Persona Reach (targetingSeeds) is on each persona via get_business.

WRITEcreate_segment

Create a custom first-party segment for a business with a rule definition, evaluated immediately.

WRITEset_segment_active

Activate or pause a segment. Paused segments stop evaluating membership and syncing.

WRITEdelete_segment

Delete a custom (non-system, non-discovered) segment.

WRITErefresh_segment

Enqueue a re-evaluation of one segment's membership.

WRITEcustomize_audience

Add or remove an interest or demographic catalog pick that Autopilot always includes on the business's next launch.

WRITEresolve_audience_suggestion

Accept or dismiss a discovered-audience suggestion. Accept creates a segment with the suggestion's exact proposed rule.

WRITEupdate_audience_sync_consent

Turn organization-wide audience sync on or off (on by default). Off stops customer-list uploads and Meta/TikTok/Reddit exposure sharing.

WRITEimport_customer_csv

Import a customer CSV for a business to seed Audience boost. Hashed at rest; raw emails are never returned. Rate limited.

Tracking

READget_tracking_setup

A business's site tag snippet, server-side conversion webhook (POST JSON to conversionWebhook.url; the token is in the path, no bearer), conversion rules, and settings. Wire carts, CRMs, and your own systems into attribution.

WRITEcreate_conversion_rule

Create a conversion rule for a business (pageview, post_action, checkout_return, or call_click). Intent-page-looking patterns require acknowledgeIntentPage: true.

WRITEupdate_conversion_rule

Set or clear a conversion rule's per-conversion value estimate. Applies to new conversions only.

WRITEdelete_conversion_rule

Delete a conversion rule.

WRITEupdate_tracking_settings

Update a business's tracking-tag settings (autocapture, lead detection, origin enforcement, view-through window). Disabling recommended settings requires acknowledgeImpact: true.

WRITEupsert_form_conversion_value

Set or clear the per-conversion value estimate for a named Form conversion (lead, signup, or subscribe). Creates a value-only carrier when needed; clearing with null removes the estimate. Applies to new conversions only.

WRITEresolve_conversion_suggestion

Accept or dismiss an AI-discovered conversion suggestion. Accept creates or links a conversion rule; dismiss remembers the verdict forever.

WRITEsend_test_conversion

Record a test conversion event for a business to verify the tag/webhook pipeline end to end.

Wallet

READget_wallet

Prepaid wallet balance and auto-refill state. Check before launching. Fund with create_wallet_checkout (hosted Stripe URL; never send card numbers).

READget_wallet_ledger

Paginated wallet ledger (newest first): deposits, daily campaign charges, refunds, adjustments, and ad-creation charges. Default 25, max 100 per page.

WRITEcreate_wallet_checkout

Create a Stripe Checkout session to fund the prepaid wallet. Returns a hosted url; never send card data through MCP. Amount is integer cents, same floor and ceiling as wallet top-up in the app. Launch 402 includes nextTool create_wallet_checkout.

WRITEcreate_billing_portal_session

Create a Stripe Customer Portal session so the human can manage saved payment methods. Returns a hosted url. 422 portal_not_configured if the billing portal is not enabled yet; use create_wallet_checkout instead.

Ad reviews

READlist_ad_reviews

List ads waiting on manual approval for this organization (kind, entityId, campaign). Empty when the org auto-approves. Approve with approve_ad before those ads can serve. Optional push: set_ad_review_webhook, or poll this tool.

READget_ad_review_webhook

Show whether this org has an ad-review webhook. Returns { configured, url }. Never returns the secret; set_ad_review_webhook mints a new one.

WRITEset_ad_review_webhook

Register an HTTPS URL (http on localhost only) to receive signed POSTs when ads need manual approval. Returns { url, secret } once. Store the secret; GET never shows it. Each call rotates the secret.

WRITEclear_ad_review_webhook

Stop sending ad-review webhooks. Clears the URL and secret.

Catalog Video

READlist_catalogs

List Catalog Video product catalogs (requires Catalog Video).

WRITEcreate_catalog

Create a Catalog Video product catalog from a shop URL, feed URL, or csvText (CSV). Requires Catalog Video.

READget_catalog_items

List synced products for a catalog (SKU, title, price, image). Paginated (limit/cursor, default 100, max 500).

WRITEsync_catalog

Enqueue a catalog re-sync from its source.

READlist_catalog_video_runs

List Catalog Video production runs for a business. Paginated (limit/cursor, default 50, max 200).

WRITEcreate_catalog_video_from_url

Kick off the shop-URL → Catalog Video auto-pipeline for a business. Idempotent on (businessId, url). Requires Catalog Video.

WRITEcreate_catalog_video_run

Create a draft Catalog Video run linked to a catalog (optional template + SKUs + defaultTier). Requires Catalog Video.

READget_catalog_video_run

Catalog Video run detail with progress counters and status.

READquote_catalog_video_run

Pre-flight price for a Catalog Video run (sample or full tranche).

WRITElaunch_catalog_video_run

Charge the first tranche and start Catalog Video production. Pass the quote's mode + quoteHash to guard against a stale quote. Metered.

WRITEupdate_catalog_video_run

Edit SKUs, aspect ratios, tier, sync cadence, refresh budget, new-item policy, or launch mode while draft/quoting/paused.

WRITEpause_catalog_video_run

Manually pause a producing/live Catalog Video run. Money-neutral.

WRITEresume_catalog_video_run

Resume a paused Catalog Video run and re-arm remaining production work.

WRITEarchive_catalog_video_run

Archive a draft/failed/paused/live Catalog Video run. Terminal, non-refunding.

WRITEretry_catalog_video_run

Clear the error on a failed Catalog Video run and resume it.

READget_catalog_video_manifest

Per-variant CDN / VAST URLs for a Catalog Video run. Paginated (limit/cursor, default 100, max 500).

READget_catalog_video_feeds

Platform supplemental feed URLs once a run is live.

READexport_catalog_video_run

Google video_link activation kit (instructions + CSV) for operator handoff.

READlist_catalog_video_templates

List Catalog Video templates for the org (optional businessId).

READget_catalog_video_template

Catalog Video template detail: status, hook count, pitched concept.

WRITEpitch_catalog_video_template

Pitch a Catalog Video template (optional hookCount for opening-hook variants) and start the human-review probe pipeline.

WRITEapprove_catalog_video_template

Human-approve a Catalog Video template before launching a run.

READlist_campaign_catalog_video_runs

List bindable Catalog Video runs for a campaign and its current binding. Requires Catalog Video.

WRITEbind_campaign_catalog_video

Bind or clear a campaign's catalogVideoRunId for native Catalog Video serving (requires Catalog Video).

Organization

WRITEretry_failed_placements

Re-run distribution for a live campaign's failed placements only; serving placements are left untouched.

READlist_recommendations

Pending cross-campaign budget moves the portfolio optimizer computed but didn't auto-apply, with the marginal-CPA evidence.

WRITEapply_recommendation

Apply a pending budget move between two campaigns (the same validated transactional path as a manual budget edit).

WRITEdismiss_recommendation

Dismiss a pending budget recommendation without moving any money.

READlist_integrations

List connected customer-data sources (provider, status, last sync). Tokens are never returned. Pass businessId to filter to one website.

WRITEsync_integration

Enqueue a sync for an already-connected customer-data source. Connect/disconnect stay in the web app.

WRITEupdate_organization

Update organization product settings. Currently just crossCampaignOptimization: enabling it lets the portfolio optimizer apply cross-campaign budget moves automatically.

READexport_org_data

Full organization data export as one large JSON document (businesses, personas, creatives, campaigns, ledger entries, delivered spend, segments, per-ad performance, and more). Rate limited to 5 requests per hour per organization.

WRITEapprove_ad

Approve one pending_review ad so it can serve. Pass kind (static, video, or audio) and entityId from list_ad_reviews. Does not launch a campaign; it only clears the approval hold on that ad.

Agency

READlist_agency_clients

List child client organizations for an agency API key. Scale license required. Refused on a client-scoped key.

WRITEcreate_agency_client

Create a child client organization under the agency. Scale license required.

READget_agency_client

Read one child client's stored experience/billing modes (Managed vs Self-serve pair), fee percent, monthly spend cap, digest routing, campaignPolicy (stored + inheritance-resolved defaults/locks), and effective rate split. Change the pair with update_agency_client operatingMode.

WRITEupdate_agency_client

Update one child client: operatingMode (managed or self_serve), the agency fee percent (null = inherit the agency default), the monthly spend cap in cents (null = no cap), digest mail routing (true = client + agency, false = agency only, null = follow experience mode), and campaignPolicy defaults/locks (null clears the child's stored policy). Only the fields you pass change.

WRITEoffboard_agency_client

Disable a child client: pause live campaigns and turn off that client's API keys. Pass confirmName matching the client name. Does not delete history. Restore with restore_agency_client.

WRITErestore_agency_client

Restore a disabled child client so they can sign in again. Does not resume paused campaigns. Pass confirmName matching the client name.

READlist_agency_client_campaigns

List campaigns that belong to one child client org.

READget_agency_client_campaign_report

Performance report for a campaign in a child client org. Spend is pass-through media at cost.

READget_agency_client_campaign_analytics

Analytics for a campaign in a child client org.

READget_agency_cost_report

CSV of pass-through media spend across child clients for agency finance.

Agent onboarding path

A fresh API key is enough for an agent to take a website from zero to a launched campaign: add_business, wait until analysis is ready, review personas with get_business, price creatives with get_creation_quote, create_campaign, confirm get_wallet, create_wallet_checkout if the balance is short, then launch_campaign (always confirm with the user before launch, because it charges the wallet). We keep that path exercised in automated tests so it stays reliable as tools change.

After launch, prefer get_campaign_report and get_campaign_customers for results: conversions and ad visits lead. Customers we introduced counts people with no prior presence before this campaign's first ad. Raw clicks are a secondary Mobile web signal.

The full workflow, step by step

This is the sequence agents are taught via the server's instructions; each step maps to one tool call:

  1. add_business with your website URL. Analysis is free and takes a minute or two. Poll list_businesses until the status is ready.
  2. get_business: review the extracted profile and the personas. Each persona has an id and Persona Reach (targetingSeeds); pass a subset as icpIds to target specific personas. After interest targeting is planned, Reach is copied into that campaign's flight targeting.
  3. create_campaign: a draft; launching is separate. Named platforms include google_ads, meta, reddit, and google_search (Search under Google, on by default when the daily budget covers it). After interest targeting is planned, Autopilot builds a recommended creative plan (reuse ready creatives, generate only gaps across targeted personas; generationDeferred: true; metered from the wallet, with starter credit applying). Pass campaignAds: false for a reuse-only plan with no generation charge. Daily budget is at least $30. After create, use update_campaign_setup to change channels, inventory tiles, Follow spend, playbook (Follow vs Open mix), geo, or icpIds (which personas the draft targets). Switch Autopilot vs Guided with update_campaign_autopilot (campaign-wide, not per channel).
  4. get_creation_quote: price the batch. Pass campaignId once lines exist for the recommended creative plan gaps (statics only where needed across personas, plus missing videos); omit campaignId for library Launch-pack pricing. Returns unit breakdown, billing mode, and remaining wallet funds. Watch generation with list_creatives.
  5. get_wallet: confirm the balance covers at least the first day. If it does not, create_wallet_checkout and open the returned URL. Then launch_campaign. A 402 from launch includes nextTool: "create_wallet_checkout".
  6. Manual-approval orgs: list_ad_reviews then approve_ad before those ads can serve. Optional: set_ad_review_webhook for a signed POST when new ads need review.
  7. get_campaign_report: spend, conversions, revenue, CPA, ROAS, per-platform breakdowns. Manage spend with update_campaign_budget, update_campaign_end_date, update_campaign_landing_url, update_campaign_autopilot, pause_campaign, resume_campaign, end_campaign.
  8. Optimize: get_campaign_analytics for daily trends and persona/segment/device breakdowns, get_campaign_ads for per-ad numbers, then set_ad_serving to pause the weak ads (on live or paused campaigns, pass a reasonCode; when resuming, use resume_optimizer_pause or resume_user_pause to match how the ad was paused). Wire your own conversion data in with get_tracking_setup so the loop runs on real outcomes.

A tool call and its result look like this (all money values are integer cents):

// → tools/call
{ "name": "create_campaign",
  "arguments": {
    "businessId": "018f3a2b-...",
    "name": "Summer Sale",
    "objective": "sales",
    "dailyBudgetCents": 5000,
    "offer": "Free shipping over $40"
  } }

// ← result
{ "campaign": { "id": "018f3c91-...", "status": "draft" },
  "campaignAdsEnqueued": false, "generationDeferred": true }

Use cases & example prompts

Paste these into any connected agent and adjust the specifics. They exercise the whole surface:

Sign up from Cursor

No API key yet. The agent creates the account, the human enters the email code, then the agent reconnects with the returned key.

Connect to Waverunner MCP. Start signup with my email, open the verification link, wait until I enter the one-time code, then save the API key and list my businesses.

Zero to live campaign

You have a website and a budget, and want an agent to handle everything: analysis, personas, ad generation, and launch.

Add acme-coffee.com to Waverunner, wait for the analysis, show me the personas it found, then create a $50/day sales campaign with the offer “free shipping over $40”. Show me the generated ads and the wallet balance, then ask me before launching.

Morning performance check

A recurring assistant task: pull yesterday's numbers and flag anything that needs a decision.

Pull the report for every live Waverunner campaign. Summarize spend, conversions, CPA, and ROAS, compare platforms, and tell me if anything looks off or is worth pausing.

Budget management

Shift money toward what works without logging into the dashboard.

My “Summer Sale” campaign is converting at 4x ROAS and “Brand Awareness” is barely spending. Raise Summer Sale to $80/day and drop Brand Awareness to $15/day.

Spend guardrails

Stop spending fast when priorities change: pausing keeps the campaign resumable, ending is permanent.

We're out of inventory until Thursday. Pause every live Waverunner campaign now, and remind me to resume them Thursday morning.

Audience review

Review Website audiences and platform sync state before planning the next campaign.

List my Waverunner audiences. How many people are in the high-intent segment, and which platform audiences are synced and ready for Delivery?

Closed-loop creative optimization

The full optimization loop: pull daily and per-ad data, join it with your own numbers (margins, inventory, CRM), decide, and push the changes back.

Pull the last 30 days of analytics and the per-ad performance for my “Summer Sale” Waverunner campaign. Cross-reference the conversion revenue with the product margins in my spreadsheet, then pause any ad whose margin-adjusted CPA is above $40 and tell me if the budget should move.

Approve ads from your own queue

The org requires manual approval. Register a webhook so new ads arrive as a signed POST instead of polling.

Set my Waverunner ad-review webhook to https://example.com/hooks/ads. Save the secret, then whenever a signed POST arrives, list pending reviews and approve the ones I named.

Wire in your own conversion data

Attribution is only as good as the conversions it sees. Feed them from any backend so reports and per-ad numbers reflect reality.

Get my Waverunner tracking setup and write a small script that posts a conversion to the webhook whenever a Stripe payment succeeds, including the order id and the customer email.

Errors & limits

Failed tool calls return the HTTP status and the same JSON error the REST API sends, so agents can react precisely:

ErrorMeaningWhat to do
401 unauthorizedInvalid or revoked API key or OAuth token. Missing Authorization on initialize, tools/list, ping, or signup tools is not an error (signup only). Missing Authorization on any other method is 401.Send a valid Settings API key as Authorization: Bearer, or complete Cursor OAuth. Do not call signup_start while a Bearer is set.
402 insufficient_fundsThe wallet can't cover the first day's budgetCall create_wallet_checkout, open the Stripe URL, then launch again
422 portal_not_configuredStripe Customer Portal is not enabled yetUse create_wallet_checkout until the billing portal is enabled
404 *_not_foundThe id doesn't exist in your organizationRe-list (businesses/campaigns) and use a returned id
409 no_servable_adsNothing ready to serve: BYO, Catalog Video, or prepaid with no finished ad; Autopilot if ads cannot start or generation already failedUpload ads, add a buyer type, or wait for generation. Autopilot starts ads at launch if none were requested yet
409 byo_missing_serve_familiesUpload-your-ads campaign is missing a finished ad for a required surface familyUpload the missing mobile, TV, or follow ad, then launch again
422 illegal_comboAgency client update mixed Managed and Self-serve fields illegallySend operatingMode managed or self_serve, or a legal experience/billing pair
409 invalid_statusThe campaign isn't in a state that allows the actionCheck list_campaigns (e.g. only live campaigns pause)
409 last_serving_adPausing this ad would leave its persona with nothing to serveResume or generate another ad for that persona first
400 override_requiredLive or paused campaign: Autopilot manages this creative; reasonCode is requiredPass reasonCode (and optional note) on set_ad_serving, then retry
400 resume_reason_mismatchResume used the wrong dedicated reason for how the ad was pausedUse resume_optimizer_pause for Autopilot pauses, resume_user_pause for manual pauses
422 invalid_inputA parameter failed validationFix the field named in the issues array and retry
429 rate_limitedToo many requests this minuteBack off and retry (120 reads / 30 writes per minute)

Rate limits are shared with the REST API: 120 reads and 30 writes per minute per key, plus 120 MCP requests per minute. Analysis and ad generation are subject to the daily fair-use caps on the pricing page.

Money safety

launch_campaign and resume_campaign move media money: launching charges the first day's budget from your prepaid wallet immediately (and the daily budget each active day after); resuming a paused campaign re-charges the current day. Ad generation through API and MCP (inside create_campaign) is quoted per unit, priced up front by get_creation_quote, charged to the wallet (starter credit applies), and rejected with a 402 (nothing created) when the wallet cannot cover it. In the product UI, campaign ads for businesses are included with the campaign. Adding businesses and analysis are always free, and a launch fails cleanly (never overdrafts) when the wallet can't cover day one. Agents are told to confirm with you before launching, resuming, or ending, and money only ever leaves the prepaid wallet you topped up, never a card on file (unless you opted into auto-refill). To fund from the agent, call create_wallet_checkout and open the hosted Stripe URL.