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.
Authentication
Keys, scopes, and which accounts a key can reach.
Errors
The envelope and what each code means.
Rate limits
Per-key limits and the separate agent-run caps.
Agent runs
Hand the copilot a brief and poll for the result.
REST or MCP?
Both surfaces reach the same account. They are for different callers.
| REST API | MCP server | |
|---|---|---|
| Caller | Your code | An agent a person is driving |
| Shape | Versioned URLs, typed error codes | Tools an LLM picks between |
| Auth | bsk_… API keys | OAuth 2.1, approved in a browser |
| Best at | Deterministic reads and writes | Open-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.