Skip to content

Errors and quotas

Every failed request returns a JSON body in the google.rpc.Status format together with a matching HTTP status code. This page describes the envelope, lists every error code the API can return, and explains the request quota.

Error envelope

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "queryError": "UNRECOGNIZED_FIELD" },
            "message": "Error in query: unrecognized field 'campaign.foo'."
          }
        ],
        "requestId": "01a11460b9a44b0d8ae5"
      }
    ]
  }
}
Path Type Description
error.code number Same as the HTTP status code
error.message string Generic description of the failure class
error.status string gRPC status name: INVALID_ARGUMENT, UNAUTHENTICATED, PERMISSION_DENIED, RESOURCE_EXHAUSTED, NOT_FOUND, METHOD_NOT_ALLOWED
error.details[0].@type string Always type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure
error.details[0].errors[0].errorCode object A single-key object. The key is the error category (queryError, requestError, authenticationError, authorizationError, quotaError); the value is the specific code
error.details[0].errors[0].message string Human-readable detail — names the bad field, header, token or value
error.details[0].requestId string Request id; also sent as the x-request-id response header on every request, successful or not

To handle errors programmatically read error.details[0].errors[0].errorCode and branch on its key and value. 404 NOT_FOUND (unknown URL) and 405 METHOD_NOT_ALLOWED (wrong HTTP method) are the only errors without a details array.

HTTP status to category

HTTP status Category key Meaning Retry?
400 INVALID_ARGUMENT queryError The GAQL query is invalid No — fix the query
400 INVALID_ARGUMENT requestError The request body or paging parameters are invalid No — fix the request
401 UNAUTHENTICATED authenticationError Missing or invalid access token, or missing developer token No — fix the headers
403 PERMISSION_DENIED authorizationError Developer token not allowed, wrong login-customer-id, or customer not accessible No — fix the headers / customer id
404 NOT_FOUND — Unknown URL (typo in the path, wrong version) No
405 METHOD_NOT_ALLOWED — GET used on googleAds:search or googleAds:searchStream No — use POST
429 RESOURCE_EXHAUSTED quotaError Rate limit exceeded Yes — wait Retry-After seconds

All error codes

queryError (HTTP 400)

Raised while parsing or validating the GAQL query. See GAQL › Query errors for the trigger of each code.

EXPECTED_SELECT, EXPECTED_FROM, EXPECTED_BY, BAD_FIELD_NAME, BAD_RESOURCE_TYPE_IN_FROM_CLAUSE, UNRECOGNIZED_FIELD, PROHIBITED_RESOURCE_TYPE_IN_SELECT_CLAUSE, PROHIBITED_METRIC_IN_SELECT_OR_WHERE_CLAUSE, PROHIBITED_SEGMENT_IN_SELECT_OR_WHERE_CLAUSE, EXPECTED_FILTERS_ON_DATE_RANGE, PROHIBITED_FIELD_IN_ORDER_BY_CLAUSE, PROHIBITED_FIELD_IN_WHERE_CLAUSE, INVALID_VALUE_WITH_DURING_OPERATOR, INVALID_DATE_FORMAT, INVALID_VALUE_WITH_BETWEEN_OPERATOR, OPERATOR_FIELD_MISMATCH, BAD_OPERATOR, BAD_VALUE, EXPECTED_LIST, EXPECTED_AND, BAD_LIMIT_VALUE, BAD_SYMBOL, UNEXPECTED_INPUT, UNEXPECTED_END_OF_QUERY.

Example — resource name misspelled:

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "queryError": "BAD_RESOURCE_TYPE_IN_FROM_CLAUSE" },
            "message": "Error in query: resource 'campaigns' is not available. Use one of: ad_group, ad_group_ad, campaign, customer, geo_target_constant."
          }
        ],
        "requestId": "d41a8b4992d840f4933c"
      }
    ]
  }
}

Example — ORDER BY on a field that is not selected:

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "queryError": "PROHIBITED_FIELD_IN_ORDER_BY_CLAUSE" },
            "message": "Error in query: ORDER BY field 'campaign.name' must appear in SELECT."
          }
        ],
        "requestId": "52420009c4134e479d77"
      }
    ]
  }
}

Example — OR is not supported:

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "queryError": "UNEXPECTED_INPUT" },
            "message": "Error in query: unexpected input 'OR'."
          }
        ],
        "requestId": "b569c9aefc6047c1b7d0"
      }
    ]
  }
}

requestError (HTTP 400)

Raised when the request body or paging parameters are wrong, before the query is parsed.

Code Trigger
INVALID_INPUT Body is not valid JSON, or pageSize is not an integer
MISSING_REQUIRED_FIELD Body has no string field named query
INVALID_PAGE_SIZE pageSize outside 1 … 10000 (search only)
INVALID_PAGE_TOKEN pageToken is malformed — not a value returned by this API as nextPageToken (search only)
{
  "error": {
    "code": 400,
    "message": "Request body must be a JSON object with a 'query' string.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "requestError": "MISSING_REQUIRED_FIELD" },
            "message": "Required field 'query' is missing."
          }
        ],
        "requestId": "fbe03c8b06294974bb91"
      }
    ]
  }
}
{
  "error": {
    "code": 400,
    "message": "pageSize must be between 1 and 10000.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "requestError": "INVALID_PAGE_SIZE" },
            "message": "pageSize must be between 1 and 10000."
          }
        ],
        "requestId": "50d8c43734564bb0b353"
      }
    ]
  }
}
{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "requestError": "INVALID_PAGE_TOKEN" },
            "message": "The pageToken is not valid for this query."
          }
        ],
        "requestId": "35b7d8209c184e199ae3"
      }
    ]
  }
}

authenticationError (HTTP 401)

Code Trigger
AUTHENTICATION_ERROR Authorization: Bearer … header missing (or not a Bearer scheme)
OAUTH_TOKEN_INVALID Access token unknown or expired
DEVELOPER_TOKEN_PARAMETER_MISSING developer-token header missing
{
  "error": {
    "code": 401,
    "message": "Request had invalid authentication credentials.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authenticationError": "OAUTH_TOKEN_INVALID" },
            "message": "The access token is invalid or expired."
          }
        ],
        "requestId": "75170ed3ac3a4452b0a7"
      }
    ]
  }
}

Full examples for every variant are in Authentication.

authorizationError (HTTP 403)

Code Trigger
DEVELOPER_TOKEN_NOT_ON_ALLOWLIST developer-token value is not recognised
INVALID_LOGIN_CUSTOMER_ID_SERVING_CUSTOMER_ID_COMBINATION login-customer-id is present and is not 9990001111
USER_PERMISSION_DENIED The {customerId} in the URL is not in customers:listAccessibleCustomers
{
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authorizationError": "USER_PERMISSION_DENIED" },
            "message": "User doesn't have permission to access customer 1234567890. Call customers:listAccessibleCustomers to see the customers you can query."
          }
        ],
        "requestId": "aa7e765e6f7c42f68923"
      }
    ]
  }
}

quotaError (HTTP 429)

Code Trigger
RESOURCE_EXHAUSTED More than 60 requests in the trailing 60 seconds for your access token

The response carries a Retry-After header with the number of seconds to wait; the same number appears in the detail message.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
x-request-id: 0ad62385e17a4af6af96
{
  "error": {
    "code": 429,
    "message": "Resource has been exhausted (e.g. check quota).",
    "status": "RESOURCE_EXHAUSTED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "quotaError": "RESOURCE_EXHAUSTED" },
            "message": "Too many requests. Retry in 60 seconds."
          }
        ],
        "requestId": "0ad62385e17a4af6af96"
      }
    ]
  }
}

NOT_FOUND (HTTP 404)

An unknown path (for example /v18/customers/123/googleAds:foo, or /v17/...) returns a google.rpc.Status body without details:

{
  "error": {
    "code": 404,
    "message": "The requested URL /v18/customers/123/googleAds:foo was not found on this server.",
    "status": "NOT_FOUND"
  }
}

METHOD_NOT_ALLOWED (HTTP 405)

Both search endpoints accept POST only; customers:listAccessibleCustomers is GET. Sending GET to a search endpoint returns a google.rpc.Status body without details. The check happens before authentication, so the response is the same with or without headers:

{
  "error": {
    "code": 405,
    "message": "GET is not allowed on /v18/customers/5423891542/googleAds:searchStream; googleAds:search and googleAds:searchStream take POST.",
    "status": "METHOD_NOT_ALLOWED"
  }
}

Quotas and rate limiting

Limit Value
Requests per minute 60 per access token, measured over a sliding 60-second window
Scope All endpoints and all customers together — ten customers queried in a loop share the same 60
Counted Every request whose three headers are valid, successful or not — including requests that then fail with USER_PERMISSION_DENIED, a queryError or a requestError. Requests rejected for a missing/invalid access token, developer token or login-customer-id are not counted
On breach HTTP 429, quotaError: RESOURCE_EXHAUSTED, Retry-After: <seconds>
Result size Not limited: searchStream returns the whole result set in batches, search up to 10,000 rows per page

Staying within quota

  • Prefer fewer, larger queries. One searchStream call for 30 days of ad-level rows costs one request; thirty daily calls cost thirty. A full month at the finest grain (ad × country × day) for one customer is under 2,000 rows and comes back in two batches of a single request.
  • Use searchStream for pulls, search for exploration. Paging through search costs one request per page.
  • Cache static data. customers:listAccessibleCustomers, geo_target_constant and entity attributes change rarely; refresh them once per run, not per query.
  • Back off on 429. Sleep for Retry-After seconds (never less than 1) and retry the same request. Do not retry 400/401/403 — they will fail again until the request is fixed.
import time
import requests

def post_with_retry(url, headers, body, max_attempts=5):
    for attempt in range(max_attempts):
        resp = requests.post(url, headers=headers, json=body, timeout=60)
        if resp.status_code != 429:
            return resp
        wait = int(resp.headers.get("Retry-After", "60"))
        time.sleep(max(wait, 1))
    return resp