# 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](README.md#what-still-requires-a-human-once-on-chain).

## 1. GET discovery

```bash
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:

```bash
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](../sovereign-tier.md#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](../sovereign-tier.md#discovery-how-an-agent-finds-all-this).

## 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`.
- `exp` must be within the discovery document's `max_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 `Active` or `Grace` on-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](../guide/errors.md). 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](README.md) — the overview and the self-onboarding model.
- [Agent tokens (`agent-zk`)](../guide/agent-tokens.md) — the mode explained for humans.
- [Sovereign tier](../sovereign-tier.md) — the full reference.
