# Pagination

`GET /v1/jobs` and `GET /v1/search` return pages. Paging is cursor-based.

## Parameters

| Parameter | Type | Default | Allowed |
| --- | --- | --- | --- |
| `limit` | integer | `20` | `1` to `100` |
| `cursor` | string | none — start at the beginning | the `next_cursor` of the previous page |

## Page shape

```json
{
  "items": [ { "id": 536359, "title": "…" } ],
  "has_more": true,
  "next_cursor": "NTM2MzY1"
}
```

| Key | Type | Meaning |
| --- | --- | --- |
| `items` | array of [Job](/api-documentation/field-reference) | The jobs on this page, ordered by `id` ascending. |
| `has_more` | boolean | `true` if at least one more job follows this page. |
| `next_cursor` | string or `null` | Pass it as `cursor` to get the next page; `null` when `has_more` is `false`. |

## How to walk all pages

1. Request the first page without `cursor`.
2. Process `items`.
3. If `has_more` is `true`, request again with the same parameters plus
   `cursor=<next_cursor>`; otherwise stop.

Complete loops in Python and JavaScript are in the [Quick start](/api-documentation/quickstart#3-page-through-all-jobs).

## Behaviour you can rely on

- **Order is by `id` ascending**, for both list and search. Because the cursor marks the last
  `id` you received, you never see the same job twice in one walk.
- **Cursors are opaque.** Pass them back unchanged. A cursor this API did not issue answers
  `422 {"error": "invalid cursor"}`.
- **Cursors do not expire.** A cursor holds no server-side state, so you can resume a walk later.
- **Keep the same query when paging a search.** The cursor only marks a position; send the same
  `q` with every page.
- **A page can be shorter than `limit`** when your monthly allowance has fewer jobs left than
  `limit`. `has_more` still tells you whether more jobs follow. See
  [Errors & limits](/api-documentation/errors-and-limits).
