Contract

Building a licensed agent

The full HTTP contract an agent speaks: every field, every refusal reason, the obligations it must honour, and the acceptance checklist for a reference implementation.

Browse documentation

Use these HTTP endpoints to authenticate an agent, request permission and retrieve licensed content from a publisher.


1. What an agent is here

An agent is a program that fetches somebody else's content for an automated
purpose — training a model, building a search index, grounding an answer — and
wants to do it with permission that can be proved afterwards.

The shape of the exchange is three parties, and you are the first:

  1. You, the agent. You want a resource. You hold a credential a
    publisher's customer minted for you.
  2. The License Server (this system). It reads the publisher's terms, decides
    whether your request is licensed, and mints a short-lived token when it
    is.
  3. The publisher's own server (the Resource Server). It serves the
    content. It never decides licensing itself — it asks the License Server about
    your token and then serves or refuses.

The important consequence: the token is not permission to fetch, it is
evidence of permission.
You present it to the publisher's server, which
verifies it independently. An agent that holds a token and never presents it has
done nothing wrong and nothing useful.


2. The flow, end to end

  ┌────────┐   1. GET the RSL document      ┌──────────────────┐
  │ agent  │ ─────────────────────────────► │ publisher origin │
  │        │ ◄───────────────────────────── │  (/licence.xml)  │
  │        │      terms, prices, server URL  └──────────────────┘
  │        │
  │        │   2. POST /olp/token           ┌──────────────────┐
  │        │ ─────────────────────────────► │  License Server  │
  │        │ ◄───────────────────────────── │                  │
  │        │      access_token (or refusal)  └──────────────────┘
  │        │
  │        │   3. GET the resource          ┌──────────────────┐
  │        │      Authorization: License …  │ publisher origin │
  │        │ ─────────────────────────────► │ (Resource Server)│
  │        │ ◄───────────────────────────── │        │         │
  └────────┘      the content                └───────┼─────────┘
                                                     │ 4. POST /olp/introspect
                                                     ▼
                                             ┌──────────────────┐
                                             │  License Server  │
                                             └──────────────────┘

Step 4 is not yours to make. It is shown because it explains why step 3 can
fail even when step 2 succeeded: the publisher asks independently, and its own
availability rules apply on top.


3. Step 1 — read the published document

The publisher's terms are an RSL 1.0 document (Really Simple Licensing).
Find it the way RSL says: the License: line in robots.txt, or a
<link rel="license"> in the page.

What you need from it, per <content> element:

Element What it tells you
<content url="…"> the path prefix these terms cover
server="…" the License Server to ask. No server attribute means there is no token to get — the licence is public in the document itself and you proceed under it. This is the market default, and it is the one case where you never call us at all
<license> one offer. There may be several
<permits type="usage"> what this offer allows: search, ai-train, ai-input, ai-index, or the aggregates all / ai-all
<prohibits> what is refused at any price. Repeated inside every <license> — it belongs to the content, not the sale
<payment type="…"> the price of this offer
<terms> the human-readable governing prose. Read it: it is where the publisher says what an offer actually covers

Several <license> elements under one <content> is normal

A publisher may sell the same section more than one way — a section
subscription beside a single-article purchase. Each is its own <license>
with its own <payment> and its own <permits>.

This is the part most likely to surprise you. Two offers on one <content>
may permit different uses. The cheaper one may permit search only while the
dearer one also permits ai-train. Read each <license> as a separate product.


4. Step 2 — ask for a token

POST /licensing/v1/tenants/{tenant_id}/olp/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&resource=https://publisher.example/reports/q1
&license=<the exact <license> element you are asking under>

Three required fields and nothing optional:

  • grant_type is always client_credentials.
  • resource is the exact URL you want. Query strings are part of a
    resource's identity here — …/report and …/report?ref=x are two different
    resources, and a token for one does not cover the other. Send no fragment.
  • license is the <license> element, sent back as XML.

How license decides which offer you are buying under

This is the mechanism to get right. You are not asked "which offer do you
want" in a separate field. The <payment> inside the <license> you submit
is your choice. Send the subscription element and you are asking under the
subscription; send the purchase element and you are asking under the purchase.

Rules for constructing it:

  • Start from the published element, do not build one from scratch. The payment
    declaration is compared byte-canonically, and a rebuilt one will not match.
  • Narrow the <permits type="usage"> to the one use you are asking for.
    You must ask for one concrete use at a time — never an aggregate like all.
  • Keep everything else exactly as published: the prohibitions, the reporting
    obligations, the payment.
  • You may drop permissions. You may not add any, widen a condition, drop a
    prohibition, or change the payment.

A submission that fails those rules is refused, and the refusal names which
rule (submitted_license_broader, submitted_license_drops_prohibition,
submitted_license_payment_mismatch, and others).

Success

{
  "access_token": "…",
  "token_type": "License",
  "expires_in": 600,
  "resource": "https://publisher.example/reports/q1",
  "assetrouter_token_id": "…",
  "assetrouter_grant_id": "…"
}

assetrouter_grant_id is absent on a free token, which rests on no Grant.

Read expires_in; never hard-code it. The lifetime is the publisher's
tenant setting, 600 seconds by default, and two publishers may answer
differently. A client that assumed a constant keeps presenting a token the
gate has already stopped accepting.

The response deliberately does not tell you how far the token reaches. You
learn that from the publisher's gate, which is the party enforcing it. Do not
try to infer a region and pre-filter your crawl against it.

Refusal

{
  "error": "invalid_license",
  "error_description": "…",
  "assetrouter_reason": "external_fulfillment_required",
  "assetrouter_rsl": {
    "license_server": "https://…/olp",
    "license": "https://…/rsl/…"
  }
}

Read error and error_description — those two are the conformant OLP
contract. Everything under assetrouter_ is an extension you may ignore, but
assetrouter_reason is the machine string worth logging and branching on.

The status is 400 for every refusal. This is deliberate: OLP defines
400 as the only error status for this endpoint, so a 402 sent to a client
that did not ask for one has undefined behaviour.

The opt-in 402

If your registration has the extension recorded, a
fulfillment_required outcome arrives as 402 instead, with the payment
entry point in the body:

{
  "assetrouter_response_type": "…",
  "error": "invalid_license",
  "assetrouter_license_decision": "…",
  "assetrouter_fulfillment_uri": "https://publisher.example/pay/…",
  "assetrouter_rsl": { … }
}

Build against both. The 400 is what every client gets and what you must
handle; the 402 is a shortcut that saves you re-reading the document. If
assetrouter_response_type is missing or unrecognised, fall back to the RSL
payment entry point.


5. Step 3 — use the token

GET /reports/q1 HTTP/1.1
Host: publisher.example
Authorization: License <access_token>

Scheme is License, not Bearer. One header, one value.

Then handle what comes back:

Status License error Meaning What to do
200 — licensed and served proceed
401 invalid_request your request or authorization header was malformed fix the request
401 invalid_token missing, malformed, unknown, expired or revoked token get a new one; if it fails again, stop
402 invalid_token no acquired licence, or payment incomplete go to the fulfilment entry point
403 insufficient_scope active token whose licence does not cover this resource or this operation do not retry with the same token
404 / 410 — the publisher no longer serves it not a licensing problem
503 server_error the publisher could not safely confirm retry with backoff

403 is not a retry condition. The token is fine and the answer will not
change by asking again.

Handle the 402 row even though nothing produces it today. It exists in the
contract publishers implement against, and it will start arriving when a
payment path ships. A client that already has the branch needs no change that
day.


6. Obligations you must honour

The licence may oblige you to do something, and those obligations are in the
document and in the token's own frozen <license>:

  • Attribution. A <payment type="attribution"> offer means visible credit
    and a working link, before use. It is the price.
  • Reporting. A <reporting> declaration means the publisher expects access
    reports. Honour the profile it names.

Surface every obligation to your caller before licensed use, rather than
recording it and moving on.


7. What the agent must never do

Your integration must meet these requirements:

  • No privileged access. Public HTTP only — no database, no fixture, no
    internal endpoint, no staff path.
  • Never log a secret or a token value. Not in logs, not in exception
    messages, not in a crash report. Write a redaction test and keep it.
  • Idempotent retries. A retried call must not produce a second Grant.
  • One use per request. Never submit an aggregate as a request; all as a
    request would be asking for whatever the vocabulary grows into next.
  • No inventing terms. If the document does not say it, you do not have it.

8. Credentials

Your agent authenticates with a client_id and a client_secret over HTTP
Basic. The secret begins with ag_sk_ — that is part of the value, send it
whole — is shown once, at issuance, and only its digest is stored: there
is no endpoint that will show it to you again. Rotation issues a new secret
with an overlap window, so both work briefly.

One credential works across publishers. You never receive a secret through a
publisher: you mint it yourself through a connect session, which section 11
describes. Naming the domain your agent runs on is optional there; it is what
a later step can verify to raise the credential's assurance, and an agent with
no stable name leaves it empty and buys like any other.


9. Sandbox versus live

They are separate deployments with separate databases, credentials, signing
keys and issuer URIs. A sandbox token fails closed against a live issuer and
vice versa
— that is asserted behaviour, not a happy accident. The
environment appears in every signed artifact.

Build and test against the sandbox. Never carry a credential across.


10. Suggested shape for the project

A thin client plus an executable example is enough:

  • Client: authenticate, POST /olp/token, apply the token to a request,
    surface structured outcomes. Typed errors, not raw HTTP. A 402 and a
    fulfillment_required must arrive as typed results carrying the governing
    RSL reference, never as a generic HTTPError.
  • Reference agent: a runnable script that completes the whole flow —
    discover the document, pick an offer, get a token, fetch the resource, print
    what happened.

Acceptance checklist

The agent is done when all of these hold against the sandbox:

  • [ ] It authenticates, obtains a token, retrieves through the publisher's
    server, and surfaces the observed outcome.
  • [ ] A 402 and a fulfillment_required are surfaced as typed results
    carrying the governing RSL reference.
  • [ ] Secrets and token values never appear in logs or exception messages,
    proved by a redaction test.
  • [ ] A retried idempotent call does not create a second Grant.
  • [ ] The whole flow completes with no privileged application access and no
    direct database reach.
  • [ ] Every frozen reporting and attribution obligation is surfaced to the
    caller before licensed use.
  • [ ] On a <content> carrying two <license> elements, submitting each one
    is served under that offer, and the token's frozen licence carries that
    offer's <payment>.

11. Questions to settle with the License Server operator

Before you start, get these four things:

  1. The tenant id in the route prefix.
  2. The base URL of the sandbox deployment.
  3. A connect link, and whether the opt-in 402 extension is recorded on
    your registration. The link is how you obtain a credential — see below.
  4. The publisher origin and the URL of its published RSL document.

Where your credential comes from

Nobody sends you a client id and secret. You mint your own, once, through a
connect session: the publisher opens one for you and gives you a one-time
link, you open it in a browser, and the page hands you both values.

GET {base}/{environment}/connect/{link}

The page asks for the host your agent runs on, which is optional — an
agent with no stable name leaves it empty and buys like any other; naming one
is what a later step can verify to raise the credential's assurance — plus an
optional label. Submitting it registers your agent and renders the client id
and the secret.

The secret is on that page and nowhere else. It is not mailed, not
repeated, and not recoverable: only its digest is stored. Copy it before you
close the tab. If you lose it, the credential has to be rotated.

The link opens once. A link that is unknown, expired, already redeemed, or
belonging to another environment renders the same page and answers 404 — one
answer for every failure, so a wrong guess learns nothing.


Telling us what you find

Send it to licensing@assetrouter.eu.

You are the first to build against this specification, so a gap you hit is a
gap nobody has reported. Worth sending: a step you could not complete as
written, a response shape that did not match what is described here, a value in
section 11 that turned out not to be enough to start. A note saying which part
was unclear is more useful than a working agent we never hear about — the
implementation proves your reading; the note fixes the document.


Vocabulary

Term Meaning
RSL Really Simple Licensing 1.0, the document format the publisher's terms are written in
OLP the token endpoint protocol (/olp/token, /olp/introspect, /olp/key)
Offer one <license> element — one way to buy what a <content> covers
Grant the durable record that you bought something; outlives any token
Token short-lived evidence cut from a Grant, presented to the publisher
Region how far a token reaches; you are not told it, the publisher enforces it
Resource Server the publisher's own server, which serves content and asks us about your token