> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.spyglass.so/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.spyglass.so/_mcp/server.

# Errors

> One error shape, ten codes, and which ones are worth retrying.

Every failure returns the same envelope:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Invalid request",
    "details": [
      { "path": "", "message": "Provide one of ids, names, igUsername, tiktokUsername or query." }
    ]
  }
}
```

`code` is stable and safe to branch on. `message` is written for humans and may
change — don't match on it. `details` appears only on validation failures, and
lists each offending field.

## Codes

| Status | Code                    | What happened                                                                                                                                                              |
| ------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`       | The body or query string failed validation. `details` names the fields.                                                                                                    |
| 401    | `invalid_api_key`       | The key is missing, malformed, unknown, or revoked.                                                                                                                        |
| 402    | `out_of_credits`        | The team's monthly credits are exhausted.                                                                                                                                  |
| 403    | `membership_ended`      | You're no longer a member of the key's team.                                                                                                                               |
| 403    | `subscription_inactive` | The team's subscription isn't active.                                                                                                                                      |
| 404    | `brand_not_found`       | No brand or creator matches that id.                                                                                                                                       |
| 404    | `not_found`             | The path isn't an endpoint. Check it against the reference.                                                                                                                |
| 405    | `method_not_allowed`    | Wrong HTTP method for the path. The `Allow` header lists what it accepts. `HEAD` is refused on the metered `GET` endpoints because it would bill without returning a body. |
| 413    | `payload_too_large`     | The request body exceeds 100 KB.                                                                                                                                           |
| 429    | `rate_limited`          | A rate limit was exceeded. Carries `Retry-After`.                                                                                                                          |
| 500    | `internal_error`        | Something broke on our side.                                                                                                                                               |

## What to retry

**Retry `429`** after the number of seconds in the `Retry-After` header. Don't
retry sooner — it counts against the same window.

**Retry `500`** once or twice with backoff. Nothing was billed, so a retry can't
double-charge.

**Don't retry anything else.** `400`, `401`, `403`, `404`, `405` and `413` will
fail identically until you change the request, the key, or the plan. `402` will
keep failing until credits are added — retrying in a loop just wastes calls
against your rate limit.

## None of these are billed

Every error above is free. You're only charged for rows actually returned, so a
failed call never draws credits — including `500`.

## Distinguishing the two 403s

Both mean "your access is gone", but the fix differs:

* `membership_ended` — *you* were removed from the team. Create a key from a
  team you're still in.
* `subscription_inactive` — the *team's* plan lapsed. Someone with billing
  access needs to renew.