Errors

The error envelope, what each code means, and which ones are worth retrying.

Every failure returns the same shape, with a matching HTTP status:

{
  "error": {
    "code": "proposal_rejected",
    "message": "Campaign not found in this account."
  }
}

Branch on code. message is written for a human reading a log and may be reworded.

Codes

CodeStatusMeansRetry?
unauthorized401Missing, unknown, revoked or expired key — or a credential that isn't a bsk_ key.No. Fix the credential.
forbidden403Valid key, but its scope is too low or the account is outside its reach.No.
invalid_request400The body or a query parameter failed validation. message names the field.No. Fix the request.
not_found404The entity does not exist in this account.No.
account_not_found404The project exists but has no connected Google Ads account.No. Connect one.
conflict409State race — the proposal was already decided, a sync is already running, a run already finished.Only after re-reading state.
proposal_rejected422The request was well-formed; the policy or Google Ads refused it.No — the same body will be refused again.
rate_limited429Too many requests. The body carries retry_after_ms.Yes, after waiting.
upstream_error502Google Ads was unreachable or errored.Yes, with backoff.
internal500A bug on our side.Yes, with backoff.

The two that get confused

409 vs 422. A 409 means the world changed under you — re-read and decide again. A 422 means your request was understood and declined; replaying it verbatim will fail identically.

403 vs 404. A key that cannot reach an account gets 403 for account paths, and not_found for a run outside its reach. That asymmetry is deliberate: a 403 on a run id would confirm the id is real.

Retrying safely

GET and DELETE are idempotent. So is PUT /optimization-targets — it replaces the whole object.

POST endpoints that propose changes are not idempotent: retrying creates a second proposal. Only retry on 429, 502 and 500, where the first attempt provably did not land. For agent runs, send an Idempotency-Key header — a retry with the same key replays the original run instead of starting a new one.

Looking for the MCP surface's errors? See MCP errors — it uses an { ok, error } envelope, not this one.

On this page