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, or the same bsk_… key
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.

A bsk_ key works on both surfaces and is verified by the same code on each, so revoking it stops REST and MCP together. An MCP OAuth grant is a separate credential: revoking one does not touch the other.

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