{
  "spec": "rs-interop/1",
  "name": "RS-KYA interop profile",
  "note": "This profile describes the exact shape of the credentials Rightshold issues today, so any implementer can read, check and verify one without asking. A field-correspondence note states only that a member carries a named value or uses a named member name — correspondence is not a statement that a document conforms to the specification the name comes from. What is not asserted is listed in full, in this same document, under not_asserted. Nothing here confers a level, a status or a link: those come only from the live RS-KYA record.",
  "formats": [
    {
      "id": "rs-kya-credential/1",
      "kind": "credential document, JSON",
      "described_by": {
        "spec": "rs-kya/0.1",
        "schema": "https://www.rightshold.com/schemas/kya-credential-v1.json"
      },
      "fetch": {
        "method": "GET",
        "url": "https://www.rightshold.com/v1/kya/{number|handle|agent_id}/credential",
        "returns": "{ credential, signature, token, how_to_verify }"
      },
      "ttl_days": 7,
      "field_mapping": [
        {
          "member": "@context",
          "carries": "an array whose entry is \"https://www.w3.org/ns/credentials/v2\"",
          "correspondence": "The context URL the W3C Verifiable Credentials Data Model 2.0 defines for its documents. Carrying the URL names the vocabulary the member names come from; it is not a conformance statement.",
          "asserts": "field correspondence only"
        },
        {
          "member": "type",
          "carries": "\"VerifiableCredential\" followed by \"RightsholdKYACredential\"",
          "correspondence": "\"VerifiableCredential\" is the data model's base type name; the second entry is the Rightshold type.",
          "asserts": "field correspondence only"
        },
        {
          "member": "spec",
          "carries": "the RS-KYA spec id, rs-kya/0.1",
          "correspondence": "A Rightshold member with no counterpart in the data model; it names the issuing standard version.",
          "asserts": "field correspondence only"
        },
        {
          "member": "issuer",
          "carries": "an object whose id is the issuer's HTTPS URL, with the issuer name and id",
          "correspondence": "The data model permits a URL as an issuer identifier. This platform's issuer identifier is a URL, not a DID — see not_asserted.issuer_did.",
          "asserts": "field correspondence only"
        },
        {
          "member": "key_id",
          "carries": "the id of the issuer signing key",
          "correspondence": "A Rightshold member; the key itself is published at /.well-known/rightshold-issuer.json and in the JWKS.",
          "asserts": "field correspondence only"
        },
        {
          "member": "validFrom / validUntil",
          "carries": "the validity window as instants",
          "correspondence": "The data model's member names for the start and end of a credential's validity.",
          "asserts": "field correspondence only"
        },
        {
          "member": "credentialSubject.id",
          "carries": "\"rs:agent:\" followed by the registration number, or the agent id when unregistered",
          "correspondence": "A Rightshold identifier form for the subject.",
          "asserts": "field correspondence only"
        },
        {
          "member": "credentialSubject.did",
          "carries": "when the agent has a key, that key's did:key identifier",
          "correspondence": "A did:key identifier is derived from the key itself (multicodec ed25519-pub, multibase base58btc), so it names the agent's key, not an account on this platform.",
          "asserts": "field correspondence only"
        },
        {
          "member": "credentialSubject",
          "carries": "level, level_name, status and the verified components the level rests on",
          "correspondence": "The RS-KYA payload: which facts were verified, stated as data.",
          "asserts": "field correspondence only"
        },
        {
          "member": "credentialStatus",
          "carries": "type \"RightsholdLiveStatus\"; id is the live lookup URL for the subject",
          "correspondence": "A Rightshold status type. Status is answered by re-reading the live record — see not_asserted.status_list.",
          "asserts": "field correspondence only"
        },
        {
          "member": "schema",
          "carries": "the URL of the published JSON Schema for this document",
          "correspondence": "The schema is published at the URL it names and describes this exact shape.",
          "asserts": "field correspondence only"
        },
        {
          "member": "signature (response envelope, not a document member)",
          "carries": "base64 Ed25519 over the canonical JSON (sorted keys, no whitespace) of the credential document",
          "correspondence": "The proof travels beside the document, not inside it — see not_asserted.embedded_proof.",
          "asserts": "field correspondence only"
        }
      ],
      "profile_requirements": [
        {
          "id": "P1",
          "requirement": "@context is an array containing \"https://www.w3.org/ns/credentials/v2\"."
        },
        {
          "id": "P2",
          "requirement": "type is an array containing both \"VerifiableCredential\" and \"RightsholdKYACredential\"."
        },
        {
          "id": "P3",
          "requirement": "spec is the RS-KYA spec id (rs-kya/0.1)."
        },
        {
          "id": "P4",
          "requirement": "issuer.id is an https URL and key_id names the signing key."
        },
        {
          "id": "P5",
          "requirement": "validFrom and validUntil are instants, validFrom before validUntil."
        },
        {
          "id": "P6",
          "requirement": "credentialSubject carries id (rs:agent:…), agent_id, a level (L0–L5), a status, and the components object."
        },
        {
          "id": "P7",
          "requirement": "credentialSubject.did, when present, is a did:key identifier for the agent's key."
        },
        {
          "id": "P8",
          "requirement": "credentialStatus.type is \"RightsholdLiveStatus\" and credentialStatus.id is an https URL."
        },
        {
          "id": "P9",
          "requirement": "schema names the published credential schema (…/schemas/kya-credential-v1.json)."
        }
      ],
      "verify": {
        "signature_and_live_level": "POST https://www.rightshold.com/v1/kya/verify with { credential, signature } or { token } — checks the signature against the published issuer keys, the expiry, and the live record; fails on a dropped level, a changed key or a non-active status.",
        "offline": "Verify the envelope signature against the issuer key named by key_id at https://www.rightshold.com/.well-known/rightshold-issuer.json, check validUntil, then read credentialStatus.id for the live level."
      }
    },
    {
      "id": "kya+jwt",
      "kind": "compact token, JWT",
      "fetch": "issued beside the credential document by the same fetch",
      "ttl_seconds": 3600,
      "field_mapping": [
        {
          "member": "header",
          "carries": "alg \"EdDSA\", typ \"kya+jwt\", kid naming the issuer key",
          "correspondence": "A JWT header; the key set is published at /.well-known/jwks.json.",
          "asserts": "field correspondence only"
        },
        {
          "member": "iss",
          "carries": "the issuer's HTTPS URL",
          "correspondence": "The RFC 7519 registered claim name for the issuer; the value is the same URL the credential document's issuer.id carries.",
          "asserts": "field correspondence only"
        },
        {
          "member": "sub",
          "carries": "the same subject id the credential document carries",
          "correspondence": "The RFC 7519 registered claim name for the subject.",
          "asserts": "field correspondence only"
        },
        {
          "member": "iat / nbf / exp",
          "carries": "issued-at, not-before and expiry, seconds",
          "correspondence": "The RFC 7519 registered claim names for the token's validity window.",
          "asserts": "field correspondence only"
        },
        {
          "member": "cnf.jkt",
          "carries": "the RFC 7638 JWK thumbprint of the agent's Ed25519 key, when the agent has one",
          "correspondence": "cnf is the confirmation member RFC 7800 defines; its jkt value is the thumbprint of the key the presenter must prove. The same thumbprint the credential's components carry.",
          "asserts": "field correspondence only"
        },
        {
          "member": "kya",
          "carries": "the level, status, registration number and verified facts, as data",
          "correspondence": "A Rightshold claim object with no counterpart in RFC 7519.",
          "asserts": "field correspondence only"
        }
      ],
      "verify": {
        "signature_and_live_level": "POST https://www.rightshold.com/v1/kya/verify with { token }; offline verification against /.well-known/jwks.json, then require the presenter to prove the key named by cnf.jkt."
      }
    }
  ],
  "profile_check": {
    "method": "POST",
    "url": "https://www.rightshold.com/v1/interop/check",
    "accepts": "{ credential, signature } (the credential endpoint's envelope) or { credential } or the credential document itself",
    "returns": "per-requirement pass/fail against rs-kya-credential/1, shape only — no signature verified, no record read"
  },
  "test_vectors": {
    "positive": {
      "name": "live-credential",
      "how": "Fetch a credential for any public agent from the fetch URL above, then POST the response's credential member (or the whole envelope) to the profile check. Every requirement passes.",
      "expect": "P1–P9 all pass"
    },
    "negative": [
      {
        "name": "context-removed",
        "alteration": "remove the @context member from a passing credential",
        "must_fail": [
          "P1"
        ]
      },
      {
        "name": "rightshold-type-removed",
        "alteration": "remove \"RightsholdKYACredential\" from the type array",
        "must_fail": [
          "P2"
        ]
      },
      {
        "name": "spec-replaced",
        "alteration": "set spec to a different spec id",
        "must_fail": [
          "P3"
        ]
      },
      {
        "name": "validity-inverted",
        "alteration": "set validUntil before validFrom",
        "must_fail": [
          "P5"
        ]
      },
      {
        "name": "level-removed",
        "alteration": "remove credentialSubject.level",
        "must_fail": [
          "P6"
        ]
      },
      {
        "name": "status-type-replaced",
        "alteration": "set credentialStatus.type to any other value",
        "must_fail": [
          "P8"
        ]
      }
    ]
  },
  "status": {
    "vc_conformance_statement": "none",
    "issuer_did": "not_published",
    "trust_registry_listings": "none",
    "erc8004_read_bridge": "live",
    "erc8004_kya_alias": "not_published",
    "erc8004_write_half": "disabled"
  },
  "erc8004": {
    "read_bridge": {
      "spec": "rightshold-erc8004-bridge-v1",
      "index": "https://www.rightshold.com/v1/erc8004",
      "resolve": "https://www.rightshold.com/v1/erc8004/{chain_id}/{agent_id}",
      "page": "https://www.rightshold.com/erc8004"
    },
    "kya_alias": "not published — see not_asserted.erc8004_alias",
    "write_half": "disabled in code — see not_asserted.erc8004_write"
  },
  "not_asserted": [
    {
      "id": "vc_conformance",
      "statement": "Rightshold makes no statement that its credential conforms to the W3C Verifiable Credentials Data Model or to any other external specification. The document uses that data model's member names and its context URL, and departs from strict serializations in the items below. Any conformance statement waits on an owner decision."
    },
    {
      "id": "issuer_did",
      "statement": "The issuer is identified by its HTTPS URL only. No DID document for the issuer is published, and no DID identifier of any method is asserted for the issuer. Agent keys do carry did:key identifiers — those identify the agent's key, never the issuer."
    },
    {
      "id": "embedded_proof",
      "statement": "The credential's signature travels beside the document in the response envelope, as a base64 Ed25519 signature over the document's canonical JSON — not as a proof member embedded in the document. A document on its own does not carry its proof; verification pairs it with the envelope signature or re-reads the live record."
    },
    {
      "id": "status_list",
      "statement": "credentialStatus is the platform's own live-status type: status is answered by re-reading the live record at the status URL. No status list is published, and no status-list type from any external specification is used."
    },
    {
      "id": "registry_listings",
      "statement": "Rightshold's issuer metadata is not submitted to, and is not listed in, any external trust registry."
    },
    {
      "id": "erc8004_alias",
      "statement": "The ERC-8004 read bridge resolves an ERC-8004 registry identity into an evidence document, and no more: no ERC-8004 identifier is accepted as an alias in the RS-KYA lookup, and no link between an ERC-8004 identity and an RS-KYA record is asserted. The bridge asserts a link only under its countersign rule, which public records cannot satisfy today."
    },
    {
      "id": "erc8004_write",
      "statement": "Rightshold writes nothing to the ERC-8004 registries. The posting half of the bridge is disabled in code and refuses every call, pending an owner decision."
    }
  ],
  "generated_at": "2026-11-11T08:05:41.447Z"
}