{
  "openapi": "3.1.0",
  "info": {
    "title": "OktoID platform API",
    "version": "v1",
    "description": "The public gateway of OktoID, a national-scale self-sovereign digital identity platform. Its routes accept the staff population (`x-oktoid-population`). Generated from the contracts by apps/gateway/scripts/openapi.ts; never edited by hand. Health routes are internal and are not part of this API."
  },
  "servers": [
    {
      "url": "https://api.{domain}",
      "variables": {
        "domain": {
          "default": "oktoid.app"
        }
      }
    }
  ],
  "tags": [
    {
      "name": "subjects"
    },
    {
      "name": "persons"
    },
    {
      "name": "grants"
    },
    {
      "name": "me"
    },
    {
      "name": "identifiers"
    },
    {
      "name": "meta"
    }
  ],
  "paths": {
    "/api/v1/subjects": {
      "post": {
        "operationId": "createSubject",
        "summary": "Create an organization below the caller scope",
        "description": "Requires `Idempotency-Key`. `kind` may be omitted from the body; the gateway then reads it as `organization`, the only kind slice 0 creates. The new subject path is `parentPath` (default: the caller scope path) plus `slug`; a parent outside the caller scope is refused.\n\nTwo rules on `displayName` beyond its schema, both because an organization name is published in the trust tree once the organization is admitted. Its letters must come from the scripts this deployment allows (Latin and Arabic unless the operator configured otherwise), and no unbroken run of letters may mix two scripts (a space, hyphen, slash, digit or any punctuation ends a run, so a Latin word joined to an Arabic word by a hyphen is accepted); digits, punctuation and combining marks are always allowed. A name that fails is refused with 400 and code `subject.name_script`. And it must not read the same as the name of an organization above the new one or beside it, once capitals, character width, invisible characters and spacing are set aside: 409 with code `subject.name_taken`, whose detail names the path of that organization. Only organizations inside the caller scope are compared, so a 201 is not a promise: the same comparison is made across the whole trust tree when the organization is admitted.",
        "tags": ["subjects"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubjectCreateRequest"
              },
              "examples": {
                "organization": {
                  "summary": "An organization beneath the anchor",
                  "value": {
                    "kind": "organization",
                    "slug": "acme",
                    "displayName": "Acme Licensing Office",
                    "parentPath": "/authority"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The subject was created.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubjectView"
                },
                "examples": {
                  "organization": {
                    "summary": "An organization beneath the anchor",
                    "value": {
                      "id": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "kind": "organization",
                      "displayName": "Acme Licensing Office",
                      "path": "/authority/acme",
                      "status": "active",
                      "createdAt": "2026-03-03T09:41:07.552Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "The request body has an unsupported media type. Send application/json. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "subject.create"
      }
    },
    "/api/v1/subjects/{id}": {
      "get": {
        "operationId": "getSubject",
        "summary": "Read one subject",
        "description": "A subject that does not exist and one outside the caller scope get the same 404.",
        "tags": ["subjects"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The subject.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubjectView"
                },
                "examples": {
                  "organization": {
                    "summary": "An organization beneath the anchor",
                    "value": {
                      "id": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "kind": "organization",
                      "displayName": "Acme Licensing Office",
                      "path": "/authority/acme",
                      "status": "active",
                      "createdAt": "2026-03-03T09:41:07.552Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "subject.read"
      }
    },
    "/api/v1/persons": {
      "post": {
        "operationId": "createPerson",
        "summary": "Create a person below the caller scope",
        "description": "Requires `Idempotency-Key`. The person is a leaf below `parentPath` (default: the caller scope path); a parent outside the caller scope, or a parent that is itself a person, is refused. `givenName`, `familyName` and `email` are personal data: stored encrypted under the person's own key, never logged, and absent from the reply once the person is erased. An address another person already has (compared without regard to case) is refused with 409 and code `person.email_taken`. `invitation` says where the invitation stands.",
        "tags": ["persons"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonCreateRequest"
              },
              "examples": {
                "invited": {
                  "summary": "A person invited to the staff realm as she is created",
                  "value": {
                    "givenName": "Layla",
                    "familyName": "Haddad",
                    "email": "layla.haddad@acme.example.org",
                    "parentPath": "/authority/acme",
                    "invite": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The person was created.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonView"
                },
                "examples": {
                  "invited": {
                    "summary": "Created, with the invitation on its way",
                    "value": {
                      "id": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "kind": "person",
                      "path": "/authority/acme/0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "status": "active",
                      "createdAt": "2026-03-03T09:44:30.118Z",
                      "erased": false,
                      "givenName": "Layla",
                      "familyName": "Haddad",
                      "email": "layla.haddad@acme.example.org",
                      "invitation": {
                        "state": "pending"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "The request body has an unsupported media type. Send application/json. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "person.create"
      }
    },
    "/api/v1/persons/{id}": {
      "get": {
        "operationId": "getPerson",
        "summary": "Read one person",
        "description": "A person that does not exist and one outside the caller scope get the same 404. After erasure the reply has `erased: true` and no personal field.",
        "tags": ["persons"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The person.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonView"
                },
                "examples": {
                  "person": {
                    "summary": "A person whose invitation was sent",
                    "value": {
                      "id": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "kind": "person",
                      "path": "/authority/acme/0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "status": "active",
                      "createdAt": "2026-03-03T09:44:30.118Z",
                      "erased": false,
                      "givenName": "Layla",
                      "familyName": "Haddad",
                      "email": "layla.haddad@acme.example.org",
                      "invitation": {
                        "state": "sent"
                      }
                    }
                  },
                  "erased": {
                    "summary": "A person after erasure",
                    "value": {
                      "id": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "kind": "person",
                      "path": "/authority/acme/0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "status": "active",
                      "createdAt": "2026-03-03T09:44:30.118Z",
                      "erased": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "person.read"
      }
    },
    "/api/v1/persons/{id}/invitation": {
      "post": {
        "operationId": "invitePerson",
        "summary": "Invite a person to the staff realm again",
        "description": "Requires `Idempotency-Key`; takes no body. Sends a fresh invitation to the address on file, for an invitation that expired, failed or was never asked for. An invitation still in progress is answered as it stands and nothing new is sent. Takes the `person.create` permission. An erased person, and one with no address on file (the root account), are refused with 409 `state`.",
        "tags": ["persons"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "202": {
            "description": "The invitation as it now stands: `pending` when a fresh one was requested.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonInviteReply"
                },
                "examples": {
                  "requested": {
                    "summary": "A fresh invitation, requested",
                    "value": {
                      "personId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "invitation": {
                        "state": "pending"
                      }
                    }
                  },
                  "failed": {
                    "summary": "A failed invitation, answered as it stands",
                    "value": {
                      "personId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "invitation": {
                        "state": "failed",
                        "failureReason": "provider_conflict"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "person.create"
      }
    },
    "/api/v1/grants": {
      "post": {
        "operationId": "createGrant",
        "summary": "Grant a bundle to a grantee for an organization",
        "description": "Requires `Idempotency-Key`. Delegation narrows: the bundle's permissions must be a subset of the caller's own, for an organization inside the caller's own subtree; anything else is refused in the standard shape. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (`principalCacheSeconds`, never past the token's own expiry), and a revoked grant authorises until that entry expires.",
        "tags": ["grants"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantCreateRequest"
              },
              "examples": {
                "administrator": {
                  "summary": "The organization's administrator",
                  "value": {
                    "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                    "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                    "bundle": "ENTITY_ADMIN"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The grant was created.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantView"
                },
                "examples": {
                  "active": {
                    "summary": "The grant, active",
                    "value": {
                      "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
                      "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "bundle": "ENTITY_ADMIN",
                      "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
                      "status": "active",
                      "createdAt": "2026-03-03T09:46:02.871Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "The request body has an unsupported media type. Send application/json. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "grant.create"
      },
      "get": {
        "operationId": "listGrants",
        "summary": "List the grants inside the caller scope",
        "description": "Filtered by organization, grantee and status, newest first, in pages: pass `nextCursor` back as `cursor`. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (`principalCacheSeconds`, never past the token's own expiry), and a revoked grant authorises until that entry expires.",
        "tags": ["grants"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActingFor"
          },
          {
            "name": "organization",
            "in": "query",
            "required": false,
            "description": "Only grants for this organization.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "name": "grantee",
            "in": "query",
            "required": false,
            "description": "Only grants held by this grantee.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "enum": ["active", "revoked"]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grants.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantListReply"
                },
                "examples": {
                  "page": {
                    "summary": "A page with more to read",
                    "value": {
                      "grants": [
                        {
                          "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
                          "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                          "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                          "bundle": "ENTITY_ADMIN",
                          "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
                          "status": "active",
                          "createdAt": "2026-03-03T09:46:02.871Z"
                        }
                      ],
                      "nextCursor": "page-2.0199c1f4-7c20"
                    }
                  },
                  "last": {
                    "summary": "The last page, empty",
                    "value": {
                      "grants": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "grant.read"
      }
    },
    "/api/v1/grants/{id}/revoke": {
      "post": {
        "operationId": "revokeGrant",
        "summary": "Revoke a grant",
        "description": "Requires `Idempotency-Key`; takes no body. Revoking an already revoked grant answers the same revoked grant. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (`principalCacheSeconds`, never past the token's own expiry), and a revoked grant authorises until that entry expires.",
        "tags": ["grants"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The grant, revoked.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantView"
                },
                "examples": {
                  "revoked": {
                    "summary": "The grant, revoked",
                    "value": {
                      "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
                      "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "bundle": "ENTITY_ADMIN",
                      "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
                      "status": "revoked",
                      "createdAt": "2026-03-03T09:46:02.871Z",
                      "revokedAt": "2026-04-14T15:20:44.309Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "grant.revoke"
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "summary": "The organizations the caller holds a grant at",
        "description": "Answers the caller's own active grants: for each, the organization id (the value to send as `Oktoid-Acting-For` on every other call), its path and the bundle code. It acts for no organization, so it takes no `Oktoid-Acting-For` (a header sent is ignored) and needs no permission. A caller with no grant, a token that maps to no principal and a suspended principal all get 200 with an empty list. Read fresh on every call, not through the principal cache, so a grant made or revoked shows at once.",
        "tags": ["me"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's active grants, possibly none.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeGetReply"
                },
                "examples": {
                  "administrator": {
                    "summary": "An organization's administrator",
                    "value": {
                      "grants": [
                        {
                          "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                          "path": "/authority/acme",
                          "bundle": "ENTITY_ADMIN"
                        }
                      ]
                    }
                  },
                  "none": {
                    "summary": "A caller with no grant",
                    "value": {
                      "grants": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "none"
      }
    },
    "/api/v1/identifiers": {
      "post": {
        "operationId": "createIdentifier",
        "summary": "Create a decentralised identifier for an organization",
        "description": "Requires `Idempotency-Key`. Names an organization inside the caller scope; the issuance module reads the organization itself and takes nothing else about it from the body. An organization may hold one identifier that has not failed: a second is refused with 409 and code `identifier.exists`.\n\nTwo methods exist. `webvh`, the default, publishes a signed, append-only log: every change is an entry chained to the one before, so a verifier can tell a key rotation from a substitution, and the next update key is committed in advance, so a lost key does not freeze the identifier. `web` publishes one replaceable document with no history and no recovery, and whoever controls the domain can replace it; choose it only for verifiers that resolve nothing else. A `webvh` identifier is also served in `did:web` form beside its log, so it rarely needs choosing. The method is fixed at creation, and a deployment may not permit `web`, in which case asking for it is refused with 400 and code `identifier.method_not_permitted`, at `body.method`.\n\nThe answer is 202 and the identifier in state `placing`: its keys are generated and its document rendered just after this reply, once the organization has been placed on an agent. Read the identifier at the `Location` given until it is `prepared`, then preview it and publish it. Nothing is published by creating.\n\n`host` serves the identifier on a delegated host instead of the organization's own publisher host. Its DNS must already point at the platform: that is checked at every publication, which is refused with 409 and code `identifier.host_unverified` while the name resolves elsewhere; correct the DNS record and publish again. A name that is an address, or that sits under the platform's own domains anywhere but beneath the organization's own, is refused with 400 and code `identifier.host_refused`, at `body.host`. Without `host`, the organization's own publisher host is used, and when another identifier already serves that host the answer is 409 with code `identifier.host_taken`; the answer never says whose.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentifierCreateRequest"
              },
              "examples": {
                "default": {
                  "summary": "A did:webvh identifier on the organization own host",
                  "value": {
                    "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e"
                  }
                },
                "delegatedWeb": {
                  "summary": "A did:web identifier on a delegated host",
                  "value": {
                    "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                    "method": "web",
                    "host": "id.acme.example"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The identifier, in state `placing`. Read it again.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Location": {
                "description": "Where to read the identifier: `/api/v1/identifiers/{id}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierView"
                },
                "examples": {
                  "placing": {
                    "summary": "Created, waiting to be placed",
                    "value": {
                      "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "path": "/authority/acme",
                      "method": "webvh",
                      "history": true,
                      "host": "acme.identity.example.org",
                      "status": "placing",
                      "version": 0,
                      "versionLimit": 24,
                      "createdAt": "2026-03-03T10:02:11.004Z",
                      "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "The request body has an unsupported media type. Send application/json. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.create"
      },
      "get": {
        "operationId": "listIdentifiers",
        "summary": "List the identifiers inside the caller scope",
        "description": "Filtered by organization and status, in pages: pass `nextCursor` back as `cursor`. A cursor this list did not issue is refused with 400 and code `identifier.cursor_invalid`, at `query.cursor`. An identifier outside the caller scope is never listed.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ActingFor"
          },
          {
            "name": "organization",
            "in": "query",
            "required": false,
            "description": "Only the identifier of this organization.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only identifiers in this state.",
            "schema": {
              "enum": ["placing", "prepared", "published", "failed"]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "The most entries to return in one page, at most 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `nextCursor` of the previous page. A value this list did not issue is refused with 400.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The identifiers.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierListReply"
                },
                "examples": {
                  "one": {
                    "summary": "One published identifier",
                    "value": {
                      "identifiers": [
                        {
                          "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                          "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                          "path": "/authority/acme",
                          "method": "webvh",
                          "history": true,
                          "host": "acme.identity.example.org",
                          "status": "published",
                          "version": 1,
                          "versionLimit": 24,
                          "createdAt": "2026-03-03T10:02:11.004Z",
                          "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                          "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                          "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                          "renderedAt": "2026-03-03T10:02:12.870Z",
                          "previewedAt": "2026-03-03T10:05:40.215Z",
                          "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                          "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                          "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                          "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                          "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                          "retiredUpdateKeyIds": [
                            "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"
                          ],
                          "publishedAt": "2026-03-03T10:06:02.593Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.read"
      }
    },
    "/api/v1/identifiers/{id}": {
      "get": {
        "operationId": "getIdentifier",
        "summary": "Read one identifier",
        "description": "An identifier that does not exist and one outside the caller scope get the same 404. This is the route to poll after creating: `status` moves from `placing` to `prepared` within seconds. `method` and `history` say which kind of identifier it is: `history` is `false` for `did:web`, whose changes cannot be verified against what came before.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The identifier.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierView"
                },
                "examples": {
                  "prepared": {
                    "summary": "Prepared and previewed, waiting to be published",
                    "value": {
                      "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "path": "/authority/acme",
                      "method": "webvh",
                      "history": true,
                      "host": "acme.identity.example.org",
                      "status": "prepared",
                      "version": 0,
                      "versionLimit": 24,
                      "createdAt": "2026-03-03T10:02:11.004Z",
                      "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                      "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
                      "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                      "renderedAt": "2026-03-03T10:02:12.870Z",
                      "previewedAt": "2026-03-03T10:05:40.215Z",
                      "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
                    }
                  },
                  "published": {
                    "summary": "Published and resolvable",
                    "value": {
                      "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "path": "/authority/acme",
                      "method": "webvh",
                      "history": true,
                      "host": "acme.identity.example.org",
                      "status": "published",
                      "version": 1,
                      "versionLimit": 24,
                      "createdAt": "2026-03-03T10:02:11.004Z",
                      "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                      "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                      "renderedAt": "2026-03-03T10:02:12.870Z",
                      "previewedAt": "2026-03-03T10:05:40.215Z",
                      "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                      "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                      "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                      "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                      "retiredUpdateKeyIds": [
                        "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"
                      ],
                      "publishedAt": "2026-03-03T10:06:02.593Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.read"
      }
    },
    "/api/v1/identifiers/{id}/preview": {
      "get": {
        "operationId": "previewIdentifier",
        "summary": "Read the exact DID document that will be published",
        "description": "The document as it will be published, the URLs it will be served at and the keys behind it. Reading it is what allows publication: publish is refused until the current document has been previewed, and a document rendered again since the last preview needs a new one. `document` is canonical JSON text; parse it to read it, and compare `documentDigest` with the SHA-256 of its bytes to know nothing changed it on the way. For a `webvh` identifier, `parallelDocument` is the `did:web` form served beside the log. An identifier still in `placing` has no document yet and is refused with 409; read the identifier until it is `prepared`.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The publication preview.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierPreview"
                },
                "examples": {
                  "first": {
                    "summary": "The first version of a did:webvh identifier",
                    "value": {
                      "identifier": {
                        "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                        "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                        "path": "/authority/acme",
                        "method": "webvh",
                        "history": true,
                        "host": "acme.identity.example.org",
                        "status": "prepared",
                        "version": 0,
                        "versionLimit": 24,
                        "createdAt": "2026-03-03T10:02:11.004Z",
                        "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                        "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                        "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
                        "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                        "renderedAt": "2026-03-03T10:02:12.870Z",
                        "previewedAt": "2026-03-03T10:05:40.215Z",
                        "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                        "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
                      },
                      "version": 1,
                      "document": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:web:acme.identity.example.org\"],\"assertionMethod\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"authentication\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"id\":\"did:webvh:{SCID}:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:webvh:{SCID}:acme.identity.example.org\",\"id\":\"did:webvh:{SCID}:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
                      "documentDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                      "parallelDocument": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:webvh:{SCID}:acme.identity.example.org\"],\"assertionMethod\":[\"did:web:acme.identity.example.org#key-1\"],\"authentication\":[\"did:web:acme.identity.example.org#key-1\"],\"id\":\"did:web:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:web:acme.identity.example.org\",\"id\":\"did:web:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
                      "servedAt": [
                        "https://acme.identity.example.org/.well-known/did.jsonl",
                        "https://acme.identity.example.org/.well-known/did.json"
                      ],
                      "scid": null,
                      "scidPlaceholder": "{SCID}"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.read"
      }
    },
    "/api/v1/identifiers/{id}/publish": {
      "post": {
        "operationId": "publishIdentifier",
        "summary": "Publish the previewed DID document",
        "description": "Requires `Idempotency-Key`; takes no body. The agent signs the previewed document with keys that never leave their token, and the result is written to the identifier host: the log at `/.well-known/did.jsonl` and the `did:web` form at `/.well-known/did.json` for `webvh`, the second alone for `web`. The identifier then resolves, and a resolver may go on seeing the previous version for up to five minutes.\n\nRefused with 409 when the current document has not been previewed, when the identifier is not `prepared`, and when another publication of the same identifier won the race; read the identifier again and decide. What is signed is compared with what was previewed before a byte is written. If they differ nothing is published, the identifier becomes `failed` with `failureReason: document-mismatch` and its keys are destroyed, and the refusal is recorded in the audit trail.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The identifier, in state `published`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierView"
                },
                "examples": {
                  "published": {
                    "summary": "Published and resolvable",
                    "value": {
                      "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "path": "/authority/acme",
                      "method": "webvh",
                      "history": true,
                      "host": "acme.identity.example.org",
                      "status": "published",
                      "version": 1,
                      "versionLimit": 24,
                      "createdAt": "2026-03-03T10:02:11.004Z",
                      "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                      "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                      "renderedAt": "2026-03-03T10:02:12.870Z",
                      "previewedAt": "2026-03-03T10:05:40.215Z",
                      "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                      "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                      "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                      "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                      "retiredUpdateKeyIds": [
                        "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"
                      ],
                      "publishedAt": "2026-03-03T10:06:02.593Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.publish"
      }
    },
    "/api/v1/identifiers/{id}/update": {
      "post": {
        "operationId": "updateIdentifier",
        "summary": "Begin a key rotation for a published identifier",
        "description": "Requires `Idempotency-Key`; takes no body. Generates a new assertion key and renders the document again with it. The identifier returns to state `prepared` and the version already published stays served; preview the new document, then publish it. For `webvh` the publication appends one log entry, signed by the current update key, that a verifier can check against the entry before. For `web` it overwrites the document, and nothing lets a verifier tell that from a substitution. The replaced key is kept and listed in `retiredAssertionKeyIds`, because a credential signed under it must stay verifiable. The host and the method cannot be changed. An identifier that is not `published` is refused with 409.",
        "tags": ["identifiers"],
        "security": [
          {
            "staffBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            }
          },
          {
            "$ref": "#/components/parameters/ActingFor"
          }
        ],
        "responses": {
          "200": {
            "description": "The identifier, in state `prepared` again.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentifierView"
                },
                "examples": {
                  "prepared": {
                    "summary": "A new document rendered, waiting for preview and publication",
                    "value": {
                      "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                      "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                      "path": "/authority/acme",
                      "method": "webvh",
                      "history": true,
                      "host": "acme.identity.example.org",
                      "status": "prepared",
                      "version": 0,
                      "versionLimit": 24,
                      "createdAt": "2026-03-03T10:02:11.004Z",
                      "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                      "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
                      "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                      "renderedAt": "2026-03-03T10:02:12.870Z",
                      "previewedAt": "2026-03-03T10:05:40.215Z",
                      "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                      "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed. callerShould: fix-and-retry.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Authentication is required. callerShould: authenticate.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token is valid but maps to no usable principal. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "The resource was not found. The same answer for a subject that does not exist and one outside the caller's scope. callerShould: stop.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The resource is not in a state that allows this (callerShould: re-read-and-decide), or A concurrent change conflicted with this request (callerShould: re-read-and-retry-once). Told apart by the type URI; the `code` member names the refusal (for example `subject.slug_taken`). A conflict the module marked not retryable carries callerShould: re-read-and-decide.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. The rate limit for this caller was reached: 600 a minute per authenticated principal, 60 a minute per client address before authentication (both configurable). callerShould: retry-with-backoff, after the number of seconds in `Retry-After`.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "An internal error occurred. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "A system we depend on failed. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The service cannot serve this right now. callerShould: retry-with-backoff.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-oktoid-population": "staff",
        "x-oktoid-surface": "public",
        "x-oktoid-permission": "identifier.update"
      }
    },
    "/api/v1/version": {
      "get": {
        "operationId": "getVersion",
        "summary": "The service and the release it was built from",
        "description": "Unauthenticated, and answered by both surfaces. The deploy agent waits on this until the release it deployed is the one reported. `version` is what the image build set: `0.0.0+` and the abbreviated commit the image was built from, or `dev` for a build that set none.",
        "tags": ["meta"],
        "security": [],
        "responses": {
          "200": {
            "description": "The release.",
            "headers": {
              "X-Correlation-Id": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "examples": {
                  "release": {
                    "summary": "An image built from a commit",
                    "value": {
                      "service": "gateway",
                      "version": "0.0.0+7e3ad643a1f2"
                    }
                  }
                },
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": ["service", "version"],
                  "properties": {
                    "service": {
                      "const": "gateway"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "staffBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "An access token from the staff realm, whose `aud` contains `oktoid-api` (a WSO2 machine token also lists its client id). A token from any other realm, including the holder realm, is refused with 401."
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Whole seconds to wait before the next request.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "CorrelationId": {
        "description": "The correlation id of the request, on every response including 401.",
        "schema": {
          "type": "string"
        }
      }
    },
    "parameters": {
      "ActingFor": {
        "name": "Oktoid-Acting-For",
        "in": "header",
        "required": false,
        "description": "The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it.",
        "schema": {
          "type": "string",
          "pattern": "^[\\x21-\\x7e]{8,128}$"
        }
      }
    },
    "schemas": {
      "SubjectView": {
        "type": "object",
        "required": ["id", "kind", "displayName", "path", "status", "createdAt"],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "kind": {
            "anyOf": [
              {
                "type": "string",
                "const": "organization"
              },
              {
                "type": "string",
                "const": "person"
              }
            ]
          },
          "displayName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "path": {
            "type": "string",
            "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
            "maxLength": 1024
          },
          "status": {
            "anyOf": [
              {
                "type": "string",
                "const": "active"
              },
              {
                "type": "string",
                "const": "suspended"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "SubjectCreateRequest": {
        "type": "object",
        "required": ["kind", "slug", "displayName"],
        "properties": {
          "kind": {
            "type": "string",
            "const": "organization"
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$",
            "allOf": [
              {
                "pattern": "^(?!..--)"
              }
            ]
          },
          "displayName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "parentPath": {
            "type": "string",
            "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
            "maxLength": 1024
          }
        },
        "additionalProperties": false
      },
      "PersonView": {
        "type": "object",
        "required": ["id", "kind", "path", "status", "createdAt", "erased"],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "kind": {
            "type": "string",
            "const": "person"
          },
          "path": {
            "type": "string",
            "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
            "maxLength": 1024
          },
          "status": {
            "anyOf": [
              {
                "type": "string",
                "const": "active"
              },
              {
                "type": "string",
                "const": "suspended"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "erased": {
            "type": "boolean"
          },
          "givenName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "familyName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "email": {
            "type": "string",
            "format": "email",
            "minLength": 3,
            "maxLength": 254
          },
          "invitation": {
            "type": "object",
            "required": ["state"],
            "properties": {
              "state": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "none"
                  },
                  {
                    "type": "string",
                    "const": "pending"
                  },
                  {
                    "type": "string",
                    "const": "sent"
                  },
                  {
                    "type": "string",
                    "const": "failed"
                  }
                ]
              },
              "failureReason": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "provider_rejected"
                  },
                  {
                    "type": "string",
                    "const": "provider_conflict"
                  },
                  {
                    "type": "string",
                    "const": "account_linked_elsewhere"
                  },
                  {
                    "type": "string",
                    "const": "person_erased"
                  },
                  {
                    "type": "string",
                    "const": "other"
                  }
                ]
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "PersonCreateRequest": {
        "type": "object",
        "required": ["givenName", "familyName", "email"],
        "properties": {
          "givenName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "familyName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^(?![\\u061c\\u200e\\u200f])(?=.*[^\\p{White_Space}\\p{Default_Ignorable_Code_Point}])(?!.*[\\p{M}\\u200c\\u200d]{5})[^\\u0000-\\u001f\\u007f-\\u009f\\u200b\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff\\ud800-\\udfff]*(?<![\\u061c\\u200e\\u200f])$",
            "x-normalize": "NFC"
          },
          "email": {
            "type": "string",
            "format": "email",
            "minLength": 3,
            "maxLength": 254
          },
          "parentPath": {
            "type": "string",
            "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
            "maxLength": 1024
          },
          "invite": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PersonInviteReply": {
        "type": "object",
        "required": ["personId", "invitation"],
        "properties": {
          "personId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "invitation": {
            "type": "object",
            "required": ["state"],
            "properties": {
              "state": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "none"
                  },
                  {
                    "type": "string",
                    "const": "pending"
                  },
                  {
                    "type": "string",
                    "const": "sent"
                  },
                  {
                    "type": "string",
                    "const": "failed"
                  }
                ]
              },
              "failureReason": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "provider_rejected"
                  },
                  {
                    "type": "string",
                    "const": "provider_conflict"
                  },
                  {
                    "type": "string",
                    "const": "account_linked_elsewhere"
                  },
                  {
                    "type": "string",
                    "const": "person_erased"
                  },
                  {
                    "type": "string",
                    "const": "other"
                  }
                ]
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "GrantView": {
        "type": "object",
        "required": [
          "id",
          "granteeId",
          "organizationId",
          "bundle",
          "grantedBy",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "granteeId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "organizationId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "bundle": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]{0,63}$"
          },
          "grantedBy": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "status": {
            "anyOf": [
              {
                "type": "string",
                "const": "active"
              },
              {
                "type": "string",
                "const": "revoked"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "GrantCreateRequest": {
        "type": "object",
        "required": ["granteeId", "organizationId", "bundle"],
        "properties": {
          "granteeId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "organizationId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "bundle": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]{0,63}$"
          }
        },
        "additionalProperties": false
      },
      "GrantListReply": {
        "type": "object",
        "required": ["grants"],
        "properties": {
          "grants": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "granteeId",
                "organizationId",
                "bundle",
                "grantedBy",
                "status",
                "createdAt"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                },
                "granteeId": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                },
                "organizationId": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                },
                "bundle": {
                  "type": "string",
                  "pattern": "^[A-Z][A-Z0-9_]{0,63}$"
                },
                "grantedBy": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                },
                "status": {
                  "anyOf": [
                    {
                      "type": "string",
                      "const": "active"
                    },
                    {
                      "type": "string",
                      "const": "revoked"
                    }
                  ]
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "revokedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "additionalProperties": false
            },
            "maxItems": 200
          },
          "nextCursor": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          }
        },
        "additionalProperties": false
      },
      "MeGetReply": {
        "type": "object",
        "required": ["grants"],
        "properties": {
          "grants": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["organizationId", "path", "bundle"],
              "properties": {
                "organizationId": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
                },
                "path": {
                  "type": "string",
                  "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
                  "maxLength": 1024
                },
                "bundle": {
                  "type": "string",
                  "pattern": "^[A-Z][A-Z0-9_]{0,63}$"
                }
              },
              "additionalProperties": false
            },
            "maxItems": 200
          }
        },
        "additionalProperties": false
      },
      "IdentifierView": {
        "type": "object",
        "required": [
          "id",
          "organizationId",
          "path",
          "method",
          "history",
          "host",
          "status",
          "version",
          "createdAt",
          "createdBy"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "The identifier. The `{id}` of the read, preview, publish and update routes."
          },
          "organizationId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "The organization the identifier belongs to: its subject id."
          },
          "path": {
            "type": "string",
            "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
            "maxLength": 1024,
            "description": "The organization's path when the identifier was created."
          },
          "method": {
            "anyOf": [
              {
                "type": "string",
                "const": "webvh"
              },
              {
                "type": "string",
                "const": "web"
              }
            ],
            "description": "The DID method, chosen at creation and never changed. `webvh`: a signed, append-only log, so every change can be verified against the one before it. `web`: one replaceable document with no history, so a key change cannot be told from a substitution and whoever controls the domain can replace it."
          },
          "history": {
            "type": "boolean",
            "description": "Whether the identifier keeps a verifiable history: `true` for `webvh`, `false` for `web`."
          },
          "host": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
            "maxLength": 253,
            "description": "The host the identifier is served on: the organization's own publisher host, `<slug>.identity.<domain>`, or the delegated host named at creation."
          },
          "status": {
            "anyOf": [
              {
                "type": "string",
                "const": "placing"
              },
              {
                "type": "string",
                "const": "prepared"
              },
              {
                "type": "string",
                "const": "published"
              },
              {
                "type": "string",
                "const": "failed"
              }
            ],
            "description": "Where it stands. `placing`: created, waiting to be placed on an agent; read again shortly. `prepared`: keys generated and a document rendered, waiting to be previewed and published. `published`: served and resolvable. `failed`: could not be published; its keys were destroyed and nothing was written."
          },
          "did": {
            "type": "string",
            "pattern": "^did:(?:webvh:[1-9A-HJ-NP-Za-km-z]{32,64}|web):[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
            "maxLength": 336,
            "description": "The DID. Absent while `placing`. For `webvh` it is also absent until the first version is published, because the DID is derived from the first log entry and there is none yet; the preview shows the placeholder in its place."
          },
          "scid": {
            "type": "string",
            "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,64}$",
            "description": "The self-certifying identifier of a `did:webvh`: the part that stays the same if the identifier moves to another host. Absent until the first version is published, and always for `did:web`."
          },
          "version": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000,
            "description": "How many versions have been published: 0 until the first, then the number of the log entry (or, for `did:web`, of the document) now served."
          },
          "versionLimit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "description": "The most versions this identifier can reach. A `did:webvh` log is sent whole to the agent that signs its next entry, and that transport carries a bounded number of entries, so the update that makes `version` equal to this is the last one possible; plan a new identifier before then. Read it from here rather than assuming a number: the limit belongs to the transport and will be raised. Absent for `did:web`, which has no log and no limit."
          },
          "versionId": {
            "type": "string",
            "pattern": "^[1-9][0-9]{0,9}-[1-9A-HJ-NP-Za-km-z]{32,64}$",
            "description": "The `versionId` of the log entry now served. Absent until the first publication, and always for `did:web`, which has no log."
          },
          "assertionKeyId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "The key credentials are signed with: the RFC 7638 thumbprint of its public key. Absent while `placing`. An update replaces it."
          },
          "updateKeyId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "The key that signs the next log entry, as a thumbprint. While a version waits to be published it is the key that will sign that version; once the version is published it is the key that version committed to, and the key that signed is spent and listed in `retiredUpdateKeyIds` only. Absent for `did:web`, where nothing signs the document."
          },
          "nextUpdateKeyId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "The update key committed to in advance (pre-rotation), as a thumbprint: the log already carries its hash, so a lost update key does not freeze the identifier. Present while a version waits to be published, as the key that version commits to; once it is published that key is `updateKeyId`, and this is absent until an update begins. Absent for `did:web`."
          },
          "retiredAssertionKeyIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            },
            "maxItems": 64,
            "uniqueItems": true,
            "description": "Assertion keys an update replaced, oldest first. They are kept, because a credential signed under one must stay verifiable. Absent when there are none."
          },
          "retiredUpdateKeyIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            },
            "maxItems": 64,
            "uniqueItems": true,
            "description": "Update keys that signed a published log entry, oldest first, as thumbprints. Each was destroyed once its entry was published: a `did:webvh` update key signs exactly one entry, and past entries are verified from the public keys in the log. Its length is the number of spent keys. Absent when there are none, and always for `did:web`."
          },
          "pendingDiscardKeyIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            },
            "maxItems": 8,
            "uniqueItems": true,
            "description": "Keys that are no longer needed and not yet confirmed destroyed, as thumbprints: a spent update key, or keys generated for a document that was never published. The next operation on the identifier destroys them first. Normally absent. A list that stays is worth an operator's look; nothing published depends on these keys."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the identifier was created."
          },
          "createdBy": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "Who created it: a subject id, never a name."
          },
          "renderedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the document now waiting, or last published, was rendered. A preview older than this no longer counts."
          },
          "previewedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the document was last previewed. A render after the preview leaves this in place, so it alone does not say the current document was seen: compare `previewedDigest`."
          },
          "previewedBy": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "Who last previewed it: a subject id. Present exactly when `previewedAt` is."
          },
          "previewedDigest": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "SHA-256 of the document that preview showed. The preview covers the identifier only while this equals the `documentDigest` a preview returns now. Present exactly when `previewedAt` is."
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the version now served was written out. Absent until then."
          },
          "failureReason": {
            "anyOf": [
              {
                "type": "string",
                "const": "document-mismatch"
              },
              {
                "type": "string",
                "const": "agent-refused"
              },
              {
                "type": "string",
                "const": "other"
              }
            ],
            "description": "Why it failed, present when `status` is `failed` and only then. `document-mismatch`: what was signed was not what was previewed, so nothing was written. `agent-refused`: the keys could not be generated or used. `other`: anything else. In every case create a new identifier."
          }
        },
        "additionalProperties": false,
        "description": "An organization's decentralised identifier (DID): where it is served, which keys stand behind it and where it stands. Every actor is a subject id and every key a thumbprint. The DID document itself is only in the preview.",
        "examples": [
          {
            "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
            "path": "/authority/acme",
            "method": "webvh",
            "history": true,
            "host": "acme.identity.example.org",
            "status": "placing",
            "version": 0,
            "versionLimit": 24,
            "createdAt": "2026-03-03T10:02:11.004Z",
            "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914"
          },
          {
            "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
            "path": "/authority/acme",
            "method": "webvh",
            "history": true,
            "host": "acme.identity.example.org",
            "status": "prepared",
            "version": 0,
            "versionLimit": 24,
            "createdAt": "2026-03-03T10:02:11.004Z",
            "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
            "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
            "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
            "renderedAt": "2026-03-03T10:02:12.870Z",
            "previewedAt": "2026-03-03T10:05:40.215Z",
            "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
          },
          {
            "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
            "path": "/authority/acme",
            "method": "webvh",
            "history": true,
            "host": "acme.identity.example.org",
            "status": "published",
            "version": 1,
            "versionLimit": 24,
            "createdAt": "2026-03-03T10:02:11.004Z",
            "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
            "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
            "renderedAt": "2026-03-03T10:02:12.870Z",
            "previewedAt": "2026-03-03T10:05:40.215Z",
            "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
            "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
            "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
            "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
            "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
            "publishedAt": "2026-03-03T10:06:02.593Z"
          },
          {
            "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
            "path": "/authority/acme",
            "method": "webvh",
            "history": true,
            "host": "acme.identity.example.org",
            "status": "prepared",
            "version": 1,
            "versionLimit": 24,
            "createdAt": "2026-03-03T10:02:11.004Z",
            "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "assertionKeyId": "pZ2cW8rN5uYh3Jt0Xq7LbD4sVf6GmK1aEoRiTnCyUw8",
            "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
            "renderedAt": "2026-09-01T08:14:03.120Z",
            "previewedAt": "2026-09-01T08:15:27.644Z",
            "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
            "previewedDigest": "9e52491553e488a9999e6b59d802e259a56759bf864c18e14b7ea7c7a3fd064d",
            "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
            "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
            "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
            "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
            "publishedAt": "2026-03-03T10:06:02.593Z",
            "nextUpdateKeyId": "H5nKq2Yx8RtV0cLm3WbZ7dPf4JsA6uEoG1iTyNwCrXk",
            "retiredAssertionKeyIds": ["dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw"]
          }
        ]
      },
      "IdentifierCreateRequest": {
        "type": "object",
        "required": ["organizationId"],
        "properties": {
          "organizationId": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "The organization to create the identifier for: an active organization inside the scope the caller acts for. It may hold one identifier that has not failed."
          },
          "method": {
            "anyOf": [
              {
                "type": "string",
                "const": "webvh"
              },
              {
                "type": "string",
                "const": "web"
              }
            ],
            "default": "webvh",
            "description": "The DID method. Leave it out for `webvh`, the default: a signed, append-only log that lets a verifier tell a key rotation from a substitution and survives a lost key. Choose `web` only for verifiers that resolve nothing else: it is one replaceable document with no history and no recovery, and whoever controls the domain can replace it. The choice is permanent for this identifier, and a deployment may not permit `web` at all."
          },
          "host": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
            "maxLength": 253,
            "description": "A delegated host to serve the identifier on, such as `id.acme.example`, whose DNS already points at the platform. Leave it out to use the organization's own publisher host, `<slug>.identity.<domain>`. A name under the platform's own domains is refused."
          }
        },
        "additionalProperties": false,
        "description": "An identifier to create for an organization.",
        "examples": [
          {
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e"
          },
          {
            "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
            "method": "web",
            "host": "id.acme.example"
          }
        ]
      },
      "IdentifierListReply": {
        "type": "object",
        "required": ["identifiers"],
        "properties": {
          "identifiers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "organizationId",
                "path",
                "method",
                "history",
                "host",
                "status",
                "version",
                "createdAt",
                "createdBy"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                  "description": "The identifier. The `{id}` of the read, preview, publish and update routes."
                },
                "organizationId": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                  "description": "The organization the identifier belongs to: its subject id."
                },
                "path": {
                  "type": "string",
                  "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
                  "maxLength": 1024,
                  "description": "The organization's path when the identifier was created."
                },
                "method": {
                  "anyOf": [
                    {
                      "type": "string",
                      "const": "webvh"
                    },
                    {
                      "type": "string",
                      "const": "web"
                    }
                  ],
                  "description": "The DID method, chosen at creation and never changed. `webvh`: a signed, append-only log, so every change can be verified against the one before it. `web`: one replaceable document with no history, so a key change cannot be told from a substitution and whoever controls the domain can replace it."
                },
                "history": {
                  "type": "boolean",
                  "description": "Whether the identifier keeps a verifiable history: `true` for `webvh`, `false` for `web`."
                },
                "host": {
                  "type": "string",
                  "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
                  "maxLength": 253,
                  "description": "The host the identifier is served on: the organization's own publisher host, `<slug>.identity.<domain>`, or the delegated host named at creation."
                },
                "status": {
                  "anyOf": [
                    {
                      "type": "string",
                      "const": "placing"
                    },
                    {
                      "type": "string",
                      "const": "prepared"
                    },
                    {
                      "type": "string",
                      "const": "published"
                    },
                    {
                      "type": "string",
                      "const": "failed"
                    }
                  ],
                  "description": "Where it stands. `placing`: created, waiting to be placed on an agent; read again shortly. `prepared`: keys generated and a document rendered, waiting to be previewed and published. `published`: served and resolvable. `failed`: could not be published; its keys were destroyed and nothing was written."
                },
                "did": {
                  "type": "string",
                  "pattern": "^did:(?:webvh:[1-9A-HJ-NP-Za-km-z]{32,64}|web):[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
                  "maxLength": 336,
                  "description": "The DID. Absent while `placing`. For `webvh` it is also absent until the first version is published, because the DID is derived from the first log entry and there is none yet; the preview shows the placeholder in its place."
                },
                "scid": {
                  "type": "string",
                  "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,64}$",
                  "description": "The self-certifying identifier of a `did:webvh`: the part that stays the same if the identifier moves to another host. Absent until the first version is published, and always for `did:web`."
                },
                "version": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 1000000,
                  "description": "How many versions have been published: 0 until the first, then the number of the log entry (or, for `did:web`, of the document) now served."
                },
                "versionLimit": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 1000000,
                  "description": "The most versions this identifier can reach. A `did:webvh` log is sent whole to the agent that signs its next entry, and that transport carries a bounded number of entries, so the update that makes `version` equal to this is the last one possible; plan a new identifier before then. Read it from here rather than assuming a number: the limit belongs to the transport and will be raised. Absent for `did:web`, which has no log and no limit."
                },
                "versionId": {
                  "type": "string",
                  "pattern": "^[1-9][0-9]{0,9}-[1-9A-HJ-NP-Za-km-z]{32,64}$",
                  "description": "The `versionId` of the log entry now served. Absent until the first publication, and always for `did:web`, which has no log."
                },
                "assertionKeyId": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$",
                  "description": "The key credentials are signed with: the RFC 7638 thumbprint of its public key. Absent while `placing`. An update replaces it."
                },
                "updateKeyId": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$",
                  "description": "The key that signs the next log entry, as a thumbprint. While a version waits to be published it is the key that will sign that version; once the version is published it is the key that version committed to, and the key that signed is spent and listed in `retiredUpdateKeyIds` only. Absent for `did:web`, where nothing signs the document."
                },
                "nextUpdateKeyId": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$",
                  "description": "The update key committed to in advance (pre-rotation), as a thumbprint: the log already carries its hash, so a lost update key does not freeze the identifier. Present while a version waits to be published, as the key that version commits to; once it is published that key is `updateKeyId`, and this is absent until an update begins. Absent for `did:web`."
                },
                "retiredAssertionKeyIds": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  },
                  "maxItems": 64,
                  "uniqueItems": true,
                  "description": "Assertion keys an update replaced, oldest first. They are kept, because a credential signed under one must stay verifiable. Absent when there are none."
                },
                "retiredUpdateKeyIds": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  },
                  "maxItems": 64,
                  "uniqueItems": true,
                  "description": "Update keys that signed a published log entry, oldest first, as thumbprints. Each was destroyed once its entry was published: a `did:webvh` update key signs exactly one entry, and past entries are verified from the public keys in the log. Its length is the number of spent keys. Absent when there are none, and always for `did:web`."
                },
                "pendingDiscardKeyIds": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  },
                  "maxItems": 8,
                  "uniqueItems": true,
                  "description": "Keys that are no longer needed and not yet confirmed destroyed, as thumbprints: a spent update key, or keys generated for a document that was never published. The next operation on the identifier destroys them first. Normally absent. A list that stays is worth an operator's look; nothing published depends on these keys."
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the identifier was created."
                },
                "createdBy": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                  "description": "Who created it: a subject id, never a name."
                },
                "renderedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the document now waiting, or last published, was rendered. A preview older than this no longer counts."
                },
                "previewedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the document was last previewed. A render after the preview leaves this in place, so it alone does not say the current document was seen: compare `previewedDigest`."
                },
                "previewedBy": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                  "description": "Who last previewed it: a subject id. Present exactly when `previewedAt` is."
                },
                "previewedDigest": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{64}$",
                  "description": "SHA-256 of the document that preview showed. The preview covers the identifier only while this equals the `documentDigest` a preview returns now. Present exactly when `previewedAt` is."
                },
                "publishedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the version now served was written out. Absent until then."
                },
                "failureReason": {
                  "anyOf": [
                    {
                      "type": "string",
                      "const": "document-mismatch"
                    },
                    {
                      "type": "string",
                      "const": "agent-refused"
                    },
                    {
                      "type": "string",
                      "const": "other"
                    }
                  ],
                  "description": "Why it failed, present when `status` is `failed` and only then. `document-mismatch`: what was signed was not what was previewed, so nothing was written. `agent-refused`: the keys could not be generated or used. `other`: anything else. In every case create a new identifier."
                }
              },
              "additionalProperties": false,
              "description": "An organization's decentralised identifier (DID): where it is served, which keys stand behind it and where it stands. Every actor is a subject id and every key a thumbprint. The DID document itself is only in the preview.",
              "examples": [
                {
                  "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                  "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                  "path": "/authority/acme",
                  "method": "webvh",
                  "history": true,
                  "host": "acme.identity.example.org",
                  "status": "placing",
                  "version": 0,
                  "versionLimit": 24,
                  "createdAt": "2026-03-03T10:02:11.004Z",
                  "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914"
                },
                {
                  "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                  "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                  "path": "/authority/acme",
                  "method": "webvh",
                  "history": true,
                  "host": "acme.identity.example.org",
                  "status": "prepared",
                  "version": 0,
                  "versionLimit": 24,
                  "createdAt": "2026-03-03T10:02:11.004Z",
                  "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                  "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
                  "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                  "renderedAt": "2026-03-03T10:02:12.870Z",
                  "previewedAt": "2026-03-03T10:05:40.215Z",
                  "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
                },
                {
                  "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                  "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                  "path": "/authority/acme",
                  "method": "webvh",
                  "history": true,
                  "host": "acme.identity.example.org",
                  "status": "published",
                  "version": 1,
                  "versionLimit": 24,
                  "createdAt": "2026-03-03T10:02:11.004Z",
                  "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                  "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                  "renderedAt": "2026-03-03T10:02:12.870Z",
                  "previewedAt": "2026-03-03T10:05:40.215Z",
                  "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                  "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                  "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                  "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                  "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
                  "publishedAt": "2026-03-03T10:06:02.593Z"
                },
                {
                  "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                  "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                  "path": "/authority/acme",
                  "method": "webvh",
                  "history": true,
                  "host": "acme.identity.example.org",
                  "status": "prepared",
                  "version": 1,
                  "versionLimit": 24,
                  "createdAt": "2026-03-03T10:02:11.004Z",
                  "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "assertionKeyId": "pZ2cW8rN5uYh3Jt0Xq7LbD4sVf6GmK1aEoRiTnCyUw8",
                  "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                  "renderedAt": "2026-09-01T08:14:03.120Z",
                  "previewedAt": "2026-09-01T08:15:27.644Z",
                  "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                  "previewedDigest": "9e52491553e488a9999e6b59d802e259a56759bf864c18e14b7ea7c7a3fd064d",
                  "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                  "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                  "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                  "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
                  "publishedAt": "2026-03-03T10:06:02.593Z",
                  "nextUpdateKeyId": "H5nKq2Yx8RtV0cLm3WbZ7dPf4JsA6uEoG1iTyNwCrXk",
                  "retiredAssertionKeyIds": [
                    "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw"
                  ]
                }
              ]
            },
            "maxItems": 200,
            "description": "One page of the identifiers inside the scope the caller acts for. Empty when there are none."
          },
          "nextCursor": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Present when there are more. Send it back as `cursor` for the next page; it is opaque."
          }
        },
        "additionalProperties": false,
        "description": "A page of identifiers.",
        "examples": [
          {
            "identifiers": [
              {
                "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                "path": "/authority/acme",
                "method": "webvh",
                "history": true,
                "host": "acme.identity.example.org",
                "status": "published",
                "version": 1,
                "versionLimit": 24,
                "createdAt": "2026-03-03T10:02:11.004Z",
                "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                "renderedAt": "2026-03-03T10:02:12.870Z",
                "previewedAt": "2026-03-03T10:05:40.215Z",
                "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
                "publishedAt": "2026-03-03T10:06:02.593Z"
              }
            ]
          }
        ]
      },
      "IdentifierPreview": {
        "type": "object",
        "required": ["identifier", "version", "document", "documentDigest", "servedAt"],
        "properties": {
          "identifier": {
            "type": "object",
            "required": [
              "id",
              "organizationId",
              "path",
              "method",
              "history",
              "host",
              "status",
              "version",
              "createdAt",
              "createdBy"
            ],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                "description": "The identifier. The `{id}` of the read, preview, publish and update routes."
              },
              "organizationId": {
                "type": "string",
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                "description": "The organization the identifier belongs to: its subject id."
              },
              "path": {
                "type": "string",
                "pattern": "^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$",
                "maxLength": 1024,
                "description": "The organization's path when the identifier was created."
              },
              "method": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "webvh"
                  },
                  {
                    "type": "string",
                    "const": "web"
                  }
                ],
                "description": "The DID method, chosen at creation and never changed. `webvh`: a signed, append-only log, so every change can be verified against the one before it. `web`: one replaceable document with no history, so a key change cannot be told from a substitution and whoever controls the domain can replace it."
              },
              "history": {
                "type": "boolean",
                "description": "Whether the identifier keeps a verifiable history: `true` for `webvh`, `false` for `web`."
              },
              "host": {
                "type": "string",
                "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
                "maxLength": 253,
                "description": "The host the identifier is served on: the organization's own publisher host, `<slug>.identity.<domain>`, or the delegated host named at creation."
              },
              "status": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "placing"
                  },
                  {
                    "type": "string",
                    "const": "prepared"
                  },
                  {
                    "type": "string",
                    "const": "published"
                  },
                  {
                    "type": "string",
                    "const": "failed"
                  }
                ],
                "description": "Where it stands. `placing`: created, waiting to be placed on an agent; read again shortly. `prepared`: keys generated and a document rendered, waiting to be previewed and published. `published`: served and resolvable. `failed`: could not be published; its keys were destroyed and nothing was written."
              },
              "did": {
                "type": "string",
                "pattern": "^did:(?:webvh:[1-9A-HJ-NP-Za-km-z]{32,64}|web):[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
                "maxLength": 336,
                "description": "The DID. Absent while `placing`. For `webvh` it is also absent until the first version is published, because the DID is derived from the first log entry and there is none yet; the preview shows the placeholder in its place."
              },
              "scid": {
                "type": "string",
                "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,64}$",
                "description": "The self-certifying identifier of a `did:webvh`: the part that stays the same if the identifier moves to another host. Absent until the first version is published, and always for `did:web`."
              },
              "version": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1000000,
                "description": "How many versions have been published: 0 until the first, then the number of the log entry (or, for `did:web`, of the document) now served."
              },
              "versionLimit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000000,
                "description": "The most versions this identifier can reach. A `did:webvh` log is sent whole to the agent that signs its next entry, and that transport carries a bounded number of entries, so the update that makes `version` equal to this is the last one possible; plan a new identifier before then. Read it from here rather than assuming a number: the limit belongs to the transport and will be raised. Absent for `did:web`, which has no log and no limit."
              },
              "versionId": {
                "type": "string",
                "pattern": "^[1-9][0-9]{0,9}-[1-9A-HJ-NP-Za-km-z]{32,64}$",
                "description": "The `versionId` of the log entry now served. Absent until the first publication, and always for `did:web`, which has no log."
              },
              "assertionKeyId": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{43}$",
                "description": "The key credentials are signed with: the RFC 7638 thumbprint of its public key. Absent while `placing`. An update replaces it."
              },
              "updateKeyId": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{43}$",
                "description": "The key that signs the next log entry, as a thumbprint. While a version waits to be published it is the key that will sign that version; once the version is published it is the key that version committed to, and the key that signed is spent and listed in `retiredUpdateKeyIds` only. Absent for `did:web`, where nothing signs the document."
              },
              "nextUpdateKeyId": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{43}$",
                "description": "The update key committed to in advance (pre-rotation), as a thumbprint: the log already carries its hash, so a lost update key does not freeze the identifier. Present while a version waits to be published, as the key that version commits to; once it is published that key is `updateKeyId`, and this is absent until an update begins. Absent for `did:web`."
              },
              "retiredAssertionKeyIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$"
                },
                "maxItems": 64,
                "uniqueItems": true,
                "description": "Assertion keys an update replaced, oldest first. They are kept, because a credential signed under one must stay verifiable. Absent when there are none."
              },
              "retiredUpdateKeyIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$"
                },
                "maxItems": 64,
                "uniqueItems": true,
                "description": "Update keys that signed a published log entry, oldest first, as thumbprints. Each was destroyed once its entry was published: a `did:webvh` update key signs exactly one entry, and past entries are verified from the public keys in the log. Its length is the number of spent keys. Absent when there are none, and always for `did:web`."
              },
              "pendingDiscardKeyIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_-]{43}$"
                },
                "maxItems": 8,
                "uniqueItems": true,
                "description": "Keys that are no longer needed and not yet confirmed destroyed, as thumbprints: a spent update key, or keys generated for a document that was never published. The next operation on the identifier destroys them first. Normally absent. A list that stays is worth an operator's look; nothing published depends on these keys."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the identifier was created."
              },
              "createdBy": {
                "type": "string",
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                "description": "Who created it: a subject id, never a name."
              },
              "renderedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the document now waiting, or last published, was rendered. A preview older than this no longer counts."
              },
              "previewedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the document was last previewed. A render after the preview leaves this in place, so it alone does not say the current document was seen: compare `previewedDigest`."
              },
              "previewedBy": {
                "type": "string",
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                "description": "Who last previewed it: a subject id. Present exactly when `previewedAt` is."
              },
              "previewedDigest": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "SHA-256 of the document that preview showed. The preview covers the identifier only while this equals the `documentDigest` a preview returns now. Present exactly when `previewedAt` is."
              },
              "publishedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the version now served was written out. Absent until then."
              },
              "failureReason": {
                "anyOf": [
                  {
                    "type": "string",
                    "const": "document-mismatch"
                  },
                  {
                    "type": "string",
                    "const": "agent-refused"
                  },
                  {
                    "type": "string",
                    "const": "other"
                  }
                ],
                "description": "Why it failed, present when `status` is `failed` and only then. `document-mismatch`: what was signed was not what was previewed, so nothing was written. `agent-refused`: the keys could not be generated or used. `other`: anything else. In every case create a new identifier."
              }
            },
            "additionalProperties": false,
            "description": "An organization's decentralised identifier (DID): where it is served, which keys stand behind it and where it stands. Every actor is a subject id and every key a thumbprint. The DID document itself is only in the preview.",
            "examples": [
              {
                "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                "path": "/authority/acme",
                "method": "webvh",
                "history": true,
                "host": "acme.identity.example.org",
                "status": "placing",
                "version": 0,
                "versionLimit": 24,
                "createdAt": "2026-03-03T10:02:11.004Z",
                "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914"
              },
              {
                "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                "path": "/authority/acme",
                "method": "webvh",
                "history": true,
                "host": "acme.identity.example.org",
                "status": "prepared",
                "version": 0,
                "versionLimit": 24,
                "createdAt": "2026-03-03T10:02:11.004Z",
                "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
                "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                "renderedAt": "2026-03-03T10:02:12.870Z",
                "previewedAt": "2026-03-03T10:05:40.215Z",
                "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
              },
              {
                "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                "path": "/authority/acme",
                "method": "webvh",
                "history": true,
                "host": "acme.identity.example.org",
                "status": "published",
                "version": 1,
                "versionLimit": 24,
                "createdAt": "2026-03-03T10:02:11.004Z",
                "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
                "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                "renderedAt": "2026-03-03T10:02:12.870Z",
                "previewedAt": "2026-03-03T10:05:40.215Z",
                "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
                "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
                "publishedAt": "2026-03-03T10:06:02.593Z"
              },
              {
                "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
                "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
                "path": "/authority/acme",
                "method": "webvh",
                "history": true,
                "host": "acme.identity.example.org",
                "status": "prepared",
                "version": 1,
                "versionLimit": 24,
                "createdAt": "2026-03-03T10:02:11.004Z",
                "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "assertionKeyId": "pZ2cW8rN5uYh3Jt0Xq7LbD4sVf6GmK1aEoRiTnCyUw8",
                "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
                "renderedAt": "2026-09-01T08:14:03.120Z",
                "previewedAt": "2026-09-01T08:15:27.644Z",
                "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
                "previewedDigest": "9e52491553e488a9999e6b59d802e259a56759bf864c18e14b7ea7c7a3fd064d",
                "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
                "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
                "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
                "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
                "publishedAt": "2026-03-03T10:06:02.593Z",
                "nextUpdateKeyId": "H5nKq2Yx8RtV0cLm3WbZ7dPf4JsA6uEoG1iTyNwCrXk",
                "retiredAssertionKeyIds": ["dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw"]
              }
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "description": "The version publishing would make: 1 for a new identifier, one more than the version served for an update."
          },
          "document": {
            "type": "string",
            "minLength": 2,
            "maxLength": 65536,
            "description": "The DID document, as its RFC 8785 canonical JSON text. Parse it to read it. It holds `@context`, `id`, `alsoKnownAs`, one assertion key as a `Multikey` verification method, `assertionMethod` and `authentication`, and nothing else. Before the first publication of a `webvh` identifier it carries the literal `scidPlaceholder` wherever the self-certifying identifier will be; see `scid`."
          },
          "documentDigest": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "SHA-256 of the UTF-8 bytes of `document`, in lowercase hex. It is what the preview records on the identifier as `previewedDigest`."
          },
          "parallelDocument": {
            "type": "string",
            "minLength": 2,
            "maxLength": 65536,
            "description": "For a `webvh` identifier, the `did:web` form of the same document, served beside the log at `/.well-known/did.json` for verifiers that resolve only `did:web`: its `id` is the `did:web` DID and its `alsoKnownAs` names the `did:webvh` one. It names the same key and no other. Absent for a `web` identifier, whose `document` is already that file."
          },
          "servedAt": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "pattern": "^https://[^\\s]+$",
              "maxLength": 2048
            },
            "minItems": 1,
            "maxItems": 4,
            "description": "Every URL the identifier will be served at once published: `/.well-known/did.jsonl` and `/.well-known/did.json` on its host for `webvh`, the second alone for `web`."
          },
          "scid": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,64}$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The self-certifying identifier in `document`, for a `webvh` identifier. `null` before the first publication: the identifier is a hash of the first log entry, so it does not exist yet, and `document` and `parallelDocument` carry `scidPlaceholder` where it will stand. Publication puts the real value there and changes nothing else. From the second version on it is the value itself. Absent for a `web` identifier."
          },
          "scidPlaceholder": {
            "type": "string",
            "const": "{SCID}",
            "description": "The literal text that stands for the self-certifying identifier in `document` and `parallelDocument`. Present exactly when `scid` is `null`."
          }
        },
        "additionalProperties": false,
        "description": "The publication preview: the identifier and exactly the DID document that will be published. Publication is refused until the current document has been previewed.",
        "examples": [
          {
            "identifier": {
              "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
              "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
              "path": "/authority/acme",
              "method": "webvh",
              "history": true,
              "host": "acme.identity.example.org",
              "status": "prepared",
              "version": 0,
              "versionLimit": 24,
              "createdAt": "2026-03-03T10:02:11.004Z",
              "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
              "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
              "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
              "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
              "renderedAt": "2026-03-03T10:02:12.870Z",
              "previewedAt": "2026-03-03T10:05:40.215Z",
              "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
              "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
            },
            "version": 1,
            "document": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:web:acme.identity.example.org\"],\"assertionMethod\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"authentication\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"id\":\"did:webvh:{SCID}:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:webvh:{SCID}:acme.identity.example.org\",\"id\":\"did:webvh:{SCID}:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
            "documentDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
            "parallelDocument": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:webvh:{SCID}:acme.identity.example.org\"],\"assertionMethod\":[\"did:web:acme.identity.example.org#key-1\"],\"authentication\":[\"did:web:acme.identity.example.org#key-1\"],\"id\":\"did:web:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:web:acme.identity.example.org\",\"id\":\"did:web:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
            "servedAt": [
              "https://acme.identity.example.org/.well-known/did.jsonl",
              "https://acme.identity.example.org/.well-known/did.json"
            ],
            "scid": null,
            "scidPlaceholder": "{SCID}"
          },
          {
            "identifier": {
              "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
              "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
              "path": "/authority/acme",
              "method": "webvh",
              "history": true,
              "host": "acme.identity.example.org",
              "status": "prepared",
              "version": 1,
              "versionLimit": 24,
              "createdAt": "2026-03-03T10:02:11.004Z",
              "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
              "assertionKeyId": "pZ2cW8rN5uYh3Jt0Xq7LbD4sVf6GmK1aEoRiTnCyUw8",
              "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
              "renderedAt": "2026-09-01T08:14:03.120Z",
              "previewedAt": "2026-09-01T08:15:27.644Z",
              "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
              "previewedDigest": "9e52491553e488a9999e6b59d802e259a56759bf864c18e14b7ea7c7a3fd064d",
              "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
              "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
              "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
              "retiredUpdateKeyIds": ["uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"],
              "publishedAt": "2026-03-03T10:06:02.593Z",
              "nextUpdateKeyId": "H5nKq2Yx8RtV0cLm3WbZ7dPf4JsA6uEoG1iTyNwCrXk",
              "retiredAssertionKeyIds": ["dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw"]
            },
            "version": 2,
            "document": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:web:acme.identity.example.org\"],\"assertionMethod\":[\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org#key-1\"],\"authentication\":[\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org#key-1\"],\"id\":\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org\",\"id\":\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb\",\"type\":\"Multikey\"}]}",
            "documentDigest": "9e52491553e488a9999e6b59d802e259a56759bf864c18e14b7ea7c7a3fd064d",
            "parallelDocument": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org\"],\"assertionMethod\":[\"did:web:acme.identity.example.org#key-1\"],\"authentication\":[\"did:web:acme.identity.example.org#key-1\"],\"id\":\"did:web:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:web:acme.identity.example.org\",\"id\":\"did:web:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb\",\"type\":\"Multikey\"}]}",
            "servedAt": [
              "https://acme.identity.example.org/.well-known/did.jsonl",
              "https://acme.identity.example.org/.well-known/did.json"
            ],
            "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ"
          }
        ]
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem document. The type is https://oktoid.app/problems/<slug>; clients branch on it.",
        "required": ["type", "title", "status", "correlationId", "callerShould"],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "correlationId": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "The owning module's stable code for the refusal, such as subject.slug_taken or idempotency.key_reused. Present on 400, 409, 502 and 503 problems that a module raised."
          },
          "callerShould": {
            "enum": [
              "fix-and-retry",
              "authenticate",
              "stop",
              "re-read-and-decide",
              "re-read-and-retry-once",
              "retry-with-backoff"
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["path", "message"],
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "failedChecks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
