API · Reference

How to Handle CoinMarketCap API Errors and Validation Responses

CoinMarketCap APIUpdated 14 September 2026 · 12 min read
How to Handle CoinMarketCap API Errors and Validation Responses, shown as a halftone robot reading a data panel on cobalt.

A failed CoinMarketCap API call tells you what went wrong twice: once in the HTTP status, and more precisely in the status object inside the body. Reading only the first is why integrations retry errors that will never clear.

Find Your Code

Code HTTP Meaning Retry?
1001 401 Key is invalid No, regenerate it
1002 401 No key on the request No, add the header
1003 402 Key not activated No, activate the plan
1004 402 Subscription expired No, renew
1005 403 Endpoint requires a key No, add the key
1006 403 Plan excludes this endpoint No, upgrade
1007 403 Key disabled No, contact support
1008 429 Per-minute rate limit Yes, after ~60 seconds
1009 429 Daily allowance spent Only after the daily reset
1010 429 Monthly allowance spent No, not this period
1011 429 Per-IP rate limit Yes, with lower concurrency
400 400 Bad parameter No, fix the request
500 500 Server-side issue Yes, with backoff
4001 400 Unresolvable identifier on /v5/cmc-ai/coins/latest No, fix the identifier or set skip_invalid
4002 400 Missing a required parameter, observed on /v1/dex/holders/* No, add the parameter

Key Takeaways

  • Check error_code before consuming data even on HTTP 200. A 200 with a non-zero code carries no valid data.
  • Four different limits return 429, and only the code separates them: 1008 per minute, 1009 per day, 1010 per month, 1011 per IP.
  • error_code is a number on some endpoints and a JSON string on others, split by endpoint family rather than by whether you sent a key. Normalise before comparing.
  • 1006 and 1007 return 403 and no retry fixes either: the plan excludes the endpoint, or the key is disabled.
  • A nonexistent symbol returns HTTP 200 with error_code "0", so check that the response contains each identifier you asked for.
  • Two codes exist that no CoinMarketCap documentation lists: 4001 for an unresolvable identifier, and 4002 for a missing required parameter.

The Response Envelope

Every response carries a status object, on success and on failure. It always includes the server time as timestamp, the credits consumed as credit_count, the processing time in milliseconds as elapsed, and on failure error_code and error_message.

json
{
  "status": {
    "timestamp": "2026-09-10T07:52:27.273Z",
    "error_code": 1002,
    "error_message": "API key missing.",
    "elapsed": 0,
    "credit_count": 0
  }
}

Two headers are expected on every request: Accept: application/json, and Accept-Encoding: deflate, gzip to keep responses compact.

The Type of error_code Depends on Which Endpoint You Call

This is the detail most likely to cost an afternoon. error_code arrives as a JSON number on some endpoints and a quoted string on others, and the boundary does not follow any rule you can apply from the outside.

Measured 10 September 2026 by reading the raw response text of each call and checking whether the value was quoted:

error_code as Endpoints observed
Number, e.g. 1002 /v1/cryptocurrency/listings/latest, /v2/cryptocurrency/info, /v2/cryptocurrency/ohlcv/historical, /v1/exchange/map, /v1/global-metrics/quotes/latest, /v1/content/latest, /v1/key/info
String, e.g. "1002" /v3/cryptocurrency/listings/latest, /v3/cryptocurrency/quotes/latest, /v3/fear-and-greed/latest, /v1/simple/price, /v1/dex/search, /v4/cmc-ai/latest, /v5/cmc-ai/latest, /v5/cmc-ai/coins/map, and any unrecognised path

Twenty live observations against pro-api.coinmarketcap.com, 10 September 2026.

Three things make this harder to reason about than it first appears, each measured rather than assumed.

You can reproduce this in ten seconds without a key. Both calls below are unauthenticated, cost nothing, and return 1002:

bash
# /v1 prints "error_code":1002 , a number
curl -s "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?slug=bitcoin" | grep -o '"error_code":[^,]*'

# /v3 prints "error_code":"1002" , a string
curl -s "https://pro-api.coinmarketcap.com/v3/cryptocurrency/quotes/latest?slug=bitcoin" | grep -o '"error_code":[^,]*'

The first prints "error_code":1002 and the second prints "error_code":"1002". Same code, same second, same parameter, different JSON type. The /v1 envelope also carries a notice field that the /v3 envelope omits. If you take nothing else from this section, run those two commands against whichever endpoints your own code touches before you write a comparison against an integer.

The keyless prefix does not decide it. Calling an endpoint through /public-api returns the same type as calling it directly. /public-api/v2/cryptocurrency/ohlcv/historical returns a number; /public-api/v3/cryptocurrency/quotes/latest returns a string. The type belongs to the endpoint, not to the surface you reach it through.

Sending a key does not decide it either. A keyed request to /v4/cmc-ai/coins/latest on an expired subscription returns "1004" as a string, with a valid key present on the request.

The version number is not a reliable proxy. Most /v1 endpoints return a number, but /v1/simple/price and the whole /v1/dex/* family return strings. The split tracks which service handles the endpoint, which is not something a client can see.

The case a migration walks into

/v1/cryptocurrency/listings/latest returns a number and /v3/cryptocurrency/listings/latest returns a string. Same logical endpoint, and the v1 version sits on the deprecated reference, so following CoinMarketCap’s own deprecation advice silently changes the type in your error handler.

The practical conclusion is not to memorise the table. It is that no strict equality check against a numeric literal is safe anywhere, so normalise once at the point of parsing and stop thinking about it.

python
def error_code(payload):
    raw = payload.get("status", {}).get("error_code", 0)
    try:
        return int(raw)
    except (TypeError, ValueError):
        return -1          # unparseable: treat as an unknown failure

Parse defensively more generally. Read only the fields you need, so new properties added later are ignored rather than breaking a parser, and wrap type-sensitive parsing such as dates in error handling. Keyed error responses, for instance, carry a notice field alongside the five documented keys.

HTTP Status Codes

Status Meaning What to do
200 Success, or a failure carrying a non-zero error_code Check error_code before using data
400 The request could not be processed, usually an invalid argument Check parameters against the endpoint reference
401 Missing or invalid credentials Verify the key and the X-CMC_PRO_API_KEY header
402 Overdue balance or an unactivated plan Settle it in the Developer Portal billing tab
403 The plan does not include this endpoint, or the key is disabled Check the plan-to-endpoint map, or contact support
429 A limit was exceeded Read the code: slow down, or wait for the period to reset
500 Unexpected server-side issue Retry with exponential backoff, then check the status dashboard

Full Error Code Reference

Code Constant HTTP Meaning Fix Source
1001 API_KEY_INVALID 401 The key is not valid Regenerate it in the Developer Portal Observed
1002 API_KEY_MISSING 401 No key on the request Add the X-CMC_PRO_API_KEY header Observed
1003 API_KEY_PLAN_REQUIRES_PAYEMENT 402 The key is not activated Activate the plan Documented
1004 API_KEY_PLAN_PAYMENT_EXPIRED 402 The subscription has expired Renew the subscription Observed
1005 API_KEY_REQUIRED 403 This endpoint requires a key Include the key; not available keyless Observed
1006 API_KEY_PLAN_NOT_AUTHORIZED 403 The plan does not include this endpoint Upgrade, or use an endpoint on the current tier Observed
1007 API_KEY_DISABLED 403 The key has been disabled Contact support. Retries will not help Documented
1008 API_KEY_PLAN_MINUTE_RATE_LIMIT_REACHED 429 Per-minute request rate exceeded Wait 60 seconds, then retry Documented
1009 API_KEY_PLAN_DAILY_RATE_LIMIT_REACHED 429 Daily limit exhausted Wait for the daily reset, or upgrade Documented
1010 API_KEY_PLAN_MONTHLY_RATE_LIMIT_REACHED 429 Monthly limit exhausted Wait for the monthly reset, or upgrade Documented
1011 IP_RATE_LIMIT_REACHED 429 Too many concurrent requests from one IP Reduce concurrency from that address Documented

Observed records a code confirmed by a live request on 10 or 14 September 2026. Documented records a code taken from the errors and rate limits guide: the four rate-limit codes are untested by design, since exercising them means deliberately exhausting a plan.

The Four 429s Are Not Interchangeable

One status code covers limits whose reset windows range from a minute to a month.

  • 1008Clears in 60s

    The per-minute request rate. A short backoff recovers automatically.

  • 1009Clears at daily reset

    Credit exhaustion for the day. No amount of backoff clears it.

  • 1010Clears next period

    Credit exhaustion for the month. The billing cycle has to roll over, or the plan has to change.

  • 1011Clears on lower concurrency

    An IP-level limit rather than a key-level one, which usually means several workers sharing one address.

A retry loop that treats all four alike will hammer a monthly cap every few seconds until the billing cycle turns over.

python
import time
import requests

TRANSIENT = {1008, 1011}                                  # backoff clears these
EXHAUSTED = {1009, 1010}                                  # only a reset or upgrade clears these
FATAL = {1001, 1002, 1003, 1004, 1005, 1006, 1007, 400, 4001, 4002}

def call(url, headers, params, retries=4, base_delay=1):
    for attempt in range(retries):
        r = requests.get(url, headers=headers, params=params)
        code = error_code(r.json())                       # normalised, see above

        if code in FATAL:
            raise RuntimeError(f"Not retryable: {code}")
        if code in EXHAUSTED:
            raise RuntimeError(f"Allowance exhausted: {code}")
        if code in TRANSIENT or r.status_code >= 500:
            time.sleep(base_delay * (2 ** attempt))
            continue
        if code:
            raise RuntimeError(f"API error {code}")

        return r.json()

    raise RuntimeError("Max retries exceeded")

The separation matters more than the delays. Backoff belongs to 1008 and 1011; everything else needs a decision rather than a wait.

Errors a Retry Cannot Fix

Three codes look transient and are not. 1006 means the plan does not cover the endpoint, so the request will fail identically until the plan changes. 1007 means the key has been disabled, which support has to resolve. 1005 means the endpoint requires authentication and the request arrived without a key, which is configuration rather than permission.

A browser call fails differently

A call made from browser JavaScript against a keyed endpoint fails with a CORS error rather than an API error code, because client-side calls are not supported. Proxy through a backend, which also keeps the key out of the browser.

Code 4001 on the CMC AI Endpoints

The /v5/cmc-ai family introduces one additional code. On /v5/cmc-ai/coins/latest, which accepts comma-separated lists of crypto_id, slug or symbol, an identifier that cannot be resolved fails the whole request with 4001 by default.

That default is worth changing for most batch work. Passing skip_invalid=true drops the unresolvable identifiers and returns the valid ones, so one bad symbol in a list of fifty does not cost the other forty-nine. Coins skipped this way are not charged.

bash
# One bad identifier fails everything (skip_invalid defaults to false)
GET /v5/cmc-ai/coins/latest?symbol=BTC,ETH,ZZZNOTREAL

# Same request, partial results instead of a failure
GET /v5/cmc-ai/coins/latest?symbol=BTC,ETH,ZZZNOTREAL&skip_invalid=true

The same endpoint enforces mutual exclusivity: exactly one of crypto_id, slug or symbol is required, and supplying two is a request error rather than a silent preference for one.

The declared type and the observed type disagree

These endpoints return error_code as a string live, measured 10 September 2026, even though the shipped reference declares the field as type: integer. Their status object includes the documented notice field. Normalise the value rather than trusting either the declared type or the observed one.

Behaviour Worth Knowing Before You Debug

Validation already exists on the current endpoints, and three of its behaviours surprise people.

Request Result
ids=abc 400, with a message naming the field and its expected format
ids omitted 400, 'ids' is a required parameter
limit=999999 400, 'limit' should be a positive number in range [1, 5000]
ids=1,1 200, duplicates accepted rather than rejected
symbol=ZZZNOTREAL 200, error_code "0", no indication the asset is unknown
A path that does not exist 200, error_code "500", The system is busy, please try again later!

Live requests to the keyless endpoints, executed 10 September 2026.

The last two are the ones to code around.

A nonexistent symbol returns success. Nothing in the envelope reports that the asset you asked for is missing, so check that the response actually contains each identifier you requested. This is the strongest argument for using CoinMarketCap IDs from /cryptocurrency/map: a symbol matching several cryptocurrencies resolves silently to the highest market cap, a symbol matching none returns silently empty, and neither surfaces as an error.

A mistyped path returns HTTP 200 with a “system is busy” message. That reads like a transient outage and is not one, so a client that retries on it will retry forever against a URL that will never exist. Check the path against the endpoint reference before assuming the service is degraded.

Keep the key out of the URL

Pass credentials in the X-CMC_PRO_API_KEY header. A key in a query string is recorded in server logs, proxy logs and browser history, and it leaks through the referrer header, so a URL-borne key should be treated as compromised and rotated.

Common Mistakes

  • Retrying every 429.

    Four limits share that status. 1008 clears in a minute; 1010 does not clear this month.

  • Ignoring error_code on a 200.

    A 200 with a non-zero code carries no data. Check the code first, always.

  • Comparing error_code to a numeric literal.

    It is a number on the older /v1 and /v2 endpoints and a string on /v3, DEX, CMC AI and keyless. Normalise once at parse time.

  • Backing off on a 403.

    1006 and 1007 are plan and key states, and will return the same answer indefinitely.

  • Retrying “the system is busy”.

    On an unknown path that message arrives with HTTP 200 and will never clear. Verify the path first.

  • Assuming a missing asset produces an error.

    A nonexistent symbol returns 200 with error_code "0".

A 500 Is Not Always Transient

The status table says to retry a 500 with exponential backoff, and that is right for a genuine server blip. It is not always what a 500 means here. Two behaviours observed on 11 September 2026 break the assumption.

An unrecognised path returns HTTP 200 with error_code "500" and “The system is busy, please try again later!”. A typo in a path segment therefore reads to a client as a successful call reporting a transient fault. /v9/cmc-ai/latest returns a clean 404, so version routing is correct and sub-path routing is not.

At least one real endpoint returns a persistent 500. /v1/dex/holders/trend/list returned HTTP 500 with the same “system is busy” message both without parameters and with valid ones, while its sibling endpoints answered normally in the same pass.

In both cases a correct retry-with-backoff implementation retries forever. So cap your retries on 500 rather than looping indefinitely, and treat a 500 that survives a few attempts as a permanent failure to surface, not a transient one to keep hammering. Log the full path when you do: the most likely cause is that the URL is wrong rather than the server being busy.

Two Codes the Documentation Does Not List

The published error table runs 1001 to 1011. Two more codes are returned live and appear in no error reference.

4001 is documented, but only in passing: it appears in the skip_invalid parameter description on /v5/cmc-ai/coins/latest, not in the error table. It means an identifier could not be resolved.

4002 appears nowhere at all. Its message is “Missing required parameter.”, and it was confirmed by controlled test on 11 September 2026: /v1/dex/holders/count and /v1/dex/holders/tag_count both declare platform and tokenAddress as required. Omitting either returned HTTP 400 with error_code 4002. Supplying both returned HTTP 200. Every other endpoint tested in the same pass declares no required parameter and returned a plain 400 instead, with a different message.

So 4002 means a required parameter is missing, and it is raised by a different validation layer from the generic 400. Both are permanent failures: no retry fixes either, and neither is charged.

That distinction is useful in a handler. A 400 may mean your value was wrong; a 4002 means a parameter was absent entirely, which is a different fix and usually a coding error rather than a data error.

Do not treat 1001 to 1011 as exhaustive

If you are writing a classifier, add both 4001 and 4002 to the fatal set. The published range demonstrably is not the whole set.

Where to Go Next

For what a call costs and which endpoints publish no cost at all, read the credits guide. For 4001 in the context of the endpoints that raise it, see the CMC AI section of the Pro API reference.

FAQ

What does error code 1008 mean?

The per-minute request rate limit for your key has been exceeded, returned with HTTP 429. It resets every 60 seconds, so a short exponential backoff recovers from it.

What is the difference between 1008, 1009 and 1010?

All three are 429s. 1008 is the per-minute request rate and clears in 60 seconds. 1009 is the daily allowance and clears at the daily reset. 1010 is the monthly allowance and clears when the billing period resets. Only 1008 responds to backoff.

Why am I getting a 403 with a valid API key?

Two codes produce that. 1006 means your plan does not include the endpoint you called. 1007 means the key has been disabled and needs support to resolve.

Why does a successful HTTP 200 contain no data?

Because error_code is non-zero. The API returns 200 with an error in the status object in some failure cases, so error_code has to be checked before reading data.

Is error_code a number or a string?

Both, depending on the endpoint. The older /v1 and /v2 market-data endpoints return a number such as 1002. The /v3 endpoints, the DEX family, the CMC AI family and the keyless surface return a string such as "1002". Sending a key does not change it. Normalise the value at parse time.

What does error code 4001 mean?

On /v5/cmc-ai/coins/latest it means one of the identifiers you passed could not be resolved. By default that fails the whole request. Pass skip_invalid=true to drop the bad identifiers and return the valid ones instead.

Why did my request for an unknown symbol return 200 with no error?

A symbol that matches nothing returns a success envelope. The response simply will not contain that asset, so check for each identifier you asked for rather than relying on error_code.

I get "The system is busy, please try again later!" on every attempt. Is the API down?

Check the path first. An unknown endpoint path returns that message with HTTP 200, and no amount of retrying will change it. If the path is correct, check the API status dashboard.

Should I pass my API key as a query parameter?

No. Use the X-CMC_PRO_API_KEY header. Keys in URLs are captured by logs, history and referrer headers.

Why does my browser request fail when the same call works from my server?

Client-side calls to keyed endpoints are not supported and fail with a CORS error, by design, so that keys are not exposed in the browser. Proxy the request through your own backend.