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
| Code | Status | Means | Retry? |
|---|---|---|---|
unauthorized | 401 | Missing, unknown, revoked or expired key — or a credential that isn't a bsk_ key. | No. Fix the credential. |
forbidden | 403 | Valid key, but its scope is too low or the account is outside its reach. | No. |
invalid_request | 400 | The body or a query parameter failed validation. message names the field. | No. Fix the request. |
not_found | 404 | The entity does not exist in this account. | No. |
account_not_found | 404 | The project exists but has no connected Google Ads account. | No. Connect one. |
conflict | 409 | State race — the proposal was already decided, a sync is already running, a run already finished. | Only after re-reading state. |
proposal_rejected | 422 | The request was well-formed; the policy or Google Ads refused it. | No — the same body will be refused again. |
rate_limited | 429 | Too many requests. The body carries retry_after_ms. | Yes, after waiting. |
upstream_error | 502 | Google Ads was unreachable or errored. | Yes, with backoff. |
internal | 500 | A 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.