Authorization
OAuth 2.1 for a person, a bsk_ API key for a headless agent. What each credential reaches, how to revoke it, and the projectId-first flow.
The MCP server takes two credentials, and nothing else:
- OAuth 2.1 for a client a person is driving. There is no token to mint, copy, or store: every client
discovers the authorization server from
/api/mcp, registers itself, and sends you to a browser to approve. The consent screen lives at/oauth/consent; protected-resource metadata is served at/.well-known/oauth-protected-resourcefor automatic discovery. - A
bsk_API key for a headless agent — a scheduled job, a gateway on a server, anything with no browser to approve in. It is the same key the REST API takes, minted in Settings → API, sent asAuthorization: Bearer bsk_…. It is verified by the same code on both surfaces, so revoking a key stops REST and MCP together.
Personal access tokens (gads_…) were removed. If a client config still sends an Authorization: Bearer gads_…
header, delete it — it no longer authenticates anything. For an interactive client, OAuth needs nothing in its
place; for a headless one, send a bsk_ key instead.
What a session reaches
An OAuth session is always workspace-scoped with write access: every account in the workspace, the
setup tools, the money-moving gads_propose_* writes, and the approval tools. It acts as the person who
approved it, and the safe-apply policy is what bounds money-moving
changes — bounded ones auto-apply, the rest queue.
An API key is narrower on purpose. It carries:
- A ranked scope —
read<write<admin. Areadkey gets the read tools only. - A separate
canApprovegrant. The approval tools (gads_approve_action,gads_reject_action,gads_revert_action) need it on top ofwrite, so a key that may propose cannot wave its own proposal through. - An account reach — one pinned account, a chosen few, or the whole workspace.
- An expiry, and revocation that does not sign anyone out.
A key never auto-applies. It is an agent credential, so every change it proposes queues for review — even one the safe-apply policy would have applied for a person. claude.ai connectors cannot use a key; they only speak OAuth.
See Connect Claude Code and Connect claude.ai for the per-client steps, and Authentication for minting a key.
The projectId-first flow
An OAuth session — and any key not pinned to one account — reaches several accounts, so account-scoped tools have to be told which account you mean:
List accounts
gads_list_ad_accounts() returns every account the credential reaches, each with a projectId.
Pass projectId
Give the account's projectId to every account-scoped tool — gads_get_optimization_plan,
gads_list_campaigns, the gads_propose_* writes, and so on. Omitting it is the most common
"no account is in scope" error.
Workspace-level tools — gads_list_ad_accounts, gads_get_connection_status, gads_discover_ad_accounts,
gads_import_ad_account, and the approval tools — take no projectId.
Revoking access
OAuth: remove the connector (claude.ai) or the server entry (Claude Code, Codex) and revoke the OAuth
grant. Verification is revocation-aware, so a revoked session stops working on its next call rather than at
some later expiry. Revoking a grant does not touch your bsk_ keys.
API key: revoke it in Settings → API. The next request fails on both MCP and REST — there is one key and one verifier, not a copy per surface.
Rate limits
Limits are per user, not per session: 60 reads/min and 10 writes/min. Opening a second client does not raise your ceiling. See Rate limits.