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

# Pagination + errors

> List pagination, optional filters, and observed error response shapes.

## Pagination

List endpoints use zero-indexed pagination.

| Parameter   | Default | Notes                   |
| ----------- | ------- | ----------------------- |
| `page`      | `0`     | First page is `0`.      |
| `page_size` | `50`    | Keep values reasonable. |

Most list responses include a `pagination` wrapper:

```json theme={null}
{
  "pagination": {
    "total_count": 562,
    "total_pages": 12,
    "current_page": 0,
    "next_page": 1,
    "page_size": 50
  }
}
```

`GET /locations/{id}/open_hours` is not paginated.

## Error response shapes

Three error envelope shapes occur on the API. Branch on the HTTP
status code first, then read the matching shape below.

### Envelope A - modern

Returned by `/check_ins`, `/users/me/*`, and
`/payment_intents/*` on `400` validation and `404 resource_not_found`
errors. Note this envelope is **status-agnostic**: the same shape
appears on both 400 and 404, so branch on the HTTP status, not the
envelope.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_not_found",
    "message": "No active status found for user.",
    "param": null
  }
}
```

### Invalid API key - 401 (envelope A)

Returned by `/restaurants*` and `/locations*` when the `X-API-Key`
header carries an expired or revoked key. (A **missing** key returns an
empty-body 401 instead — see below.)

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid or revoked API key.",
    "param": null
  }
}
```

### Envelope C - routing layer 404

Returned when the path does not match a known route, such as typos,
deprecated routes, or version-prefix mistakes.

```json theme={null}
{
  "status": 404,
  "display_message": "Something went wrong. Please try again in a moment.",
  "message": "Endpoint not found.",
  "error": "EndpointNotFoundException",
  "error_code": "internal0007",
  "timestamp": "2026-05-12T00:41:22.919648516Z"
}
```

### 401 with empty body

A **missing credential returns HTTP 401 with an empty body** and a
`WWW-Authenticate: Bearer` header — on **both** route families. Member
routes (`/users/me/*`, `/payment_intents/*`) return it when the bearer
is missing, malformed, or expired (the header carries
`error="invalid_token"` for a bad JWT);
Discovery routes (`/restaurants*`, `/locations*`, the `/check_ins`
venue feed including `/check_ins/{id}`) return it when the `X-API-Key`
header is absent. Do not try to parse JSON on these 401s —
read the `WWW-Authenticate` header. (An *invalid* API key is the one
exception that returns a JSON body; see above.)

### 403 insufficient scope

A valid bearer that lacks the scope a member route requires returns
**HTTP 403** with an empty body and a
`WWW-Authenticate: Bearer error="insufficient_scope"` header. The
route exists; the token simply isn't authorized for it, distinct
from the empty-body 401 (missing/invalid token) and from a 404
(route doesn't exist). See [Authentication → OAuth scopes](/concepts/authentication#oauth-scopes).

## Filtering `GET /check_ins`

`GET /check_ins` is an anonymized venue feed (no member identity) and
accepts optional filters
(`[restaurant, location, created_after, created_before]`), but
a bare `GET /check_ins` with no filter is valid and returns the full
paginated set. (Earlier launch builds required at least one filter;
that requirement was dropped.)

## Timestamp format

`created_after` and `created_before` take **ISO 8601 strings**:
`2026-04-01T00:00:00Z`. Epoch seconds are rejected with 400:
`"Parse attempt failed for value [1715468700]"`.

## Unknown query parameters

Unknown query parameters are silently ignored. A typo in a filter
name, such as `?restaurants=...` instead of `?restaurant=...`, is
treated as if the filter was not supplied, so `/check_ins` returns the
unfiltered set rather than an error, and you get more rows than you
expected rather than a 400.

## Error code reference

| Code                        | Meaning                                                                                                          |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `invalid_request_error`     | Request body or parameters are malformed.                                                                        |
| `invalid_parameter`         | A specific parameter is invalid or missing.                                                                      |
| `resource_not_found`        | The resource does not exist.                                                                                     |
| `payment0030`               | Member has insufficient FLY for Payment Intent confirm.                                                          |
| `paymentIntent0003`         | The Payment Intent is not in a state that allows this operation.                                                 |
| `invalid_api_key`           | The `X-API-Key` is expired or revoked (HTTP 401, envelope A). A *missing* key returns an empty-body 401 instead. |
| `insufficient_scope`        | The bearer token lacks the OAuth scope the member route requires (HTTP 403, in the `WWW-Authenticate` header).   |
| `EndpointNotFoundException` | The route does not exist. Check the path and version prefix.                                                     |

## With the SDK

The published [`@flynetdev/core`](/concepts/typescript-client) SDK
normalizes every envelope above into a single `FlynetError`. Instead of
branching on HTTP status and matching envelope shapes, branch on
`error.kind`:

```ts theme={null}
import { FlynetError } from "@flynetdev/core";

try {
  await member.listCheckIns();
} catch (err) {
  if (err instanceof FlynetError) {
    switch (err.kind) {
      case "insufficient_scope": // 403, token missing the required scope
      case "unauthorized":       // empty-body 401, missing/invalid bearer
      case "rate_limited":       // 429
      case "network":            // transport failure, no HTTP response
        // handle...
    }
  }
}
```

`kind` is one of `unauthorized`, `insufficient_scope`, `forbidden`,
`not_found`, `validation`, `rate_limited`, `server`, `network`, or
`unknown`. The SDK maps the raw responses for you: the empty-body 401 →
`unauthorized`, the 403 `insufficient_scope` → `insufficient_scope`, a 429
→ `rate_limited`, Envelope A `400` validation → `validation`, and a
failed request that never reached the API → `network`.
