Self-Register an Agent
This guide explains how a developer can onboard an agent into Guardian without opening a ticket or waiting for an administrator to manually provision a signing key.
The self-service flow is:
- Create or select a local signing DID.
- Mint a short-lived provisioning token from Guardian.
- Register that DID with the gateway.
- Start sending signed traffic through the gateway.
For most users, the best experience is viper did register. It uses the same gateway registration endpoint as every other runtime, but removes most of the manual steps.
What self-registration does
Self-registration lets an agent runtime attach its signing key to a user-scoped agent record in the Guardian registry.
When registration succeeds, the gateway:
- creates or updates the agent record
- marks the signing key as active
- binds the provisioning token subject to the agent
- records a registration event for audit
After that, requests signed with the registered did:key can be verified and forwarded by the gateway.
Recommended path: Viper browser flow
Use this path when you are running Viper locally and want the fewest manual steps.
Prerequisites
- You already have a local DID configured for the target app.
llm_gateway_api_urlis set in your Viper config.guardian_urlis set in your Viper config.
If you have not assigned a DID yet, do that first:
viper did add --assign codexThen register:
viper did register --app codex --agent-name "Jordan Codex Agent"What happens next:
- Viper opens the Guardian UI in your browser.
- You authenticate with Guardian.
- Guardian mints a short-lived provisioning token for your agent.
- The browser hands that token back to your local Viper process over a loopback callback.
- Viper calls
POST /v1/agents/registrationswith your assigneddid:key.
This is the simplest self-service onboarding flow in the platform today.
Headless path: mint a token, then register
Use this path for CI, remote shells, or any flow where browser handoff is inconvenient.
Step 1: Mint a provisioning token from the control plane
Call the control-plane registration token endpoint:
POST /v1/agent-registration-tokens
Minimal request:
{ "agent_name": "CI Codex Agent", "agent_handle": "ci-codex"}Example:
curl -X POST "$CONTROL_PLANE_URL/v1/agent-registration-tokens" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ --data '{ "agent_name": "CI Codex Agent", "agent_handle": "ci-codex" }'Example response:
{ "agent_id": "agent:jordan-1a2b3c4d:ci-codex", "registration_ttl_seconds": 900, "expires_at": "2026-04-07T18:15:00Z", "registration_token": "ggr1.<payload_b64url>.<sig_b64url>", "correlation_id": "..."}Notes:
agent_nameis required.agent_handleis optional, but useful when you want a deterministic, user-scopedagent_id.- The token is currently valid for 15 minutes.
- The minted token is scoped to exactly one
agent_id.
Step 2: Pass the token to Viper
viper did register \ --app codex \ --agent-name "CI Codex Agent" \ --provisioning-token "$REGISTRATION_TOKEN"Viper reads the agent_id from the token, uses the DID already assigned to the target app, and registers that DID with the gateway.
Manual path: custom runtime calls the gateway directly
Use this path only if you are not using Viper.
Your runtime must:
- hold an Ed25519 or P-256 signing key
- represent the public key as a
did:key - obtain a provisioning token from Guardian first
Call the gateway registration endpoint:
POST /v1/agents/registrations
Minimal request:
{ "agent_id": "agent:jordan-1a2b3c4d:ci-codex", "did": "did:key:z6Mkt2HssyBGaThf7ofBwQvVcPLXnBWvgZp7s3K4DkCyLLjz", "did_source": "Software (ed25519)", "display_name": "CI Codex Agent", "provisioning_token": "ggr1.<payload_b64url>.<sig_b64url>"}did_source records the type of integrity signer that produced the DID — use "Software (ed25519)" or "Software (p256)" for software-managed keys, or "YubiKey" for hardware-backed keys.
Example:
curl -X POST "$LLM_GATEWAY_URL/v1/agents/registrations" \ -H "Content-Type: application/json" \ --data '{ "agent_id": "agent:jordan-1a2b3c4d:ci-codex", "did": "did:key:z6Mkt2HssyBGaThf7ofBwQvVcPLXnBWvgZp7s3K4DkCyLLjz", "did_source": "Software (ed25519)", "display_name": "CI Codex Agent", "provisioning_token": "ggr1.<payload_b64url>.<sig_b64url>" }'Example success response:
{ "agent_id": "agent:jordan-1a2b3c4d:ci-codex", "agent_status": "active", "key_status": "active", "registration_result": "created", "registered_at": 1771874800, "vc": { "@context": ["https://www.w3.org/ns/credentials/v2", "..."], "type": ["VerifiableCredential", "GuardianAgentCredential"], "issuer": "did:key:...", "credentialSubject": { "id": "did:key:z6Mkt..." }, "proof": { "type": "Ed25519Signature2020", "...": "..." } }, "correlation_id": "..."}vc is the W3C Verifiable Credential the gateway issues for the registered DID (Ed25519Signature2020). Persist it — Viper pins it as the agent’s credential, content-addresses it, and includes its CID in the signed X-Viper-Credential-CID list on later requests. The older X-Viper-Verifiable-Credential-CID header is deprecated and accepted only for migration compatibility.
For the self-service path, agent_id, did, did_source, and provisioning_token are the required fields. The gateway derives the initial user binding from the provisioning token subject.
What happens after registration
Once the gateway accepts the registration:
- the agent can sign requests with
keyid="did:key:..." - the gateway resolves trust from the registry instead of a pre-provisioned key list
- future key rotation can happen by registering the same
agent_idwith a newdid:key
Possible registration_result values:
created: Guardian created a new agent record.updated: Guardian updated an existing record, such as a rotated key.noop: the submitteddid:keywas already active for that agent and no registry state changed.
Important behavior and limits
- Provisioning tokens are single-use. Guardian records token use by
jtiand rejects replay. - A provisioning token only works for the
agent_idencoded in that token. - Revoked agents cannot self-register again until they are restored through the control plane.
- The registration endpoint must be enabled on the gateway and backed by the Postgres registry.
In practice, that means:
- if a token expires, mint a new one
- if you are unsure whether a request succeeded, check the agent in Guardian before retrying with a fresh token
- do not build a client flow that assumes the same token can be reused safely
Troubleshooting
401 registration_invalid_token
Common causes:
- the token expired
- the token was already used
- the token was minted for a different
agent_id - the gateway and control plane are not using compatible registration token settings
409 registration_conflict
Common causes:
- the
did:keyis already registered to another agent - the target agent has been revoked
Viper says no DID is configured
Create or assign a DID first:
viper did add --assign codexSecurity notes
- Do not log provisioning tokens in plaintext.
- Do not log private key material or seed values.
- Keep provisioning token TTLs short.
- Treat the provisioning token as a bootstrap credential, not a long-lived secret.