Enrollment API
Confidential. Do not share without explicit permission.
Introduction
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.
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.
Bulk file management
See the Files API for the shared objects used by the Enrollment API for uploading and downloading files:
Uploadobject: manages the upload and validation workflow used to create a file objectFileobject: 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 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 indexnow files | 12 months |
| Minimum retention for result files | 24 hours |
| Minimum temporary upload URL validity | 24 hours |
| Minimum temporary download URL validity | 24 hours |
If a request exceeds an applicable operational limit, the API returns the error limit_exceeded.
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.
Attributes
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The unique identifier for the licensee. |
name | string | Yes | Display name of the licensee. |
url | string | Yes | Official website URL of the licensee. Must be a valid URL. |
status | enum | Yes | The current status of the licensee: active | inactive. |
Retrieve a licensee
Returns the licensee object for the specified id.
Endpoint
GET /licensees/{id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | id of the licensee object to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
licensee | object | Licensee object for the requested licensee. |
Example response
{
"licensee": {
"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 object 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. |
Example response
{
"licensees": [
{
"licensee": {
"id": "lic_ai_lab_001",
"name": "Example AI Lab 1 Name",
"url": "https://example.com",
"status": "active"
}
},
{
"licensee": {
"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.
Attributes
| Name | Type | Required | Description |
|---|---|---|---|
upload | object | Yes | Upload object for the corresponding repertoire file. |
Create a repertoire
To submit a repertoire file, a partner creates a repertoire object. The request returns a repertoire object containing the upload object for that file.
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 |
|---|---|---|---|
file | object | Yes | File metadata for the corresponding repertoire file. |
Response fields
| Name | Type | Description |
|---|---|---|
repertoire | object | Repertoire object for the upload workflow. |
Example request
{
"file": {
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"validate_only": false
}
}Example response
{
"repertoire": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "ready",
"created": 1760000000,
"updated": null,
"completed": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null,
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"url_expires": 1760086400,
"validate_only": false
}
}
}
}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 object for the specified id. Partners can use this endpoint to monitor the status of uploading and validating a repertoire file to create a repertoire object.
When the upload object reaches a terminal state, the upload.result_url field provides a temporary presigned URL for downloading the corresponding repertoire result file.
Endpoint
GET /repertoires/{id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | upload.id of the repertoire object to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
repertoire | object | Repertoire object for the requested repertoire. |
Example response
{
"repertoire": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "failed",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9",
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
}Repertoire result file
Repertoire uploads return a standard result file. The following additional row-level error_code values may appear for repertoire uploads:
| Value | Description | Action |
|---|---|---|
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": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"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": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "failed",
"error_code": "validation_failed",
"rows_processed": 13,
"rows_skipped": 0,
"errors": [
{
"row_number": 14,
"column": "scope_url",
"error_code": "duplicate_key",
"error_description": "scope_url appears more than once in the uploaded file."
}
]
}
}List all repertoires
Returns a list of available repertoire objects in reverse chronological order. The most recent repertoire object whose upload.file.validate_only is false and whose upload.status is succeeded is the repertoire of record for operational use.
To retrieve a specific repertoire object directly, use GET /repertoires/{id}.
Endpoint
GET /repertoiresQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of repertoire objects to return. |
starting_after | string | No | upload.id of the last repertoire object from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
repertoires | array of objects | List of repertoire objects. Each array item contains a repertoire object. |
has_more | boolean | Whether additional repertoire objects are available. |
Example response
{
"repertoires": [
{
"repertoire": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "processing",
"created": 1760002000,
"updated": 1760002060,
"completed": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null,
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760002000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
},
{
"repertoire": {
"upload": {
"id": "job_M4xq8Rk2Vn7pT5cY9wL1bD6h",
"status": "succeeded",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9",
"file": {
"id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
}
],
"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 includes the metadata needed to download the corresponding report file.
Attributes
| Name | Type | Required | Description |
|---|---|---|---|
period_start | string (YYYY-MM-DD) | Yes | Inclusive UTC start date of the reporting period. |
period_end | string (YYYY-MM-DD) | Yes | Inclusive UTC end date of the reporting period. |
file | object | Yes | File object for the corresponding report file. |
Retrieve a report
Returns the report object for the specified id. The report object includes a temporary presigned file.url for downloading the corresponding report file.
Endpoint
GET /reports/{id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | file.id of the report object to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
report | object | Report object for the requested report. |
Example response
{
"report": {
"period_start": "2025-04-04",
"period_end": "2025-04-04",
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1759160000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 1048576,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d.csv.gz",
"url_expires": 1759160400,
"validate_only": false
}
}
}List all reports
Returns a list of available report objects in reverse chronological order. For a given reporting period, the report object 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.
To retrieve a specific report object directly, use GET /reports/{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 object from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
reports | array of objects | List of report objects. Each array item contains a report object. |
has_more | boolean | Whether additional report objects are available. |
Example response
{
"reports": [
{
"report": {
"period_start": "2025-04-05",
"period_end": "2025-04-05",
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1759160000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 1048576,
"sha256": "8f3c1a7d4b92e6f0c5a8d1e3b7f4092c6e1a5d8f3b0c7e2d9a4f1c6b8e3d7a5",
"url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d.csv.gz",
"url_expires": 1759160400,
"validate_only": false
}
}
},
{
"report": {
"period_start": "2025-04-04",
"period_end": "2025-04-04",
"file": {
"id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"created": 1759070000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 3128576,
"sha256": "b83c1f4e92d7a6c5e1f0438b7c2d9e6a4f1b8c7d5e2a9f3c6b1d4e7f8a2c5d9",
"url": "https://files.rslcollective.org/file_P7mT3vX9qR4nK8yC2wL6dH1z.csv.gz",
"url_expires": 1759074000,
"validate_only": false
}
}
}
],
"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 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 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.
An indexnow object provides incremental, ordered updates to URLs within content scopes already present in the partner’s current effective repertoire. It does not declare new content scopes or replace the repertoire.
Attributes
| Name | Type | Required | Description |
|---|---|---|---|
upload | object | Yes | Upload object for the corresponding indexnow file. |
Create an IndexNow
To submit an indexnow file, a partner creates an indexnow object. The request returns an indexnow object containing the upload object for that file.
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 /indexnowsBody parameters
| Name | Type | Required | Description |
|---|---|---|---|
file | object | Yes | File metadata for the corresponding indexnow file. |
Response fields
| Name | Type | Description |
|---|---|---|
indexnow | object | IndexNow object for the upload workflow. |
Example request
{
"file": {
"schema_version": "1.0",
"format": "csv",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7"
}
}Example response
{
"indexnow": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "ready",
"created": 1760000000,
"updated": null,
"completed": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null,
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": "https://files.rslcollective.org/file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"url_expires": 1760086400,
"validate_only": false
}
}
}
}Retrieve an IndexNow
Returns the indexnow object for the specified id. Partners can use this endpoint to monitor the status of uploading and validating an indexnow file to create an indexnow object.
When the upload object reaches a terminal state, the upload.result_url field provides a temporary presigned URL for downloading the corresponding indexnow result file.
Endpoint
GET /indexnows/{id}Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | upload.id of the IndexNow object to retrieve. |
Response fields
| Name | Type | Description |
|---|---|---|
indexnow | object | IndexNow object for the requested indexnow file. |
Example response
{
"indexnow": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "failed",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"result_url": "https://files.rslcollective.org/result_9aK2mQ7xT4vN8pR3cW6yZ1bD.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9",
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
}IndexNow result file
Indexnow uploads return a standard result file. The following additional row-level error_code values may appear for indexnow uploads:
| Value | Description |
|---|---|
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": {
"id": "job_01JV1N0A9Q6M2X7P4R8T3K5N1C",
"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/{id}.
Endpoint
GET /indexnowsQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Maximum number of indexnow objects to return. |
starting_after | string | No | upload.id of the last indexnow object from the previous page. |
Response fields
| Name | Type | Description |
|---|---|---|
indexnows | array of objects | List of indexnow objects. Each array item contains an indexnow object. |
has_more | boolean | Whether additional indexnow objects are available. |
Example response
{
"indexnows": [
{
"indexnow": {
"upload": {
"id": "job_9aK2mQ7xT4vN8pR3cW6yZ1bD",
"status": "processing",
"created": 1760002000,
"updated": 1760002060,
"completed": null,
"result_url": null,
"result_url_expires": null,
"result_sha256": null,
"file": {
"id": "file_f7Lp2Vx9qM4nT8rC3wK6yH1d",
"created": 1760002000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
},
{
"indexnow": {
"upload": {
"id": "job_M4xq8Rk2Vn7pT5cY9wL1bD6h",
"status": "succeeded",
"created": 1760000000,
"updated": 1760001200,
"completed": 1760001200,
"result_url": "https://files.rslcollective.org/result_M4xq8Rk2Vn7pT5cY9wL1bD6h.json",
"result_url_expires": 1760087600,
"result_sha256": "f26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b9",
"file": {
"id": "file_P7mT3vX9qR4nK8yC2wL6dH1z",
"created": 1760000000,
"format": "csv",
"schema_version": "1.0",
"compression": "gzip",
"size": 2096128,
"sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
"url": null,
"url_expires": null,
"validate_only": false
}
}
}
}
],
"has_more": false
}IndexNow file
An indexnow file defines URL-level changes within previously declared content scopes. Each row defines the current change for one URL within a licensed content scope. Rows in an indexnow file are ordered by change time, and must be processed sequentially from top to bottom to apply changes in the correct order.
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. |
timestamp | integer | Yes | 10 chars | UTC Unix timestamp when the URL-level change occurred. |
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,timestamp
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-1,removed,1774223900
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-2,added,1774223940
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-3,updated,1774223970
pub_001,https://example.com,https://example.com/,https://example.com/articles/example-3,deleted,1774223995Appendix: 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:
$ref: "#/components/schemas/LicenseeListResponse"
/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/LicenseeObject"
"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 object created
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/RepertoireObject"
"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:
$ref: "#/components/schemas/RepertoireListResponse"
/repertoires/{id}:
get:
summary: Retrieve a repertoire
operationId: getRepertoire
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/RepertoireObject"
"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:
$ref: "#/components/schemas/ReportListResponse"
/reports/{id}:
get:
summary: Retrieve a report
operationId: getReport
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/ReportObject"
"404":
$ref: "#/components/responses/Error"
/indexnows:
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 object created
headers:
Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/IndexNowObject"
"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: listIndexNows
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:
$ref: "#/components/schemas/IndexNowListResponse"
/indexnows/{id}:
get:
summary: Retrieve an IndexNow
operationId: getIndexNow
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/IndexNowObject"
"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
description: Inner licensee resource object.
required: [id, name, url, status]
properties:
id:
type: string
name:
type: string
url:
type: string
format: uri
status:
type: string
enum: [active, inactive]
LicenseeObject:
type: object
description: Response wrapper containing a single licensee object.
required: [licensee]
properties:
licensee:
$ref: "#/components/schemas/Licensee"
LicenseeListResponse:
type: object
description: Paginated list response containing licensee objects.
required: [licensees, has_more]
properties:
licensees:
type: array
items:
$ref: "#/components/schemas/LicenseeObject"
has_more:
type: boolean
RepertoireRequest:
type: object
required: [file]
properties:
file:
$ref: "./files.openapi.yaml#/components/schemas/FileInput"
Repertoire:
type: object
description: Inner repertoire resource object.
required: [upload]
properties:
upload:
$ref: "./files.openapi.yaml#/components/schemas/Upload"
RepertoireObject:
type: object
description: Response wrapper containing a single repertoire object.
required: [repertoire]
properties:
repertoire:
$ref: "#/components/schemas/Repertoire"
RepertoireListResponse:
type: object
description: Paginated list response containing repertoire objects.
required: [repertoires, has_more]
properties:
repertoires:
type: array
items:
$ref: "#/components/schemas/RepertoireObject"
has_more:
type: boolean
Report:
type: object
description: Inner report resource 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"
ReportObject:
type: object
description: Response wrapper containing a single report object.
required: [report]
properties:
report:
$ref: "#/components/schemas/Report"
ReportListResponse:
type: object
description: Paginated list response containing report objects.
required: [reports, has_more]
properties:
reports:
type: array
items:
$ref: "#/components/schemas/ReportObject"
has_more:
type: boolean
IndexNowRequest:
type: object
required: [file]
properties:
file:
$ref: "./files.openapi.yaml#/components/schemas/FileInput"
IndexNow:
type: object
description: Inner indexnow resource object.
required: [upload]
properties:
upload:
$ref: "./files.openapi.yaml#/components/schemas/Upload"
IndexNowObject:
type: object
description: Response wrapper containing a single indexnow object.
required: [indexnow]
properties:
indexnow:
$ref: "#/components/schemas/IndexNow"
IndexNowListResponse:
type: object
description: Paginated list response containing indexnow objects.
required: [indexnow, has_more]
properties:
indexnow:
type: array
items:
$ref: "#/components/schemas/IndexNowObject"
has_more:
type: booleanRepertoire 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:
- 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.
- name: timestamp
type: integer
required: true
minimum: 0
maximum: 9999999999
description: UTC Unix timestamp when the URL-level change occurred.
constraints: {}
validation_errors:
- 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: 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: 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.
