API Reference

Read your Google Ads warehouse, propose and approve changes, and hand the Ads copilot a brief — from your own code.

The REST API at /api/v1 is for your own code: a nightly report, a Slack bot, an internal dashboard, a cron job that watches for wasted spend. It reads the same warehouse the dashboard reads and writes through the same safe-apply policy.

REST or MCP?

Both surfaces reach the same account. They are for different callers.

REST APIMCP server
CallerYour codeAn agent a person is driving
ShapeVersioned URLs, typed error codesTools an LLM picks between
Authbsk_… API keysOAuth 2.1, approved in a browser
Best atDeterministic reads and writesOpen-ended "figure out what's wrong"

If your code knows exactly what it wants, use REST. If you want judgement applied, either use MCP or POST a brief to agent runs — that is the same copilot, reachable over HTTP.

The two surfaces are independently revocable: revoking a key does not touch an MCP OAuth grant, and revoking a grant does not touch your keys.

Three minutes to your first call

Create a key in Settings → API, then list what it can reach:

curl "$BIDANDSCALE_URL/api/v1/accounts" \
  -H "Authorization: Bearer $BIDANDSCALE_API_KEY"

Every account-scoped endpoint takes a projectId from that response:

curl "$BIDANDSCALE_URL/api/v1/accounts/$PROJECT_ID/campaigns?days=30" \
  -H "Authorization: Bearer $BIDANDSCALE_API_KEY"

Conventions

Money is micros. 1000000 micros is one unit of the account's currency. Every response carrying money also carries currency_code. There are no floats — a rounded euro is a wrong euro.

snake_case on the wire, in request bodies, query params and responses alike.

Lists return { "data": [...], "has_more": boolean }. Where a list is genuinely unbounded (/history) it also returns next_before, a cursor to pass back as ?before=.

Errors are always { "error": { "code": "…", "message": "…" } }. See Errors.

Freshness. Reads come from a warehouse synced from Google Ads, not live. Endpoints built on it return a data_freshness block — check it before acting on numbers.

No CORS. Keys are server-side secrets and browser requests are not supported. A key in a front-end bundle is a key anyone can read.

Writes are proposals

No endpoint mutates Google Ads directly. A write creates a proposal which either queues for approval or, if you pass auto_apply and the policy rates the change safe, is applied by a worker.

{
  "action_id": "…",
  "disposition": "queued",
  "risk_tier": "review",
  "policy_reasons": ["Raise above the safe uplift band."],
  "approvals_url": "https://…/app/…/approvals"
}

auto_apply defaults to false. The dashboard and MCP let the policy auto-apply bounded changes because a human is watching; an HTTP call is the one surface where nobody is, so you have to opt in.

disposition is not terminal. auto_applied means "accepted for apply" — the worker re-runs the policy and can still send a change back to the queue (for example on the 20-per-day auto-apply cap). Read /accounts/{projectId}/history for the settled outcome.

On this page