Open Content Label · specification

OCL spec v0.1 (draft)

An open, machine-readable label saying who a piece of digital content suits (age) and what it does to the viewer (influence), signed by the rater who made it. Anyone may rate, attach, read and check labels under this spec, free of charge.

Draft of 31 October 2026, published by Shinifrontiers Technologies Private Limited as a proposal (including to the EU KIDS Act process). Field names may change before v1.0. Plain-language explainer: the OCL fingerprint.

1. Roles and principles

2. The label

A label is a JSON object (UTF-8). All fields below are required in v0.1.

FieldTypeMeaning
label_idstringRater's ID for this label (letters and digits, up to 64).
spec_versionstring"0.1"
item_typestring"website" in v0.1. Reserved: web_page, app, video, chatbot.
subjectstringWhat was rated. For a website: its host name, lower case (www. is ignored when checking).
raterstringHost name of the rater that signed the label. New in v0.1.
age_bandstringOne of all_ages, mild, teen, older_teen, adult: the youngest age for whom the content is not unsuitable, judged from evidence of harm, never from who the content is written for.
age_band_scalesobjectThe band on local scales (table below).
influenceobjectAll 12 categories, each an integer 0–3 (0 none, 1 some, 2 clear, 3 strong).
flagsarrayZero or more of undisclosed_ad, unverified_claim, imitation_risk, body_pressure, hate_or_targeting.
confidencestringhigh, medium or low.
methodstringThe rater's method and version, e.g. ocl-website-0.1.
rated_atstringDate and time of rating, ISO 8601 with offset.
expires_onstringLast valid date, YYYY-MM-DD. At most 90 days after rating for websites, because websites change.
signaturestringDetached signature (section 3).

Age band on local scales

age_bandIndiaEU KIDS Act (proposal)PEGI
all_agesU3-12 (child apps only)3
mild7+3-12 (child apps only)7
teen13+13-1412
older_teen16+15-1716
adultA18+18

Influence categories

fun, education, inspiration (what the content gives) and fight, threat_fear, sexual, substances, risky_stunts, commercial_push, political, body_pressure, hate (what it pushes).

Example

{
  "label_id": "754fe7cfd476531e0b95fb6c",
  "spec_version": "0.1",
  "item_type": "website",
  "subject": "example.com",
  "rater": "rate.shinifrontiers.com",
  "age_band": "teen",
  "age_band_scales": {"india": "13+", "eu_kids_act": "13-14", "pegi": "12"},
  "influence": {"fun": 2, "education": 1, "inspiration": 0, "fight": 0, "threat_fear": 0,
                "sexual": 0, "substances": 0, "risky_stunts": 1, "commercial_push": 1,
                "political": 0, "body_pressure": 0, "hate": 0},
  "flags": ["imitation_risk"],
  "confidence": "medium",
  "method": "ocl-website-0.1",
  "rated_at": "2026-10-31T09:00:00+00:00",
  "expires_on": "2027-01-29",
  "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6Im9jbC1yYXRlci0xIn0..<signature>"
}

3. Signing

  1. Canonical form: the label without its signature field, serialised as JSON with keys sorted, no spaces (separators , and :), UTF-8, non-ASCII characters left as they are.
  2. Header: {"alg":"EdDSA","kid":"<key id>"}.
  3. Signing input: base64url(header) + "." + base64url(canonical form) (base64url without padding).
  4. Signature: Ed25519 (pure EdDSA, RFC 8032) over the signing input.
  5. Detached form in the label: "signature": "<header>..<signature>" (empty middle part), so the label stays readable JSON.
  6. Compact token for pages: <header>.<canonical payload>.<signature>, a standard JWS that carries the whole label.

Rater keys

Each rater publishes its public keys as a JWK set at https://<rater>/.well-known/ocl-keys.json, readable from any origin:

{"keys": [{"kty": "OKP", "crv": "Ed25519", "kid": "ocl-rater-1", "x": "<public key, base64url>", "use": "sig"}]}

Only public parts are published. A rater keeps a retired key in this file until every label signed with it has expired. Our keys: /.well-known/ocl-keys.json; our signing key is held in a hardware-backed key service and cannot be exported.

4. Attaching a label to a website

A website publishes either or both of these. The file is preferred.

File: /.well-known/ocl.json

{
  "ocl": "0.1",
  "site": "https://example.com",
  "labels": [{
    "paths": ["/*"],
    "label": "https://rate.shinifrontiers.com/label/754fe7cfd476531e0b95fb6c",
    "rater": "rate.shinifrontiers.com",
    "signed_label": { ...the signed label... }
  }]
}

Tags in the <head> of each page

<link rel="ocl-label" href="https://rate.shinifrontiers.com/label/754fe7cfd476531e0b95fb6c">
<meta name="ocl" content="<compact token>">

In v0.1 one label covers the whole site (paths: ["/*"]). Per-path labels, apps (manifest entry and store listing), video (C2PA assertion, metadata added by stream copy) and chatbots (OCL-Label response header) are planned for later versions and described in the explainer; they are not final in v0.1.

5. Checking a label

  1. Find the label: https://<host>/.well-known/ocl.json, else the start page's <meta name="ocl"> with its <link rel="ocl-label">. Neither present: the site is unrated.
  2. Rater: the entry's rater, or the host of the ocl-label link.
  3. Keys: fetch https://<rater>/.well-known/ocl-keys.json over HTTPS and pick the key whose kid matches the signature header.
  4. Signature: verify as in section 3. The label's own rater field must equal that rater (www. ignored).
  5. Binding: the label's subject must equal the site checked (www. ignored), so a label copied to another site fails.
  6. Expiry: today must be on or before expires_on.

The label is valid only if all checks pass. Whether to trust the rater is then the checker's (or regulator's) decision. Proposed default for platforms: content with no valid label is treated as unrated and hidden from child accounts.

Reference checker: our label check page, and its open API GET https://rate.shinifrontiers.com/verify?site=example.com (JSON). It checks labels from any rater.

6. Changes

Comments and proposals: contact@shinifrontiers.com. This specification may be implemented by anyone without charge.