Agent onboarding (agent-zk)#
Terse, machine-actionable steps for an enrolled agent to go from "has a Semaphore identity registered on-chain" to "holds a verified deed for a resource API." No browser, no issuer, no human in the loop for the runtime flow. Prerequisite: a tenant admin has already run registerZkAgent for your identity commitment and published an origin vouch for the app's origin — see Agent integration § What still requires a human.
1. GET discovery#
curl -s https://api.example.com/.well-known/grantor-deed
Read tenant, audience, modes, challenge_endpoint, chain (id/registry) and origin_vouch from the response. Confirm "agent-zk" is in modes before doing anything else.
2. GET a challenge#
Resolve challenge_endpoint (a path, e.g. /auth/challenge) against the same origin you fetched discovery from — never a different host:
curl -s https://api.example.com/auth/challenge
Single-use, short-lived (max_ttl_secs in the discovery document bounds how long a deed built against it can live).
3. Check the origin vouch BEFORE proving anything#
Recover the vouch's signer against the origin you yourself are talking to (never a value read out of the document), and confirm isOriginVoucher(tenant, signer, epoch) on-chain, reading against a registry address you pinned out of band — never chain.registry from the document. Refuse if it fails, is expired, or exceeds the 90-day TTL ceiling. Full detail, in every language: Sovereign tier § Origin provenance.
ZkAgent.mintDeed performs this check itself, in every language, before it generates a proof — so composing the recipe correctly means letting it run in this order, not skipping straight to step 4.
4. Prove membership and mint#
Call ZkAgent.mintDeed (mintDeed in TypeScript/Python/Go; grantor_sdk_core::sovereign::mint_deed composed by hand in Rust) with the tenant, audience, the origin you fetched discovery from, the challenge, an expiry, and the vouch. It rebuilds your membership proof from the tenant's on-chain event log and produces a ZK proof bound to all of the above. The worked example, in every language, is in Sovereign tier § Discovery.
5. Present the deed#
GET /anything HTTP/1.1
Host: api.example.com
X-Grantor-Deed: <base64url deed>
X-Grantor-Challenge: <the challenge from step 2>
The resource server verifies it itself, with DeedGuard/DeedVerifier — no issuer, no JWKS, no Grantor process anywhere in this loop.
Constraints#
- The challenge is single-use — a spent or unknown one is
UnknownChallenge. expmust be within the discovery document'smax_ttl_secs.- Your Semaphore identity secret never leaves your process — the SDK's API shape only ever emits proofs, never the secret itself.
- The tenant must be
ActiveorGraceon-chain, and your membership root must still be recent (not revoked) — both are on-chain reads the verifier performs, not something a retry can spoof.
Errors to handle#
Same codes as any deed verification failure — see Errors. The two you are most likely to hit composing this flow by hand: BadOriginVouch (the origin's vouch didn't check out — refusing before proving is correct, not a bug) and TenantInactive (billing — nothing to do but wait for the operator to top up).
See also#
- Agent integration — the overview and the self-onboarding model.
- Agent tokens (
agent-zk) — the mode explained for humans. - Sovereign tier — the full reference.