Skip to content

Errors and rate limiting

The error envelope

Every failed request returns a JSON body with a single error object. The HTTP status is 400 for all application errors, including authentication failures and the rate limit, so always look at error.code, not just the status.

{
  "error": {
    "message": "(#100) Tried accessing nonexisting field (budget) on node type (Campaign)",
    "type": "OAuthException",
    "code": 100,
    "fbtrace_id": "AgPFmAMRIgRiW8BDjh1EI"
  }
}
Key Type Description
message string Human-readable explanation. Often prefixed with the code in parentheses, e.g. (#100).
type string OAuthException for almost everything; GraphMethodException when an object id does not exist.
code int Machine-readable error code. Branch on this.
error_subcode int Present on some errors (e.g. 33 for unknown object, 2446079 for the rate limit).
fbtrace_id string Request trace id. Include it when asking for help.

Successful responses never contain an error key, and error responses never contain data. A robust client checks "error" in body first.

Error codes

HTTP code type Meaning What to do
400 104 OAuthException Missing access token. No access_token parameter and no Authorization: Bearer header. Send the token. See Authentication.
400 190 OAuthException Invalid access token. The token is not recognised. Check for typos / trailing whitespace; ask the instructor if the token is really yours. Do not retry blindly.
400 100 OAuthException Invalid parameter or field. Unknown field name, bad level, bad time_range, bad limit, bad cursor, unsupported breakdown, and so on. The message says which. Fix the request. Retrying unchanged will fail again.
400 100 + error_subcode 33 GraphMethodException Object does not exist (or you cannot access it): an unknown act_..., campaign, ad set or ad id. Check the id. Ids come from the listing endpoints; do not invent them.
400 17 + error_subcode 2446079 OAuthException Rate limit reached (more than 60 requests in the last 60 seconds). Wait 60 seconds, then retry the same request. See below.
400 100 GraphMethodException Unsupported HTTP method. The API is read-only; a POST, PUT, DELETE or other non-GET request to any edge returns (POST) Unsupported method on this edge; use GET. Use GET.
400 2500 OAuthException Unknown path. The URL does not match any endpoint: wrong version (/v20.0/...), misspelled edge, extra path segments. Check the URL against this reference. Only /v21.0/... exists.

Examples

104 — missing token

{
  "error": {
    "message": "An access token is required to request this resource.",
    "type": "OAuthException",
    "code": 104,
    "fbtrace_id": "A4J_EQTPMXEW7jShSsHi7"
  }
}

190 — invalid token

{
  "error": {
    "message": "Invalid OAuth access token - Cannot parse access token",
    "type": "OAuthException",
    "code": 190,
    "fbtrace_id": "AokyI0dkKgRSzsQdx1qqT"
  }
}

100 — invalid field

{
  "error": {
    "message": "(#100) installs is not valid for fields param. please check https://ads-docs.huydata.com/meta/insights/ for all valid values",
    "type": "OAuthException",
    "code": 100,
    "fbtrace_id": "AoU73Oo7DoYUfoWTsJcNe"
  }
}

100 / 33 — object does not exist

{
  "error": {
    "message": "Unsupported get request. Object with ID 'act_123' does not exist, cannot be loaded due to missing permissions, or does not support this operation.",
    "type": "GraphMethodException",
    "code": 100,
    "error_subcode": 33,
    "fbtrace_id": "AoeFaty9DVAYm_-nQjVGt"
  }
}

17 — rate limit

{
  "error": {
    "message": "(#17) User request limit reached",
    "type": "OAuthException",
    "code": 17,
    "error_subcode": 2446079,
    "fbtrace_id": "AmAy2EokYB-wiFkYgTJmO"
  }
}

100 — unsupported method

{
  "error": {
    "message": "(POST) Unsupported method on this edge; use GET",
    "type": "GraphMethodException",
    "code": 100,
    "fbtrace_id": "ARZsCflsQXSq0AI85bY2X"
  }
}

2500 — unknown path

{
  "error": {
    "message": "Unknown path components: /v20.0/me/adaccounts",
    "type": "OAuthException",
    "code": 2500,
    "fbtrace_id": "AxMLIvBknpLU7_-d5ItCE"
  }
}

Other code 100 messages you may meet

Message Fix
(#100) Tried accessing nonexisting field (xyz) on node type (Campaign) Use a field from the object's field table.
(#100) Param limit must be a positive integer limit must be 1..500.
(#100) The after cursor provided is invalid Pass paging.cursors.after unchanged.
(#100) Invalid parameter: level must be one of account, campaign, adset, ad Bad level.
(#100) ad_name is not available at this level; raise the level parameter Dimension finer than level.
(#100) time_range must be a JSON object with 'since' and 'until' in YYYY-MM-DD URL-encode the JSON; check the date format.
(#100) Invalid parameter: breakdowns value 'age' is not supported (only country) Only country.
(#100) Tried accessing nonexisting field (adsets) on node type (AdSet) /adsets only exists under campaigns, /ads under campaigns and ad sets.

Rate limiting

Each access token may make 60 requests per minute, measured over a sliding window of the last 60 seconds. Every request counts, including /me, errors and empty pages. The limit is per token, so your pulls do not interfere with other people's.

Monitoring usage: the x-app-usage header

Every successful response carries an x-app-usage header with a JSON value. call_count is the percentage of the per-minute quota you have used in the current window:

x-app-usage: {"call_count": 50, "total_cputime": 25, "total_time": 25}

When call_count approaches 100, slow down. (total_cputime and total_time are reported for compatibility and track call_count.)

What happens at the limit

The 61st request within 60 seconds is rejected with HTTP 400 and error code 17:

HTTP/1.1 400 Bad Request
x-app-usage: {"call_count": 100, "total_cputime": 50, "total_time": 50}
x-fb-ads-insights-throttle: {"app_id_util_pct": 100, "acc_id_util_pct": 100, "ads_api_access_tier": "development_access"}

{
  "error": {
    "message": "(#17) User request limit reached",
    "type": "OAuthException",
    "code": 17,
    "error_subcode": 2446079,
    "fbtrace_id": "AmAy2EokYB-wiFkYgTJmO"
  }
}

Rejected requests are not counted against the quota, but the window does not reset either: you must let the oldest requests age out. There is no Retry-After header.

How to handle it

  1. On code == 17, wait 60 seconds, then retry the same request (or the same paging.next URL). Do not skip the page.
  2. Keep a steady pace: at most 1 request per second is always safe.
  3. Use limit=500 on every list and insights request to cut the number of pages.
  4. Pull at the coarsest granularity that answers your question. One level=ad, time_increment=1, breakdowns=country request per account per 3-day window (see Data freshness) is a handful of pages per account, well within the limit.
  5. Never retry 100, 104, 190 or 2500 without changing the request; they are not transient.

Health check

GET https://graph.huydata.com/health (no version segment, no token) returns the service status and the latest report date currently readable:

{ "status": "ok", "platform": "meta", "latest_report_date": "2026-10-29" }

It does not count against your rate limit.