Reference

The /.well-known/id config

The file on your domain that says where sign-ins may return, how codes are redeemed, what you require, what you declare about data, and whether you accept delegation.

Discovery

For a domain D, wellknown.id tries both:

  • HTTPS: GET https://D/.well-known/id, returning JSON.
  • DNS: a TXT record at _wellknown-id.D, with the value wellknownid=cbor:<base64url(CBOR)>. The CBOR follows the same schema as the JSON. A record may span several TXT strings, which are joined in order.

If both give a valid config, DNS wins. Results are cached for at most an hour (a few minutes today). The DNS form is size-constrained, so it should carry only redirect_uris, and jwks, token_endpoint_auth_method and self_issued where they're used; publish policies over HTTPS.

Serve the file to any origin (Access-Control-Allow-Origin: *): it's public, and a holder's wallet in a browser reads your terms from it.

Schema (version 1)

{
  "version": 1,
  "relying_parties": [
    {
      "redirect_uris": ["https://acme.example/callback"],
      "jwks": { "keys": [{ "kty": "OKP", "crv": "Ed25519", "x": "…", "kid": "2026-09" }] },
      "token_endpoint_auth_method": "private_key_jwt",
      "client_name": "Acme",
      "logo_uri": "https://acme.example/logo.svg",
      "policy": "deny not_p256 if principal.key_type != \"P-256\";",
      "self_issued": "forbid"
    }
  ],
  "policy": "…karu source applying to every entry on this domain…",
  "privacy": {
    "controller": "Acme Ltd",
    "notice": { "url": "https://acme.example/privacy", "version": "3", "sha256": "…64 hex digits…" },
    "collects": ["age_equal_or_over"],
    "purposes": ["age checks at checkout"],
    "retention": "P30D",
    "erasure_endpoint": "https://acme.example/wellknown/erasure"
  },
  "delegation": {
    "accept": true,
    "hpke_public_key": { "kty": "OKP", "crv": "X25519", "x": "…" },
    "mailbox": "https://acme.example/mailbox",
    "scopes": [{ "name": "read-statements", "description": "Read your statements" }],
    "max_days": 90
  }
}
FieldRequired
versionyes1. Unknown versions are refused.
relying_parties[].redirect_urisyesAbsolute https URIs on this domain or its subdomains, matched exactly. A URI may appear in only one entry.
relying_parties[].jwksnoYour public keys, { "keys": [ … ] }: Ed25519 (OKP) or P-256 (EC). A key with private members (d and the rest) is refused.
relying_parties[].token_endpoint_auth_methodnonone (the default: a public client, with PKCE) or private_key_jwt, which needs jwks.
client_name, logo_urinoShown on the sign-in pages.
relying_parties[].self_issuednoallow or forbid (the default): whether this entry takes self-issued tokens straight from the person's page, verified by your site (the web SDK). A request for one that the entry forbids is refused before the person is asked anything.
relying_parties[].policy, policynokaru source (policies). The top-level one applies to every entry and an entry's to that entry; both apply. Self-contained (no import), at most 32 KiB. One that doesn't compile refuses the client.
privacyno, for nowWhat you collect and why: controller, notice (url, version, sha256 as 64 lowercase hex digits), collects (claim names), purposes, retention (an ISO 8601 duration such as P30D, or words) and erasure_endpoint (HTTPS), where holders' signed erasure requests are posted. Wallets fetch it only when the holder acts (withdrawing consent, asking for erasure), never in the background.
delegationnoWhether you accept sign-ins by someone acting for a holder: accept, hpke_public_key (the X25519 key grants are sealed to), mailbox (the HTTPS URL of the mailbox you read grants from; wellknown.id's if absent), scopes (what may be delegated, each a lower-case name and a description shown to the holder) and max_days (the longest a grant may run: 365 at most, and by default). Delegation has the whole of it.

mfa_providers is reserved for MFA orchestration later.

Your client_id

Your client_id is your domain: a bare, lowercase hostname, internationalised names in punycode, with no port, path or trailing dot. https://Acme.example/ and acme.example mean the same. The types as wellknown.id reads them are in the API reference.