Python: Registry
LogRegistry
Section titled “LogRegistry”from dnsid import LogRegistryHolds one factory per log method; constructs bound LogReader instances on demand.
For C2SP, prefer create_dnsid_managed_verification_registry() or
create_c2sp_tlog_verification_registry(...) over manual registration.
IdentityManager uses the registry to bind the local log and to construct
counterparty readers during verification.
LogRegistry constructor
Section titled “LogRegistry constructor”LogRegistry() -> NoneInitialize an empty registry with no registered log method factories.
register
Section titled “register”LogRegistry.register(method: str, factory: LogReaderFactory) -> NoneRegister a factory for method (e.g. ‘c2sp-tlog’).
Arguments:
method(str): Log method name; must match[a-z][a-z0-9-]*.factory(LogReaderFactory): Callable that builds a LogReader bound to a full lr string.
Raises:
ArgumentError: If method does not match[a-z][a-z0-9-]*.
new_reader
Section titled “new_reader”LogRegistry.new_reader(lr: str) -> LogReaderConstruct a LogReader bound to the full log reference lr.
Splits lr on the first ’:’ to extract the method prefix, then calls the registered factory.
Arguments:
lr(str): Full log reference string, e.g."c2sp-tlog:public:https://log.dnsid.ai#<stream-id>".
Returns:
LogReader— A LogReader bound to lr, or a NoopLogReader when the method has no registered factory.
Raises:
ParseError: If lr is malformed.
RegistryClient
Section titled “RegistryClient”from dnsid import RegistryClientBases: AbstractRegistryClient
Operator-side client for managing the local agent’s own registration.
The primary contract is the DNSid registry API under /api/v1:
POST /api/v1/agent— register an agentPOST /api/v1/agent/{fqdn}/verify— request verificationPOST /api/v1/agent/{fqdn}/challenge— submit the signed challenge noncePOST /api/v1/agent/{fqdn}/proof— submit managed Live proofPOST /api/v1/agent/{fqdn}/proof/reissue— reissue managed Live proofGET /api/v1/agent/{fqdn}/status— fetch lifecycle statePOST /api/v1/agent/{fqdn}/record— fetch canonical record contentPOST /api/v1/agent/{fqdn}/signature— submit signature for publicationPOST /api/v1/agent/{fqdn}/retire— retire an agentDELETE /api/v1/agent/{fqdn}— unregister
NOT used by VerifyDomain; protocol verification always fetches the signed su endpoint.
Hosted mutation calls require owner credentials (an organization session or
API key), supplied as api_key and sent as an Authorization: Bearer
header. Loopback registry calls do not require credentials. An agent bearer
token is not sufficient. Legacy status reads are public, but Live status
requires the same owner authentication because it
may expose a proof challenge. A configured credential is sent on all reads.
The credential is never included in repr()/str(), exceptions, or logs.
Restore/reactivation operations (un-retiring or un-revoking an agent) are
deliberately not exposed: the registry treats RETIRED and REVOKED as
terminal, so recovery is an operator/registry-console action, not an SDK
call. The signed _dnsid TXT record is authoritative in DNS, not in
the registry API — read the effective published values (e.g. ku/su) by
resolving and verifying the record with IdentityManager.verify_domain.
RegistryClient constructor
Section titled “RegistryClient constructor”RegistryClient(base_url: str | None = None, *, api_key: str | None = None) -> NoneInitialize the client with a registry base URL and optional credential.
Arguments:
base_url(str | None): HTTPS registry base URL, or HTTP loopback URL for the local registry; defaults toDEFAULT_REGISTRY_URL(the local registry fromdnsid local up). Hosted use requires an explicit URL; seednsid.registry_client_from_environment. A trailing slash is stripped. — defaultNoneapi_key(str | None): Owner session or organization API-key credential sent as anAuthorization: Bearerheader. Whitespace-only values are treated as absent; without one hosted clients can only read legacy status. The loopback registry does not require a credential. Constructors never read the environment themselves. — defaultNone
Raises:
ValueError: If base_url is not a safe HTTPS or loopback HTTP URL, or api_key contains control characters that could corrupt the Authorization header.
register_agent
Section titled “register_agent”RegistryClient.register_agent(input: AgentRegistrationInput) -> AgentRegistrationRegister an agent with the registry.
POST /api/v1/agent
Pass domain to register a name you control (self-managed), or
zone_id to have the registry assign a name in a delegated zone
(registry-managed). The two are mutually exclusive, and
managed=True requires zone_id. environment defaults to
"production"; "sandbox" is also accepted. For Live names use
register_live_agent.
register_live_agent
Section titled “register_live_agent”RegistryClient.register_live_agent(input: LiveAgentRegistrationInput, idempotency_key: str) -> LiveProvisioningResponseStart managed Live registration and return its validated proof challenge.
POST /api/v1/agent sends fixed tier="live" and managed=true
discriminators. Sign the exact base64url-decoded challenge_message
bytes; use the derived LiveProvisioningResponse.domain for the
proof route.
verify_agent
Section titled “verify_agent”RegistryClient.verify_agent(domain: str) -> AgentRegistrationRequest registry verification for a registered self-managed agent.
POST /api/v1/agent/{domain}/verify
revoke_agent
Section titled “revoke_agent”RegistryClient.revoke_agent(domain: str, agent_id: str, reason: RegistryRevocationReason) -> LifecycleResultRevoke an agent through the registry-owned terminal lifecycle flow.
POST /api/v1/agent/{domain}/revoke
The registry owns persistence and the transparency-log append; this convenience method does not prepare or append a second local event.
Arguments:
domain(str): Agent FQDN to revoke.agent_id(str): Immutable registry agent ID for safe retries after domain re-registration.reason(RegistryRevocationReason): Typed owner-authorized registry revocation reason.
Returns:
LifecycleResult— The registry lifecycle transition result.
Raises:
ArgumentError: If reason is not aRegistryRevocationReason.
retire_agent
Section titled “retire_agent”RegistryClient.retire_agent(domain: str, agent_id: str) -> LifecycleResultRetire an agent while retaining its key for historical verification.
POST /api/v1/agent/{domain}/retire with the immutable agent_id.
cancel_agent
Section titled “cancel_agent”RegistryClient.cancel_agent(domain: str) -> LifecycleResultCancel an agent that is still inside the registration workflow.
POST /api/v1/agent/{domain}/cancel. A PENDING agent cannot be
revoked (the registry answers 409 INVALID_TRANSITION); cancelling
is the transition that removes it. Mirrors cancelAgent in the
TypeScript SDK and CancelAgent in Go.
submit_challenge_signature
Section titled “submit_challenge_signature”RegistryClient.submit_challenge_signature(domain: str, nonce: str, signature: bytes | str) -> LifecycleResultSubmit the signed verification challenge to prove key possession.
POST /api/v1/agent/{domain}/challenge
During the VERIFICATION lifecycle state the registry exposes a
challenge nonce on the status document (raw["challenge"]). The
agent signs the base64url-decoded nonce bytes with its active signing
key and submits the signature here. The registry accepts the
challenge and completes verification asynchronously; use
wait_for_status with target_state="VERIFIED" afterwards.
The nonce is single-use — a fresh one must be fetched before retrying.
Arguments:
domain(str): Agent FQDN.nonce(str): Challenge nonce exactly as returned by the status endpoint.signature(bytes | str): Raw signature bytes (base64url-encoded automatically), or an already base64url-encoded signature string.
submit_live_proof
Section titled “submit_live_proof”RegistryClient.submit_live_proof(domain: str, request: LiveProofRequest) -> LiveProofResponseSubmit signed proof for managed Live provisioning.
POST /api/v1/agent/{domain}/proof requires owner/session/API-key
credentials, not an agent bearer token. request.request_id is sent
as the required Idempotency-Key header.
reissue_live_proof
Section titled “reissue_live_proof”RegistryClient.reissue_live_proof(domain: str, request: LiveProofReissueRequest) -> LiveProofReissueResponseRequest a replacement challenge for an expired managed Live proof.
POST /api/v1/agent/{domain}/proof/reissue requires owner/session/API-key
credentials, not an agent bearer token. request.request_id is sent
as the required Idempotency-Key header. The original public key is
validated locally and binds the replacement transcript, but is not sent.
The replacement challenge supersedes every earlier challenge; only the
latest message may be signed.
get_status
Section titled “get_status”RegistryClient.get_status(domain: str) -> str | NoneFetch the raw lifecycle status string from the registry.
GET /api/v1/agent/{domain}/status
Returns the uppercased status string (e.g. "ACTIVE", "VERIFIED"),
or None if the agent is not registered (HTTP 404). Live status
requires owner/session/API-key authentication; legacy status is public.
get_agent_status
Section titled “get_agent_status”RegistryClient.get_agent_status(domain: str) -> RegistryAgentStatus | NoneFetch registry lifecycle state.
GET /api/v1/agent/{domain}/status
Returns None if the agent is not registered (HTTP 404). Live status
requires owner/session/API-key authentication; legacy status is public.
Returns a RegistryAgentStatus with computed boolean flags that
mirror the TypeScript RegistryAgentStatus interface.
get_registration
Section titled “get_registration”RegistryClient.get_registration(domain: str) -> AgentRegistration | NoneReturn the current registry registration without conflating status namespaces.
wait_for_status
Section titled “wait_for_status”RegistryClient.wait_for_status(domain: str, *, timeout: float = 120.0, interval: float = 5.0, target_state: str | None = None) -> RegistryAgentStatusPoll the registry until the agent reaches a settled or target state.
Returns when the agent is published, failed, or terminal — or when
target_state (a raw registry status string such as "VERIFIED") is
reached. Raises VerificationError(LOG_ERROR) on timeout or if a
failed/terminal state is reached before target_state.
Arguments:
domain(str): Agent FQDN.timeout(float): Maximum seconds to wait before raising. — default120.0interval(float): Seconds between status polls. — default5.0target_state(str | None): Raw registry status string to wait for specifically. — defaultNone
async_wait_for_status
Section titled “async_wait_for_status”RegistryClient.async async_wait_for_status(domain: str, *, timeout: float = 120.0, interval: float = 5.0, target_state: str | None = None) -> RegistryAgentStatusAsync variant of wait_for_status.
Sleeps with asyncio.sleep between polls so the event loop is not
blocked. The status fetch itself is synchronous (httpx).
unregister_agent
Section titled “unregister_agent”RegistryClient.unregister_agent(domain: str) -> NoneBest-effort unregister. Silently ignores 404 and 405.
DELETE /api/v1/agent/{domain}
canonical_record_content
Section titled “canonical_record_content”RegistryClient.canonical_record_content(domain: str, signing_kid: str) -> CanonicalRecordContentResponseFetch the registry’s canonical TXT record content for signing.
POST /api/v1/agent/{domain}/record
Returns a CanonicalRecordContentResponse with the canonical
byte string and the registry’s view of the active signing key ID.
The SDK MUST validate both before signing; this response is not trusted input.
publish_signature
Section titled “publish_signature”RegistryClient.publish_signature(domain: str, sig: str) -> PublishedRecordSubmit the record signature to complete the publish workflow.
POST /api/v1/agent/{domain}/signature
sig MUST be the profile-owned sg value. For draft 01 this is the bare unpadded base64url signature bytes.
prepare_issuance
Section titled “prepare_issuance”RegistryClient.prepare_issuance(domain: str, idempotency_key: str) -> PreparedRegistryEventFetch exact raw ISSUANCE bytes for operational countersigning.
POST /api/v1/agent/{domain}/tlog/issuance/prepare has no request
body. Both the response bytes and DNSID-Log-Reference header are
untrusted and must be validated by the C2SP prepared-event binding.
prepare_key_rotation
Section titled “prepare_key_rotation”RegistryClient.prepare_key_rotation(domain: str, request: KeyRotationPreparationRequest, idempotency_key: str) -> PreparedRegistryEventRequest a registry-prepared KEY_ROTATION envelope.
POST /api/v1/agent/{domain}/tlog/key-rotation/prepare
This operation requires owner/session/API-key credentials, not an
agent bearer token. The idempotency key travels as transport metadata
(Idempotency-Key header); reusing it with different key material fails server-side, as
does a second preparation against the same previous key. Only public
JWK members are sent — a request.public_key carrying private
material is rejected locally before any request.
Returns the exact canonical prepared-envelope bytes and bound log
reference as UNTRUSTED input: the caller must parse both with the bound log implementation
(dnsid.c2sp_tlog.parse_prepared_event), independently reproduce
the signed bytes, and verify domain, previous key, new key, log
context, and stream-chain fields before adding any signature.
submit_prepared_event
Section titled “submit_prepared_event”RegistryClient.submit_prepared_event(domain: str, entry_bytes: bytes, idempotency_key: str) -> SubmissionResultSubmit exact complete canonical entry bytes for append.
POST /api/v1/agent/{domain}/tlog/events
The bytes are sent unchanged as the application/json request body; a timeout or indeterminate result is retried with the same bytes and the same idempotency key. The registry’s durable idempotency mapping returns the original pending/accepted result for the same (identity, key, byte-hash) triple and rejects key reuse with different bytes.
AgentRegistrationInput
Section titled “AgentRegistrationInput”from dnsid import AgentRegistrationInputInput for registering an agent with the registry.
AgentRegistration
Section titled “AgentRegistration”from dnsid import AgentRegistrationAgent registration record returned by the registry.
LiveAgentRegistrationInput
Section titled “LiveAgentRegistrationInput”from dnsid import LiveAgentRegistrationInputInput for the dedicated managed Live registration operation.
LiveChallengeTranscript
Section titled “LiveChallengeTranscript”from dnsid import LiveChallengeTranscriptValidated transcript encoded by a managed Live proof challenge.
LiveProvisioningResponse
Section titled “LiveProvisioningResponse”from dnsid import LiveProvisioningResponseProof challenge returned when managed Live provisioning starts.
LiveProofRequest
Section titled “LiveProofRequest”from dnsid import LiveProofRequestSigned proof submitted after managed Live registration.
LiveProofResponse
Section titled “LiveProofResponse”from dnsid import LiveProofResponseDurable handoff status returned after managed Live proof.
LiveProofReissueRequest
Section titled “LiveProofReissueRequest”from dnsid import LiveProofReissueRequestRequest for a replacement managed Live proof challenge.
The original public key is local validation context and is not sent to the registry.
LiveProofReissueResponse
Section titled “LiveProofReissueResponse”from dnsid import LiveProofReissueResponseReplacement challenge returned for an expired managed Live proof.
LifecycleResult
Section titled “LifecycleResult”from dnsid import LifecycleResultLifecycle transition result.
Returned by registry mutation endpoints such as submit_challenge_signature.
RegistryAgentStatus
Section titled “RegistryAgentStatus”from dnsid import RegistryAgentStatusRegistry workflow status, kept separate from protocol AgentStatus.
CanonicalRecordContentResponse
Section titled “CanonicalRecordContentResponse”from dnsid import CanonicalRecordContentResponseRegistry-prepared canonical TXT record content for a local identity signing workflow.
PublishedRecord
Section titled “PublishedRecord”from dnsid import PublishedRecordRecord returned after a successful publish_to_registry call.
PreparedRegistryEvent
Section titled “PreparedRegistryEvent”from dnsid import PreparedRegistryEventRaw untrusted bytes and log context returned by a prepare endpoint.
KeyRotationPreparationRequest
Section titled “KeyRotationPreparationRequest”from dnsid import KeyRotationPreparationRequestInput for RegistryClient.prepare_key_rotation.
previous_key_id is the RFC 7638 thumbprint of the current operational key — the registry rejects a stale or concurrent rotation. public_key is one pending public signing key including kid and alg; private JWK members are forbidden and never leave the SDK.
KeyRotationResult
Section titled “KeyRotationResult”from dnsid import KeyRotationResultDurable recovery state for a managed operational-key rotation.
Entry bytes, their hash, key bindings, and the idempotency key are fixed
once first persisted. When activated is False, call
IdentityManager.resume_key_rotation(registry_client, result) to retry
the exact same bytes under the same idempotency key. Application signing
remains paused until accepted submission, local activation/supersession,
and durable state reconciliation all complete.
SubmissionResult
Section titled “SubmissionResult”from dnsid import SubmissionResultTyped result of RegistryClient.submit_prepared_event.
state is one of “pending”, “accepted”, or “rejected”. A pending or otherwise indeterminate submission is retried with the exact same entry bytes and idempotency key; the entry is never regenerated.
Attributes:
accepted(bool): Return True when the registry reported the appended entry as accepted.