Authentication & scopes

OAuth 2.1 for a person, a tsk_ API key for a headless agent. What each credential reaches, 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-resource for automatic discovery.
  • A tsk_ 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 Workspace → Settings → API keys (or a project's settings, for a one-account key) and sent as Authorization: Bearer tsk_…. It is verified by the same code on both surfaces, so revoking a key stops REST and MCP together.

Personal access tokens (asa_…) were removed. If you still have an asa_ value in an Authorization header in a client config, delete the header — it no longer authenticates anything. For an interactive client, OAuth needs nothing in its place; for a headless one, send a tsk_ 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 asa_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. A read key gets the read tools only.
  • A separate canApprove grant. The approval tools (asa_approve_action, asa_reject_action, asa_revert_action) need it on top of write, 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 Install the MCP server for the per-client connect steps.

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

asa_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 — asa_get_optimization_plan, asa_list_campaigns, the asa_propose_* writes, and so on. Omitting it is the most common "no account is in scope" error.

Workspace-level tools — asa_list_ad_accounts, asa_get_connection_status, asa_discover_ad_accounts, asa_import_ad_account — 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 tsk_ keys.

API key: revoke it in Settings → API keys. The next request fails on both MCP and REST — there is one key and one verifier, not a copy per surface.

Rate limits are per user: 60 reads/min, 10 writes/min.

On this page