{"openapi":"3.1.0","x-legal-effect":"no_determination","x-disclaimer":"An APR ID is a persistent identifier for a registry record. Issuance does not create, transfer, certify, or adjudicate legal personhood, identity, authorship, copyright, trademark, publicity rights, ownership, or licensing authority. Except where a specific verification method is displayed, information is an assertion supplied by the identified submitter.","info":{"title":"Artificial Peoples Registry API","version":"0.1.0","description":"An APR ID is a persistent identifier for a registry record. Issuance does not create, transfer, certify, or adjudicate legal personhood, identity, authorship, copyright, trademark, publicity rights, ownership, or licensing authority. Except where a specific verification method is displayed, information is an assertion supplied by the identified submitter.\n\nAPR is the authority layer for artificial personas: canonical records, permanent APR IDs, a public resolver, and a lookup API. Reading and resolving are free and unauthenticated; writing requires a Registration Agency Bearer credential. The contract is append-only — fields and error codes are added, never renamed or removed. See /legal-effect and /docs on this same origin.","contact":{"name":"Artificial Peoples Registry","email":"hello@artificialpeoples.com"}},"servers":[{"url":"https://artificialpeoples.com"}],"tags":[{"name":"Resolver","description":"Free, unauthenticated reads."},{"name":"Registration Agencies","description":"Bearer-authenticated writes, issued under a signed agreement."},{"name":"Operations","description":"Readiness and self-description."}],"paths":{"/healthz":{"get":{"operationId":"getHealthz","tags":["Operations"],"summary":"Database and current-schema readiness","description":"Intentionally opaque beyond ok/not-ok — no database detail is ever returned.","responses":{"200":{"description":"Ready.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Healthz"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"503":{"description":"Not ready — database unreachable, or the current schema is behind the latest required migration.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Healthz"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/attestation-statement":{"get":{"operationId":"getAttestationStatement","tags":["Registration Agencies"],"summary":"The current attestation statement text and version","description":"RA-authenticated, like `GET /personas/lookup` — not public. The statement text a Registration Agency attests to via `attestation.statement_version` on `POST /personas` (DECISIONS.md \"The attestation model\") is recorded on every attestation but otherwise never shown; this is where the agency reads what a cited version actually says. The text is uncounseled draft legal text — DECISIONS.md #20's open counsel questions name its wording, and #21(d)'s interim rule keeps user-facing text from an uncounseled draft behind the counsel gate — so it is shown only to an authenticated agency, never published on the public record. PR #27 (Codex), round 3, P1.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The current statement.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestationStatement"}}}},"401":{"description":"Missing, malformed, or unknown Bearer credential.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenapiDocument","tags":["Operations"],"summary":"This document","parameters":[{"name":"If-None-Match","in":"header","description":"One or more quoted ETags, or `*`. An exact (weak-tolerant) match answers 304 with no body. `If-Modified-Since` is deliberately never honoured as a 304 basis — HTTP-date's one-second resolution cannot distinguish unchanged from changed-within-the-same-second.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The OpenAPI 3.1 description of this API.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"public, max-age=300"},"ETag":{"description":"Opaque, quoted validator. Send back as `If-None-Match` to revalidate.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","description":"An OpenAPI 3.1 Document object. Its top-level members are fixed by the OpenAPI specification, so the legal-effect envelope every other JSON response carries as `legal_effect` and `disclaimer` travels here as the two specification extensions `x-legal-effect` and `x-disclaimer`, with the same values.","required":["openapi","info","paths","x-legal-effect","x-disclaimer"],"properties":{"openapi":{"type":"string","const":"3.1.0"},"x-legal-effect":{"type":"string","const":"no_determination"},"x-disclaimer":{"type":"string","const":"An APR ID is a persistent identifier for a registry record. Issuance does not create, transfer, certify, or adjudicate legal personhood, identity, authorship, copyright, trademark, publicity rights, ownership, or licensing authority. Except where a specific verification method is displayed, information is an assertion supplied by the identified submitter."}}}}}},"304":{"description":"Unchanged since the entity tag in `If-None-Match`."},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/id/{id}.json":{"get":{"operationId":"getRecordJson","tags":["Resolver"],"summary":"Machine-readable record","description":"An issued ID always resolves (ID-SPEC §5) — the only 404 is an ID that was never issued. **Note:** `GET /id/{id}` (no `.json` suffix) serves the exact same record as a plain server-rendered HTML page; the representation is chosen by this URL suffix, not by the `Accept` header. That HTML form is not separately documented here.","parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","description":"One or more quoted ETags, or `*`. An exact (weak-tolerant) match answers 304 with no body. `If-Modified-Since` is deliberately never honoured as a 304 basis — HTTP-date's one-second resolution cannot distinguish unchanged from changed-within-the-same-second.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The record, in whatever shape its current lifecycle state permits.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"public, max-age=300"},"ETag":{"description":"Opaque, quoted validator. Send back as `If-None-Match` to revalidate.","schema":{"type":"string"}},"Last-Modified":{"description":"RFC 9110 HTTP-date. Informational only — see `If-Modified-Since` below for why it never drives a 304.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"301":{"description":"A non-canonical but valid ID form; redirects to the canonical URL."},"304":{"description":"Unchanged since `If-None-Match`.","headers":{"ETag":{"description":"Opaque, quoted validator. Send back as `If-None-Match` to revalidate.","schema":{"type":"string"}},"Last-Modified":{"description":"RFC 9110 HTTP-date. Informational only — see `If-Modified-Since` below for why it never drives a 304.","schema":{"type":"string"}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/id/{id}/history":{"get":{"operationId":"getRecordHistory","tags":["Resolver"],"summary":"Append-only change history for one record (whole, or paged with limit)","parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string"},"description":"Exclusive cursor; defaults to \"0\"."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Omit it and the response is the record's whole history in one page (`has_more` is then always false) — the shape this endpoint had before pagination existed. Send it (1–500) for a bounded page."}],"responses":{"200":{"description":"OK.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoryPage"}}}},"301":{"description":"A non-canonical ID form; redirects to the canonical URL."},"400":{"description":"since or limit was not a bounded decimal value.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_cursor"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_limit"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/id/{id}/avatar":{"get":{"operationId":"getRecordAvatar","tags":["Resolver"],"summary":"Avatar image, proxied by APR","description":"Never served for a non-active record, or one with no avatar_url. A tighter rate limit than the rest of the API — the only public route that makes an outbound fetch.","parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","description":"One or more quoted ETags, or `*`. An exact (weak-tolerant) match answers 304 with no body. `If-Modified-Since` is deliberately never honoured as a 304 basis — HTTP-date's one-second resolution cannot distinguish unchanged from changed-within-the-same-second.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The image bytes.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"public, max-age=60, must-revalidate"},"ETag":{"description":"Digests the BYTES, not the record — a submitter can replace the image at the same URL without the record changing at all.","schema":{"type":"string"}},"Content-Type":{"schema":{"type":"string"},"description":"The upstream image's content type."}},"content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"301":{"description":"A non-canonical ID form; redirects to the canonical URL."},"304":{"description":"Unchanged since `If-None-Match`."},"404":{"description":"Not a valid ID shape, never issued, not active, or it has no avatar.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"no_avatar"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"429":{"description":"Too many avatar requests from this client address: this route allows 60 per minute per address (its own limiter, on top of the global 600). Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"502":{"description":"The upstream host could not be fetched.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"avatar_unavailable"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/p/{slug}":{"get":{"operationId":"resolveSlug","tags":["Resolver"],"summary":"Human-friendly alias","description":"Redirects to the canonical `/id/{id}` URL. Also accepts the combined display form `{slug}--{id}` (split on the LAST `--`), which redirects straight to the ID without a slug lookup. Never returns JSON.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"301":{"description":"The combined `{slug}--{id}` form; redirects straight to `/id/{id}`."},"302":{"description":"A bare slug that resolves; redirects to `/id/{id}`."},"404":{"description":"No record has this slug.","content":{"text/html":{"schema":{"type":"string"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/changes":{"get":{"operationId":"getChanges","tags":["Resolver"],"summary":"The ordered, cross-record sync feed","description":"Consumer sync primitive — poll this rather than re-fetching records. Append-only.","parameters":[{"name":"since","in":"query","schema":{"type":"string"},"description":"Exclusive cursor; defaults to \"0\"."},{"name":"limit","in":"query","schema":{"type":"string","pattern":"^\\d+$"},"description":"Unsigned decimal. Omit for the default (100); `0` also means the default, never a zero-row page. A value above 500 clamps to 500 rather than being refused, so an existing consumer's `limit=1000` keeps working. Only a non-decimal value is refused, with `400 bad_limit`."}],"responses":{"200":{"description":"OK.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangesPage"}}}},"400":{"description":"since was not an unsigned decimal value, or limit was not an unsigned decimal (an out-of-range limit clamps rather than reaching this response — see its parameter description).","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_cursor"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_limit"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas":{"post":{"operationId":"createPersona","tags":["Registration Agencies"],"summary":"Register a persona; returns a permanent ID","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","display_name","persona_type","ai_disclosure","controller"],"additionalProperties":false,"properties":{"slug":{"type":"string","minLength":1,"maxLength":80},"subject_type":{"type":"string","enum":["persona","voice","visual_likeness","character","work_set","natural_voice","natural_likeness"]},"verification_id":{"description":"A completed, unattached domain-control verification to consume at registration; REQUIRED for natural_voice and natural_likeness. Send the decimal STRING that POST /verifications returned — the underlying value is a PostgreSQL bigint, and one beyond 2^53 rounds in a client that parses it as a JSON number. The JSON number earlier releases returned is still accepted.","anyOf":[{"type":"string","pattern":"^[1-9][0-9]{0,18}$"},{"type":"integer","minimum":1,"maximum":9007199254740991}]},"display_name":{"type":"string","minLength":1,"maxLength":200},"persona_type":{"$ref":"#/components/schemas/PersonaTypeValue","description":"The accepted values are narrower than PersonaTypeValue's full enum: they depend on subject_type (the request body's own subject_type on registration, `persona` if omitted; the record's existing, immutable subject_type on PATCH). A value outside that narrowed set is refused with `422 invalid_persona_type`, whose `accepted` array is the list for that specific subject_type."},"ai_disclosure":{"$ref":"#/components/schemas/AiDisclosureValue","description":"The accepted values are narrower than AiDisclosureValue's full enum, the same way persona_type's are: `natural` is accepted only when subject_type (the request body's own subject_type on registration, `persona` if omitted; the record's existing, immutable subject_type on PATCH) is `natural_voice` or `natural_likeness`, and every other value only when it is not. A value outside that narrowed set is refused with `422 invalid_ai_disclosure`, whose `accepted` array is the list for that specific subject_type."},"bio":{"type":"string","maxLength":5000},"avatar_url":{"type":"string","maxLength":1000,"pattern":"^https://"},"links":{"type":"array","maxItems":25,"items":{"type":"object","required":["type","url"],"additionalProperties":false,"properties":{"type":{"type":"string","enum":["official_site","platform_profile","streaming","social","press","catalogue","other"]},"url":{"type":"string","minLength":12,"maxLength":2000,"pattern":"^https://"},"label":{"type":"string","maxLength":120}}}},"asserted_first_public_use_date":{"type":"string","format":"date"},"external_ref":{"type":"string","minLength":1,"maxLength":200,"pattern":"^\\S(.*\\S)?$","description":"The agency's own opaque, stable identifier for this registration (its retry-idempotency anchor), e.g. a UUID. It is write-once and it SURVIVES ERASURE, so it must carry no personal data: never an email address, a name, or anything derived from one."},"attestation":{"type":"object","required":["authority_basis","evidence_classes","statement_version"],"additionalProperties":false,"properties":{"authority_basis":{"$ref":"#/components/schemas/AgencyAuthorityBasisValue"},"evidence_classes":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceClassValue"},"minItems":1,"maxItems":7,"uniqueItems":true,"description":"What class(es) of evidence the agency holds — a SET, not a bag. If `none_held` is present it must be the only element: it asserts that no evidence is held, which cannot sit beside a claim that evidence IS held. On a natural_voice/natural_likeness registration, none_held is refused outright (PR #27, Codex, round 3, P1): such an attestation asserts authority or consent over an identifiable natural person, so the agency must name the evidence class it actually holds. none_held is ALSO refused (422 invalid_attestation, on POST or on a PATCH ai_disclosure change) wherever ai_disclosure is or becomes digital_replica, on ANY subject (PR #27, Codex, round 18): a digital replica IS the voice or likeness of an identifiable natural person, so an attestation naming no evidence class contradicts the basis it claims — the same pairing own_creation gets in AgencyAuthorityBasisValue's own description above.\n\nevidence_classes must also SUPPORT the stated authority_basis — at least one class from the basis's supporting set, not merely a class the agency happens to hold. The requirement's SCOPE differs by basis (PR #27, Codex, rounds 4 and 5, P1): subject_consent → signed_consent is required on EVERY subject — subject_consent (\"that person's documented consent is held\") inherently asserts a held document, so none_held contradicts the basis itself regardless of what is being registered; licence_or_assignment → licence_agreement; authorised_representative → authority_letter; employment_or_commission → licence_agreement or authority_letter are each required only for natural_voice/natural_likeness — those bases assert a legal relationship the agency need not itself hold a copy of, so the requirement applies only where the attestation is the record's sole accountability mechanism over an identifiable natural person. own_creation is refused outright above (it has no supporting-evidence requirement in any case); legal_process is registry-operator use only."},"statement_version":{"type":"string","minLength":1,"maxLength":20,"const":"0.1","description":"Must equal the CURRENT attestation statement version (\"0.1\"), served together with the statement text itself at `GET /attestation-statement`. A missing or outdated value is refused with `422 invalid_attestation`, naming the current version."},"evidence_reference":{"type":"string","minLength":1,"maxLength":200,"pattern":"^\\S(.*\\S)?$","description":"An OPAQUE internal pointer the agency alone can resolve in its own records — never the evidence itself, and never a DIRECT identifier: a name, an email address, a phone number, a government identifier, or a document URL. Good: an internal ticket number (\"TICKET-48213\") or a content hash (\"sha256:3f29…\") — an internal code the agency alone can resolve is exactly what this field is for, and identifies no one to APR or to anyone outside the agency. Bad: a name, an email address, or a URL to the evidence document itself. Write-once; nulled on erasure."},"attester_reference":{"type":"string","minLength":1,"maxLength":200,"pattern":"^\\S(.*\\S)?$","description":"An OPAQUE, agency-side operator id for the individual who attested — the agency's own internal staff code, e.g. \"op-482\" — never a DIRECT identifier: a name, an email address, a phone number, or a government identifier. An internal operator id the agency alone can resolve is exactly what this field is for; it identifies no one to APR or to anyone outside the agency. Nulled on erasure."}},"description":"The submission attestation (DECISIONS.md \"The attestation model\"). `statement_version` must equal the CURRENT version, currently \"0.1\" — an explicit acknowledgement that the submitting agency has read the statement it is citing. The statement's own text is uncounseled draft legal text and is not reproduced here; an authenticated agency reads it at `GET /attestation-statement` (same credential as this request)."},"controller":{"type":"object","required":["type","email"],"additionalProperties":false,"properties":{"type":{"type":"string","enum":["individual","organization"]},"display_name":{"type":"string","maxLength":200},"email":{"type":"string","format":"email","minLength":6,"maxLength":320},"legal_name":{"type":"string","maxLength":200}}}},"if":{"required":["subject_type"],"properties":{"subject_type":{"enum":["natural_voice","natural_likeness"]}}},"then":{"required":["attestation"]}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationReceipt"}}}},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"The agency was suspended between authentication and the write transaction's re-check (`reauthorize()` runs inside every write).","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"409":{"description":"A retry conflict, or a slug that was retired by an earlier redaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"slug_taken"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"external_ref_taken"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"slug_retired"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"422":{"description":"A semantic rule the JSON Schema above cannot express.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_slug"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_ai_disclosure"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/AiDisclosureValue"},"description":"The values accepted for THIS request — the full closed set when ai_disclosure itself was unrecognized, or narrowed to the one subject_type's list (DECISIONS.md \"AI-disclosure taxonomy\") when it was a recognized value that does not fit that subject."},"meanings":{"type":"object","description":"value → meaning, so the declaration qualifier travels with the accepted values."},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_persona_type"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/PersonaTypeValue"},"description":"The values accepted for THIS request — the full closed set when persona_type itself was unrecognized, or narrowed to the one subject_type's list (DECISIONS.md \"Persona-type taxonomy\") when it was a recognized value that does not fit that subject."},"meanings":{"type":"object"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_url"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"array","items":{"type":"string"}}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_attestation"},"legal_effect":{"type":"string","const":"no_determination"},"accepted_authority_basis":{"type":"array","items":{"$ref":"#/components/schemas/AgencyAuthorityBasisValue"}},"accepted_evidence_classes":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceClassValue"}},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"avatar_not_permitted"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"verification_required"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"attestation_required"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"verification_not_usable"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/lookup":{"get":{"operationId":"lookupPersona","tags":["Registration Agencies"],"summary":"Find the caller's own record, by external_ref or slug","description":"The retry-idempotency primitive. Scoped to the caller's own records — a miss and another agency's record are both 404.","security":[{"bearerAuth":[]}],"parameters":[{"name":"external_ref","in":"query","schema":{"type":"string"}},{"name":"slug","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","headers":{"Cache-Control":{"description":"Freshness policy for this representation.","schema":{"type":"string"},"example":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupReceipt"}}}},"400":{"description":"Neither or both of external_ref/slug were provided; provide exactly one.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_lookup"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}}}},"401":{"description":"Missing, malformed, or unknown Bearer credential.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}},"404":{"description":"No such record of the caller's.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_found"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/{id}":{"patch":{"operationId":"updatePersona","tags":["Registration Agencies"],"summary":"Update mutable public fields of the caller's own record","description":"Optional compare-and-set via `If-Match`. Lifecycle state, verification, slug, `registered_via` and `external_ref` are not patchable here.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"If-Match","in":"header","description":"The record's current `record_version` (from a prior response body — never derived from an ETag, which is a different, opaque value) as a **plain decimal integer**, not a quoted entity tag. Makes the update a compare-and-set: on a mismatch nothing is applied and the response is `409 version_conflict` carrying `current_version`. Omit for an unconditional update.","required":false,"schema":{"type":"string","pattern":"^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"additionalProperties":false,"properties":{"display_name":{"type":"string","minLength":1,"maxLength":200},"persona_type":{"$ref":"#/components/schemas/PersonaTypeValue","description":"The accepted values are narrower than PersonaTypeValue's full enum: they depend on subject_type (the request body's own subject_type on registration, `persona` if omitted; the record's existing, immutable subject_type on PATCH). A value outside that narrowed set is refused with `422 invalid_persona_type`, whose `accepted` array is the list for that specific subject_type."},"ai_disclosure":{"$ref":"#/components/schemas/AiDisclosureValue","description":"The accepted values are narrower than AiDisclosureValue's full enum, the same way persona_type's are: `natural` is accepted only when subject_type (the request body's own subject_type on registration, `persona` if omitted; the record's existing, immutable subject_type on PATCH) is `natural_voice` or `natural_likeness`, and every other value only when it is not. A value outside that narrowed set is refused with `422 invalid_ai_disclosure`, whose `accepted` array is the list for that specific subject_type."},"bio":{"type":["string","null"],"maxLength":5000},"avatar_url":{"type":["string","null"],"maxLength":1000,"pattern":"^https://"},"links":{"type":"array","maxItems":25,"items":{"type":"object","required":["type","url"],"additionalProperties":false,"properties":{"type":{"type":"string","enum":["official_site","platform_profile","streaming","social","press","catalogue","other"]},"url":{"type":"string","minLength":12,"maxLength":2000,"pattern":"^https://"},"label":{"type":"string","maxLength":120}}}},"asserted_first_public_use_date":{"type":["string","null"],"format":"date"},"controller_display_name":{"type":["string","null"],"maxLength":200}}}}}},"responses":{"200":{"description":"Applied. Returns the record at its new version — ordinarily RecordActive, but a lifecycle operation (withdraw, deprecate, redact) that commits between this write's row-lock release and the handler's final fetch can hand back a pointer, tombstone, or redaction reservation instead, so the response is the full Record union, not RecordActive alone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"Malformed If-Match, a body that failed validation or content-type parsing (ValidationError's `code` enum), or a body with no recognised field.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ValidationError"},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"bad_if_match"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"no_patchable_fields"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"The record was registered by a different Registration Agency, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"409":{"description":"Not updatable in its current state, or an If-Match precondition failed.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_redacted"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","current_version"],"properties":{"error":{"type":"string","const":"version_conflict"},"legal_effect":{"type":"string","const":"no_determination"},"current_version":{"type":"integer"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"422":{"description":"A semantic rule the JSON Schema above cannot express.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"avatar_not_permitted"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_url"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"array","items":{"type":"string"}}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_ai_disclosure"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/AiDisclosureValue"},"description":"The values accepted for THIS request — the full closed set when ai_disclosure itself was unrecognized, or narrowed to the one subject_type's list (DECISIONS.md \"AI-disclosure taxonomy\") when it was a recognized value that does not fit that subject."},"meanings":{"type":"object","description":"value → meaning, so the declaration qualifier travels with the accepted values."},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_persona_type"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/PersonaTypeValue"},"description":"The values accepted for THIS request — the full closed set when persona_type itself was unrecognized, or narrowed to the one subject_type's list (DECISIONS.md \"Persona-type taxonomy\") when it was a recognized value that does not fit that subject."},"meanings":{"type":"object"},"detail":{"type":"string"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/{id}/relations":{"post":{"operationId":"createRelation","tags":["Registration Agencies"],"summary":"Assert a relation to another record","description":"An assertion, like a rights assertion — never proof that a licence exists.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","type"],"additionalProperties":false,"properties":{"to":{"type":"string","minLength":12,"maxLength":20},"type":{"type":"string","enum":["uses_voice","uses_likeness","portrays_character","includes_work_set","derived_from","succeeds"]},"basis":{"type":"string","maxLength":60},"note":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelationReceipt"}}}},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the agency of record for the source, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"409":{"description":"Either endpoint is not active, or this exact edge already exists.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"target_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"relation_exists"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"422":{"description":"The target is not usable, or the pair does not fit type.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_target"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_relation"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/{id}/relations/{relationId}":{"delete":{"operationId":"deleteRelation","tags":["Registration Agencies"],"summary":"Retract a relation — soft, so the history stays legible","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"relationId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Retracted (or already was)."},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"The record was registered by a different Registration Agency, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"No such relation on this record, the ID is not valid, or it was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_found"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/{id}/evidence":{"post":{"operationId":"createEvidence","tags":["Registration Agencies"],"summary":"Declare third-party evidence bearing on a record","description":"DECISIONS.md \"The external-evidence record: its shape\" — a row says the persona's own Registration Agency records that a party asserts evidence of a kind, at a locator, bears on a proposition about this record. Only the persona's own agency of record may declare it.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["evidence_kind","proposition","asserted_by","locator"],"additionalProperties":false,"properties":{"evidence_kind":{"type":"string","minLength":1,"maxLength":60},"proposition":{"type":"string","minLength":1,"maxLength":80},"asserted_by":{"type":"string","minLength":1,"maxLength":200,"pattern":"^\\S(.*\\S)?$","description":"The third party said to ASSERT this evidence — a platform, a registry, a vendor — never the declaring agency and never the person or persona the evidence is about. PUBLIC the moment it is stored, rendered as \"<asserted_by> declares …\" on the record page: a subject's name here would falsely make them the declarant and publish personal data. Nulled on erasure."},"locator":{"type":"string","minLength":1,"maxLength":2000,"description":"PUBLIC the moment it is stored — served in the record's JSON and on its page, cached and crawled. Opaque: a reference APR never fetches or vets (an external identifier, a registry entry, a manifest URL, a platform's own record id). Must contain nothing personal — never a name, an email address, a document about a person, or anything that identifies someone; APR cannot detect that and the declaring agency is responsible for it. Nulled on erasure."},"content_hash":{"type":"string","maxLength":100,"description":"Optional; `sha256:` + 64 hex when present. PUBLIC the moment it is stored, like the locator, and cacheable: hash only content that is itself public and high-entropy (a manifest, a published file), never a low-entropy identifier or a document about a person — a hash of personal data is a correlation value, not an anonymisation. Nulled on erasure."},"asserted_at":{"type":"string","format":"date-time","description":"When the assertion was made. Optional — defaults to the moment the registry records it. Refused with 422 invalid_asserted_at when more than 5 minutes ahead of the registry's own database clock — the clock that stores and later publishes this value, not the API server's host clock."},"supersedes_id":{"type":"string","description":"Decimal string; PostgreSQL bigint, naming an existing ACTIVE evidence item of this persona's."}}}}}},"responses":{"201":{"description":"Recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceDeclared"}}}},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the agency of record for this persona, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"409":{"description":"The record is not usable.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_redacted"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"422":{"description":"The kind/proposition pair, locator, content hash, supersedes_id, or asserted_at was not acceptable.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_evidence_kind"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceKindValue"}},"meanings":{"type":"object"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","accepted","meanings","detail"],"properties":{"error":{"type":"string","const":"invalid_proposition"},"legal_effect":{"type":"string","const":"no_determination"},"accepted":{"type":"array","items":{"$ref":"#/components/schemas/EvidencePropositionValue"}},"meanings":{"type":"object"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_locator"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_content_hash"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_supersedes"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_asserted_at"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/personas/{id}/evidence/{evidenceId}":{"delete":{"operationId":"deleteEvidence","tags":["Registration Agencies"],"summary":"Withdraw a declaration — soft, so the history stays legible","description":"Inserts a new `withdrawn` row superseding the active one, the same append-only shape every state change on this table uses; never rewrites the row in place.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"A 12-symbol APR ID (hyphens/case/`I`/`L`/`O` are normalised before lookup).","schema":{"type":"string"}},{"name":"evidenceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]{0,18}$"},"description":"The evidence id, as the declare response returned it: a decimal string. Never format it through a JS number — the underlying value is a PostgreSQL bigint and one beyond 2^53 would round, addressing a different row."}],"responses":{"204":{"description":"Withdrawn (or already was)."},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the agency of record for this persona, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"No such evidence on this record, the ID is not valid, or it was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_found"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"409":{"description":"The record is not usable — lifecycle state is re-read under the same lock as the evidence row, so a redaction (or deprecation/withdrawal) that committed while this request was in flight is answered here rather than as a write against an erased record. An already-withdrawn or superseded row on a still-active record remains a 204, unconditionally idempotent.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_redacted"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/verifications":{"post":{"operationId":"createVerification","tags":["Registration Agencies"],"summary":"Create a control-verification challenge","description":"The ACME pattern, minus account keys. Omit `persona_id` for a free-standing challenge a later registration can consume (required for `natural_voice`/`natural_likeness`).","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["proposition","method","subject"],"additionalProperties":false,"properties":{"proposition":{"type":"string","enum":["domain_control_verified"]},"method":{"type":"string","enum":["dns_txt","https_well_known"]},"subject":{"type":"string","minLength":4,"maxLength":253},"persona_id":{"type":"string","minLength":12,"maxLength":20}}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationChallenge"}}}},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the agency of record for persona_id, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_agency_of_record"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"Not a valid ID shape, or a valid shape that was never issued.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_a_valid_apr_id"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"id_not_issued"},"legal_effect":{"type":"string","const":"no_determination"}}}]}]}}}},"409":{"description":"The attached record is not usable, or the pending-challenge cap was reached.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"record_redacted"},"legal_effect":{"type":"string","const":"no_determination"},"status":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"too_many_pending"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"422":{"description":"The subject is not an acceptable bare public hostname.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"invalid_subject"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/verifications/{id}/check":{"post":{"operationId":"checkVerification","tags":["Registration Agencies"],"summary":"Run the check — retryable while pending, idempotent once verified","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]{0,18}$"},"description":"The verification id, as POST /verifications returned it: a decimal string. Never format it through a JS number — the underlying value is a PostgreSQL bigint and one beyond 2^53 would round, addressing a different verification."}],"responses":{"200":{"description":"Verified, or the check ran and the proof was not found yet (`check: \"failed\"` — not an error).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/VerificationVerified"},{"$ref":"#/components/schemas/VerificationCheckFailed"}]}}}},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the owning agency, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_owner"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"No such verification.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_found"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}},"409":{"description":"Settled elsewhere: revoked, or the challenge window closed. Never the caller's own guess about why — always what is actually committed. A concurrent check that SUCCEEDED is not documented here: settledElsewhere's `verified` branch replies through replyVerified, which never calls `.code()`, so it serves 200 with the 200 response's own VerificationVerified shape above, not this status (proven live by src/integration.test.ts, \"reports success when a concurrent check won the race\").","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error","verification_id","status","detail","verified_at","expires_at"],"properties":{"error":{"type":"string","const":"verification_expired"},"legal_effect":{"type":"string","const":"no_determination"},"verification_id":{"type":"string","description":"Decimal string; PostgreSQL bigint. Not a JSON number — a bigint beyond 2^53 would round in a client that parses it as one, and this value is reused as the {id} path parameter of the check and revoke operations and as `verification_id` on POST /personas. That request accepts either this string or the JSON number earlier releases returned."},"status":{"type":"string","const":"expired"},"detail":{"type":"string"},"verified_at":{"type":"string"},"expires_at":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"verification_revoked"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"challenge_expired"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"verification_attachment_changed"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}},"/verifications/{id}":{"delete":{"operationId":"deleteVerification","tags":["Registration Agencies"],"summary":"Revoke a challenge or a completed verification — soft, and idempotent","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]{0,18}$"},"description":"The verification id, as POST /verifications returned it: a decimal string. Never format it through a JS number — the underlying value is a PostgreSQL bigint and one beyond 2^53 would round, addressing a different verification."}],"responses":{"204":{"description":"Revoked (or already was)."},"400":{"description":"The request body failed validation before any route code ran — either JSON Schema validation (Fastify's own ajv: a required field is missing, a value is the wrong type, or an unknown property was sent) or the content-type parser that runs before it (the body was not valid JSON, or was empty). Distinct from the routes' own `error` codes: this shape carries a `code` from ValidationError's enum instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Unauthenticated, or the credential was revoked mid-transaction.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"unauthorized"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the credential was revoked"}}}]}]}}}},"403":{"description":"Not the owning agency, or the agency was suspended mid-transaction.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_owner"},"legal_effect":{"type":"string","const":"no_determination"}}}]},{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"agency_not_active"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string","const":"the registration agency is not active"}}}]}]}}}},"404":{"description":"No such verification.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"not_found"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}},"409":{"description":"The challenge was attached to a record while this request was running; retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"verification_attachment_changed"},"legal_effect":{"type":"string","const":"no_determination"},"detail":{"type":"string"}}}]}}}},"413":{"description":"The request body exceeded the 64 KiB limit that applies before any route code runs (the largest legitimate field is a 5 000-character bio).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"}}}},"415":{"description":"Content-Type was not `application/json` (or `text/plain`) — checked before any route code or body parsing runs, so this can happen even for a well-formed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsupportedMediaType"}}}},"429":{"description":"Too many requests from this client address in the current window: the global limiter allows 600 requests per minute per address, for every caller alike, and runs before any handler. Credentials behind one address share that bucket; per-agency quotas are future, post-authentication work. Wait for `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimited"}}}},"default":{"description":"An unhandled server error. Never hand-thrown by route code — the root error handler's catch-all (src/http.ts) — so every operation in this document can return it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"internal_error"},"legal_effect":{"type":"string","const":"no_determination"}}}]}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A Registration Agency's API key, issued under a signed agreement (scripts/issue-key.ts)."}},"schemas":{"LegalEffect":{"type":"string","enum":["registry_record","identifier_pointer_only","tombstone_only","identifier_reserved; personal fields removed","assertion_recorded; not evaluated","control_verification_only","no_determination"],"description":"Machine-readable summary of what THIS response asserts — branch on this, and on `status`, never on which fields are present. Append-only vocabulary. See docs/LEGAL-EFFECT.md.\n\n- `registry_record` — an active record: identifier plus submitter-supplied assertions.\n- `identifier_pointer_only` — a merged record: resolves, but only as a pointer to its survivor.\n- `tombstone_only` — a withdrawn record: the identifier is reserved; nothing else.\n- `identifier_reserved; personal fields removed` — identifier and history survive a privacy erasure; personal fields do not.\n- `assertion_recorded; not evaluated` — an agency's assertion was recorded; APR has not evaluated it.\n- `control_verification_only` — a narrow channel-control proof at a point in time; see `limitations`.\n- `no_determination` — this response settles nothing."},"Envelope":{"type":"object","required":["legal_effect","disclaimer"],"description":"Present on every JSON object response, in every state, added by a root `preSerialization` hook — including error bodies, /healthz, and /changes. A route that does not set a more specific `legal_effect` gets `no_determination`.","properties":{"legal_effect":{"$ref":"#/components/schemas/LegalEffect"},"disclaimer":{"type":"string","const":"An APR ID is a persistent identifier for a registry record. Issuance does not create, transfer, certify, or adjudicate legal personhood, identity, authorship, copyright, trademark, publicity rights, ownership, or licensing authority. Except where a specific verification method is displayed, information is an assertion supplied by the identified submitter.","description":"The full legal-effect statement, verbatim and unabbreviated, on every response."}}},"ErrorEnvelope":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code. The contract is append-only: new codes are added, none is ever renamed or removed."},"detail":{"description":"Human-readable elaboration. A plain string for most codes; an array of strings for `invalid_url`, one entry per problem found."}}}]},"PayloadTooLarge":{"description":"The root error handler's 4xx shape for Fastify's body-limit rejection, plus the envelope.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["statusCode","error","message","code"],"properties":{"statusCode":{"type":"integer","const":413},"error":{"type":"string","const":"Payload Too Large"},"message":{"type":"string"},"code":{"type":"string","const":"FST_ERR_CTP_BODY_TOO_LARGE"},"legal_effect":{"type":"string","const":"no_determination"}}}]},"UnsupportedMediaType":{"description":"The root error handler's 4xx shape for Fastify's content-type-parser rejection, plus the envelope.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["statusCode","error","message","code"],"properties":{"statusCode":{"type":"integer","const":415},"error":{"type":"string","const":"Unsupported Media Type"},"message":{"type":"string"},"code":{"type":"string","const":"FST_ERR_CTP_INVALID_MEDIA_TYPE"},"legal_effect":{"type":"string","const":"no_determination"}}}]},"RateLimited":{"description":"The root error handler's 4xx shape for `@fastify/rate-limit`'s 429, plus the envelope.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"type":"integer","const":429},"error":{"type":"string","const":"Too Many Requests"},"message":{"type":"string"},"code":{"type":"string"},"legal_effect":{"type":"string","const":"no_determination"}}}]},"ValidationError":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["statusCode","error","message","code"],"properties":{"statusCode":{"type":"integer"},"error":{"type":"string","description":"The HTTP reason phrase, e.g. \"Bad Request\"."},"message":{"type":"string"},"code":{"type":"string","enum":["FST_ERR_VALIDATION","FST_ERR_CTP_INVALID_JSON_BODY","FST_ERR_CTP_EMPTY_JSON_BODY"],"description":"`FST_ERR_VALIDATION` is ajv's own code, for a schema violation. The other two are Fastify's content-type parser, which runs BEFORE ajv and shares this same 4xx shape: a body that is not valid JSON, or an empty body sent with `content-type: application/json`."},"legal_effect":{"type":"string","const":"no_determination"}}}]},"AiDisclosureValue":{"type":"string","enum":["fully_synthetic","digital_replica","human_fronted","hybrid","natural"],"description":"Closed vocabulary (DECISIONS.md \"AI-disclosure taxonomy\"). Every value is the submitter's own declaration, never something APR has verified. `natural` is defined only for `natural_voice` / `natural_likeness` subject records; every other value declares something a natural subject contradicts, so it is accepted on every OTHER subject_type and refused on those two. The full enum below is every value this field can ever take; which of them are ACCEPTED on a given record is narrower and determined by its subject_type — see `ai_disclosure` on the request bodies below, and the `422 invalid_ai_disclosure` response, whose `accepted` array is this narrowed list (`[\"natural\"]`, or the four other values), not the full vocabulary.\n\n- `fully_synthetic` — The submitter declares that voice, likeness and performances are generated, and no identifiable natural person's voice or likeness is used or imitated.\n- `digital_replica` — The submitter declares that the voice or likeness is a synthetic replica of an identifiable natural person, used on a basis the submitter records in its attestation (the person may appear as a natural_voice/natural_likeness subject).\n- `human_fronted` — The submitter declares that an identifiable natural person performs or presents as the persona, with generated material behind it.\n- `hybrid` — The submitter declares that this record is a mix not captured by the other values, described in the bio.\n- `natural` — The submitter declares that the record describes an identifiable natural person's own voice or likeness and nothing about it is synthetic — the value for natural_voice / natural_likeness subject records, whose existence is what lets a digital_replica point at its source."},"PersonaTypeValue":{"type":"string","enum":["musician","vocalist","band_or_group","dj_or_producer","visual_artist","writer","actor_or_performer","presenter_or_host","character","companion_or_assistant","creator_or_influencer","brand_mascot","voice","visual_likeness","work_set","other"],"description":"Closed vocabulary (DECISIONS.md \"Persona-type taxonomy\"). A category of activity for `persona` subjects (every value except `voice`, `visual_likeness` and `work_set` — a persona may itself be a `character`); for the non-persona subjects it instead names the kind of thing registered, and exactly one value fits: `voice` for `voice` / `natural_voice`, `visual_likeness` for `visual_likeness` / `natural_likeness`, `character` for `character`, `work_set` for `work_set`. The full enum below is every value this field can ever take; which of them are ACCEPTED on a given record is narrower and determined by its subject_type — see `persona_type` on the request bodies below, and the `422 invalid_persona_type` response, whose `accepted` array is this narrowed list, not the full vocabulary.\n\n- `musician` — A musician.\n- `vocalist` — A vocalist or singer.\n- `band_or_group` — A band or performing group.\n- `dj_or_producer` — A DJ or music producer.\n- `visual_artist` — A visual artist.\n- `writer` — A writer or author.\n- `actor_or_performer` — An actor or performer.\n- `presenter_or_host` — A presenter or host.\n- `character` — A named character — the kind of thing registered for `character` subject records.\n- `companion_or_assistant` — A conversational companion or assistant.\n- `creator_or_influencer` — A content creator or influencer.\n- `brand_mascot` — A brand mascot or spokesperson character.\n- `voice` — A voice — the kind of thing registered for `voice` and `natural_voice` subject records.\n- `visual_likeness` — A visual likeness — the kind of thing registered for `visual_likeness` and `natural_likeness` subject records.\n- `work_set` — An identified body of works — the kind of thing registered for `work_set` subject records.\n- `other` — A persona type not covered by the other values."},"SubjectType":{"type":"string","enum":["persona","voice","visual_likeness","character","work_set","natural_voice","natural_likeness"],"description":"What the identifier denotes. `persona` is the default. `natural_voice`/`natural_likeness` are about a real person and require a completed verification at registration."},"AuthorityBasisValue":{"type":"string","enum":["own_creation","subject_consent","licence_or_assignment","authorised_representative","employment_or_commission","legal_process"],"description":"Closed vocabulary (DECISIONS.md \"The attestation model\"). The BASIS of the submitter's authority to register — never evidence that the basis is true, which APR does not verify. The full set, including legal_process (registry-operator use only) — used for RESPONSE and lookup schemas, which may one day echo an operator-recorded attestation back. What a Registration Agency may itself SUBMIT is the narrower AgencyAuthorityBasisValue.\n\n- `own_creation` — the controller created the persona; no identifiable natural person's voice or likeness is used or imitated\n- `subject_consent` — an identifiable natural person's voice or likeness is used, and that person's documented consent is held\n- `licence_or_assignment` — the rights used were obtained by written licence or assignment from their holder\n- `authorised_representative` — the submitter acts for the controller under written authority (label, manager, agent)\n- `employment_or_commission` — the persona was created as a work made in employment or on commission, and the employer/commissioner is the controller\n- `legal_process` — a court order or statutory basis; registry-operator use only"},"AgencyAuthorityBasisValue":{"type":"string","enum":["own_creation","subject_consent","licence_or_assignment","authorised_representative","employment_or_commission"],"description":"Closed vocabulary (DECISIONS.md \"The attestation model\"), narrowed to what a Registration Agency credential may submit: AuthorityBasisValue without legal_process, which every caller of POST /personas is refused (422 invalid_attestation) because it is registry-operator use only. Narrower still for natural_voice/natural_likeness registrations: own_creation is also refused there (422 invalid_attestation) — it asserts that no identifiable natural person's voice or likeness is used or imitated, which contradicts a record that is by definition about one. own_creation is ALSO refused (422 invalid_attestation, on POST or on a PATCH ai_disclosure change) wherever ai_disclosure is or becomes digital_replica, on any subject: the two directly contradict each other, while human_fronted and hybrid remain permitted alongside own_creation. The accepted set on any given request is the subject_type-scoped list in that 422's accepted_authority_basis.\n\n- `own_creation` — the controller created the persona; no identifiable natural person's voice or likeness is used or imitated\n- `subject_consent` — an identifiable natural person's voice or likeness is used, and that person's documented consent is held\n- `licence_or_assignment` — the rights used were obtained by written licence or assignment from their holder\n- `authorised_representative` — the submitter acts for the controller under written authority (label, manager, agent)\n- `employment_or_commission` — the persona was created as a work made in employment or on commission, and the employer/commissioner is the controller"},"EvidenceClassValue":{"type":"string","enum":["signed_consent","licence_agreement","authority_letter","platform_account_control","domain_control","identity_document_reviewed","none_held"],"description":"Closed vocabulary (DECISIONS.md \"The attestation model\"). What CLASS of evidence the submitting agency holds for the stated authority_basis — never the evidence itself, which APR never stores.\n\n- `signed_consent` — a signed consent document from the identifiable natural person\n- `licence_agreement` — a written licence or assignment agreement\n- `authority_letter` — a letter or other written grant of authority from the controller\n- `platform_account_control` — control of the platform account or channel through which the persona operates\n- `domain_control` — a completed domain-control verification — the same proof natural_voice/natural_likeness registration already consumes at birth\n- `identity_document_reviewed` — an identity document the submitting agency reviewed\n- `none_held` — no evidence is held for this basis"},"AttestationRecord":{"type":["object","null"],"description":"The attestation recorded with this registration, or null if none was submitted. Never carries evidence_reference/attester_reference — the agency's own opaque pointers, never echoed back. RA-authenticated surfaces only: nothing about an attestation is public until counsel has reviewed the statement text (DECISIONS.md #20).","required":["authority_basis","evidence_classes","statement_version","policy_version","attested_at"],"properties":{"authority_basis":{"$ref":"#/components/schemas/AuthorityBasisValue"},"evidence_classes":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceClassValue"}},"statement_version":{"type":"string"},"policy_version":{"type":"string"},"attested_at":{"type":"string","format":"date-time"}}},"RelationType":{"type":"string","enum":["uses_voice","uses_likeness","portrays_character","includes_work_set","derived_from","succeeds"],"description":"What kind of edge is being asserted between two records."},"LinkItem":{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["official_site","platform_profile","streaming","social","press","catalogue","other"]},"url":{"type":"string","format":"uri"},"label":{"type":"string"}}},"ControllerPublic":{"type":"object","description":"The controller's PUBLIC label only — `email`/`legal_name` are private and never appear here.","properties":{"type":{"type":"string","enum":["individual","organization"]},"display_name":{"type":["string","null"]}}},"RelationItem":{"type":"object","description":"An assertion by the registering agency, published in both directions. Never proof that a licence exists or is valid.","required":["direction","type","record_id","subject_type","display_name","asserted_by","created_at"],"properties":{"direction":{"type":"string","enum":["outgoing","incoming"]},"type":{"$ref":"#/components/schemas/RelationType"},"record_id":{"type":"string"},"subject_type":{"$ref":"#/components/schemas/SubjectType"},"display_name":{"type":"string"},"basis":{"type":["string","null"]},"note":{"type":["string","null"]},"asserted_by":{"type":"string","description":"`ra:<actor_handle>` of the asserting agency."},"created_at":{"type":"string","format":"date-time"}}},"EvidenceKindValue":{"type":"string","enum":["c2pa_manifest_reference","platform_classification","registry_crosswalk"],"description":"Closed, PAIRED vocabulary (DECISIONS.md \"The external-evidence record: its shape\") — each kind bears on exactly one proposition today (EvidencePropositionValue), pinned by a named migration check constraint. Every meaning is the asserting party's own declaration, never something APR has verified.\n\n- `c2pa_manifest_reference` — the asserting party declares that a signed C2PA manifest at the locator references this APR ID (docs/C2PA-BINDING-PROFILE.md §2.1)\n- `platform_classification` — the asserting party declares that a named platform classified this subject under its own AI-disclosure policy\n- `registry_crosswalk` — the asserting party declares that this record and an external identifier (ISNI, MBID, QID, …) denote the same subject (docs/REGISTRY-CROSSWALK.md §7)"},"EvidencePropositionValue":{"type":"string","enum":["c2pa_manifest_references_record","platform_classification_declared","same_subject_as_external_id"],"description":"The proposition each EvidenceKindValue bears on — kept as a separate column/enum from evidence_kind because the model is `evidence bears on a proposition`, and a kind may bear on more than one in a future widening.\n\n- `c2pa_manifest_references_record` — the asserting party declares that a signed C2PA manifest at the locator references this APR ID (docs/C2PA-BINDING-PROFILE.md §2.1)\n- `platform_classification_declared` — the asserting party declares that a named platform classified this subject under its own AI-disclosure policy\n- `same_subject_as_external_id` — the asserting party declares that this record and an external identifier (ISNI, MBID, QID, …) denote the same subject (docs/REGISTRY-CROSSWALK.md §7)"},"EvidenceItem":{"type":"object","description":"Third-party evidence an agency declared bears on this record — never APR-verified, never a badge. `legal_effect` is `no_determination` on every item, same as the record as a whole. Active declarations only; a superseded or withdrawn item is never republished.","required":["evidence_id","kind","proposition","declared_by","asserted_by","locator","content_hash","limitations","status","declared_at","supersedes_id","legal_effect"],"properties":{"evidence_id":{"type":"string","description":"Decimal string; PostgreSQL bigint. Not a JSON number — a bigint beyond 2^53 would round in a client that parses it as one."},"kind":{"$ref":"#/components/schemas/EvidenceKindValue"},"proposition":{"$ref":"#/components/schemas/EvidencePropositionValue"},"declared_by":{"type":"string","description":"`ra:<actor_handle>` of the persona's own agency of record."},"asserted_by":{"type":["string","null"],"description":"The third party said to ASSERT this evidence — a platform, a registry, a vendor — as free text; never the declaring agency (that is `declared_by`) and never the person or persona the evidence is about. Rendered publicly as \"<asserted_by> declares …\", so a subject's name here would falsely make them the declarant and publish personal data. Null after erasure."},"locator":{"type":["string","null"],"description":"Opaque and PUBLIC — a reference APR never fetches or vets. Null after erasure."},"content_hash":{"type":["string","null"],"description":"`sha256:` + 64 hex, when present. Optional even before erasure."},"limitations":{"type":"string","description":"Fixed, server-written text per kind."},"status":{"type":"string","enum":["active","superseded","withdrawn"]},"declared_at":{"type":"string","format":"date-time"},"supersedes_id":{"type":["string","null"],"description":"Decimal string; PostgreSQL bigint, when present."},"legal_effect":{"type":"string","const":"no_determination"}}},"EvidenceDeclared":{"description":"Evidence was recorded — APR has not evaluated it. `legal_effect` is `no_determination`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/EvidenceItem"}]},"VerificationItem":{"type":"object","description":"The one thing on a record that is not self-declared. Names its proposition, subject, method, verifier, dates, and limitations — never a bare badge.","required":["proposition","subject","method","status","verified_at","expires_at","limitations","standard_version","verifier"],"properties":{"proposition":{"type":"string","enum":["domain_control_verified"]},"subject":{"type":"string","description":"The domain that was proven controlled."},"method":{"type":"string","enum":["dns_txt","https_well_known"]},"status":{"type":"string","enum":["verified","expired"],"description":"Derived at read time from the annual expiry window, never silently dropped."},"verified_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"},"limitations":{"type":"string"},"standard_version":{"type":"string"},"verifier":{"type":"string"}}},"RightsAssertionItem":{"type":"object","description":"A rights claim over some facet of the record — informational, rendered publicly under the record's verification level, never a licence grant. Multiple parties may assert different roles over different scopes of the same record.","required":["role","scope","scope_note","basis","party_display_name","created_at"],"properties":{"role":{"type":"string","enum":["brand_owner","copyright_holder","publicity_rights_holder","representative","licensing_contact"],"description":"Who the party is to the record."},"scope":{"type":"string","enum":["persona","name","voice","visual_likeness","music","writings","other"],"description":"Which facet of the record the claim covers."},"scope_note":{"type":["string","null"],"description":"Required (non-null) when `scope` is `other`; null for every other scope. Also null after redaction unless `scope` is `other`, in which case it is masked rather than cleared — src/subject-data.ts."},"basis":{"type":"string","enum":["trademark","copyright","publicity_rights","contract","other"],"description":"The claimed legal basis."},"party_display_name":{"type":"string","description":"Never null, including after redaction: the column is NOT NULL, so an erased assertion's name is masked to a literal placeholder rather than cleared."},"created_at":{"type":"string","format":"date-time"}}},"RecordHead":{"type":"object","required":["id","display","label","url","subject_type","status","created_at","updated_at","record_version"],"properties":{"id":{"type":"string","description":"The canonical, 12-symbol APR ID."},"display":{"type":"string","description":"\"APR 3F7K-9Q2M-W6TN\" display form."},"label":{"type":"string"},"url":{"type":"string","format":"uri"},"subject_type":{"$ref":"#/components/schemas/SubjectType"},"status":{"type":"string","enum":["active","deprecated","withdrawn"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"record_version":{"type":"integer","description":"A global event sequence, NOT per-record 1..n. Opaque and monotonic — read it from responses, never invent it client-side. Feeds PATCH's `If-Match`."}}},"RecordActive":{"description":"An active record. `legal_effect` is `registry_record`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/RecordHead"},{"type":"object","properties":{"status":{"type":"string","const":"active"},"legal_effect":{"type":"string","const":"registry_record"}}},{"type":"object","required":["slug","display_name","persona_type","ai_disclosure","bio","avatar_url","links","verification_level","registered_via","controller","rights_assertions","relations","verifications","asserted_first_public_use_date"],"properties":{"slug":{"type":"string"},"deprecated_in_favor_of":{"type":["string","null"]},"display_name":{"type":"string"},"persona_type":{"$ref":"#/components/schemas/PersonaTypeValue"},"ai_disclosure":{"$ref":"#/components/schemas/AiDisclosureValue"},"bio":{"type":["string","null"]},"avatar_url":{"type":["string","null"],"format":"uri"},"links":{"type":"array","items":{"$ref":"#/components/schemas/LinkItem"}},"verification_level":{"type":"string","description":"Frozen-unused pending scoped verification records (`declared` on every record today) — never captioned publicly; see the `verifications` array instead."},"registered_via":{"type":"string","description":"The registering agency's public display label."},"controller":{"$ref":"#/components/schemas/ControllerPublic"},"rights_assertions":{"type":"array","items":{"$ref":"#/components/schemas/RightsAssertionItem"}},"relations":{"type":"array","items":{"$ref":"#/components/schemas/RelationItem"}},"verifications":{"type":"array","items":{"$ref":"#/components/schemas/VerificationItem"}},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceItem"},"description":"Always present (an empty array if no evidence has been declared) on records served from this version of the API onward. Deliberately absent from `required` above: a representation a strict generated client cached before this field existed carries no `evidence` at all, and the append-only contract (CLAUDE.md) forbids retroactively making that earlier shape invalid."},"asserted_first_public_use_date":{"type":["string","null"],"format":"date"},"reinstated_at":{"type":["string","null"],"format":"date-time"}}}]},"RecordPointer":{"description":"A merged record — resolves, but only as a pointer to its survivor. `legal_effect` is `identifier_pointer_only`; no profile fields are published.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/RecordHead"},{"type":"object","properties":{"status":{"type":"string","const":"deprecated"},"legal_effect":{"type":"string","const":"identifier_pointer_only"}}},{"type":"object","required":["deprecated_in_favor_of","deprecated_at"],"properties":{"deprecated_in_favor_of":{"type":"string","description":"The survivor's APR ID."},"deprecated_at":{"type":["string","null"],"format":"date-time"}}}]},"RecordTombstone":{"description":"A withdrawn record — `legal_effect` is `tombstone_only`; no profile fields are published.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/RecordHead"},{"type":"object","properties":{"status":{"type":"string","const":"withdrawn"},"legal_effect":{"type":"string","const":"tombstone_only"}}},{"type":"object","required":["withdrawn_at","reason_category"],"properties":{"withdrawn_at":{"type":["string","null"],"format":"date-time"},"reason_category":{"type":["string","null"]}}}]},"RecordRedacted":{"description":"A record whose personal fields were removed at the data subject's request — the identifier and its change history survive; `legal_effect` is \"identifier_reserved; personal fields removed\". Deliberately omits `deprecated_in_favor_of` even when the record was also a merge survivor's source: publishing the pointer would republish the association the erasure was asked to stop making.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/RecordHead"},{"type":"object","properties":{"legal_effect":{"type":"string","const":"identifier_reserved; personal fields removed"}}},{"type":"object","required":["redacted_at"],"properties":{"redacted_at":{"type":["string","null"],"format":"date-time"},"deprecated_at":{"type":["string","null"],"format":"date-time"},"withdrawn_at":{"type":["string","null"],"format":"date-time"}}}]},"Record":{"description":"The shape a record's JSON body takes depends on its lifecycle state — branch on `status` and `legal_effect`, not on which fields are present.","oneOf":[{"$ref":"#/components/schemas/RecordActive"},{"$ref":"#/components/schemas/RecordPointer"},{"$ref":"#/components/schemas/RecordTombstone"},{"$ref":"#/components/schemas/RecordRedacted"}]},"HistoryEvent":{"type":"object","required":["cursor","event_id","event_type","actor","created_at"],"properties":{"cursor":{"type":"string","description":"Opaque decimal delivery position — treat as an opaque string, never a JavaScript number."},"event_id":{"type":"string","description":"The permanent, deduplication-key event identity."},"event_type":{"type":"string"},"actor":{"type":"string","description":"`ra:<actor_handle>` or `registry:<actor_handle>`."},"created_at":{"type":"string","format":"date-time"}}},"ChangeEvent":{"allOf":[{"$ref":"#/components/schemas/HistoryEvent"},{"type":"object","required":["persona_id"],"properties":{"persona_id":{"type":"string"}}}]},"HistoryPage":{"description":"Append-only change history for one record: the whole history when `limit` is omitted, a bounded page when it is sent.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["id","events","next_cursor","has_more"],"properties":{"id":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/HistoryEvent"}},"next_cursor":{"type":"string"},"has_more":{"type":"boolean"}}}]},"ChangesPage":{"description":"The ordered, cross-record sync feed — the primitive consumers poll.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["events","next_cursor"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/ChangeEvent"}},"next_cursor":{"type":"string"}}}]},"RegistrationReceipt":{"description":"The permanent identifier just issued. `legal_effect` is `registry_record` — the same value every active record carries.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["id","display","label","url","slug","subject_type","external_ref","record_version","attestation"],"properties":{"id":{"type":"string"},"display":{"type":"string"},"label":{"type":"string"},"url":{"type":"string","format":"uri"},"slug":{"type":"string"},"subject_type":{"$ref":"#/components/schemas/SubjectType"},"external_ref":{"type":["string","null"]},"record_version":{"type":"integer","description":"The birth version — the `created` event's id."},"legal_effect":{"type":"string","const":"registry_record"},"attestation":{"$ref":"#/components/schemas/AttestationRecord"}}}]},"LookupReceiptHead":{"type":"object","required":["id","slug","external_ref","status","redacted_at","record_version","attestation"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"external_ref":{"type":["string","null"]},"status":{"type":"string","enum":["active","deprecated","withdrawn"]},"redacted_at":{"type":["string","null"],"format":"date-time","description":"Redaction is independent of `status` — a redacted record still reads whatever `status` it had before erasure. Narrowed to `null` on every variant except LookupReceiptRedacted."},"record_version":{"type":"integer"},"attestation":{"$ref":"#/components/schemas/AttestationRecord"}}},"LookupReceiptActive":{"description":"The caller's own active record. `legal_effect` is `registry_record`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/LookupReceiptHead"},{"type":"object","properties":{"status":{"type":"string","const":"active"},"redacted_at":{"type":"null","const":null},"legal_effect":{"type":"string","const":"registry_record"}}}]},"LookupReceiptPointer":{"description":"The caller's own record, now merged into a survivor. `legal_effect` is `identifier_pointer_only`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/LookupReceiptHead"},{"type":"object","properties":{"status":{"type":"string","const":"deprecated"},"redacted_at":{"type":"null","const":null},"legal_effect":{"type":"string","const":"identifier_pointer_only"}}}]},"LookupReceiptTombstone":{"description":"The caller's own record, now withdrawn. `legal_effect` is `tombstone_only`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/LookupReceiptHead"},{"type":"object","properties":{"status":{"type":"string","const":"withdrawn"},"redacted_at":{"type":"null","const":null},"legal_effect":{"type":"string","const":"tombstone_only"}}}]},"LookupReceiptRedacted":{"description":"The caller's own record, now redacted. `status` is deliberately left as LookupReceiptHead's broad enum rather than narrowed — redaction is an axis independent of status (CLAUDE.md, \"Redaction is an INDEPENDENT axis from status\"): an erased record still reads whatever `status` it had before erasure. `legal_effect` is `\"identifier_reserved; personal fields removed\"`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"$ref":"#/components/schemas/LookupReceiptHead"},{"type":"object","required":["redacted_at"],"properties":{"redacted_at":{"type":"string","format":"date-time"},"legal_effect":{"type":"string","const":"identifier_reserved; personal fields removed"}}}]},"LookupReceipt":{"description":"RA-authenticated: scoped to the caller's own records; carries `external_ref`, never public. The shape depends on the record's lifecycle state — the same discrimination as `Record` above: branch on `status` and `legal_effect`, not on which fields are present. `legal_effect` comes from the same `legalEffectFor` mapping (src/lifecycle.ts) every other JSON surface uses, so a redacted record — `status` unchanged, since redaction is independent of it — reports LookupReceiptRedacted here too, never LookupReceiptActive.","oneOf":[{"$ref":"#/components/schemas/LookupReceiptActive"},{"$ref":"#/components/schemas/LookupReceiptPointer"},{"$ref":"#/components/schemas/LookupReceiptTombstone"},{"$ref":"#/components/schemas/LookupReceiptRedacted"}]},"VerificationChallenge":{"description":"Exactly what to publish, where. The token is not a secret — publishing it is the proof. `legal_effect` is `no_determination` — issuing a challenge settles nothing.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["verification_id","proposition","method","subject","status","challenge_expires_at","limitations","instructions"],"properties":{"verification_id":{"type":"string","description":"Decimal string; PostgreSQL bigint. Not a JSON number — a bigint beyond 2^53 would round in a client that parses it as one, and this value is reused as the {id} path parameter of the check and revoke operations and as `verification_id` on POST /personas. That request accepts either this string or the JSON number earlier releases returned."},"proposition":{"type":"string","enum":["domain_control_verified"]},"method":{"type":"string","enum":["dns_txt","https_well_known"]},"subject":{"type":"string"},"persona_id":{"type":["string","null"]},"status":{"type":"string","const":"pending"},"challenge_expires_at":{"type":"string","format":"date-time"},"limitations":{"type":"string"},"legal_effect":{"type":"string","const":"no_determination"},"instructions":{"description":"Shape depends on `method`: a DNS TXT record, or a well-known HTTPS URL and body.","oneOf":[{"type":"object","required":["record_name","record_type","record_value","note"],"properties":{"record_name":{"type":"string"},"record_type":{"type":"string","const":"TXT"},"record_value":{"type":"string"},"note":{"type":"string"}}},{"type":"object","required":["url","body","note"],"properties":{"url":{"type":"string","format":"uri"},"body":{"type":"string"},"note":{"type":"string"}}}]}}}]},"VerificationVerified":{"description":"The single shape for a verified outcome — the success path, the idempotent retry, and the lost-compare-and-set re-read all return exactly this. `legal_effect` is `control_verification_only` — a narrow channel-control proof, always.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["verification_id","status","proposition","subject","verified_at","expires_at","limitations"],"properties":{"verification_id":{"type":"string","description":"Decimal string; PostgreSQL bigint. Not a JSON number — a bigint beyond 2^53 would round in a client that parses it as one, and this value is reused as the {id} path parameter of the check and revoke operations and as `verification_id` on POST /personas. That request accepts either this string or the JSON number earlier releases returned."},"status":{"type":"string","const":"verified"},"proposition":{"type":"string","enum":["domain_control_verified"]},"subject":{"type":"string"},"verified_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"limitations":{"type":"string"},"legal_effect":{"type":"string","const":"control_verification_only"}}}]},"VerificationCheckFailed":{"description":"The check ran; the proof is not there yet. 200, not an error — retry once the token is published. `legal_effect` is `no_determination`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["verification_id","status","check","reason","attempts"],"properties":{"verification_id":{"type":"string","description":"Decimal string; PostgreSQL bigint. Not a JSON number — a bigint beyond 2^53 would round in a client that parses it as one, and this value is reused as the {id} path parameter of the check and revoke operations and as `verification_id` on POST /personas. That request accepts either this string or the JSON number earlier releases returned."},"status":{"type":"string","const":"pending"},"check":{"type":"string","const":"failed"},"reason":{"type":"string"},"attempts":{"type":"integer"},"legal_effect":{"type":"string","const":"no_determination"}}}]},"RelationReceipt":{"description":"An agency's assertion was recorded — APR has not evaluated it. `legal_effect` is `assertion_recorded; not evaluated`.","allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["relation_id","from","to","type"],"properties":{"relation_id":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"type":{"$ref":"#/components/schemas/RelationType"},"legal_effect":{"type":"string","const":"assertion_recorded; not evaluated"}}}]},"Healthz":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string","const":"not_ready","description":"Present only when `ok` is false."},"legal_effect":{"type":"string","const":"no_determination"}}}]},"AttestationStatement":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","description":"The current attestation statement (DECISIONS.md \"The attestation model\") — the boilerplate every Registration Agency attests to on `POST /personas`, not any persona's own recorded attestation (which stays RA-authenticated-only, same as this one). PR #27 (Codex), round 3, P1.","required":["statement_version","statement","policy_version","policy_document"],"properties":{"statement_version":{"type":"string","const":"0.1"},"statement":{"type":"string","description":"Draft — not yet counsel-reviewed (DECISIONS.md #20's open counsel questions include the wording itself). #21(d)'s interim rule keeps user-facing text from an uncounseled draft behind the counsel gate, so this is served only to an authenticated agency, never published on the public record; a future revision bumps statement_version rather than silently rewriting what an already-cited version means."},"policy_version":{"type":"string","const":"RAA-0.2-draft"},"policy_document":{"type":"string","const":"Registration Agency Agreement §4(4) and the Evidence Retention Schedule"}}}]}}}}