---
id: sort-and-paging
slug: /reference/query-grammar/sort-and-paging
title: 'Sort and Paging'
description: 'Ordering results with + and -, and paging with page=size,start — including the argument order that is commonly reversed.'
sidebar_label: 'Sort and Paging'
sidebar_position: 5
tags: ['reference', 'query-grammar', 'sorting', 'pagination']
---

## Sort

```text
sort_token  = [ sort_term , { "," , sort_term } ] ;
sort_term   = [ "+" ] , attribute_name
            | "-" , attribute_name ;
```

| Prefix | Direction                |
| ------ | ------------------------ |
| `+`    | Ascending — **the default** |
| `-`    | Descending               |
| none   | Ascending                |

Terms are comma-separated and applied in order, so the first term is the major
sort:

```text
GET /rest-1.v1/Data/Story?sel=Name,Estimate&sort=Estimate
GET /rest-1.v1/Data/Story?sort=-Estimate,+Name
```

The second sorts by Estimate descending, then by Name ascending within each
Estimate.

Each term is a full
[attribute path](/reference/query-grammar/attribute-paths), so you can sort by a
value reached through a relation:

```text
?sort=Scope.Name,-ChangeDate
```

:::note `+` and `-` mean something different in `where`
In a [filter expression](/reference/query-grammar/filter-expressions#unary-existence)
the same two characters are the unary existence operators: `-Estimate` there
means "has no estimate", not "descending". The characters are shared; the
meaning comes from which parameter they appear in.
:::

A `+` in a URL query string is decoded as a space by many servers and clients.
Since ascending is the default, the safest habit is to omit `+` entirely and
write only the `-` prefixes you need. If you do send `+`, encode it as `%2B`.

### Default Ordering

No ordering is guaranteed when `sort` is absent from a `/Data` request — do not
depend on the order of an unsorted list request.

There is one exception. On a **history** request with no `sort`, the server adds
an implicit ascending sort on `Moment`. Supplying your own `sort` replaces it
rather than adding to it, so a sorted history response is no longer
chronological.

### Errors

A `sort` value that does not parse, or that names a non-existent attribute,
produces `400 Bad Request` with the message `Invalid SORT parameter`.

`sort` is ignored without complaint on single-asset `/Data/{Type}/{id}` requests.
Note that `deleted` and `needTotal` _are_ applied on those same URLs; see
[which parameters apply to which request shape](/reference/query-grammar#which-parameters-apply-to-which-request-shape).

## Paging

```text
paging_token  = page_size , [ "," , page_start ] ;
page_size     = INT ;
page_start    = INT ;
```

:::danger Size comes first
`page=<size>,<start>` — the **first** number is how many assets to return, the
**second** is how many to skip. This is the opposite of the `(start, size)`
ordering most pagination APIs use, and getting it backwards is silent: you get a
valid response containing the wrong window.

Note also that `query.v1` uses a **named** mapping — `page: { start: 0, size: 50 }`
— so a query ported between the two surfaces cannot carry the ordering across.
:::

`start` counts assets to skip, so it advances by `size` per page, not by one:

```text
GET /rest-1.v1/Data/Story?sel=Name&page=5,0     # assets 1–5
GET /rest-1.v1/Data/Story?sel=Name&page=5,5     # assets 6–10
GET /rest-1.v1/Data/Story?sel=Name&page=5,10    # assets 11–15
```

`start` is optional; `page=5` is `page=5,0`. Both values are plain non-negative
integers — no sign, no decimal point.

Because paging is applied after sorting, a stable `sort` is a prerequisite for
correct paging. Paging an unsorted `/Data` list can repeat or skip assets
between pages.

### Reading the Response

A list response carries three attributes on its root element:

```xml
<Assets total="10" pageSize="5" pageStart="0">
```

| Attribute   | Meaning                                                              |
| ----------- | -------------------------------------------------------------------- |
| `total`     | How many assets satisfied the query in total, ignoring paging — omitted when the server did not compute it |
| `pageSize`  | The requested page size                                               |
| `pageStart` | The requested page start                                             |

When `page` is omitted, `pageSize` comes back as `2147483647` — `Int32.MaxValue`
— meaning the response is unpaged. Treat that value as "no limit", not as a real
page size.

**"No limit" means no limit.** An unpaged list request attempts to return every
matching asset, so `page` is effectively mandatory on any query that could match
more than a handful. See [Query size and performance](/guides/query-size-and-performance).

:::danger A missing `total` means "not computed", not "no results"
`total` follows two rules:

- **Unpaged responses always carry it**, and it is always a real count — with
  no `page` the server counts what it returns.
- **Paged responses carry it only when the server was asked to compute it.**
  Otherwise the attribute is omitted entirely (XML and JSON alike; it is never
  written as `-1` or `null` on this endpoint).

On a paged query, whether the count is computed is controlled by the
[`needTotal` parameter](/reference/query-grammar#parameter-map), and **its default
is per-instance**: it comes from the `ApiDefaultNeedTotal` server setting. That
is the trap — a client that drives a pager off `total` can work correctly
against one Agility instance and break against another with no code change,
because the default flipped underneath it.

Always send `needTotal=true` explicitly when you need the count, and treat a
missing `total` as "unknown" rather than as zero. `needTotal` is applied on both
list and single-asset requests, unlike `page` and `sort`.
:::

### Errors

A `page` value that is not an integer produces `400 Bad Request` with the
message `Invalid PAGE parameter`. Extra comma-separated values beyond the second
are ignored rather than rejected.

`page` is ignored without complaint on single-asset `/Data/{Type}/{id}`
requests, for the same reason `sort` is.

## Putting It Together

```text
GET /rest-1.v1/Data/Story
    ?sel=Name,Estimate,ToDo
    &where=Estimate='5'
    &sort=ToDo
    &page=3,0
    &find=Ticket
    &findin=Name
```

The first 3 of the Story assets sorted by their To Do, with their Name, Estimate
and To Do attributes, that have an Estimate of 5 and a Name containing the word
Ticket.

## Related Topics

- [Attribute paths](/reference/query-grammar/attribute-paths)
- [Filter expressions](/reference/query-grammar/filter-expressions)
- [Grammar reference](/reference/query-grammar/ebnf)
- [Query size and performance](/guides/query-size-and-performance) — why an unpaged request is a
  problem, and the request shape that avoids it
- [Performance](/guides/performance)
