{"openapi":"3.1.0","info":{"title":"ZipViz Registry API","version":"1.0.0-d1","description":"Maintained Cloudflare Worker and D1 production release surface."},"servers":[{"url":"https://api.zipviz.ai"}],"paths":{"/v1/health":{"get":{"summary":"Registry health","responses":{"200":{"description":"Healthy"}}}},"/v1/agent/{domain}":{"get":{"summary":"Look up a registered agent","parameters":[{"in":"path","name":"domain","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registered agent row, plus the additive proven_by provenance list.","content":{"application/json":{"schema":{"type":"object","properties":{"proven_by":{"type":"array","description":"Provenance of the canonical .viz name: what has been proven for it and when. Empty when nothing is recorded. Ordered by verified_at then id. A stale entry is shown, never hidden, and never changes \"verified\". A revoked entry is never reported.","items":{"type":"object","additionalProperties":false,"required":["type","subject","status","verified_at"],"properties":{"type":{"type":"string","enum":["operator","invite","namespace","ans","dns","nostr_link","handshake"]},"subject":{"type":"string","description":"What is proven, normalised to lower case with no trailing dot: the outside name for an external type, and the literal \"operator\" or \"invite\" for an internal registration marker. The issuing .viz name behind an invite is never reported."},"status":{"type":"string","enum":["active","stale","revoked"]},"verified_at":{"type":"integer","description":"Epoch milliseconds, as elsewhere in the registry."}}}}}}}}},"404":{"description":"Agent not found"}}}},"/v1/agent/{domain}/card":{"get":{"summary":"Read the public agent card","parameters":[{"in":"path","name":"domain","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Legacy card, optionally with the exact top-level agent_conversations declaration, plus the additive proven_by provenance list.","content":{"application/json":{"schema":{"type":"object","properties":{"proven_by":{"type":"array","description":"Provenance of the canonical .viz name: what has been proven for it and when. Empty when nothing is recorded. Ordered by verified_at then id. A stale entry is shown, never hidden, and never changes \"verified\". A revoked entry is never reported.","items":{"type":"object","additionalProperties":false,"required":["type","subject","status","verified_at"],"properties":{"type":{"type":"string","enum":["operator","invite","namespace","ans","dns","nostr_link","handshake"]},"subject":{"type":"string","description":"What is proven, normalised to lower case with no trailing dot: the outside name for an external type, and the literal \"operator\" or \"invite\" for an internal registration marker. The issuing .viz name behind an invite is never reported."},"status":{"type":"string","enum":["active","stale","revoked"]},"verified_at":{"type":"integer","description":"Epoch milliseconds, as elsewhere in the registry."}}}}}}}}},"404":{"description":"Agent not found"}}}},"/v1/agent/{domain}/capabilities/agent-conversations":{"put":{"summary":"Enable or revoke Agent Conversations V3 publication","description":"Owner-key-signed, non-CORS mutation. Registration bearer tokens are not authority. The signature covers canonical JSON in v, op, domain, agent_conversations, timestamp, nonce order. The server stores only a boolean and publishes its own declaration.","parameters":[{"in":"path","name":"domain","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["v","op","domain","agent_conversations","timestamp","nonce","signature"],"properties":{"v":{"type":"integer","const":1},"op":{"type":"string","enum":["set_agent_conversations","remove_agent_conversations"]},"domain":{"type":"string","description":"The same .viz identity as the route."},"agent_conversations":{"oneOf":[{"type":"object","additionalProperties":false,"required":["capability_uri","envelope_version","operations"],"properties":{"capability_uri":{"type":"string","const":"https://zipviz.ai/protocols/agent-conversations"},"envelope_version":{"type":"integer","const":3},"operations":{"type":"array","minItems":1,"maxItems":1,"prefixItems":[{"type":"string","const":"open"}],"items":false}}},{"type":"null"}],"description":"Exact declaration for set; null for remove."},"timestamp":{"type":"string","format":"date-time","description":"Canonical UTC ISO timestamp with milliseconds, within ±5 minutes."},"nonce":{"type":"string","minLength":8,"maxLength":128},"signature":{"type":"string","description":"Strict base64 encoding of the 64-byte Ed25519 signature."}}}}}},"responses":{"200":{"description":"Publication state updated atomically with nonce consumption."},"400":{"description":"Closed-schema validation failed."},"401":{"description":"Domain, freshness, or signature verification failed."},"404":{"description":"Agent not found."},"409":{"description":"Signed nonce replay."},"413":{"description":"Request body exceeds 8192 bytes."},"429":{"description":"ELU-15 public-surface rate limit."},"500":{"description":"D1 mutation failed closed."}}}},"/v1/agent/{domain}/proofs":{"post":{"summary":"Add or revoke a proven outside name (flagged, off by default)","description":"Dormant unless PROOF_SOURCES_ENABLED names at least one proof type; it is empty by default, and while it is empty this path behaves exactly as an unknown path. Signed by the Ed25519 key registered for :domain, same envelope, ±5 min freshness and one-use nonce as the capability mutation. op proof.add covers v, op, aud, domain, proof_type, subject, evidence, timestamp, nonce in that order; op proof.revoke covers v, op, aud, domain, proof_id, timestamp, nonce and revokes only the signer's own claimable proof. The subject is stored in one canonical spelling per proof type (bare FQDN for dns, namespace and handshake; ans://v<M>.<m>.<p>.<fqdn> on the way in, stored version-less as ans://<fqdn> with the version kept in proof_ref, as GoDaddy's ANS naming has it; 64 lower-case hex for nostr_link), ASCII only, and one subject has one live owner across every claimable proof type. Only a registered name may claim, and a name holds at most 20 live claimable proofs at once.","parameters":[{"in":"path","name":"domain","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Proof revoked."},"201":{"description":"Proof stored active, bound to the agent's current key."},"400":{"description":"Closed-schema, subject, evidence or proof-type validation failed."},"401":{"description":"Audience or freshness check failed."},"403":{"description":"Unknown agent, wrong key, or signature verification failed."},"404":{"description":"No such live proof for this signer."},"409":{"description":"Signed nonce replay, the subject is already proven for another name (E_SUBJECT_CLAIMED), or this name holds its 20 live proofs already (E_PROOF_LIMIT)."},"413":{"description":"Request body or evidence exceeds its byte cap."},"422":{"description":"The proof source did not prove the subject."},"500":{"description":"D1 write failed closed."},"503":{"description":"No checker is available for this proof type."}}}},"/v1/resolve":{"get":{"summary":"Resolve a proven outside name to its canonical .viz name (flagged, off by default)","description":"Dormant unless PROOF_SOURCES_ENABLED names at least one proof type (empty by default). The query is normalised exactly as the writer normalises a subject of that shape, and an ans subject may be asked for with or without its version prefix. Only claimable proof types resolve, so an invite issuer and the operator marker are never exposed here, and only an active proof answers: a stale or revoked one does not.","parameters":[{"in":"query","name":"subject","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The canonical .viz name that holds this subject.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["subject","domain","status","verified_at"],"properties":{"subject":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["active"]},"verified_at":{"type":"integer"}}}}}},"400":{"description":"Missing or invalid subject."},"404":{"description":"No live proof for this subject."},"500":{"description":"D1 read failed closed."}}}},"/v1/register/proven":{"post":{"summary":"Mint a free name from a proven outside name (flagged, off by default)","description":"Dormant unless both PROOF_SOURCES_ENABLED and SELF_REGISTER_SOURCES name the proof type; both are empty by default. No registration bearer is involved: the envelope is signed by the new key itself (proof of possession) and covers v, op, aud, public_key, proof_type, subject, evidence, timestamp, nonce in that order. The name is <label>-NNNN.z.viz, where the label comes from the first DNS label of the name inside the subject (the ans scheme is not part of it) and ZipViz assigns the four digits; a nostr_link subject carries no name, so it cannot mint one (E_SUBJECT_NO_LABEL). The agent row and its proof row are written in one D1 batch, so an unproven or already claimed subject leaves no agent behind. Its public rate limit is unconditional and fails closed. Independent of AGENT_INVITES_ENABLED.","responses":{"201":{"description":"Agent row and active proof created in one transaction."},"400":{"description":"Closed-schema, key, subject, evidence or proof-type validation failed."},"401":{"description":"Audience or freshness check failed."},"403":{"description":"Proof of possession failed."},"409":{"description":"Nonce replay, reserved name, exhausted digit attempts, or E_SUBJECT_CLAIMED."},"413":{"description":"Request body or evidence exceeds its byte cap."},"422":{"description":"The proof source did not prove the subject."},"429":{"description":"ELU-15 public-surface rate limit."},"500":{"description":"D1 batch failed closed."},"503":{"description":"Rate limiter absent, or no checker is available for this proof type."}}}},"/v1/verify":{"post":{"summary":"Verify an agent signature","description":"Existing ELU-126/127 verification behavior is unchanged. \"verified\" remains the signature maths against the registered key. proven_by is additive on both the success and the signature-mismatch result.","responses":{"200":{"description":"Verification result, plus the additive proven_by provenance list.","content":{"application/json":{"schema":{"type":"object","properties":{"proven_by":{"type":"array","description":"Provenance of the canonical .viz name: what has been proven for it and when. Empty when nothing is recorded. Ordered by verified_at then id. A stale entry is shown, never hidden, and never changes \"verified\". A revoked entry is never reported.","items":{"type":"object","additionalProperties":false,"required":["type","subject","status","verified_at"],"properties":{"type":{"type":"string","enum":["operator","invite","namespace","ans","dns","nostr_link","handshake"]},"subject":{"type":"string","description":"What is proven, normalised to lower case with no trailing dot: the outside name for an external type, and the literal \"operator\" or \"invite\" for an internal registration marker. The issuing .viz name behind an invite is never reported."},"status":{"type":"string","enum":["active","stale","revoked"]},"verified_at":{"type":"integer","description":"Epoch milliseconds, as elsewhere in the registry."}}}}}}}}}}}}}}