HomeEssays

Essay · Technical · August 2026

How to verify a content license offline, in five lines of code

A content rights credential is a JWT-VC: a W3C Verifiable Credential signed with Ed25519. Here is what verifying one actually checks — signature, issuer, scope, revocation — and why every one of them runs offline.

The short answer. A content rights credential is a JWT-VC: a W3C Verifiable Credential encoded as a compact JWS and signed with Ed25519. Verifying one means checking the signature against the issuer’s published key, confirming the credential covers the asset and use you care about, and checking it has not been revoked. All three run locally, in milliseconds, with no API key and no network call to the party that issued it.

Why offline is the whole point

Almost every “verify this license” system in the market works by asking a server. You send an identifier, a vendor answers yes or no, and your compliance position is now a claim about that vendor’s uptime, honesty, and continued existence.

That fails in exactly the situation the verification exists for. The awkward question about your training corpus does not arrive next week. It arrives in 2030, from a regulator or an opposing counsel, about ingestion that happened in 2026. If answering requires a live API call to a company that may have been acquired, repriced, or wound up, you do not have evidence. You have a dependency.

A signed credential inverts that. The proof travels with the artifact. Copy it, archive it, hand it to a regulator’s own technical staff on a USB stick — it still verifies, against a key you can also archive, with no cooperation from the issuer or from us. That property is not a feature we added. It is the reason the credential is shaped the way it is.

The five lines

import { verify } from "@licensefoundry/sdk";

const result = await verify(credentialJwt);
if (!result.valid) throw new Error(result.reason);
// result.rights → { train: "granted", rag: "granted", embed: "granted", display: "denied" }

The Python SDK (licensefoundry on PyPI) has the same shape. There is also a zero-dependency open reference verifier — content-license-verify — implemented in both TypeScript and Python and parity-tested across the two, so you can check one implementation against the other rather than taking either on trust.

Verification is free for everyone, permanently, with no metering and no key. That is deliberate: a verification layer somebody can price is a verification layer somebody can switch off.

What those five lines actually do

Underneath, verify performs four checks. Worth knowing them, because if you ever need to explain your compliance position to someone technical, these are the four things you are claiming.

1. Signature — is this credential authentic and unaltered?

The credential is a compact JWS. Its header names the algorithm (EdDSA over Ed25519) and the key. The verifier recomputes the signature over the header and payload and compares. Any change to any byte of the payload — a right flipped from denied to granted, a date moved, a scope widened — invalidates it.

This is the check that turns a claim into evidence. A rights assertion in a spreadsheet is something a party says. A rights assertion under a signature is something a party said and cannot later have said differently.

2. Issuer — whose key is that, and does the domain vouch for it?

The credential’s iss is a did:web identifier, which resolves by simple HTTPS convention to a document at a well-known path on the issuer’s own domain, listing their public keys. No blockchain, no registry, no central authority: if you trust that example.com is controlled by Example Ltd — the same assumption underpinning every TLS connection you make — then you can check whether Example Ltd published this key.

The key set is cacheable. Fetch it once, cache it for up to 24 hours, and every subsequent verification is pure local computation. At corpus scale that matters: verification stops being a network problem and becomes arithmetic.

3. Scope — does this credential actually cover what I am about to do?

This is the check people skip, and it is where most real-world failures will happen.

A credential does not carry a single “licensed” flag. It enumerates rights per use, each with its own scope:

train    → granted · EU · 12 months
rag      → granted · global
embed    → granted · open-weights only
display  → not granted

A pipeline that treats the credential’s mere existence as permission will happily train a global model on an EU-only grant, or surface a display right that was never given. The right question is never “is this licensed?” It is “is this use, in this territory, by this model family, within this term, granted?” Those are five predicates, and a credential that answers them individually is doing its job.

4. Revocation — is it still valid?

A paid invoice cannot be un-paid. A license can absolutely be withdrawn — a deal ends, a rightsholder discovers the licensor never had the underlying right, a term is renegotiated.

Revocation uses a Bitstring Status List: a single compressed bitmap published at a URL, where each credential owns one bit. One small fetch tells you the status of every credential from that issuer, and it caches. Revoking is flipping a bit; verifiers see the change at their next cache refresh.

The privacy property is worth noticing. Because you fetch the whole list rather than querying one credential, the issuer never learns which credential you were checking. Verification does not leak your corpus back to your suppliers.

The one check nothing can do for you

A credential proves that an identified issuer signed a specific rights claim, at a specific time, and has not revoked it. It does not prove the issuer actually held the rights they granted.

Nobody’s cryptography solves that. It is a diligence question, and it is why the attestation layer exists as a separate, independent step: a rights claim signed only by the party making it is self-attestation, which is the model that AI labs’ counsel already discount. An independent co-signature — from a party that is neither in the deal nor in the money — does not make the underlying claim true. It makes it checked, by someone with no incentive in either direction. That is the same reason companies do not audit their own books.

Be precise about this distinction in your own documentation. Overclaiming what verification proves is how a good evidence position becomes a bad one under cross-examination.

Where this fits in the standards stack

Four layers, four jobs:

LayerStandardQuestion it answers
CommunicateCoMP (IAB Tech Lab)How does an AI system ask, and how does a publisher state the offer?
DescribeRSL 1.0What are this publisher’s standing terms across their paths?
VerifyW3C VC 2.0 / JWT-VCWas this specific use granted to this specific party, and does that still hold?
ReportSPUR Content TelemetryHow did the AI system actually use the content?

They compose rather than compete. CoMP defines a licenseurl field but does not specify what lives at that endpoint. RSL 1.0 §3.13 defines <legal type="proof"> and names verifiable credentials as its cryptographic-evidence layer. SPUR’s license_ref field (§5.2.3) accepts “a JWT jti claim, a CoMP package ID, or any opaque identifier both parties can resolve” — and a credential is a JWT with a jti, so nothing needs to change on either side.

Try it without installing anything

There is a live verifier on the homepage that runs entirely in the browser, using WebCrypto — no server, no login, nothing sent anywhere. Edit any character of the credential and watch the signature fail. It is the most direct way to develop the right intuition: the signature is not a badge, it is a computation over the exact bytes, and the bytes cannot be quietly improved.

Verify a real signed license in your browser

Ed25519, offline, no login — and the SDK is open source.

Try the live verifier →