Obexal Docs

Docs/AI agents/Register an agent

Register an agent

Create the agent's OAuth client, assign a human owner and an expiry date, issue a first token and verify the result in the inventory.

This walkthrough registers a complete agent identity: the OAuth client, its human owner, its expiry, a first token, and the inventory check. Read the agent identity model first if you have not.

Prerequisites

The examples use an admin API token (obx_ prefix) carrying the apps:manage permission, passed as Authorization: Bearer. Everything can also be done from the admin console (the Applications page has an AI agent option), where agent mutations additionally require a fresh MFA verification (step-up).

Note

Every example uses accounts.obexal.com, the default domain. If your organization uses a custom domain, replace it accordingly.

Create the agent client

In the console, create an application and tick AI agent (Token Exchange delegation): the client type is forced to confidential and the machine grants are set automatically. Through the API, create the client explicitly:

curl -sS -X POST https://accounts.obexal.com/v1/applications \
  -H "Authorization: Bearer $OBEXAL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support triage bot",
    "clientType": "confidential",
    "redirectUris": ["https://agents.example.eu/unused-callback"],
    "scopes": ["openid", "tickets:read", "tickets:write"],
    "grantTypes": ["client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"],
    "requireConsent": true
  }'
{
  "application": {
    "clientId": "8Zl2vQx0T9hK3mW1s5nDgA",
    "name": "Support triage bot",
    "clientType": "confidential",
    "grantTypes": ["client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"],
    "scopes": ["openid", "tickets:read", "tickets:write"],
    "tokenEndpointAuthMethod": "client_secret_basic",
    "requireConsent": true
  },
  "clientSecret": "kq2Vt7...shown-only-once"
}

Three fields deserve attention:

  • scopes is the agent's maximum perimeter: no token issued to this agent will ever exceed it. The list must include openid (client validation), so put the agent's real permissions next to it.
  • grantTypes: client_credentials lets the agent act as itself, the token-exchange URN lets it act on behalf of a user. Include only what the agent needs.
  • requireConsent: true makes the agent governed: each user must explicitly authorize it before it can act on their behalf. Omit it only for trusted first-party agents.
Note

Client validation requires at least one absolute http(s) redirectUris entry, even for a pure machine agent that never runs an interactive flow. Use a placeholder URL on a domain you own.

Warning

The clientSecret is returned once and never again (only its SHA-256 is stored). Put it in your secret manager immediately. If it is lost, rotate it.

Assign an owner and an expiry date

curl -sS -X PUT https://accounts.obexal.com/v1/admin/agents/8Zl2vQx0T9hK3mW1s5nDgA/identity \
  -H "Authorization: Bearer $OBEXAL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner": "alice@example.eu", "expiresAt": "2026-12-31T23:59:59Z"}'
# 204 No Content

owner must be the email of an existing user of your organization: it is stored as a directory reference, so the inventory can flag the agent as orphan if that person leaves. expiresAt is enforced fail-closed at issuance: past that date, every token request is refused. An empty string clears either field. A non-agent client or an unknown owner email returns 400 invalid_request; an unknown clientId returns 404.

Issue the first token

The agent authenticates with HTTP Basic on the token endpoint and uses the client_credentials grant:

curl -sS -X POST https://accounts.obexal.com/oauth/token \
  -u "8Zl2vQx0T9hK3mW1s5nDgA:$AGENT_CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d scope="tickets:read"
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjIwMjYtMDYifQ...",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "tickets:read"
}

The access token is a JWT (typ: at+jwt) with sub equal to the client_id: a machine identity, no user involved. No id_token and no refresh_token are issued for this grant. scope is optional and must be a subset of the client's scopes; the governance policy ceilings apply on top. This issuance also updates lastUsedAt.

Verify in the inventory

curl -sS https://accounts.obexal.com/v1/admin/agents \
  -H "Authorization: Bearer $OBEXAL_API_TOKEN"
{
  "agents": [
    {
      "clientId": "8Zl2vQx0T9hK3mW1s5nDgA",
      "name": "Support triage bot",
      "ownerEmail": "alice@example.eu",
      "expiresAt": "2026-12-31T23:59:59Z",
      "lastUsedAt": "2026-07-02T09:14:07Z",
      "status": "active",
      "governed": true,
      "enabled": true,
      "needsReview": true,
      "openAnomalies": 0
    }
  ]
}

needsReview stays true until a first attestation: POST /v1/admin/agents/8Zl2vQx0T9hK3mW1s5nDgA/review records it and returns 204.

Next steps

  1. Delegation with Token Exchange: have the agent act on behalf of a user.
  2. Per-agent governance policy: cap TTL, scopes and audiences before production.
  3. Secret rotation: give the secret an expiry and rotate it on schedule.