Skip to content

Agents API

The Agents API is split between two surfaces:

  • Tenant-admin (dashboard)/v1/auth/dashboard/tenants/{slug}/agents/**, session-authenticated, owner-role required for write operations.
  • End-user/v1/users/me/agents, JWT-authenticated, scoped to the caller’s user identity within the resolved tenant.

Cross-tenant access on either surface returns 404, never 403, per project convention.

The canonical machine-readable spec is the API reference. What follows is a human guide; field-level shapes are authoritative in the OpenAPI document.

POST /v1/auth/dashboard/tenants/{slug}/agents
Content-Type: application/json
{
"name": "Concierge bot",
"description": "Books rooms on behalf of guests",
"agent_class": "mcp-server",
"scopes": ["read:bookings", "write:bookings"],
"grant_types": [
"urn:ietf:params:oauth:grant-type:token-exchange",
"urn:openid:params:grant-type:ciba"
],
"agent_token_max_ttl_sec": 300,
"agent_settings": {
"rate_limits": { "api_calls_per_min": 120 },
"max_concurrent_tokens": 50
}
}

Response 201: { ...AgentResponse, client_secret } — secret revealed exactly once.

Errors:

  • 400 oauth_client/use_agents_endpoint — sent if you POSTed client_type: 'agent' to the legacy /oauth-clients endpoint. Soft-redirect — use the agents endpoint instead.
  • 403 agent/tier_unauthorized — Free tier; upgrade to Pro+.
  • 422 agent/invalid_grant_type — grant outside the agent allowlist ({token-exchange, ciba, device-code, client-credentials}).
  • 422 agent/invalid_fieldredirect_uris non-empty (agents are headless).
GET /v1/auth/dashboard/tenants/{slug}/agents
GET /v1/auth/dashboard/tenants/{slug}/agents?include_revoked=true

Cursor-paginated (default limit=20, max 100). Default selector: clientType='agent' AND revoked_at IS NULL. Pass include_revoked=true to see revoked agents.

GET /v1/auth/dashboard/tenants/{slug}/agents/{agentId}
PATCH /v1/auth/dashboard/tenants/{slug}/agents/{agentId}
POST /v1/auth/dashboard/tenants/{slug}/agents/{agentId}/rotate-secret

PATCH bumps agent_metadata_version. Rotate-secret reveals the new secret exactly once.

PATCH and rotate-secret on a revoked agent → 409 agent/already_revoked.

DELETE /v1/auth/dashboard/tenants/{slug}/agents/{agentId}

Response 200: { id, name, revoked_at, agent_metadata_version }.

Revocation is atomic — every step below either all happens or none of it does:

  1. The agent is marked revoked (conditional — a second call is a no-op).
  2. Already-issued tokens for that agent are denylisted for the remainder of their maximum lifetime, so revocation takes effect immediately rather than waiting for tokens to expire.
  3. An agent.revoked audit event (HIGH severity) + webhook fire.
  4. All tenant owners are notified by email.

Revocation is enforced on every request after signature verification, so a token issued before revocation stops working at once; the rejection is WWW-Authenticate: Bearer error="invalid_token", error_description="agent_revoked" 401.

Idempotent — re-revoking returns 200 with the existing revoked_at and fires no second audit / webhook.

GET /v1/auth/dashboard/tenants/{slug}/agents/{agentId}/activity
?cursor=&limit=50&since=&until=&actor_user_id=&action_type=

Returns { data: AgentActionResponse[], pagination: { next_cursor, has_more } }. Default limit=50, max 200. RBAC: only tenant.role === 'owner' may read other users’ actions.

AgentActionResponse fields: id, action, actor_user_id?, actor_user_email?, resource?, method_or_event?, jti?, scope_used?, ip_hash_prefix? (12 chars), user_agent_hash_prefix?, created_at. Raw IP / UA hashes are never returned.

GET /v1/users/me/agents
Authorization: Bearer <user-jwt>

Returns { data: UserAgentResponse[] }, sorted by last_action_at DESC.

UserAgentResponse fields: agent_client_id, agent_name, agent_logo_url?, agent_class?, last_action_at?, action_count, revoked_at? (this user revoked), agent_revoked_at? (tenant admin revoked).

DELETE /v1/users/me/agents/{agentClientId}
Authorization: Bearer <user-jwt>

Inserts a user_agent_revocations row keyed (tenant_id, user_id, agent_client_id) with reason='gdpr_art_7_withdrawal'. Token-issuance gates in token-exchange + CIBA reject any future mint where (act.sub, sub) matches.

Important: already-issued tokens remain valid until their TTL expires (≤900s ceiling). For instant per-token invalidation across all users, the tenant admin uses DELETE /v1/auth/dashboard/tenants/{slug}/agents/{agentId}.

Response 200: { agent_client_id, revoked_at, reason }. Idempotent.

  • Writes and reads/activity are rate-limited independently, at the standard auth-surface limits.
  • Per-agent overrides are configurable on the agent’s settings: token_exchange_per_min, ciba_initiate_per_min, device_code_per_min, client_credentials_per_min, api_calls_per_min. An override is clamped to a platform hard ceiling — a tenant cannot dial an agent above it.
  • Hits emit agent.rate_limited audit events with the offending scope and the computed limit. A 429 carries Retry-After, so back off on that rather than on a hard-coded assumption about the limit.

The Node SDK exposes the end-user surface only (admin endpoints stay in the dashboard’s session-auth realm):

import { RakomiClient } from '@rakomi/node';
const client = new RakomiClient({ apiKey: process.env.RAKOMI_API_KEY! });
const userToken = '<end-user-jwt>';
const list = await client.users.me.agents.list({ userToken });
if (list.ok) {
for (const agent of list.data.data) {
console.log(agent.agent_name, agent.action_count);
}
}
const revoked = await client.users.me.agents.revoke({
userToken,
agentClientId: 'agent_abc',
});

Both helpers return the SDK’s Result shape ({ ok: true, data } | { ok: false, error }) and never throw on expected 4xx responses. Typed errors are exported for instanceof checks: AgentsUnauthorizedError, AgentNotFoundError, AgentsRateLimitedError, AgentsNetworkError.