TwitterAPIs Docs
Getting Started

Twitter API Errors | Status Codes and JSON Shape

HTTP status codes TwitterAPIs returns, the JSON error shape, and how to handle each one in your client.

TwitterAPIs uses standard HTTP status codes. A 2xx means the call worked. Anything else carries a JSON error body that tells you what went wrong. Read the status code first, then the body.

Status codes

CodeerrorMeaningWhat to do
200noneSuccessParse the JSON body. The data is ready.
400bad_requestA parameter is missing or malformed.Fix the request before retrying.
401unauthorizedThe API key is missing, malformed, or revoked.Check the Authorization (or x-api-key) header.
402insufficient_creditsYour balance is exhausted.Top up in the dashboard, then retry the same request.
404not_foundThe resource does not exist, for example a deleted tweet, a suspended/unknown handle, or a private account.Do not retry.
429rate_limitedUpstream rate limit hit; the pool account was cooled down.Back off, then retry. See Rate limits.
502bad_gatewayThe upstream X request failed.Retry with backoff.
503no_capacity / billing_unavailableThe account pool is momentarily saturated, or billing was unreachable (we fail closed).Retry shortly.

Error shape

Every non-2xx response uses the same JSON envelope, so you can parse errors with one code path:

{
  "error": "insufficient_credits",
  "message": "Not enough credits for this request. Top up to continue."
}
FieldTypeDescription
errorstringA stable, machine-readable code (for example not_found, rate_limited, insufficient_credits). Branch on this, not on the message text.
messagestringA human-readable explanation of the failure. Safe to surface to a user; do not parse it programmatically.

Handling errors

Branch on the status code. Retry the transient classes (429 and 5xx) with exponential backoff, and surface the rest to the caller because a human needs to act.

async function call(url) {
  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.TWITTERAPIS_KEY}` },
    });

    if (res.ok) return res.json();

    // Retry only the transient classes.
    if (res.status === 429 || res.status >= 500) {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
      continue;
    }

    // 400, 401, 402, 404 need a human, not a retry.
    const err = await res.json();
    throw new Error(`${err.error}: ${err.message}`);
  }
  throw new Error("Exhausted retries");
}
import os
import time
import requests

def call(url):
    for attempt in range(4):
        res = requests.get(
            url,
            headers={"Authorization": f"Bearer {os.environ['TWITTERAPIS_KEY']}"},
        )
        if res.ok:
            return res.json()

        # Retry only the transient classes.
        if res.status_code == 429 or res.status_code >= 500:
            time.sleep(2 ** attempt * 0.5)
            continue

        # 400, 401, 402, 404 need a human, not a retry.
        err = res.json()
        raise RuntimeError(f"{err['error']}: {err['message']}")

    raise RuntimeError("Exhausted retries")

A 402 is not a bug

402 insufficient_credits means the request was valid but your balance ran out. Add credits in the dashboard and the same request will succeed. Rate-limited (429) and upstream (502) calls are never billed, so a retry costs you nothing.

FAQ

Which errors should I retry?

Retry the transient classes, 429 and 5xx, with exponential backoff. Surface 400, 401, 402, and 404 to the caller, since they need a human to act.

Should I branch on the message text?

No. Branch on the stable error code field, not the human-readable message. The message is safe to show a user but not to parse.

Is a 402 a bug?

No. A 402 insufficient_credits means the request was valid but your balance ran out. Top up in the dashboard and retry the same call.

On this page