Skip to main content

Agent Registry

Register AI agents in LumoAuth to give them their own identity, capabilities, and usage limits. Registered agents authenticate and are tracked separately from users.

Creating an agent​

Agents are usually created in the organization portal: open AI Agents → Agents (/orgs/{orgId}/portal/agents) and click Register agent. The same config can be set programmatically via the Admin API.

Registration is a three-step wizard: Who is this agent (name, description, owner, framework, optional metadata), How it proves its identity (the identity type and its binding or credential settings) and What it may do (capabilities, acting for people, budget and limits, and a review of what will be registered).

Step 1 of the Register agent wizard, Who is this agent, with the three-step progress bar

Step 3 holds the capability picker, the Acting for people switches and the budget limits:

Step 3 of the Register agent wizard, What it may do, with the grouped capability picker

Each agent has:

PropertyDescription
NameHuman-readable identifier (e.g., "Research Assistant Bot")
Agent IDThe public agt_… identifier, generated at registration
CredentialGenerated for you when the identity type needs one: an API key or a client ID and secret, each shown once on the page after registration. Workload identity, AAuth, client certificate and custom identities have no secret
CapabilitiesScopes/permissions the agent is allowed to request
Budget and limitsDaily hard limit, max steps per task and a timeout
Identity TypeHow the agent authenticates. The register form offers Workload identity, AAuth, Client certificate, Client ID and secret, API key and Custom; the type is fixed once the agent is registered
Workload IdentityExternal identity sources (Kubernetes, AWS, GCP) for cloud-native deployments
AAuth IdentityAAuth, the agent identity protocol: the agent signs each request with its own key, and tokens are bound to that key

Agent IDs are unique per organization​

The public agent_id (agt_… when generated, or caller-chosen on the legacy POST /orgs/{orgId}/api/v1/agents/register endpoint) is unique within an organization, not globally. Registering a second agent with an agent_id that already exists in the same organization returns 409 Conflict ({"error": "conflict", …}); the same id in another organization is unrelated and never blocks — one organization cannot squat on, or enumerate, another organization's agent ids. Every lookup by agent_id is scoped to the organization in the URL.

Registering through the API​

POST /orgs/{orgId}/api/v1/agents/register acts as the authenticated caller, never as an identity named in the body:

CallerRequirement
Browser sessionA user of this organization; the body must be sent as Content-Type: application/json (415 otherwise)
OAuth access tokenThe admin or admin:agents:write scope; a token with only openid profile gets 403 forbidden
API key (lmk_)The admin:agents:write scope

The user (or the key's creator) must hold agents.create. Re-registering an existing agent name updates that agent and therefore needs agents.edit; without it the call returns 409 conflict (An agent named "…" already exists in this organization) instead of overwriting its capabilities or workload identity.

The legacy inline email / password fields are an optional re-authentication for accounts without MFA only. They must be the caller's own credentials (403 otherwise), are rate limited (429 with Retry-After), and for an MFA-enrolled account they are refused with:

{ "error": "mfa_required", "error_description": "Password re-authentication is not accepted for accounts with multi-factor authentication. Omit email/password and call this endpoint with your authenticated session or access token." }

(403). Omit them and rely on the session or token. Each refusal is audited as agent.reauth_refused_mfa, agent.reauth_failed or agent.reauth_rate_limited.

Unified agent identity (AAuth linking)​

LumoAuth keeps one registry for all agents. Agents registered via the AAuth protocol (HTTPS-URL identity, per-agent keys) are linked to a core registry agent — link, not merge: both objects keep their own lifecycle and credentials, but they are navigable in both directions and the core agent carries the governance features (risk scoring, access policies, dormancy lifecycle).

  • Creating an agent with the AAuth identity type in the portal creates the core agent and its AAuth credentials together, already linked.
  • The unified agent list (AI Agents → Agents) shows every core agent on its Agents tab, with an AAuth-linked badge for agents that have AAuth credentials; the AAuth credentials and AAuth resources tabs list the AAuth side. The agent detail page (/orgs/{orgId}/portal/agents/{agentId}) gains an AAuth credentials panel (identifier, JWKS, refresh-token state, active token count).
  • Unlinked AAuth agents keep working exactly as before — linking is additive. Deleting a core agent from the portal removes its AAuth credentials with it; deleting through the admin API clears the link only.

Agent vs OAuth client​

Agents use OAuth 2.0 for authentication, with extra properties on top:

FeatureStandard OAuth ClientAgent
AuthenticationClient credentialsClient credentials, workload identity, or AAuth protocol
Identity TypeApplicationAI Agent
Usage LimitsRate limiting onlyBudget policies (tokens, API calls, costs)
DelegationNot typicallyCan act on behalf of users
UserInfo ResponseApplication infoAgent info with capabilities

Configuring capabilities​

Capabilities define what the agent can do. They map onto OAuth scopes but are shaped for agent use cases.

Capabilities are normalised when stored: lower-case, with : and - folded into . (Users:Delete → users.delete, read:* → read.*). The Ask API, JIT auto-approval and the creator-containment check all judge this canonical form, so spelling a wildcard with a colon never sidesteps a rule written with a dot. Only * and prefix.* are wildcards; a tenant admin cannot grant *, nor a catalogue permission or wildcard they do not hold themselves. Capability changes are audited as agent.updated with the old and new lists (creation and deletion as agent.created / agent.deleted).

{
"budget_policy": {
"max_tokens_per_day": 100000,
"max_api_calls_per_hour": 500,
"max_cost_per_month_usd": 50,
"require_approval_above_usd": 10
}
}
Budget enforcement

Budget limits are enforced in real time. When an agent exceeds its budget, subsequent requests are rejected until the budget resets or is increased.

Agent authentication​

Authenticating as the agent​

When you register an agent, LumoAuth mints an agent API key (agk_ prefix), shown once at creation. Present it as a Bearer token to call agent endpoints directly — it authenticates the agent as a ROLE_AGENT principal:

curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/agents/me \
-H "Authorization: Bearer agk_abc123xyz..."

The agk_ key is accepted on agent, JIT, and authz-check routes only. See API Authentication for the full reference, or use the SDK below to skip the raw HTTP.

Key expiry. Keys do not expire by default. Give them a lifetime (180 days recommended) with apiKeyExpiresInDays or an ISO-8601 apiKeyExpiresAt when creating or updating the agent through the Admin API; the response exposes apiKeyExpiresAt / apiKeyExpired. An expired key is refused with 401 even though it still matches. Rotating a key whose expiry has already passed gives the new key the recommended 180 days; a future expiry is kept. Each successful agk_ authentication records the agent's lastUsedAt (throttled to one write per five minutes), which dormancy policies and the risk score rely on.

Install the LumoAuth Agent SDK​

The lumoauth package provides the LumoAuthAgent client and a require_capability decorator used in all examples on this page.

pip install lumoauth

Environment variables (set these before running any example):

VariableDescriptionDefault
LUMOAUTH_URLLumoAuth instance URLhttps://app.lumoauth.dev
LUMOAUTH_ORG_IDYour organization ID(required)
AGENT_CLIENT_IDAgent's OAuth client ID(required)
AGENT_CLIENT_SECRETAgent's OAuth client secret(required)

All four can also be passed directly to the LumoAuthAgent() constructor.

Complete example​

from lumoauth import LumoAuthAgent, require_capability

# 1. Initialize - reads env vars automatically
agent = LumoAuthAgent()

# 2. Authenticate (OAuth 2.0 client credentials)
if not agent.authenticate():
raise SystemExit("Authentication failed - check credentials.")

# 3. Inspect identity and capabilities
info = agent.get_agent_info()
print(f"Agent : {info['name']}")
print(f"Caps : {info['capabilities']}")

budget = agent.get_budget_status()
if budget:
print(f"Tokens: {budget.get('tokens_used_today', 0)}/{budget.get('max_tokens_per_day', 'unlimited')}")

# 4. Make authenticated API calls
if agent.has_capability("read:documents"):
resp = agent.api_request("GET", f"/orgs/{agent.org_id}/api/v1/documents/123")
print(resp.json())

# 5. Token exchange for a secured MCP server (RFC 8693 — swap the agent token
# for one audience-bound to the MCP server)
mcp_token = agent.get_mcp_token("urn:mcp:financial-data")
if mcp_token:
print("MCP token obtained - pass it to your MCP client.")

Subclassing with capability gates​

Use the require_capability decorator to gate methods on agent permissions:

from lumoauth import LumoAuthAgent, require_capability

class ResearchAgent(LumoAuthAgent):
@require_capability('tool:search_web')
def search(self, query: str) -> dict:
return self.api_request(
"POST", f"/orgs/{self.org_id}/api/v1/tools/search", data={"query": query}
).json()

@require_capability('write:documents')
def save_findings(self, data: dict) -> str:
resp = self.api_request(
"POST", f"/orgs/{self.org_id}/api/v1/documents", data=data
)
return resp.json()["id"]

Agent information in UserInfo​

When an agent calls the UserInfo endpoint, the response contains agent-specific data:

{
"sub": "agent:research-bot",
"name": "Research Assistant Bot",
"agent_id": "agt_abc123def456",
"identity_type": "agent",
"capabilities": [
"read:documents",
"tool:search_web",
"tool:execute_code"
],
"budget_policy": {
"max_tokens_per_day": 100000,
"tokens_used_today": 15234,
"max_api_calls_per_hour": 500,
"api_calls_this_hour": 47
},
"organization": "acme-corp",
"workload_identity": null
}

A2A agent cards​

Registered agents can publish a signed A2A (Agent2Agent) v1.0 Agent Card so other agents and orchestrators can discover and verify them:

  • GET /orgs/{orgId}/api/v1/agents/{agentId}/agent-card — public; returns the JWS-signed AgentCard for an active agent that has declared an A2A endpoint (404 when the agent is unknown, inactive, or not A2A-configured). Cacheable for 5 minutes.
  • POST /orgs/{orgId}/api/v1/agents/agent-card/verify — authenticated; verifies a presented signed card. Body is the card JSON itself, or {"card": {...}, "jwks_uri": "https://..."} to verify against an external issuer's key set. Returns {"valid": true, "signer": …, "card_summary": …} or {"valid": false, "error": …}.

Card signatures are verified against the issuing organization's JWKS by default; failures are audited as a2a.card_verify_failed.

Publishing at the well-known location​

A2A clients expect the card at a /.well-known/ path. On the agent's edit page, open the A2A agent card section, enter the A2A endpoint URL (and optionally the A2A skills), turn on Publish at the well-known location and click Save changes.

The A2A agent card section of the agent edit page with the Publish at the well-known location switch

The same signed card is then served publicly at:

  • GET /orgs/{orgId}/agents/{agentId}/.well-known/agent.json — the signed AgentCard for one agent.
  • GET /orgs/{orgId}/.well-known/agent.json — an index of every published card in the organization: {"protocolVersion": …, "provider": {…}, "agents": [{"name", "description", "version", "url"}]}, where url is the per-agent location above. A2A defines one card per agent origin, so an organization hosting many agents publishes this index for orchestrators to enumerate.

Both responses are cacheable for 5 minutes. Publishing is opt-in per agent and only applies to agents that are active and have declared an A2A endpoint; anything else returns 404. The flag changes nothing about authentication — the card advertises how to authenticate, it is not a credential.

Importing from external agent registries​

If your agents are already registered in another directory, import them instead of retyping them. In the organization portal, open Settings → Integrations, choose Agent ecosystem and pick a provider under Agent registries (/orgs/{orgId}/portal/integrations/agent-ecosystem):

The Agent ecosystem page with the Agent registries providers

RegistryWhat is importedIdentity mapping
Microsoft Entra Agent IDService principals tagged AgentIdentity (Graph $filter is overridable), via client credentials with Application.Read.Allworkload / azure, subject azure:{tenantId}:{appId} — attestable by the Azure verifier
Okta Agent SSOOIDC service apps, apps with profile.agent/agentType, or a label prefix, via an SSWS API tokenworkload / generic OIDC with the org authorization-server issuer and the app's client id
AWS AgentCore IdentityBedrock AgentCore workload identities, over the SigV4-signed control planecustom, subject aws-agentcore:{arn} (AgentCore identities are not IAM roles, so the IRSA verifier does not apply)
Salesforce AgentforceBotDefinition records, via the connected app's client-credentials flowcustom, subject salesforce:{instance}:{DeveloperName}

After you save the connection with Add connection, run a sync from the registry's page (Preview (dry run) first, then Sync now).

How importing behaves:

  • Idempotent. An external agent is matched on (organization, registry, external id), so running a sync twice changes nothing. The generated agent_id is {provider}-{external id}, de-duplicated within the organization.
  • Inactive on arrival. New agents are created with status inactive — the registry's "registered but not yet activated" state. Review the agent, give it capabilities and a credential, then activate it. Importing can never grant access by itself.
  • Your edits win. Re-syncing refreshes name, description and metadata. Status, capabilities, credentials and a workload binding you set yourself are never overwritten; a binding is only pre-filled when the agent has none.
  • Read-only. LumoAuth never writes back to your registry.

Credentials are encrypted at rest, every outbound call is SSRF-guarded (HTTPS, public addresses, pinned DNS, no redirects, 10-second timeout), and each run is audited as agent_registry.synced or agent_registry.sync_failed with the created/updated/unchanged/skipped counts.

Integrating with agent frameworks​

Pick your framework for a complete example showing authentication, capability gating, budget checks, and token exchange for MCP servers (tool servers agents call over the Model Context Protocol).

Prerequisites

All examples require the lumoauth package (install instructions) and the environment variables LUMOAUTH_URL, LUMOAUTH_ORG_ID, AGENT_CLIENT_ID, and AGENT_CLIENT_SECRET.

FrameworkGuide
LangChain / LangGraphView example →
CrewAIView example →
AgnoView example →
Google ADKView example →
OpenAI Agents SDKView example →
Microsoft Agent FrameworkView example →