Skip to content

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:

Bulk file management

See the Files API for the shared objects used by the Enrollment API for uploading and downloading files:

  • Upload object: creates a job object used to upload and validate a file
  • Job object: tracks a file upload and validation workflow
  • File object: represents a managed file and its metadata
  • Result object: 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:

LimitValue
Maximum rows per repertoire file100 million rows
Maximum repertoire file size5 GB
Minimum retention for report files12 months
Minimum retention for result files1 hour
Minimum temporary upload URL validity1 hour
Minimum temporary download URL validity1 hour

If a request exceeds an applicable operational limit, the API returns the error limit_exceeded.

Core concepts

Core terms

TermDefinition
RSL CollectiveThe collective rights organization that administers the collective licensing program, receives usage reports from Licensees, and distributes payments to publishers.
RSL Collective LicenseA collectively negotiated license defined by the RSL Collective that specifies commercial terms used for settlement and payment calculation.
Enrollment PartnerA 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.
PublisherA rights holder whose Content Scopes are licensed under RSL and who participates in collective licensing through an Enrollment Partner.
Content ScopeA 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 AttestationA Partner-maintained authorization record confirming that the Partner has permission to enroll the Publisher in the RSL Collective program.
Rights AttestationA Partner-maintained authorization record confirming that the Publisher has rights to license a content scope.
LicenseeAn 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 RepertoireThe 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.
RepertoireThe 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 FileA file uploaded through the Enrollment API that declares the complete Repertoire for collective licensing through the RSL Collective.
Report FileA file made available through the Enrollment API that contains Repertoire usage and payment data for a specified reporting interval.
Reporting PeriodThe time interval covered by a report file.
IndexNow FileA 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 its robots.txt file
  • 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:

xml
...
<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:

xml
<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 partner
  • rights_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 partner
  • enrollment_attestation_id: stable partner-defined identifier for the enrollment attestation record

Enrollment flow

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. Licensee reporting: Licensees report licensed content scope usage data and submit licensing payments to the RSL Collective.

  7. Partner reporting: The RSL Collective reconciles usage data against the uploaded repertoires and delivers report files and licensing payments to partners.

  8. 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

http
GET /licensees/{id}

Path parameters

NameTypeRequiredDescription
idstringYesThe unique identifier for the licensee.

Response fields

NameTypeDescription
idstringThe unique identifier for the licensee.
namestringDisplay name of the licensee.
urlstringOfficial website URL of the licensee. Must be a valid URL.
statusenumThe current status of the licensee: active | inactive.

Example response

json
{
  "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

http
GET /licensees

Query parameters

NameTypeRequiredDescription
limitintegerNoMaximum number of licensee objects to return.
starting_afterstringNoid of the last licensee from the previous page.

Response fields

NameTypeDescription
licenseesarray of objectsList of licensees.
has_morebooleanWhether additional licensee objects are available.

Each object in licensees contains:

NameTypeDescription
idstringThe unique identifier for the licensee.
namestringDisplay name of the licensee.
urlstringOfficial website URL of the licensee. Must be a valid URL.
statusenumThe current status of the licensee: active | inactive.

Example response

json
{
  "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

http
POST /repertoires

Body parameters

NameTypeRequiredDescription
uploadobjectYesUpload object for the corresponding repertoire file.

Response fields

NameTypeDescription
jobobjectJob object for the corresponding repertoire file.

Example request

json
{
  "upload": {
    "format": "csv",
    "schema_version": "1.0",
    "compression": "gzip",
    "size": 2096128,
    "sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7",
    "validate_only": false
  }
}

Example response

json
{
  "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

http
GET /repertoires/{job_id}

Path parameters

NameTypeRequiredDescription
job_idstringYesIdentifier of the repertoire upload job to retrieve.

Response fields

NameTypeDescription
jobobjectJob object for the corresponding repertoire file.

Example response

json
{
  "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:

ValueDescriptionAction
missing_attestationOne or more required attestation fields are missing.Upload rejected
duplicate_scope_urlDuplicate scope_url in the uploaded CSV.Upload rejected
unknown_licensee_idexclusions contains an unknown licensee ID.Upload rejected
conflicting_scope_urlscope_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
json
{
  "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
json
{
  "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

http
GET /repertoires

Query parameters

NameTypeRequiredDescription
limitintegerNoMaximum number of repertoire objects to return.
starting_afterstringNojob_id of the last repertoire from the previous page.

Response fields

NameTypeDescription
repertoiresarray of objectsList of repertoire objects.
has_morebooleanWhether additional repertoire objects are available.

Each repertoire object contains:

NameTypeDescription
jobobjectJob object for the corresponding repertoire file.

Example response

json
{
  "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.

ColumnTypeRequiredLimitDescription
publisher_idstringYes40 charsStable partner-defined identifier for the publisher.
publisher_urlstringYes512 charsCanonical website URL representing the publisher. This field is informational.
enrollment_attestation_datetimestampYes10 digitsUTC Unix timestamp when the Enrollment Attestation was executed or recorded by the partner.
enrollment_attestation_idstringYes40 charsStable partner-defined identifier for the Enrollment Attestation record.
rights_attestation_datetimestampYes10 digitsUTC Unix timestamp when the Rights Attestation authorizing licensing of the enrolled content scope was executed or recorded by the partner.
rights_attestation_idstringYes40 charsStable partner-defined identifier for the Rights Attestation record.
scope_urlstringYes512 charsURL representing the licensed content scope governed by an RSL license declaration published under the RSL Collective License.
exclusionsstringNo1024 chars totalOptional 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

csv
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

http
GET /reports/{file_id}

Path parameters

NameTypeRequiredDescription
file_idstringYesIdentifier of the report file to retrieve.

Response fields

NameTypeDescription
period_startstring (YYYY-MM-DD)Inclusive UTC start date of the reporting period.
period_endstring (YYYY-MM-DD)Inclusive UTC end date of the reporting period.
filefile objectFile object for the corresponding report file.

Example response

json
{
  "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

http
GET /reports

Query parameters

NameTypeRequiredDescription
limitintegerNoMaximum number of report objects to return.
starting_afterstringNofile_id of the last report from the previous page.

Response fields

NameTypeDescription
reportsarray of objectsList of report objects.
has_morebooleanWhether additional report objects are available.

Each Report object contains:

NameTypeDescription
period_startstring (YYYY-MM-DD)Inclusive UTC start date of the reporting period.
period_endstring (YYYY-MM-DD)Inclusive UTC end date of the reporting period.
filefile objectFile object for the corresponding report file.

Example response

json
{
  "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.

ColumnTypeRequiredLimitDescription
licensee_idstringYes40 charsIdentifier of the licensee that reported the usage.
report_datestring (YYYY-MM-DD)Yes10 charsUTC date covered by the reported usage and payment data.
publisher_idstringYes40 charsStable partner-defined identifier for the publisher.
scope_urlstringYes512 charsURL representing the licensed content scope.
usage_countintegerYes64-bit signed integerNumber of reported usage events for the content scope on the specified report date. Must be zero or greater.
payment_amountintegerYes64-bit signed integerPayment 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_currencystringYes3 charsThree-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.

csv
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,USD

The 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

http
POST /indexnow

Body parameters

NameTypeRequiredDescription
uploadobjectYesUpload object for the corresponding indexnow file.

Response fields

NameTypeDescription
jobobjectJob object for the corresponding indexnow file.

Example request

json
{
  "upload": {
    "schema_version": "1.0",
    "format": "csv",
    "compression": "gzip",
    "size": 2096128,
    "sha256": "d26b94ae60e4554c7333034960d2146f3801f42a7516a566a014a2f1e6d9c3b7"
  }
}

Example response

json
{
  "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

http
GET /indexnow/{job_id}

Path parameters

NameTypeRequiredDescription
job_idstringYesIdentifier of the IndexNow upload job to retrieve.

Response fields

NameTypeDescription
jobobjectJob object for the corresponding indexnow file.

Example response

json
{
  "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:

ValueDescription
unknown_scope_urlscope_url does not identify a content scope in the current effective repertoire.
url_outside_scopeurl does not fall within the declared scope_url.

These indexnow-specific row-level errors cause the job to fail.

Failed result example
json
{
  "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

http
GET /indexnow

Query parameters

NameTypeRequiredDescription
limitintegerNoMaximum number of indexnow objects to return.
starting_afterstringNojob_id of the last indexnow object from the previous page.

Response fields

NameTypeDescription
indexnowarray of objectsList of indexnow objects.
has_morebooleanWhether additional indexnow objects are available.

Each indexnow object contains:

NameTypeDescription
jobobjectJob object for the corresponding indexnow file.

Example response

json
{
  "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.

ColumnTypeRequiredLimitDescription
publisher_idstringYes40 charsStable partner-defined identifier for the publisher.
publisher_urlstringYes512 charsCanonical website URL representing the publisher. This field is informational.
scope_urlstringYes512 charsCanonical URL of the declared content scope containing the URL.
urlstringYes512 charsCanonical URL whose change is being reported.
changeenumYes16 charsadded, updated, deleted, or removed.

Change values

ChangeDescription
addedThe URL is newly available within an existing declared content scope.
updatedThe URL remains available within the declared content scope, but its content has changed materially.
deletedThe URL is no longer available due to an ordinary deletion.
removedThe URL is no longer available due to a rights, legal, safety, or compliance-driven removal.

Example IndexNow file

csv
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,deleted

Appendix: 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

yaml
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

yaml
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_url

Report file CSV schema

yaml
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

yaml
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_scope

Error 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.

yaml
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.