> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flynet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Debugging

> Common error codes and what to do about them.

# Debugging

Every 4xx / 5xx response carries either an `error_code` (in JSON envelopes) or a `WWW-Authenticate` reason (on empty-body 401s). Use this table to triage. See [Pagination + errors](/concepts/pagination-errors) for the full envelope catalogue.

## Auth errors

| Code                                              | Where                                 | Cause                                                                                                                                                                 | Fix                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <a id="missing-credential" />*(no body)*          | Any 401 (`WWW-Authenticate` header)   | No credential: missing `X-API-Key` on a Discovery route, or missing/blank `Authorization: Bearer` on a member route. The body is empty — the reason is in the header. | Add the right credential for the route family: `-H "X-API-Key: $API_KEY"` for `/restaurants*` + `/locations*`, `-H "Authorization: Bearer $TOKEN"` elsewhere.                                                                                                                                                                                         |
| <a id="invalid_api_key" />`invalid_api_key`       | Discovery 401 (JSON envelope A)       | Key expired, revoked, or wrong environment.                                                                                                                           | Confirm you're using the key you received for the environment you're calling. Don't try to use a staging key against production or vice versa; the prefix isn't a guarantee, but the server-side binding is. If the key is the right one and still rejected, request a fresh one via [Support](/resources/support).                                   |
| <a id="invalid_token" />`invalid_token`           | OAuth 401 (`WWW-Authenticate` header) | Malformed JWT, expired, or signed by the wrong issuer.                                                                                                                | Refresh the token. If refresh also fails, restart the OAuth flow from [Step 1](/concepts/oauth).                                                                                                                                                                                                                                                      |
| <a id="insufficient_scope" />`insufficient_scope` | **403** (`WWW-Authenticate` header)   | Token or key is valid but missing the scope this route needs. The route exists; this is not a 404.                                                                    | Re-authorize (or remint the key) with the required scope. OAuth: `read:profile` gates `/users/me` + `/users/me/status`; `read:user_checkins` gates `/users/me/check_ins`; `read:wallets` gates `/users/me/wallets`; `read:memberships` gates `/users/me/memberships`. API key: `read:checkins` gates the `/check_ins` venue feed + `/check_ins/{id}`. |
| <a id="invalid_grant" />`invalid_grant`           | `/oauth/token` 400                    | Refresh token already used, or authorization code expired/replayed.                                                                                                   | Refresh tokens are single-use; the most recent refresh is authoritative. If you replayed a stale one, restart the OAuth flow.                                                                                                                                                                                                                         |
| <a id="invalid_client" />`invalid_client`         | `/oauth/token` 400/401                | `client_id` / `client_secret` mismatch.                                                                                                                               | Re-check the credentials in your onboarding email. The secret is backend-only; confirm it's not getting truncated or surrounded by whitespace.                                                                                                                                                                                                        |

## Payment errors

| Code                                            | Where                                    | Cause                                                                | Fix                                                                                                                                                  |
| ----------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| <a id="payment0030" />`payment0030`             | `POST /payment_intents/{id}/confirm` 400 | Member doesn't hold enough FLY.                                      | Check wallet balance before confirm. Surface a "top up" prompt or fall back to a different funding source.                                           |
| <a id="paymentIntent0003" />`paymentIntent0003` | `confirm` / `cancel` / `refund` 400      | Wrong-state transition (e.g. confirming an already-canceled intent). | Read the current `status` before acting. The state machine is one-way: `pending → paid → refunded`, or `pending → canceled`, or `pending → expired`. |

## Routing and request-shape errors

| Code                                                                | Where                     | Cause                                                   | Fix                                                                                                                                                                                                                               |
| ------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <a id="internal0007" />`internal0007` (`EndpointNotFoundException`) | 404 (routing, envelope C) | Path doesn't match a known route.                       | Check the prefix `/flynet/v1` and the resource name (e.g. `check_ins` not `checkins`, plural `restaurants` not singular). The legacy `/users/{id}/wallets` and `/users/{id}/tags` routes now 404 here; use `/users/me/*` instead. |
| <a id="invalid_request_error" />`invalid_request_error`             | 400 (modern envelope)     | Malformed body, invalid enum, or a bad parameter value. | Read `error.message` and `error.param`. Note `/check_ins` no longer requires a filter: a bare list is valid, so a missing filter is no longer an error.                                                                           |

## The 401 shape depends on the route, not the header you sent

A common gotcha: which 401 envelope you see is determined by the
route family's gating filter, not by which credential you happened
to send.

| Route you called                                                     | Header you sent                          | What you get back                                                                                                  |
| -------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `/restaurants*`, `/locations*`, `/check_ins` feed, `/check_ins/{id}` | nothing                                  | `401` **empty body** + `WWW-Authenticate: Bearer`                                                                  |
| `/restaurants*` or `/locations*`                                     | invalid `X-API-Key`                      | `401` JSON `invalid_api_key` (envelope A)                                                                          |
| `/check_ins` feed, `/check_ins/{id}`                                 | `X-API-Key` without `read:checkins`      | `403` JSON `insufficient_scope`                                                                                    |
| `/restaurants*`, `/locations*`, `/check_ins` feed, `/check_ins/{id}` | `Authorization: Bearer …` (wrong scheme) | `401` **empty body** + `WWW-Authenticate: Bearer` (the bearer carries no API key, so the route sees no credential) |
| `/users/me/*`, `/payment_intents/*`                                  | nothing                                  | `401` **empty body** + `WWW-Authenticate: Bearer`                                                                  |
| `/users/me/*`, `/payment_intents/*`                                  | invalid bearer                           | `401` **empty body** + `WWW-Authenticate: Bearer error="invalid_token"…`                                           |
| `/users/me/*`, `/payment_intents/*`                                  | valid bearer, wrong scope                | `403` **empty body** + `WWW-Authenticate: Bearer error="insufficient_scope"`                                       |
| `/users/me/*`, `/payment_intents/*`                                  | `X-API-Key: …` (wrong scheme)            | `401` **empty body** (the API key is ignored; the route's filter only knows bearers)                               |

If you accidentally send the wrong credential type, the 401 looks
exactly like a missing-credential 401: there's no friendly "you
sent the wrong header" message. Confirm you're using the right
credential for the route family before chasing token-validity bugs.
A `403` (rather than `401`) means the token is valid but lacks the
scope the route needs, not a credential-type problem.

## Silent failures worth knowing

These don't return errors, but they don't do what you'd expect either.

* **Unknown query parameters on `/check_ins` are silently ignored.** A typo like `restaurants=` instead of `restaurant=` is treated as no filter supplied. Since an unfiltered `/check_ins` is valid, you won't get an error; you'll get the full unfiltered set, which is more rows than you expected.
* **`/users/me/memberships` ignores an unrecognized filter param.** A typo like `restaurant_id=` instead of `restaurant=` returns the unfiltered membership set rather than an error.
* **Filter params on `/restaurants` (`cohort`, `cuisine`) and `/locations` (`restaurant`, `neighborhood`, `payments_enabled`, `is_club`) are accepted but not implemented server-side.** Filter client-side until they ship.
* **`Restaurant.cohort` is currently a free-form string** (the enum was removed). Don't `switch` on a fixed value set.
* **`Coordinate { latitude: 0.0, longitude: 0.0 }` indicates missing geocoding**, not a real point. Filter these out before placing map markers.
* **Member routes are subject-scoped to the token.** `/users/me/*` returns the authenticated member's own data: there's no UUID in the path and you can't read another member's data with the token. The legacy `/users/{id}/*` routes are gone (they now 404). See [OAuth → Step 3](/concepts/oauth#step-3---make-api-calls) for the model.

## Still stuck?

Contact [Support](/resources/support) and include:

* The request URL and your headers (redacted)
* The status code and response body you got
* What you expected
* The time of the call (UTC) so the team can trace it

If your response had an `X-Request-Id` header, paste that too.
