Skip to main content

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 for the full envelope catalogue.

Auth errors

CodeWhereCauseFix
MISSING_API_KEYDiscovery 401 (JSON)No X-API-Key header on /restaurants* or /locations*.Add -H "X-API-Key: $API_KEY".
INVALID_API_KEYDiscovery 401 (JSON)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.
invalid_tokenOAuth 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.
insufficient_scopeOAuth 403 (WWW-Authenticate header, empty body)Token is valid but missing the scope this route needs. The route exists; this is not a 404.Re-authorize with the required scope. read:profile gates /users/me + /users/me/status; read:checkins gates /check_ins* + /users/me/check_ins; read:wallets gates /users/me/wallets.
invalid_grant/oauth/token 400Refresh 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.
invalid_client/oauth/token 400/401client_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

CodeWhereCauseFix
payment0030POST /payment_intents/{id}/confirm 400Member doesn’t hold enough FLY.Check wallet balance before confirm. Surface a “top up” prompt or fall back to a different funding source.
paymentIntent0003confirm / cancel / refund 400Wrong-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.
resource_not_found (with flynet_merchant_id)POST /payment_intents 404Merchant id not visible to your partner app.Confirm the flynet_merchant_id from your onboarding email exactly matches what you’re sending. If it does and you still get 404, route via Support.

Routing and request-shape errors

CodeWhereCauseFix
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.
invalid_request_error400 (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 calledHeader you sentWhat you get back
/restaurants* or /locations*nothing401 JSON MISSING_API_KEY
/restaurants* or /locations*invalid X-API-Key401 JSON INVALID_API_KEY
/restaurants* or /locations*Authorization: Bearer … (wrong scheme)401 JSON MISSING_API_KEY (the OAuth bearer is ignored; the route’s filter only knows API keys)
/users/me/*, /check_ins*, /memberships, /payment_intents/*nothing401 empty body + WWW-Authenticate: Bearer
/users/me/*, /check_ins*, /memberships, /payment_intents/*invalid bearer401 empty body + WWW-Authenticate: Bearer error="invalid_token"…
/users/me/*, /check_ins*, /memberships, /payment_intents/*valid bearer, wrong scope403 empty body + WWW-Authenticate: Bearer error="insufficient_scope"
/users/me/*, /check_ins*, /memberships, /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 wrong name like restaurant_id= instead of restaurant= is treated as no filter supplied — as is user=, which is no longer a supported filter (the feed is anonymized). 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.
  • /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 for the model.

Still stuck?

Contact 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.