Enrollment API
Confidential. Do not share without explicit permission.
The RSL Platform Enrollment API enables RSL Collective Partners, including website builders, advertising networks, CDNs, and Collective Rights Organizations, to enroll their publisher customers in the RSL Collective so they can receive licensing royalties for AI use of their licensed content under the collective terms negotiated by the RSL Collective.
Introduction: RSL Collective licensing
The nonprofit RSL Collective operates a collective licensing program that enables AI labs and other Licensees to obtain rights to use the content assets of millions of online publishers and content creators in AI applications.
Instead of negotiating and administering separate agreements with individual publishers, a Licensee enters into a Collective License agreement with the RSL Collective. That agreement defines the covered uses of publisher content in AI applications, the reporting obligations that apply to those uses, and the payment terms under which those uses are authorized.
Publishers join the RSL Collective through an Enrollment Partner and reference the RSL Collective License in an RSL license declaration that governs the content assets they make available for collective licensing. Partners declare the set of publishers and licensable content scopes they are authorized to license as a Repertoire through the Enrollment API, which the RSL Collective then aggregates into the effective Licensee Repertoire made available to Licensees through the License API.
When Licensees use content assets from the Licensee Repertoire in AI applications, they report their usage and submit licensing payments to the RSL Collective. The RSL Collective then distributes the corresponding usage data and payment proceeds to participating Partners, which reconcile and remit payments to the publishers they manage, charging a service fee for operating these workflows.
API overview
Design principles
Scalability. Supports repertoires containing up to 100 million content scope declarations.
Bulk synchronization. Uses complete repertoire declarations and batch report files instead of per-object mutations, reducing integration and operational complexity at scale.
Isolated workflows. Separates repertoire management and payment reporting from payment disbursement, so financial transfers can operate in a distinct, fully audited workflow.
Auditability. Captures repertoire snapshots, report versions, and request identifiers in a form suitable for reconciliation, support, and dispute resolution.
Controlled lifecycle management. Separates validation from activation and uses explicit schema versioning and sandbox-production isolation to support safe testing and rollout.
Operations and resilience. Supports explicit heartbeats, asynchronous job states, resumable file downloads, and idempotent requests.
Common API conventions
The Common API Conventions document a set of shared API conventions used across the RSL Platform APIs, including:
- Environments
- Base URLs
- API versioning
- Authentication
- Request conventions
- Response conventions
- Timestamps and date formats
- Request identifiers
- Idempotent requests
- Pagination
- Rate limiting
- Errors
- Retries
Bulk file management
See the Files API for the shared objects used by the Enrollment API for uploading and downloading files:
Uploadobject: creates a job object used to upload and validate a fileJobobject: tracks a file upload and validation workflowFileobject: represents a managed file and its metadataResultobject: describes the outcome of uploading and validating a file
Base URL
- Production:
https://api.rslcollective.org/enrollment/v1 - Sandbox:
https://sandbox.api.rslcollective.org/enrollment/v1
Operational heartbeats
The Enrollment API defines heartbeat requirements to help partners confirm that their production integration with the Enrollment API is still operational. Active partners must successfully call at least one Enrollment API endpoint every 24 hours using valid production credentials, and any successful Enrollment API request with valid credentials satisfies the heartbeat requirement.
If the heartbeat expires, the RSL Platform sends an operational alert to the operations email address configured for the partner in the partner developer dashboard. The expired condition remains in effect until a subsequent Enrollment API request completes successfully.
Operational limits
The following operational limits apply to the Enrollment API:
| Limit | Value |
|---|---|
| Maximum rows per repertoire file | 100 million rows |
| Maximum repertoire file size | 5 GB |
| Minimum retention for report files | 12 months |
| Minimum retention for result files | 1 hour |
| Minimum temporary upload URL validity | 1 hour |
| Minimum temporary download URL validity | 1 hour |
If a request exceeds an applicable operational limit, the API returns the error limit_exceeded.
Core concepts
Core terms
| Term | Definition |
|---|---|
| RSL Collective | The collective rights organization that administers the collective licensing program, receives usage reports from Licensees, and distributes payments to publishers. |
| RSL Collective License | A collectively negotiated license defined by the RSL Collective that specifies commercial terms used for settlement and payment calculation. |
| Enrollment Partner | A Publisher-facing organization or service that integrates with the Enrollment API to enroll Publishers, register licensed Content Scopes, and receive reporting data and payment proceeds on their behalf. |
| Publisher | A rights holder whose Content Scopes are licensed under RSL and who participates in collective licensing through an Enrollment Partner. |
| Content Scope | A set of content assets, such as a website, a site section, or a dataset, governed by a single RSL license declaration that a Publisher makes available for licensing through the RSL Collective. |
| Enrollment Attestation | A Partner-maintained authorization record confirming that the Partner has permission to enroll the Publisher in the RSL Collective program. |
| Rights Attestation | A Partner-maintained authorization record confirming that the Publisher has rights to license a content scope. |
| Licensee | An entity that has entered into a collective licensing agreement with the RSL Collective and is authorized to use licensed Content Scopes in AI applications, subject to usage reporting and payment obligations. |
| Publisher Repertoire | The complete set of Content Scopes that an Enrollment Partner is authorized to license on behalf of an individual Publisher through the RSL Collective licensing program. |
| Repertoire | The complete set of Publisher Repertoires that an Enrollment Partner is authorized to license through the RSL Collective licensing program on behalf of its Publishers. |
| Repertoire File | A file uploaded through the Enrollment API that declares the complete Repertoire for collective licensing through the RSL Collective. |
| Report File | A file made available through the Enrollment API that contains Repertoire usage and payment data for a specified reporting interval. |
| Reporting Period | The time interval covered by a report file. |
| IndexNow File | A file uploaded through the Enrollment API that declares URL-level changes within previously declared Content Scopes. |
Content scopes
A Content Scope represents a set of content assets, such as a website, a site section, or a dataset, governed by a single RSL license declaration that a publisher makes available for licensing through the RSL Collective. Each content scope is identified by a scope_url that represents the location of licensed content governed by the RSL Collective License.
Examples of content scopes governed by an RSL license include:
- A website root (
https://example.com/) containing a valid RSL license declaration for the RSL Collective License, published via itsrobots.txtfile - An RSS feed (
https://example.com/feed.xml) - A web page or content file containing an embedded RSL license declaration for the RSL Collective License
License declaration
To be eligible for inclusion in the repertoire, each enrolled content scope must be governed by an RSL license declaration that references the RSL Collective License in the <standard> element of the <license> block:
...
<license>
<permits type="usage">ai-all</permits>
<payment>
<standard>https://rslcollective.org/license</standard>
</payment>
</license>
...Example RSL license declaration
For example, to make a website eligible for collective licensing through the RSL Collective, a publisher could define the following RSL license declaration for url="/" and link to that declaration from robots.txt:
<rsl xmlns="https://rslstandard.org/rsl">
<content url="/">
<license>
<permits type="usage">ai-all</permits>
<payment>
<standard>https://rslcollective.org/license</standard>
</payment>
</license>
</content>
</rsl>See also RSL Standard, Section 3.8: Standard shared licensing frameworks.
Rights attestation
Each enrolled content scope requires an auditable electronic attestation, such as click-through confirmation or electronically signed document, that the publisher owns the relevant licensing rights, or is otherwise authorized to license the enrolled content scope. The repertoire file communicates this attestation using the following fields:
rights_attestation_date: UTC Unix timestamp when the rights attestation was executed or recorded by the partnerrights_attestation_id: stable partner-defined identifier for the rights attestation record
Enrollment attestation
Each enrolled publisher requires an auditable electronic attestation, such as click-through confirmation or electronically signed document, confirming that the publisher has authorized the partner to enroll the publisher in the RSL Collective and administer licensing, reporting, and payment workflows on its behalf. The repertoire file communicates this attestation using the following fields:
enrollment_attestation_date: UTC Unix timestamp when the enrollment attestation was executed or recorded by the partnerenrollment_attestation_id: stable partner-defined identifier for the enrollment attestation record
Enrollment flow
License declaration: The publisher defines an RSL license declaration for each enrolled content scope to make the content scope available under the RSL Collective License, as described below.
Rights attestation: The publisher provides electronic attestation to a partner certifying that it owns the relevant licensing rights or is authorized to license the enrolled content scope.
Publisher enrollment: Partners obtain electronic consent from the publisher to enroll them in the RSL Collective licensing program and to manage licensing, reporting, and payment workflows on their behalf.
Partner repertoire: Partners upload their publishers' licensed content scopes, declaring their repertoire of licensed content scopes available for collective licensing under the RSL Collective License.
Licensee repertoire: The RSL Collective aggregates and shares the repertoires of its partners with licensees to enable them to determine which licensed content scopes they use in their AI applications.
Licensee reporting: Licensees report licensed content scope usage data and submit licensing payments to the RSL Collective.
Partner reporting: The RSL Collective reconciles usage data against the uploaded repertoires and delivers report files and licensing payments to partners.
Publisher reporting: Partners deliver usage data and remit licensing payments to the publishers they manage.
The Licensee object
The Licensee object represents an entity that has entered into a collective licensing agreement with the RSL Collective and is authorized to use the repertoire under the terms of that agreement.
Retrieve a licensee
Returns the licensee object for a valid identifier.
Endpoint
GET /licensees/{id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The unique identifier for the licensee. |
Response fields
| Name | Type | Description |
|---|---|---|
id | string | The unique identifier for the licensee. |
name | string | Display name of the licensee. |
url | string | Official website URL of the licensee. Must be a valid URL. |
status | enum | The current status of the licensee: active | inactive. |
Example response
{
"id": "lic_ai_lab_001",
"name": "Example AI Lab 1 Name",
"url": "https://example.com",
"status": "active"
}List all licensees
Returns a list of licensee objects. Partners can use this list to show publishers which licensees are authorized under the RSL Collective License and let publishers specify which licensees to exclude from accessing their content scopes.
Endpoint
GET /licenseesQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of licensee objects to return. |
starting_after | string | No | id of the last licensee from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
licensees | array of objects | List of licensees. |
has_more | boolean | Whether additional licensee objects are available. |
Each object in licensees contains:
| Name | Type | Description |
|---|---|---|
id | string | The unique identifier for the licensee. |
name | string | Display name of the licensee. |
url | string | Official website URL of the licensee. Must be a valid URL. |
status | enum | The current status of the licensee: active | inactive. |
Example response
{
"licensees": [
{
"id": "lic_ai_lab_001",
"name": "Example AI Lab 1 Name",
"url": "https://example.com",
"status": "active"
},
{
"id": "lic_ai_lab_002",
"name": "Example AI Lab 2 Name",
"url": "https://example.org",
"status": "active"
}
],
"has_more": false
}The Repertoire object
A Repertoire object represents the complete set of content scopes made available by a partner's publishers under the terms of the RSL Collective license agreement.
The repertoire object uses a full repertoire model rather than incremental updates. This improves reconciliation, avoids synchronization errors from missed or out-of-order updates, and simplifies large-scale integrations.
Create a repertoire
To submit a repertoire file, a partner creates a repertoire object. The request returns a job object that provides a temporary presigned job.upload_url for uploading the corresponding repertoire file resource.
After the repertoire file is uploaded and validated successfully, it becomes the active repertoire and replaces the previous active repertoire. Rows rejected with conflicting_scope_url are not included in the active repertoire.
Endpoint
POST /repertoiresBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
upload | object | Yes | Upload object for the corresponding repertoire file. |
Response fields
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding repertoire file. |
Example request
{
"upload": {
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"validate_only": false
}
}Example response
{
"job": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "ready",
"created": 1760000000,
"updated": null,
"completed": null,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url_expires": 1760086400,
"result_url": null,
"result_url_expires": null,
"result_sha256": null
}
}Conflicting content scope enrollments
A content scope can be enrolled through only one partner at a time, and within a single repertoire file, each scope_url must appear only once. If the same scope_url appears more than once in the uploaded file, the upload fails.
If the same canonical scope_url is submitted through more than one partner, the Enrollment API compares rights_attestation_date and applies the most recent submission. If two submissions have the same rights_attestation_date, the submission already in the active repertoire remains in effect. Any other conflicting submission is rejected and not applied.
Rejecting one or more conflicting rows does not by itself cause the upload to fail.
Retrieve a repertoire
Returns the repertoire job object for the specified job_id. Partners can use this endpoint to monitor the status of uploading and validating a repertoire file to create a repertoire object.
When the repertoire job object reaches a terminal state, the job.result_url field provides a temporary presigned URL for downloading the corresponding repertoire result object.
Endpoint
GET /repertoires/{job_id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | Identifier of the repertoire upload job to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding repertoire file. |
Example response
{
"job": {
"job_id": "job_M4xq8Rk2Vn7pT5cY9wL1bD6h",
"status": "failed",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": null,
"upload_url_expires": null,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9"
}
}Repertoire result object
Repertoire uploads return a standard result object. The following additional row-level error_code values may appear for repertoire uploads:
| Value | Description | Action |
|---|---|---|
missing_attestation | One or more required attestation fields are missing. | Upload rejected |
duplicate_scope_url | Duplicate scope_url in the uploaded CSV. | Upload rejected |
unknown_licensee_id | exclusions contains an unknown licensee ID. | Upload rejected |
conflicting_scope_url | scope_url conflicts with an active enrollment through another partner that has a more recent rights_attestation_date. | Row skipped |
Only conflicting_scope_url is skippable. The other repertoire-specific row-level errors cause the job to fail.
Success result example
{
"result": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"status": "succeeded",
"rows_processed": 48280,
"rows_skipped": 1,
"errors": [
{
"row_number": 14,
"column": "scope_url",
"error_code": "conflicting_scope_url",
"error_description": "`scope_url` conflicts with an active enrollment through another partner that has a more recent `rights_attestation_date`."
}
]
}
}Failed result example
{
"result": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"status": "failed",
"error_code": "validation_failed",
"rows_processed": 13,
"rows_skipped": 0,
"errors": [
{
"row_number": 14,
"column": "scope_url",
"error_code": "duplicate_scope_url",
"error_description": "Duplicate scope_url in uploaded CSV."
}
]
}
}List all repertoires
Returns a list of available repertoire objects in reverse chronological order. The most recent repertoire object with job.validate_only = false and job.status = "succeeded" is the repertoire of record for operational use.
To retrieve a specific repertoire object directly, use GET /repertoires/{job_id}.
Endpoint
GET /repertoiresQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of repertoire objects to return. |
starting_after | string | No | job_id of the last repertoire from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
repertoires | array of objects | List of repertoire objects. |
has_more | boolean | Whether additional repertoire objects are available. |
Each repertoire object contains:
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding repertoire file. |
Example response
{
"repertoires": [
{
"job": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "processing",
"created": 1760002000,
"updated": 1760002060,
"completed": null,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": null,
"upload_url_expires": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null
}
},
{
"job": {
"job_id": "job_M4xq8Rk2Vn7pT5cY9wL1bD6h",
"status": "succeeded",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"validate_only": false,
"file_id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"upload_url": null,
"upload_url_expires": null,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9"
}
}
],
"has_more": false
}Repertoire file
A Repertoire file represents the complete repertoire of a partner. The same publisher can appear in multiple rows, and each row defines one licensed content scope.
Repertoire CSV files must be UTF-8 encoded and include a header row. The header row must contain the required column names defined below, and columns may appear in any order.
| Column | Type | Required | Limit | Description |
|---|---|---|---|---|
publisher_id | string | Yes | 40 chars | Stable partner-defined identifier for the publisher. |
publisher_url | string | Yes | 512 chars | Canonical website URL representing the publisher. This field is informational. |
enrollment_attestation_date | timestamp | Yes | 10 digits | UTC Unix timestamp when the Enrollment Attestation was executed or recorded by the partner. |
enrollment_attestation_id | string | Yes | 40 chars | Stable partner-defined identifier for the Enrollment Attestation record. |
rights_attestation_date | timestamp | Yes | 10 digits | UTC Unix timestamp when the Rights Attestation authorizing licensing of the enrolled content scope was executed or recorded by the partner. |
rights_attestation_id | string | Yes | 40 chars | Stable partner-defined identifier for the Rights Attestation record. |
scope_url | string | Yes | 512 chars | URL representing the licensed content scope governed by an RSL license declaration published under the RSL Collective License. |
exclusions | string | No | 1024 chars total | Optional semicolon-delimited (;) list of licensee IDs excluded from licensing this content scope. |
Partner-defined identifiers in publisher_id, enrollment_attestation_id, and rights_attestation_id must not contain carriage return, line feed, or NULL characters.
Each licensee ID included in exclusions must be no more than 40 characters. exclusions must use semicolon (;) as the delimiter and must not contain empty items.
Example repertoire file
publisher_id,publisher_url,enrollment_attestation_date,enrollment_attestation_id,rights_attestation_date,rights_attestation_id,scope_url,exclusions
pub_001,https://example.com,1760000000,pub_att_1001,1760000100,rights_att_2001,https://example.com/,
pub_001,https://example.com,1760000000,pub_att_1001,1760000200,rights_att_2002,https://example.com/feed.xml,lic_ai_lab_002
pub_002,https://news.example.org,1760000300,pub_att_3001,1760000400,rights_att_4001,https://news.example.org/,Content scope URL canonicalization
For overlap validation and conflict detection, scope_url values are canonicalized using the WHATWG URL Standard. The canonicalized scope_url preserves the scheme, host, path, and query. A single leftmost www. label is removed from the host, and fragment identifiers are ignored.
For example, https://www.example.com/ and https://example.com/ are treated as the same scope_url. Other host labels, such as www2.example.com and www.subdomain.example.com, remain distinct.
Partners should normalize scope_url values using the same rules before submission.
The Report object
A Report object represents the licensee repertoire usage data and payment proceeds for a reporting period, and provides the metadata needed to download a report file.
Retrieve a report
Returns the report object for the specified file_id. The report object includes a temporary presigned file.download_url for downloading the corresponding report file.
Endpoint
GET /reports/{file_id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
file_id | string | Yes | Identifier of the report file to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
period_start | string (YYYY-MM-DD) | Inclusive UTC start date of the reporting period. |
period_end | string (YYYY-MM-DD) | Inclusive UTC end date of the reporting period. |
file | file object | File object for the corresponding report file. |
Example response
{
"period_start": "2025-04-04",
"period_end": "2025-04-04",
"file": {
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1759160000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 1048576,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"download_url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d.csv.gz",
"download_url_expires": 1759160400
}
}List all reports
Returns a list of available report objects in reverse chronological order. For a given reporting period, the report file with the most recent file.created timestamp is the current report of record. When a new report is issued, it supersedes the previous report of record for that reporting period.
Each Report object includes a File object for the corresponding report file. To retrieve a specific report object directly, use GET /reports/{file_id}.
Endpoint
GET /reportsQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of report objects to return. |
starting_after | string | No | file_id of the last report from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
reports | array of objects | List of report objects. |
has_more | boolean | Whether additional report objects are available. |
Each Report object contains:
| Name | Type | Description |
|---|---|---|
period_start | string (YYYY-MM-DD) | Inclusive UTC start date of the reporting period. |
period_end | string (YYYY-MM-DD) | Inclusive UTC end date of the reporting period. |
file | file object | File object for the corresponding report file. |
Example response
{
"reports": [
{
"period_start": "2025-04-05",
"period_end": "2025-04-05",
"file": {
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1759160000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 1048576,
"sha256": "8f3c1a7d4b92e6f0c5a8d1e3b7f4092c6e1a5d8f3b0c7e2d9a4f1c6b8e3d7a5",
"download_url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d.csv.gz",
"download_url_expires": 1759160400
}
},
{
"period_start": "2025-04-04",
"period_end": "2025-04-04",
"file": {
"file_id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"created": 1759070000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 3128576,
"sha256": "b83c1f4e92d7a6c5e1f0438b7c2d9e6a4f1b8c7d5e2a9f3c6b1d4e7f8a2c5d9",
"download_url": "https://files.rslcollective.org/file_P7mT3vX9qR4nK8yC2wL6dH1z.csv.gz",
"download_url_expires": 1759074000
}
}
],
"has_more": false
}Report file
This report file schema is provisional and may change before general availability. Report CSV files must be UTF-8 encoded and include a header row. The header row must contain the required column names defined below, and columns may appear in any order.
| Column | Type | Required | Limit | Description |
|---|---|---|---|---|
licensee_id | string | Yes | 40 chars | Identifier of the licensee that reported the usage. |
report_date | string (YYYY-MM-DD) | Yes | 10 chars | UTC date covered by the reported usage and payment data. |
publisher_id | string | Yes | 40 chars | Stable partner-defined identifier for the publisher. |
scope_url | string | Yes | 512 chars | URL representing the licensed content scope. |
usage_count | integer | Yes | 64-bit signed integer | Number of reported usage events for the content scope on the specified report date. Must be zero or greater. |
payment_amount | integer | Yes | 64-bit signed integer | Payment amount allocated to the content scope for the specified report date, expressed in the smallest unit of payment_currency (for example, cents for USD and whole yen for JPY). |
payment_currency | string | Yes | 3 chars | Three-letter ISO 4217 currency code for the payment amount (for example USD). |
Example report file
In this example, payment_amount is expressed in the smallest unit of payment_currency, so 4250 means USD 42.50.
licensee_id,report_date,publisher_id,scope_url,usage_count,payment_amount,payment_currency
lic_ai_lab_001,2026-03-01,pub_001,https://example.com/,125,4250,USD
lic_ai_lab_001,2026-03-01,pub_001,https://example.com/feed.xml,18,675,USD
lic_ai_lab_002,2026-03-01,pub_002,https://news.example.org/,203,7120,USDThe IndexNow object
An indexnow object lets partners notify licensees of URL-level changes to the enrollment repertoire within defined service-level requirements, rather than waiting for licensees to detect those changes through regular re-fetching of licensed content scopes. Each change notification tells licensees that a specific URL within a previously declared content scope has been added, updated, deleted, or removed, and follows the conventions of the IndexNow protocol.
Indexnow objects provide incremental, ordered updates to URLs within content scopes already present in the partner’s current effective repertoire. They do not declare new content scopes or replace the repertoire.
Create an IndexNow
To submit an indexnow file, a partner creates an indexnow object. The request returns a job object that provides a temporary presigned job.upload_url for uploading the corresponding indexnow file resource.
After the indexnow file is uploaded and validated successfully, its change notifications become active and must be applied by licensees in the order received.
Endpoint
POST /indexnowBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
upload | object | Yes | Upload object for the corresponding indexnow file. |
Response fields
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding indexnow file. |
Example request
{
"upload": {
"schema_version": "1.0",
"format": "csv",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7"
}
}Example response
{
"job": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "ready",
"created": 1760000000,
"updated": null,
"completed": null,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url_expires": 1760086400,
"result_url": null,
"result_url_expires": null,
"result_sha256": null
}
}Retrieve an IndexNow
Returns the indexnow job object for the specified job_id. Partners can use this endpoint to monitor the status of uploading and validating an indexnow file to create an indexnow object.
When the indexnow job object reaches a terminal state, the job.result_url field provides a temporary presigned URL for downloading the corresponding job result object.
Endpoint
GET /indexnow/{job_id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | Identifier of the IndexNow upload job to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding indexnow file. |
Example response
{
"job": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "failed",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": null,
"upload_url_expires": null,
"result_url": "https://files.rslcollective.org/result_9aK2mQ7xT4vN8pR3cW6yZ1bD.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9"
}
}IndexNow result object
Indexnow uploads return a standard result object. The following additional row-level error_code values may appear for indexnow uploads:
| Value | Description |
|---|---|
unknown_scope_url | scope_url does not identify a content scope in the current effective repertoire. |
url_outside_scope | url does not fall within the declared scope_url. |
These indexnow-specific row-level errors cause the job to fail.
Failed result example
{
"result": {
"job_id": "job_01JV1N0A9Q6M2X7P4R8T3K5N1C",
"file_id": "file_01JV1N0CC8Y4P6N2T7R3M5K9XD",
"status": "failed",
"error_code": "validation_failed",
"rows_processed": 13,
"rows_skipped": 0,
"errors": [
{
"row_number": 14,
"column": "url",
"error_code": "url_outside_scope",
"error_description": "url does not fall within the declared scope_url."
}
]
}
}List all IndexNows
Returns a list of available indexnow objects in reverse chronological order.
To retrieve a specific indexnow object directly, use GET /indexnow/{job_id}.
Endpoint
GET /indexnowQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of indexnow objects to return. |
starting_after | string | No | job_id of the last indexnow object from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
indexnow | array of objects | List of indexnow objects. |
has_more | boolean | Whether additional indexnow objects are available. |
Each indexnow object contains:
| Name | Type | Description |
|---|---|---|
job | object | Job object for the corresponding indexnow file. |
Example response
{
"indexnow": [
{
"job": {
"job_id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "processing",
"created": 1760002000,
"updated": 1760002060,
"completed": null,
"validate_only": false,
"file_id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"upload_url": null,
"upload_url_expires": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null
}
},
{
"job": {
"job_id": "job_M4xq8Rk2Vn7pT5cY9wL1bD6h",
"status": "succeeded",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"validate_only": false,
"file_id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"upload_url": null,
"upload_url_expires": null,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9"
}
}
],
"has_more": false
}IndexNow file
An indexnow file represents a set of URL-level changes within previously declared content scopes. Each row defines the current change for a single URL.
Indexnow CSV files must be UTF-8 encoded and include a header row. The header row must contain the required column names defined below, and columns may appear in any order.
| Column | Type | Required | Limit | Description |
|---|---|---|---|---|
publisher_id | string | Yes | 40 chars | Stable partner-defined identifier for the publisher. |
publisher_url | string | Yes | 512 chars | Canonical website URL representing the publisher. This field is informational. |
scope_url | string | Yes | 512 chars | Canonical URL of the declared content scope containing the URL. |
url | string | Yes | 512 chars | Canonical URL whose change is being reported. |
change | enum | Yes | 16 chars | added, updated, deleted, or removed. |
Change values
| Change | Description |
|---|---|
added | The URL is newly available within an existing declared content scope. |
updated | The URL remains available within the declared content scope, but its content has changed materially. |
deleted | The URL is no longer available due to an ordinary deletion. |
removed | The URL is no longer available due to a rights, legal, safety, or compliance-driven removal. |
Example IndexNow file
publisher_id,publisher_url,scope_url,url,change
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-1,removed
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-2,added
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-3,updated
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-4,deletedAppendix: Machine-readable schemas and integration artifacts
This appendix defines machine-readable artifacts for the RSL Platform Enrollment API, including:
- an OpenAPI definition for JSON-based API endpoints
- machine-readable schemas for the repertoire file, indexnow file, and report file CSV formats
- a machine-readable error catalog
Notes:
- The OpenAPI definition describes the machine-readable definition for JSON-based API endpoints and JSON response objects. It does not model CSV row schemas
- The YAML CSV schema for the repertoire file describes the machine-readable definition for that format.
- The YAML CSV schema for the indexnow file describes the machine-readable definition for that format.
- The YAML CSV schema for the report file is provisional and provided for planning purposes until licensee reporting obligations are finalized.
OpenAPI definition
openapi: 3.1.0
info:
title: RSL Platform Enrollment API
version: 1.0.0-beta
description: >
Machine-readable definition of the RSL Platform Enrollment API.
servers:
- url: https://api.rslcollective.org/enrollment/v1
description: Production
- url: https://sandbox.api.rslcollective.org/enrollment/v1
description: Sandbox
security:
- bearerAuth: []
paths:
/licensees:
get:
summary: List all licensees
operationId: listLicensees
parameters:
- name: limit
in: query
required: false
schema:
type: integer
default: 100
minimum: 1
maximum: 1000
- name: starting_after
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
type: object
required: [licensees, has_more]
properties:
licensees:
type: array
items:
$ref: "#/components/schemas/Licensee"
has_more:
type: boolean
/licensees/{id}:
get:
summary: Retrieve a licensee
operationId: getLicensee
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Licensee"
"404":
$ref: "#/components/responses/Error"
/repertoires:
post:
summary: Create a repertoire
operationId: createRepertoire
parameters:
- name: Idempotency-Key
in: header
required: false
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RepertoireRequest"
responses:
"200":
description: Repertoire job created
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/RepertoireCreated"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
"429":
$ref: "#/components/responses/Error"
get:
summary: List all repertoires
operationId: listRepertoires
parameters:
- name: limit
in: query
required: false
schema:
type: integer
default: 100
minimum: 1
maximum: 1000
- name: starting_after
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
type: object
required: [repertoires, has_more]
properties:
repertoires:
type: array
items:
$ref: "#/components/schemas/Repertoire"
has_more:
type: boolean
/repertoires/{job_id}:
get:
summary: Retrieve a repertoire
operationId: getRepertoire
parameters:
- name: job_id
in: path
required: true
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Repertoire"
"404":
$ref: "#/components/responses/Error"
/reports:
get:
summary: List all reports
operationId: listReports
parameters:
- name: limit
in: query
required: false
schema:
type: integer
default: 100
minimum: 1
maximum: 1000
- name: starting_after
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
type: object
required: [reports, has_more]
properties:
reports:
type: array
items:
$ref: "#/components/schemas/Report"
has_more:
type: boolean
/reports/{file_id}:
get:
summary: Retrieve a report
operationId: getReport
parameters:
- name: file_id
in: path
required: true
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Report"
"404":
$ref: "#/components/responses/Error"
/indexnow:
post:
summary: Create an IndexNow
operationId: createIndexNow
parameters:
- name: Idempotency-Key
in: header
required: false
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/IndexNowRequest"
responses:
"200":
description: Indexnow job created
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/IndexNow"
"400":
$ref: "#/components/responses/Error"
"401":
$ref: "#/components/responses/Error"
"403":
$ref: "#/components/responses/Error"
"409":
$ref: "#/components/responses/Error"
"429":
$ref: "#/components/responses/Error"
get:
summary: List all IndexNows
operationId: listIndexNow
parameters:
- name: limit
in: query
required: false
schema:
type: integer
default: 100
minimum: 1
maximum: 1000
- name: starting_after
in: query
required: false
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
type: object
required: [indexnow, has_more]
properties:
indexnow:
type: array
items:
$ref: "#/components/schemas/IndexNow"
has_more:
type: boolean
/indexnow/{job_id}:
get:
summary: Retrieve an IndexNow
operationId: getIndexNow
parameters:
- name: job_id
in: path
required: true
schema:
type: string
responses:
"200":
description: Successful response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/IndexNow"
"404":
$ref: "#/components/responses/Error"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: API key
headers:
RequestId:
description: Unique request identifier
schema:
type: string
responses:
Error:
description: Error response
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
schemas:
Error:
type: object
required: [error, error_description]
properties:
error:
type: string
error_description:
type: string
Licensee:
type: object
required: [id, name, url, status]
properties:
id:
type: string
name:
type: string
url:
type: string
format: uri
status:
type: string
enum: [active, inactive]
RepertoireRequest:
type: object
required: [upload]
properties:
upload:
$ref: "./files.openapi.yaml#/components/schemas/Upload"
RepertoireCreated:
type: object
required: [job]
properties:
job:
$ref: "./files.openapi.yaml#/components/schemas/Job"
Repertoire:
type: object
required: [job]
properties:
job:
$ref: "./files.openapi.yaml#/components/schemas/Job"
Report:
type: object
required: [period_start, period_end, file]
properties:
period_start:
type: string
format: date
period_end:
type: string
format: date
file:
$ref: "./files.openapi.yaml#/components/schemas/File"
IndexNowRequest:
type: object
required: [upload]
properties:
upload:
$ref: "./files.openapi.yaml#/components/schemas/Upload"
IndexNow:
type: object
required: [job]
properties:
job:
$ref: "./files.openapi.yaml#/components/schemas/Job"Repertoire file CSV schema
schema_type: rsl.enrollment_api.csv_schema
schema_version: "1.0"
file_type: repertoire_file
encoding: UTF-8
header_required: true
unknown_columns: forbidden
duplicate_columns: forbidden
columns_may_appear_in_any_order: true
columns:
- name: publisher_id
type: string
required: true
max_length: 40
description: Stable partner-defined identifier for the publisher.
- name: publisher_url
type: string
format: uri
required: true
max_length: 512
description: Canonical website URL representing the publisher. This field is informational.
- name: enrollment_attestation_date
type: integer
required: true
minimum: 0
maximum: 9999999999
description: UTC Unix timestamp when the Enrollment Attestation was executed or recorded by the partner.
- name: enrollment_attestation_id
type: string
required: true
max_length: 40
description: Stable partner-defined identifier for the Enrollment Attestation record.
- name: rights_attestation_date
type: integer
required: true
minimum: 0
maximum: 9999999999
description: UTC Unix timestamp when the Rights Attestation authorizing licensing of the enrolled content scope was executed or recorded by the partner.
- name: rights_attestation_id
type: string
required: true
max_length: 40
description: Stable partner-defined identifier for the Rights Attestation record.
- name: scope_url
type: string
format: uri
required: true
max_length: 512
description: URL representing the licensed content scope governed by an RSL license declaration published under the RSL Collective License.
- name: exclusions
type: string
required: false
max_length: 1024
list:
delimiter: ";"
item_type: string
item_max_length: 40
allow_empty_items: false
description: Optional semicolon-delimited list of licensee IDs excluded from licensing this content scope.
constraints:
uniqueness:
- [scope_url]
forbidden_characters:
publisher_id: ["\r", "\n", "\u0000"]
enrollment_attestation_id: ["\r", "\n", "\u0000"]
rights_attestation_id: ["\r", "\n", "\u0000"]
validation_errors:
- missing_attestation
- duplicate_scope_url
- unknown_licensee_id
- conflicting_scope_urlReport file CSV schema
schema_type: rsl.enrollment_api.csv_schema
schema_version: "1.0"
file_type: report_file
encoding: UTF-8
header_required: true
unknown_columns: forbidden
duplicate_columns: forbidden
columns_may_appear_in_any_order: true
status: placeholder
status_note: >
The report file schema is provisional and may change before general availability.
columns:
- name: licensee_id
type: string
required: true
max_length: 40
description: Identifier of the licensee that reported the usage.
- name: report_date
type: string
format: date
required: true
description: UTC date covered by the reported usage and payment data.
- name: publisher_id
type: string
required: true
max_length: 40
description: Stable partner-defined identifier for the publisher.
- name: scope_url
type: string
format: uri
required: true
max_length: 512
description: URL representing the licensed content scope.
- name: usage_count
type: integer
required: true
minimum: 0
description: Number of reported usage events for the content scope on the specified report date.
- name: payment_amount
type: integer
required: true
minimum: 0
description: Payment amount allocated to the content scope for the specified report date, expressed in the smallest unit of payment_currency.
- name: payment_currency
type: string
required: true
pattern: "^[A-Z]{3}$"
max_length: 3
description: Three-letter ISO 4217 currency code for the payment amount.
constraints: {}IndexNow file CSV schema
schema_type: rsl.enrollment_api.csv_schema
schema_version: "1.0"
file_type: indexnow_file
encoding: UTF-8
header_required: true
unknown_columns: forbidden
duplicate_columns: forbidden
columns_may_appear_in_any_order: true
columns:
- name: publisher_id
type: string
required: true
max_length: 40
description: Stable partner-defined identifier for the publisher.
- name: publisher_url
type: string
format: uri
required: true
max_length: 512
description: Canonical website URL representing the publisher. This field is informational.
- name: scope_url
type: string
format: uri
required: true
max_length: 512
description: Canonical URL of the declared content scope containing the URL.
- name: url
type: string
format: uri
required: true
max_length: 512
description: Canonical URL whose change is being reported.
- name: change
type: string
required: true
max_length: 16
enum: [added, updated, deleted, removed]
description: Type of URL-level change.
constraints:
uniqueness:
- [scope_url, url]
validation_errors:
- unknown_scope_url
- url_outside_scopeError catalog
This catalog defines the Enrollment API-specific machine-readable error codes used by this API in addition to the shared file-level and row-level error codes defined by the Files API. API error responses use the generic Error schema defined in the OpenAPI definition.
catalog_type: rsl.enrollment_api.error_catalog
catalog_version: 1.0.0-beta
repertoire_validation_errors:
- code: missing_attestation
category: repertoire_row
retryable: false
description: One or more required attestation fields are missing.
- code: duplicate_scope_url
category: repertoire_row
retryable: false
description: Duplicate `scope_url` in the uploaded CSV.
- code: unknown_licensee_id
category: repertoire_row
retryable: false
description: An `exclusions` field contains an unknown licensee ID.
- code: conflicting_scope_url
category: repertoire_row
retryable: false
description: "`scope_url` conflicts with an active enrollment through another partner that has a more recent `rights_attestation_date`."
indexnow_validation_errors:
- code: unknown_scope_url
category: indexnow_row
retryable: false
description: `scope_url` does not identify a content scope in the current effective repertoire.
- code: url_outside_scope
category: indexnow_row
retryable: false
description: `url` does not fall within the declared `scope_url`.Changelog
- 2026-03-31: Updated the Enrollment API to use the current Files API shared objects and workflow patterns.
- 2026-03-06: Initial Enrollment API.
