Errors and rate limits | GlitchAds API

Errors and rate limits

Every failure answers in one envelope, and every code below is a link a client can follow.

The error envelope

Every error from the REST API and from the MCP tools is the same shape: a code you can branch on, a message written to be acted on, and a doc_url pointing at the section below. Codes carrying extra context add fields alongside those three: required_scope, fields, the budget limits, retry_after and request_id, as the table says.

{
  "error": {
    "code": "budget_cap_exceeded",
    "message": "A single API call may change a budget by at most 25%. Allowed range for this campaign: 30.00 to 50.00.",
    "doc_url": "https://glitchads.ai/docs/errors#budget_cap_exceeded",
    "allowed_min": 30.0,
    "allowed_max": 50.0
  }
}

Messages name the field, the missing scope or the allowed range, so a client can correct itself without a second request to find out what went wrong.

Three answers use other shapes. The OAuth endpoints answer in the standard OAuth error format, error and error_description. A path that does not exist answers a plain 404. Errors the MCP server raises itself, such as a missing required argument, use the envelope without a doc_url.

Error codes

unauthorized HTTP401 Missing, invalid or expired credentials. With OAuth, refresh the access token, and authorize again only if the refresh fails. With an API key, check the header, that the key is active and unexpired, and that the person who created it is still a member with a role that covers its scopes.
insufficient_scope HTTP403 The credential lacks the scope named in required_scope. Reconnect approving it, or create a key that carries it.
org_access_denied HTTP403 The credential cannot act in that organization, or no organization has that slug. The two are answered the same way on purpose.
role_denied HTTP403 Your role in the organization does not allow this operation; the audit export is Owner only. A Member attempting a write sees insufficient_scope instead, because writes are outside a Member credential’s scope ceiling.
not_found HTTP404 Unknown campaign, recommendation, action, report, business profile or task, including an id that belongs to another organization. The message names which one.
validation_error HTTP400 A bad parameter. The message names the accepted values, and fields lists the parameters at fault.
budget_cap_exceeded HTTP422 The change would breach a budget limit. Past the per-change limit, allowed_min and allowed_max give the range; past the organization's monthly cap, monthly_cap and allowed_max_daily give the room left.
approval_required HTTP202 The organization requires a person to approve API and MCP writes, so the change is waiting rather than done. queued_recommendation_id names it: poll /v1/queued-writes/{id} to learn whether it was approved, rejected or failed. Reads and dry runs are never held.
sync_conflict HTTP409 Launching the campaign was blocked because it was changed directly in Google Ads since Glitch last synced it. Sync it from the dashboard, then retry.
rate_limited HTTP429 Too many requests. Wait for Retry-After, also given as retry_after.
task_failed HTTP500 A write failed while executing, for example because Google Ads rejected it. The message carries the reason, and the attempt counts against the daily write quota.
internal_error HTTP500 Our fault. Retry with backoff; if it persists, contact support with the request_id, which is also the X-Request-ID response header.

Rate limits

Requests are counted per credential, and writes per organization per day. Both are set by your plan. Every authenticated REST response carries the headers, errors included, so a client never has to guess how much room is left. A request refused as unauthorized never reaches the limit, and MCP tool results carry no HTTP headers: read retry_after from the error instead.

Standard plans Requests a minute60 Writes a day200
Larger plans Requests a minute240 Writes a day1000
# a request that went through
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 19

# a request refused with rate_limited
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 60
Retry-After: 1

X-RateLimit-Reset is the number of seconds until the allowance is full again. Larger plans can burst to 480 requests, so X-RateLimit-Remaining can be higher than the limit.

A refused request answers rate_limited with Retry-After in seconds. Wait at least that long. Another request can take the freed allowance first, so be ready to wait again.

The daily write quota exists so that a misbehaving agent cannot rewrite an account wholesale. It is shared by every credential in the organization and refills gradually through the day, not at midnight. Every write that reaches execution counts, including one that then fails, and generating a report counts as a write. Reads never count against it, and neither do dry runs or undoing an action that was already undone.

Retry guidance

Safe to retry: every read, glitch_undo_action, which is idempotent, and any call made as a dry run.

Safe to retry with the same parameters: an executed write that answered rate_limited or internal_error before it confirmed anything.

Not safe to repeat: a write that already returned an action_id. It succeeded, and sending it again performs it twice. Reverse it with an undo instead. Writes that continue in the background return a task_tracking_id; polling that is safe, but re-sending the original request starts a second job.

When a response carries Retry-After, wait that long; the daily write quota can ask for several minutes. Otherwise back off exponentially from one second, cap at sixty, and add jitter.

The asynchronous pattern

Work that takes longer than a request answers 202 Accepted with a task_tracking_id rather than holding the connection open. Campaign creation, ad creation and report generation work this way. A state change answers straight away with both a task_tracking_id and an action_id.

Poll the task until its status is completed or failed. A failed task carries an error with a reason and a message saying what to do next, and the work can be started again.