# Pagination and filtering

> How list endpoints page their results with limit and offset or with page numbers, and how to read the total.

Source: https://www.coritan.com/docs/api/pagination/

List endpoints answer one page of results at a time. Each list takes one of four sets of paging parameters, and many take filters as well. The [API reference](/docs/api/reference/) gives the parameters of every route. This page lists the conventions, the page sizes of the main lists, and how to read a total.

## Paging parameters

| Parameters | First page | Next page | Used by |
| --- | --- | --- | --- |
| `limit` and `offset` | `offset=0` | Add `limit` to `offset`. | `GET /services/` and `GET /payments/disputes` in the Client API, and most lists in the Organization API, the Commerce API and the Store API |
| `page` and `limit` | `page=1` | Add 1 to `page`. | Invoices, transactions, a service's events and support conversations in the Client API |
| `page` and `per_page` | `page=1` | Add 1 to `page`. | DNS zones and records, proxy routes, SSL certificates and products in the Client API, and services in the customer portal |
| `before_id` and `limit` | Leave out `before_id`. | Send the last answer's `next_before_id` as `before_id`. | Events, promotions, gift cards and fulfilment provider logs in the Commerce API |

`page` starts at 1, and page `n` skips the first `(n - 1) × limit` results (or `(n - 1) × per_page`). A page past the last one answers an empty list.

Lists come newest first unless the tables below say otherwise. Invoices, transactions, customers and Commerce orders break a tie on the ID, so while nothing changes, every row appears on exactly one page. On a list sorted newest first, a row added while you page pushes the others down by one, and the next page then starts with the last row of the page before. Skip IDs you already hold.

## Page sizes

Leave out `limit` or `per_page` to get the default. On most lists, a value above the largest size, or below 1, answers `422`, and the entry in `detail` names the parameter and the largest size:

```json
{
  "detail": [
    {
      "type": "less_than_equal",
      "loc": ["query", "limit"],
      "msg": "Input should be less than or equal to 200",
      "input": "500",
      "ctx": {"le": 200}
    }
  ]
}
```

### Client API

| List | Route | Paging | Default | Largest |
| --- | --- | --- | --- | --- |
| Services | [`GET /services/`](/docs/api/reference/client/services/#op-get-api-v1-services) | `limit`, `offset` | 100 | 500 |
| A service's events | [`GET /services/{service_ref}/events`](/docs/api/reference/client/services/#op-get-api-v1-services-service-ref-events) | `page`, `limit` | 50 | 200 |
| Invoices | [`GET /billing/invoices`](/docs/api/reference/client/billing/#op-get-api-v1-billing-invoices) | `page`, `limit` | 50 | 200 |
| Transactions | [`GET /billing/transactions`](/docs/api/reference/client/billing/#op-get-api-v1-billing-transactions) | `page`, `limit` | 50 | 200 |
| Disputes | [`GET /payments/disputes`](/docs/api/reference/client/payments/#op-get-api-v1-payments-disputes) | `limit`, `offset` | 50 | 100 |
| DNS zones, in order of domain | [`GET /dns/zones`](/docs/api/reference/client/dns/#op-get-api-v1-dns-zones) | `page`, `per_page` | 25 | 100 |
| A zone's records, in order of name, then type | [`GET /dns/zones/{zone_id}/records`](/docs/api/reference/client/dns/#op-get-api-v1-dns-zones-zone-id-records) | `page`, `per_page` | 100 | 1,000 |
| Proxy routes | [`GET /proxy/routes`](/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes) | `page`, `per_page` | 50 | 200 |
| SSL certificates | [`GET /ssl/certificates`](/docs/api/reference/client/ssl/#op-get-api-v1-ssl-certificates) | `page`, `per_page` | 25 | 100 |
| Products, in the order the catalogue shows them | [`GET /products/`](/docs/api/reference/client/catalog/#op-get-api-v1-products) | `page`, `per_page` | 100 | 200 |
| Support conversations, latest activity first | [`GET /chat/conversations`](/docs/api/reference/client/support/#op-get-api-v1-chat-conversations) | `page`, `limit` | 20 | 100 |
| Messages in a conversation, oldest first | [`GET /chat/conversations/{conversation_id}/messages`](/docs/api/reference/client/support/#op-get-api-v1-chat-conversations-conversation-id-messages) | `before_id`, `after_id`, `limit` | 50 | 100 |

[`GET /billing/crypto/deposits`](/docs/api/reference/client/billing/#op-get-api-v1-billing-crypto-deposits) takes `limit` alone (50 by default, at most 200) and answers the newest deposits, with no way to ask for older ones.

### Organization API and customer portal

| List | Route | Paging | Default | Largest |
| --- | --- | --- | --- | --- |
| Customers | [`GET /orgs/{org_slug}/customers`](/docs/api/reference/organizations/customers/customers/#op-get-api-v1-orgs-org-slug-customers) | `limit`, `offset` | 50 | 200 |
| Invoices | [`GET /orgs/{org_slug}/invoices`](/docs/api/reference/organizations/billing-payouts/invoices/#op-get-api-v1-orgs-org-slug-invoices) | `limit`, `offset` | 50 | 500, cut down |
| Audit log | [`GET /orgs/{org_slug}/audit-log`](/docs/api/reference/organizations/organizations-members/audit-log/#op-get-api-v1-orgs-org-slug-audit-log) | `limit`, `offset` | 50 | 200 |
| A customer's services | [`GET /orgs/{org_slug}/portal/services`](/docs/api/reference/organizations/customer-portal/services/#op-get-api-v1-orgs-org-slug-portal-services) | `page`, `per_page` | 50 | 200 |
| A customer's invoices | [`GET /orgs/{org_slug}/portal/invoices`](/docs/api/reference/organizations/customer-portal/invoices/#op-get-api-v1-orgs-org-slug-portal-invoices) | `limit`, `offset` | 50 | 500, cut down |
| A customer's transactions | [`GET /orgs/{org_slug}/portal/transactions`](/docs/api/reference/organizations/customer-portal/transactions/#op-get-api-v1-orgs-org-slug-portal-transactions) | `limit`, `offset` | 50 | 500, cut down |

Where the table says *cut down*, a larger `limit` does not fail: the list answers 500 results instead.

### Commerce API and Store API

| List | Route | Paging | Default | Largest |
| --- | --- | --- | --- | --- |
| Orders | [`GET /orgs/{org_slug}/commerce/orders`](/docs/api/reference/organizations/commerce/commerce/#op-get-api-v1-orgs-org-slug-commerce-orders) | `limit`, `offset` | 50 | 200 |
| Products, most recently changed first | [`GET /orgs/{org_slug}/commerce/products`](/docs/api/reference/organizations/catalog-services/commerce/#op-get-api-v1-orgs-org-slug-commerce-products) | `limit`, `offset` | 50 | 200 |
| Events | [`GET /orgs/{org_slug}/commerce/events`](/docs/api/reference/organizations/commerce/commerce/#op-get-api-v1-orgs-org-slug-commerce-events) | `before_id`, `limit` | 50 | 200 |
| Promotions | [`GET /orgs/{org_slug}/commerce/promotions`](/docs/api/reference/organizations/commerce/commerce/#op-get-api-v1-orgs-org-slug-commerce-promotions) | `before_id`, `limit` | 50 | 200 |
| Gift cards | [`GET /orgs/{org_slug}/commerce/gift-cards`](/docs/api/reference/organizations/commerce/commerce/#op-get-api-v1-orgs-org-slug-commerce-gift-cards) | `before_id`, `limit` | 50 | 200 |
| Store products | [`GET /orgs/{org_slug}/store/products`](/docs/api/reference/organizations/catalog-services/store/#op-get-api-v1-orgs-org-slug-store-products) | `limit`, `offset` | 20 | 100 |
| Store collections, in the store's order | [`GET /orgs/{org_slug}/store/collections`](/docs/api/reference/organizations/commerce-store-api/#op-get-api-v1-orgs-org-slug-store-collections) | `limit`, `offset` | 50 | 100 |

Store products come newest first by default. `order` changes that: `-created_at`, `created_at`, `-title` or `title`.

## Totals

Most lists answer a bare JSON array with no total. Those below say how many results match.

| Lists | How to get the total | Answer |
| --- | --- | --- |
| Invoices and transactions in the Client API | Add `with_total=true`. | `{"items": [...], "total": 12, "page": 1, "limit": 50, "counts": {...}}` |
| Customers and invoices in the Organization API, and invoices and transactions in the customer portal | Add `with_total=true`. | `{"items": [...], "total": 12, "limit": 50, "offset": 0, "counts": {...}}` |
| Proxy routes | Always there. | `{"routes": [...], "total": 12, "page": 1, "per_page": 50}` |
| SSL certificates | Always there. | `{"certificates": [...], "total": 12}` |
| Lists in the Commerce API and the Store API that take `offset` | Always there. | The results under a name such as `orders`, with `count`, `limit` and `offset` |

`total` and `count` are the number of results the filters match, across every page. `counts` gives the number for each status or type as if no status or type filter were set, with `all` for the whole list. Invoices add `open`, which is unpaid and overdue together, and Client API transactions add `credit`, which is credit added and credit used together. Organization invoices also carry `outstanding`: what the open invoices owe, for each currency.

## Filters

A filter narrows the list before it is cut into pages, so `total` and every page reflect it. A filter the route does not know is ignored.

| Filter | Lists | What it matches |
| --- | --- | --- |
| `status` | Services | One status: `pending`, `provisioning`, `active`, `suspended`, `pending_termination`, `terminated` or `failed`. |
| `resource_type` | Services | `vps` (or `cloud_compute`), `container`, `ip` (or `floating_ip`), `mail` or `smtp_relay`. Another word answers `422` with `"error": "invalid_resource_type"` and the accepted words in `allowed`. |
| `tag` | Services | Services with that tag. |
| `status_filter` | Invoices | `open`, `draft`, `unpaid`, `paid`, `cancelled`, `refunded` or `overdue`. |
| `type_filter` | Transactions | `payment`, `refund`, `credit`, `credit_add` or `credit_deduct`. In the customer portal: `payment`, `refund`, `chargeback`, `credit_add` or `credit_deduct`. |
| `record_type` and `name` | A zone's records | One record type, in any case, and one exact record name. |
| `status`, `priority` and `q` | Support conversations | `status` takes `open`, `awaiting_customer`, `awaiting_agent`, `in_progress`, `on_hold` or `closed`, and `open` matches every conversation that is not closed. `priority` takes `low`, `medium`, `high` or `critical`. `q` searches the subject and the latest message, and `#` followed by a number finds that conversation. |
| `q` | Organization customers, portal invoices and Commerce orders | Search text: a customer's email, name, handle, company or number; an invoice number; or an order number such as `#1001`, an order ID or part of an email. |
| `placed_from` and `placed_to` | Commerce orders | A date and time range. `placed_to` itself is left out. |

`resource_type`, `type_filter`, `priority` and the Client API's `status_filter` answer `422` for a word they do not list. The other filters match nothing when no result has the value you send. The reference for each route lists the rest of its filters.

## Walk every page

Ask for pages until one holds fewer results than you asked for, or until you have `total` results. This reads every service, 100 at a time:

```bash
offset=0
while :; do
  page=$(curl -s "https://api.coritan.com/api/v1/services/?limit=100&offset=$offset" \
    -H "Authorization: Bearer $CORITAN_TOKEN")
  echo "$page" | jq -c '.[]'
  [ "$(echo "$page" | jq length)" -lt 100 ] && break
  offset=$((offset + 100))
done
```

On a list that uses `before_id`, stop when `next_before_id` is `null`.

## Read new messages

Messages in a support conversation page by ID rather than by position:

No parameters
: The latest `limit` messages, oldest first.

`before_id`
: The latest `limit` messages older than that message ID, oldest first. Send the ID of the first message you hold to read further back.

`after_id`
: Up to `limit` messages newer than that message ID, oldest first. Send the ID of the last message you hold to fetch only what is new.

Internal notes from Coritan's team never appear in the list. Poll no more often than you need to: [Rate limits](/docs/api/rate-limits/) applies to every request.

When you poll `GET /orgs/{org_slug}/portal/services` for changes, add `consistency=eventual`. The answer may miss a change made a moment ago, so leave it out on the first read after a change of your own.
