# Pagination and filtering

> Page through a MISSIN list with first, after and pageInfo, narrow it with a filter, and sort it with orderBy, from the SDK, the CLI or a raw request.

Every list operation in the [API reference](https://developer.missin.co.uk/reference.md) pages the same way and takes the same kind of filter. This guide uses `CampaignsList` and `PostsList`, which are in the `campaigns` and `posts` toolsets and need a Read key.

## Pages: first, after and pageInfo

A list takes `first`, the page size, and returns that many rows in `nodes` with a `pageInfo`, and usually a `totalCount`. The SDK, the CLI and the MCP tools send 50 when you leave `first` out; a raw request must send it. To read the next page, send the `endCursor` of the page you have as `after`, and stop when `hasNextPage` is `false`.

```json
{
  "operationName": "CampaignsList",
  "variables": {
    "first": 20,
    "after": "WyJuYXR1cmFsIiwxXQ=="
  },
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "cc4c4616f86feca4fafac98d357af608114b690240d58999fe247adae9c52df6"
    }
  }
}
```

The cursor is opaque. Take it from `pageInfo.endCursor` and send it back unchanged; do not build one. Send the same filter and the same ordering with every page, because a cursor only means something for the query that made it.

**SDK**

```ts
import { createMissinClient } from "@missin/sdk";

const missin = createMissinClient({ apiKey: process.env.MISSIN_API_KEY! });

let after: string | undefined;
const names: string[] = [];
for (;;) {
  const { allCampaigns } = await missin.campaigns.list({ first: 50, after });
  names.push(...allCampaigns!.nodes.map((c) => c!.name));
  if (!allCampaigns!.pageInfo.hasNextPage) break;
  after = allCampaigns!.pageInfo.endCursor ?? undefined;
}
```

**CLI**

```bash
missin campaigns list --first 20
missin campaigns list --first 20 --after "WyJuYXR1cmFsIiwxXQ=="
```

On a terminal you get a table of the first columns. Piped, or with `--json`, you get JSON that includes `pageInfo`, so a script can follow the cursor.

## Filtering

A list takes a `filter` made of the fields of the thing it lists. Each field takes an object of operators, and `and`, `or` and `not` combine them.

```json
{
  "first": 20,
  "filter": {
    "and": [
      { "name": { "includesInsensitive": "spring" } },
      { "not": { "isDefault": { "equalTo": true } } }
    ]
  }
}
```

- A **text** field takes `equalTo`, `notEqualTo`, `in`, `notIn`, `includesInsensitive`, `isNull`, and the ordering comparisons `lessThan`, `greaterThan` and their `OrEqualTo` forms.
- A **boolean**, **number**, **date** or **id** field takes the same comparisons that make sense for it.
- `and` and `or` take a list of filters. `not` takes one.

The filter is deliberately bounded to the fields of the list itself. You cannot filter by a field of a related record, so there are no filters like "campaigns whose partner is called …". To do that, read the related list with its own filter and pass the ids you got. The exact fields and operators of each list are in its variables on its reference page.

## Sorting

Some lists take `orderBy`, a list of column orderings. `PostsList` takes values like `LIKES_DESC` and `METRICS_UPDATED_AT_DESC`; the values a list accepts are its `orderBy` enum on its reference page.

```ts
const { allPartnerPosts } = await missin.posts.list({ first: 25, orderBy: ["LIKES_DESC"] });
```

Other lists, such as `CampaignsList`, fix their order (campaigns come back by name) and take no `orderBy`. When a page of results must not shift under you, sort by a column that does not change as you read.

## Next

[Making requests](https://developer.missin.co.uk/get-started/making-requests.md) explains the request shape, [Errors](https://developer.missin.co.uk/get-started/errors.md) the refusals, and [Add a partner's posts to a campaign](https://developer.missin.co.uk/guides/partner-to-campaign.md) a task that ends with a list of posts.

Source: https://developer.missin.co.uk/guides/pagination-and-filtering
