RFC 9865

Cursor-Based Pagination of System of Cross-domain Identity Management (SCIM) Resources

πŸ“… published October 2025, from ietf

RFC 9865: Cursor-Based Pagination of System of Cross-domain Identity Management (SCIM) Resources πŸŽ€

Okay bestie, so this is RFC 9865, and it's giving standards track πŸ’… It was published in October 2025 by the IETF and has ISSN 2070-1721. The editor is M. Peterson, Ed. from Entrust, with D. Zollner (Independent) and A. Sehgal (Amazon Web Services) as co-authors. It updates RFC 7643 and RFC 7644, which means it adds new stuff to the existing SCIM documents instead of replacing them πŸ¦‹

Abstract

okay so this doc updates RFCs 7643 and 7644 by adding extra SCIM query parameters and result attributes. The goal is to let SCIM service providers use cursor-based pagination when they're built on top of existing codebases, databases, or APIs that already do cursor-based pagination really well 🧚

Status of This Memo

This is an Internet Standards Track document. 🌈

This document is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community. It has received public review and has been approved for publication by the Internet Engineering Steering Group (IESG). Further information on Internet Standards is available in Section 2 of RFC 7841.

Information about the current status of this document, any errata, and how to provide feedback on it may be obtained at https://www.rfc-editor.org/info/rfc9865.

Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. πŸ’œ

Table of Contents

1.  Introduction
  1.1.  Notational Conventions
  1.2.  Definitions
2.  Query Parameters and Response Attributes
  2.1.  Pagination Errors
  2.2.  Sorting
  2.3.  Implementing Cursors as the Only Pagination Method
  2.4.  Implementing Both Cursors and Index Pagination
3.  Querying Resources Using HTTP POST
4.  Service Provider Configuration
5.  Security Considerations
  5.1.  Threat Model and Security Environment
  5.2.  Confidentiality
  5.3.  Availability
  5.4.  Other Security References
6.  IANA Considerations
7.  References
  7.1.  Normative References
  7.2.  Informative References
Acknowledgments
Contributors
Authors' Addresses

1. Introduction

okay so there are two common patterns for pagination: index-based and cursor-based. Instead of arguing about which one is better (no thanks πŸ™…β€β™€οΈ), this doc just notices that SCIM service providers are usually built as an interoperability layer sitting on top of existing application codebases, databases, and/or APIs. Those already have a well established pagination pattern, so that's the reality we're working with πŸ’–

Here's the tricky part. Translating from an underlying cursor-based pattern into the index-based pagination defined in Section 3.4.2.4 of [RFC7644] means the SCIM service provider has to fully iterate through the underlying cursor, store all the results, and then serve indexed pages from that stored copy. This "pagination translation" adds complexity and memory requirements to a SCIM service provider, and it can be a blocker for SCIM adoption in some applications and identity systems 😭

So this document adds a simple extension to the SCIM protocol that lets service providers reuse their underlying cursors without the expensive translation step. Supporting cursor-based pagination in SCIM encourages broader interoperability for cross-application identity management, because it makes SCIM service provider implementations more appealing for applications and identity systems where cursor-based pagination is already the norm 🌸

This document updates RFCs 7643 and 7644 because it adds attributes to existing structures from those documents, as described in Section 2. These changes are invoked when using the "cursor" parameter when making SCIM search requests using GET or POST methods. ✨

1.1. Notational Conventions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. πŸŽ€

1.2. Definitions

This document uses the terms defined in Section 1.2 of [RFC7643]. πŸ“–

2. Query Parameters and Response Attributes

okay this is the juicy part πŸ“ First up is the table of URL pagination query parameters you use to ask for cursor-based pagination:

    +===========+=====================================================+
    | Parameter | Description                                         |
    +===========+=====================================================+
    | cursor    | The string value of the nextCursor attribute from a |
    |           | previous result page.  The cursor value MUST be     |
    |           | empty or omitted for the first request of a cursor- |
    |           | paginated query.  This value may only contain       |
    |           | characters from the unreserved character set        |
    |           | defined in Section 2.3 of [RFC3986].                |
    +-----------+-----------------------------------------------------+
    | count     | Specifies the desired maximum number of query       |
    |           | results per page, e.g., 10.  A negative value SHALL |
    |           | be interpreted as "0".  A value of "0" indicates    |
    |           | that no resource results are to be returned except  |
    |           | for "totalResults".  When specified, the service    |
    |           | provider MUST NOT return more, although it MAY      |
    |           | return fewer, results.  If unspecified, the maximum |
    |           | number returned is set by the service provider.     |
    +-----------+-----------------------------------------------------+

                         Table 1: Query Parameters

Let's unpack that. cursor is the nextCursor string you got from a previous page. For the first request of a cursor-paginated query, it MUST be empty or left out. It can only contain characters from the unreserved character set, which is defined in Section 2.3 of [RFC3986] πŸ¦‹

count is how many results you'd like per page, like 10. A negative value SHALL be treated as "0", and a value of "0" means no resource results come back, only "totalResults". When you set it, the service provider MUST NOT send back more results than that, though it MAY send fewer. If you don't set it, the service provider picks the max πŸ’…

Next up is the table of cursor-based attributes that come back in a paged response:

    +================+================================================+
    | Element        | Description                                    |
    +================+================================================+
    | nextCursor     | A cursor value string that MAY be used in a    |
    |                | subsequent request to obtain the next page of  |
    |                | results.  Service providers supporting cursor- |
    |                | based pagination MUST include nextCursor in    |
    |                | all paged query responses except when          |
    |                | returning the last page. nextCursor MUST be    |
    |                | omitted from a response only to indicate that  |
    |                | there are no more result pages.                |
    +----------------+------------------------------------------------+
    | previousCursor | A cursor value string that MAY be used in a    |
    |                | subsequent request to obtain the previous page |
    |                | of results.  Returning previousCursor is       |
    |                | OPTIONAL.  previousCursor MUST NOT be returned |
    |                | with the first page.                           |
    +----------------+------------------------------------------------+

                        Table 2: Response Attributes

So nextCursor is the string you send back to grab the next page. Providers that support cursor pagination MUST include nextCursor in every paged response, except when they're sending the last page. The only time nextCursor MUST be left out is to signal that there are no more pages left. πŸ₯Ή

previousCursor is the string for going back to the previous page. Sending it back is totally a MAY. Returning it is OPTIONAL, and it MUST NOT show up on the first page 🧚

Cursor values are URL-safe strings, and the client doesn't get to peek inside them (they're opaque, bestie πŸ™ˆ). To get another page of the same query, the client MUST hit the same service provider endpoint, with every query parameter and value identical to the first query, except for the cursor. That cursor SHOULD be set to a nextCursor (or previousCursor) value the service provider sent back in an earlier response. πŸ”₯

Here's an example. To grab the first 10 users whose userName starts with J, use an empty cursor and set count to 10:

GET /Users?filter=userName%20sw%20J&cursor&count=10
Host: example.com
Accept: application/scim+json
Authorization: Bearer U8YJcYYRMjbGeepD

The SCIM service provider responds to that query with pagination metadata that looks something like this (Resources omitted for brevity):

HTTP/1.1 200 OK
Content-Type: application/scim+json

{
   "totalResults":100,
   "itemsPerPage":10,
   "nextCursor":"VZUTiyhEQJ94IR",
   "schemas":["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
   "Resources":[{
      ...
   }]
}

Now, to get the next page, send the same query parameters and values, but set the cursor to the nextCursor value from before (VZUTiyhEQJ94IR) ✨

GET /Users?filter=username%20sw%20J&cursor=VZUTiyhEQJ94IR&count=10
Host: example.com
Accept: application/scim+json
Authorization: Bearer U8YJcYYRMjbGeepD

The service provider responds with:

HTTP/1.1 200 OK
Content-Type: application/scim+json

{
   "totalResults": 100,
   "itemsPerPage": 10,
   "previousCursor: "ze7L30kMiiLX6x",
   "nextCursor": "YkU3OF86Pz0rGv",
   "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
   "Resources":[{
      ...
   }]
}
bestie, heads up: the previousCursor line in that last example is missing a closing quote after the key name. I kept it exactly as the original has it, because the examples in this doc are meant to be copied verbatim πŸ™ˆ

In that example, the response includes the optional previousCursor, which tells you the service provider supports going both forward and backward through result pages. πŸ¦‹

Last thing for this part! As described in Section 3.4.1 of [RFC7644], service providers should return an accurate value for totalResults, which is the total number of resources across all pages. Service providers implementing cursor pagination that can't estimate totalResults MAY choose to omit the totalResults attribute. πŸ’œ


(Part 1 of 3 ends here. Part 2 picks up with Section 2.1 Pagination Errors πŸŽ€)

2.1. Pagination Errors ✨

Okay so, if a service provider runs into bad pagination query parameters (a wonky cursor value, a janky count value, whatever) or any other error, it SHOULD send back the right HTTP status code and a detailed JSON error response, as described in Section 3.12 of [RFC7644]. πŸ’…

For HTTP status code 400 (Bad Request) responses, there are some extra detail error types defined here. They add to the list in Table 9 ("SCIM Detail Error Keyword Values") of Section 3.12 of [RFC7644]. πŸŽ€

+===============+==================================+===============+
| scimType      | Description                      | Applicability |
+===============+==================================+===============+
| invalidCursor | Cursor value is invalid.  Cursor | GET (Section  |
|               | value SHOULD be empty to request | 3.4.2 of      |
|               | the first page and set to the    | [RFC7644])    |
|               | nextCursor or previousCursor     |               |
|               | value for subsequent queries.    |               |
+---------------+----------------------------------+---------------+
| expiredCursor | Cursor has expired.  Do not wait | GET (Section  |
|               | longer than service provider's   | 3.4.2 of      |
|               | cursorTimeout to request         | [RFC7644])    |
|               | additional pages.                |               |
+---------------+----------------------------------+---------------+
| invalidCount  | Count value is invalid.  Count   | GET (Section  |
|               | value must be between 0 and      | 3.4.2 of      |
|               | service provider's maxPageSize   | [RFC7644])    |
|               | and must be equal to the count   |               |
|               | value of the initial query.      |               |
+---------------+----------------------------------+---------------+

Table 3: Pagination Errors

2.2. Sorting πŸ¦‹

If sorting is implemented the way Section 3.4.2.3 of [RFC7644] describes it, then cursor-paged results should be sorted. Literally just keep your pages in order, bestie! 🌸

2.3. Implementing Cursors as the Only Pagination Method 🧚

A service provider MAY require cursor-based pagination to get all the results for a query. It does this by including a nextCursor value in the response, even when the query doesn't include the cursor parameter. πŸ“

Here's what that looks like:

GET /Users
Host: example.com
Accept: application/scim+json

The service provider may answer that query with a page holding defaultPageSize results plus a nextCursor value, like in the example below (Resources left out for brevity). πŸ’œ

HTTP/1.1 200 OK
Content-Type: application/scim+json

{
   "totalResults": 5000,
   "itemsPerPage": 100,
   "nextCursor": "HPq72Pax3JUaNa",
   "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
   "Resources": [{
      ...
   }]
}

2.4. Implementing Both Cursors and Index Pagination πŸ‘‘

When a service provider supports both index-based and cursor-based pagination, clients can use the 'startIndex' or 'cursor' query parameters to ask for a specific method. Also, service providers that support both pagination methods MUST choose a default pagination method to use when they get requests that don't specify a pagination query parameter. It's giving organized! ✨

Implementers of SCIM service providers that only supported index-based pagination before, and are now adding cursor-based pagination, should use index as the default pagination method. That way they won't break clients that expect index-based behavior when no pagination query parameters are specified. 🫢

SCIM clients can query the Service Provider Configuration (Section 4) endpoint to find out whether index-based, cursor-based, or both types of pagination are supported, and which one is the default. 🌈

3. Querying Resources Using HTTP POST πŸ’–

Section 3.4.3 of [RFC7644] explains how clients can run queries without putting parameters in the URL. They use the POST verb together with the /.search path extension. When posting to /.search, the client passes the parameters defined in Section 2 in the body of the POST request. Super clean! πŸ”₯

POST /User/.search
Host: example.com
Accept: application/scim+json
Authorization: Bearer U8YJcYYRMjbGeepD

{
   "schemas": [
     "urn:ietf:params:scim:api:messages:2.0:SearchRequest"],
   "attributes": ["displayName", "userName"],
   "filter": "displayName sw \"smith\"",
   "cursor": "",
   "count": 10
}

The response includes a nextCursor value. The client can use that value in a later call to grab the next page of resources. πŸ¦‹

HTTP/1.1 200 OK
Content-Type: application/scim+json

{
   "totalResults": 100,
   "itemsPerPage": 10,
   "nextCursor": "VZUTiyhEQJ94IR",
   "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
   "Resources": [{
      ...
   }]
}

4. Service Provider Configuration πŸŽ€

The /ServiceProviderConfig resource, defined in Section 4 of [RFC7644], is how clients discover a SCIM service provider's features. A SCIM service provider that implements cursor-based pagination SHOULD include the following extra attribute in the JSON document it returns from the /ServiceProviderConfig endpoint. 🧁

  • pagination: a complex type for pagination configuration options. OPTIONAL.

The following sub-attributes are defined:

  • cursor: a Boolean saying whether cursor-based pagination is supported. REQUIRED.
  • index: a Boolean saying whether index-based pagination is supported. REQUIRED.
  • defaultPaginationMethod: a string naming the pagination type the service provider falls back to when the client hasn't said which method it wants. Possible values are "cursor" and "index". OPTIONAL.
  • defaultPageSize: a positive integer for how many results go on a page by default, when no count is given in the query. OPTIONAL.
  • maxPageSize: a positive integer for the most results allowed on one page, no matter what count a query asks for. The max may be restricted further by other criteria. OPTIONAL.
  • cursorTimeout: a positive integer for the minimum number of seconds a cursor stays valid between page requests. Clients that wait too long between cursor pagination requests may get an invalid cursor error response. If no value is given, that may mean there's no cursor timeout, or that the timeout isn't a fixed duration. OPTIONAL.

Service providers may choose not to advertise their Service Provider Configuration info about the default pagination method, page size, or cursor validity. Clients MUST NOT assume that a missing Service Provider Configuration value means there are no defaults or limits on page sizes or cursor lifetimes, or that there's no default pagination method. πŸ™ˆ Service providers might not publish pagination sub-attribute values for lots of reasons. Some examples:

  • Service providers with multiple resource types may set different values for each one.
  • The default and maximum page size may depend on more than just how many resources get returned, like how big each resource on the page is.

Before using cursor-based pagination, a SCIM client MAY fetch the Service Provider Configuration document from the SCIM service provider and check that cursor-based pagination is supported. πŸ”

Here's an example request:

GET /ServiceProviderConfig
Host: example.com
Accept: application/scim+json

A service provider that supports both cursor-based and index-based pagination would return a document like the one below. (The full ServiceProviderConfig schema, defined in Section 5 of [RFC7643], is left out for brevity.) πŸ’…

HTTP/1.1 200 OK
Content-Type: application/scim+json

{
   "schemas": [
      "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
      ...

   "pagination": {
      "cursor": true,
      "index": true,
      "defaultPaginationMethod": "cursor",
      "defaultPageSize": 100,
      "maxPageSize": 250,
      "cursorTimeout": 3600
   },

   ...
}

5. Security Considerations 🚨

Okay, this part is seriously important, bestie. 😭 This section goes over the security considerations for putting cursor pagination into SCIM. This document falls under the same security and privacy considerations as the ones described in [RFC7644]. Implementers must also think about the following security aspects, to protect against deliberate attacks and accidental misuse that could hurt the system's security posture. It's imperative, no cap! πŸ’€

5.1. Threat Model and Security Environment πŸ•΅οΈβ€β™€οΈ

The threat landscape has two main types of actors:

  1. Unauthenticated and Authenticated Malicious Actors: These are people or entities with bad intentions. Their goals include getting unauthorized access to data, changing it, or deleting it through cursor-enabled queries. They might also try to drain service provider resources on purpose, to cause a denial-of-service state and lower service availability. Villain behavior, honestly. 😀
  2. Authenticated Benign Users: This group is made up of legit users who, out of confusion or not really understanding how things work, accidentally use up a lot of service provider resources. These actions aren't malicious, but they can still cause an unintended denial of service by overwhelming the service provider's capacity. Not them being evil, just them being a little lost! πŸ₯Ί

5.2. Confidentiality πŸ”’

okay so, let's talk about keeping the tea private 🫒 Here's what's needed so confidential data stays locked down:

  • Implementers MUST make sure that pagination through result sets is strictly limited to the data the actor's current identity is authorized to access. This still applies when the actor got a cursor that was made for a result set created by a different actor πŸ™…β€β™€οΈ
  • Authorization checks MUST be applied all the way through as an actor moves through the result set tied to a cursor. No matter what, having a cursor should never give an actor any extra access privileges πŸ’…
  • When possible, service providers SHOULD invalidate all cursors for an actor right after a permissions change ✨ That way, any query that runs after the change with an old cursor gets denied. Another option is for service providers to keep the existing cursors, but they must make sure any metadata tied to the result set, like record counts, is updated so it matches the new permissions accurately πŸŽ€
  • Following Section 2, cursor values are URL-safe strings that are opaque to clients. Service providers should obfuscate cursor values so clients can't read them or forge new ones πŸ•΅οΈβ€β™€οΈ Service providers should also be able to spot forged cursor values easily and immediately return an invalidCursor, as described in Section 2.1 🚫
  • The service provider MUST handle error scenarios without exposing sensitive data πŸ™ˆ For example, if an actor tries to reach a page of results outside their authorized scope, or asks for a page that doesn't exist, the service provider should respond with identical error messages. That way it doesn't reveal anything about the underlying data or why the authorization failed. It's fine, though, for the service provider to log different messages somewhere administrators or other authorized personnel can read them πŸ“

5.3. Availability πŸ›‘οΈ

the big worry with availability is Denial-of-Service (DoS) attacks 😀 If a service provider chooses to keep a lot of data or metadata for each cursor, a bunch of initial queries that create cursors could drain and eventually exhaust the service provider's resources. This could be an attacker with bad intentions, or it could happen by accident because of client testing or bugs 🫠

to reduce the risk, these strategies are recommended for service providers:

  • Clients should authenticate before retrieving large result sets πŸ” Anonymous queries that return lots of results may get an HTTP status code 400 (Bad Request) with the error type "tooMany", as outlined in Section 3.12 of [RFC7644] πŸ™…β€β™€οΈ
  • Implement rate limiting to control the volume and pace of cursor requests ⏱️ This should follow established standards for rate limiting, which you can find in [RFC6585] πŸ“š
  • Let the administrator of the service provider set a ceiling on how many cursors are allowed at any given time, or specify a maxPageSize value 🎚️ Guidance on configuring these values should be documented in the implementation administrator/installation guide πŸ“–
  • Cursor invalidation mechanisms (including the ones triggered by permissions changes) must be designed to be resource-efficient, so they can't be exploited for DoS attacks πŸ’€

5.4. Other Security References πŸ“Ž

using URIs to describe and locate resources comes with its own security considerations, as covered in Section 7 of [RFC3986] 🌐 Implementations should also refer to [BCP195] and [RFC9110] for more security considerations that matter for the underlying TLS and HTTP protocols πŸ”’


6. IANA Considerations πŸ—‚οΈ

IANA has amended the "System for Cross-domain Identity Management (SCIM) Schema URIs" registry group that was set up by [RFC7643], as described below ✨

IANA has updated the "SCIM Schema URIs for Data Resources" registry like this:

  • For the urn:ietf:params:scim:api:messages:2.0:ListResponse, Section 2 of this document has been added to the References column πŸŽ€
  • For the urn:ietf:params:scim:api:messages:2.0:SearchRequest, Section 2 of this document has been added to the References column πŸŽ€

IANA has updated the "SCIM Server-Related Schema URIs" registry like this:

  • For the urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig, Section 4 of this document has been added to the References column 🌸

7. References πŸ“š

7.1. Normative References

[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, <https://www.rfc-editor.org/info/rfc2119>.

[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005, <https://www.rfc-editor.org/info/rfc3986>.

[RFC6585] Nottingham, M. and R. Fielding, "Additional HTTP Status Codes", RFC 6585, DOI 10.17487/RFC6585, April 2012, <https://www.rfc-editor.org/info/rfc6585>.

[RFC7643] Hunt, P., Ed., Grizzle, K., Wahlstroem, E., and C. Mortimore, "System for Cross-domain Identity Management: Core Schema", RFC 7643, DOI 10.17487/RFC7643, September 2015, <https://www.rfc-editor.org/info/rfc7643>.

[RFC7644] Hunt, P., Ed., Grizzle, K., Ansari, M., Wahlstroem, E., and C. Mortimore, "System for Cross-domain Identity Management: Protocol", RFC 7644, DOI 10.17487/RFC7644, September 2015, <https://www.rfc-editor.org/info/rfc7644>.

[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, <https://www.rfc-editor.org/info/rfc8174>.

7.2. Informative References

[BCP195] Best Current Practice 195, <https://www.rfc-editor.org/info/bcp195>. At the time of writing, this BCP comprises the following:

Moriarty, K. and S. Farrell, "Deprecating TLS 1.0 and TLS 1.1", BCP 195, RFC 8996, DOI 10.17487/RFC8996, March 2021, <https://www.rfc-editor.org/info/rfc8996>.

Sheffer, Y., Saint-Andre, P., and T. Fossati, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 9325, DOI 10.17487/RFC9325, November 2022, <https://www.rfc-editor.org/info/rfc9325>.

[RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, <https://www.rfc-editor.org/info/rfc9110>.


Acknowledgments 🫢

The authors would also like to thank the following people, who gave super valuable feedback while reviewing the document: Aaron Parecki, David Brossard, Dean H. Saxe, and Pamela Dingle πŸ’œ

Contributors 🧚

The authors would like to acknowledge Paul Lanzi (IDenovate) for leading the writing of the Security Considerations section πŸ‘‘

Authors' Addresses πŸ’Œ

Matt Peterson (editor)
Entrust
Email: matt.peterson@entrust.com

Danny Zollner
Independent
Email: danny@zollnerd.com

Anjali Sehgal
Amazon Web Services
Email: anjalisg@amazon.com
πŸ“¦ where this page comes from