Skip to content

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:

  1. Create or select a local signing DID.
  2. Mint a short-lived provisioning token from Guardian.
  3. Register that DID with the gateway.
  4. 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.

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_url is set in your Viper config.
  • guardian_url is set in your Viper config.

If you have not assigned a DID yet, do that first:

Terminal window
viper did add --assign codex

Then register:

Terminal window
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/registrations with your assigned did: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:

Terminal window
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_name is required.
  • agent_handle is optional, but useful when you want a deterministic, user-scoped agent_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

Terminal window
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:

Terminal window
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_id with a new did: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 submitted did:key was already active for that agent and no registry state changed.

Important behavior and limits

  • Provisioning tokens are single-use. Guardian records token use by jti and rejects replay.
  • A provisioning token only works for the agent_id encoded 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:key is already registered to another agent
  • the target agent has been revoked

Viper says no DID is configured

Create or assign a DID first:

Terminal window
viper did add --assign codex

Security 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.