Cursor Pagination
Cursor-based pagination for SCIM queries, for service providers whose underlying store already pages by cursor.
RFC 7644
pages results by index: the client asks for a startIndex and a
count. That does not map cleanly onto every backend. Many existing codebases,
databases, and APIs already page by cursor, and translating between the two models is
awkward and often expensive. RFC 9865 adds cursor-based pagination as an alternative, so
those providers can expose what they already do.
A service provider may implement either method or both. When it supports both, the client
picks by sending either startIndex or cursor, and the provider
MUST choose a default for requests that specify neither. A provider retrofitting cursors
onto an existing index-based implementation should keep index as the default, so
clients that expect the old behaviour are not broken.
Query parameters
| Parameter | Description |
|---|---|
cursor |
The nextCursor value from a previous result page. Empty or omitted on
the first request. Restricted to the unreserved character set of RFC 3986.
|
count |
The desired maximum number of results per page. A negative value is treated as
0, which returns only totalResults. The provider must not
return more than requested, but may return fewer.
|
Response attributes
| Attribute | Description |
|---|---|
nextCursor |
Cursor for the next page. Providers supporting cursor pagination must include it in every paged response except the last — its absence is what signals that there are no more pages. |
previousCursor |
Cursor for the previous page. Optional, and never returned with the first page. Its presence tells the client the provider supports reverse traversal. |
Cursor values are URL-safe strings that are opaque to the client. To fetch another page the client repeats the original query exactly, changing only the cursor value.
GET /v2/Users?filter=userName%20sw%20J&cursor&count=10 HTTP/1.1
Host: example.com
Accept: application/scim+json
Authorization: Bearer U8YJcYYRMjbGeepDHTTP/1.1 200 OK
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 100,
"itemsPerPage": 10,
"nextCursor": "VZUTiyhEQJ94IR",
"Resources": [
{ "id": "2819c223-7f76-453a-919d-413861904646", "userName": "jsmith" },
{ "id": "c3a26dd3-27a0-4dec-a2ac-ce211e105f97", "userName": "jhalpert" }
]
}
The next request reuses every parameter and sets cursor to the
nextCursor just returned:
GET /v2/Users?filter=userName%20sw%20J&cursor=VZUTiyhEQJ94IR&count=10 HTTP/1.1
Host: example.com
Accept: application/scim+json
Authorization: Bearer U8YJcYYRMjbGeepDHTTP/1.1 200 OK
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 100,
"itemsPerPage": 10,
"previousCursor": "ze7L30kMiiLX6x",
"nextCursor": "YkU3OF86Pz0rGv",
"Resources": [
{ "id": "a4a25dd3-17a0-4dac-a2ac-ce211e125f57", "userName": "jlevinson" }
]
}
Providers should still return an accurate totalResults across all pages, but
one that cannot estimate it may omit the attribute entirely. The same parameters work with
POST /.search, passed in the request body alongside
filter and attributes rather than on the URL.
Errors
These scimType values extend the detail error keywords in RFC 7644 §3.12
and are returned with HTTP 400:
| scimType | Meaning |
|---|---|
invalidCursor |
The cursor value is not valid for this query. |
expiredCursor |
The cursor has expired. The client waited longer than the provider's cursorTimeout between page requests. |
invalidCount |
The count is outside 0–maxPageSize, or differs from the count used in the initial query. |
Discovery
A provider implementing cursor pagination should advertise a pagination complex
attribute from /ServiceProviderConfig:
- cursor boolean, required
- Whether cursor-based pagination is supported.
- index boolean, required
- Whether index-based pagination is supported.
- defaultPaginationMethod "cursor" | "index"
- Which method applies when the client specifies neither.
- defaultPageSize integer
- Page size used when the query omits
count. - maxPageSize integer
- Ceiling on results per page, whatever
countasks for. - cursorTimeout seconds
- Minimum time a cursor stays valid between page requests.
Every sub-attribute except cursor and index is optional, and a
provider may legitimately publish none of them — page limits can vary per resource
type, or depend on response size rather than a resource count. Clients must not read a
missing value as "no limit" or "no default".