Skip to content

🧪 Beta — Available today; the interface contract may still change.

Let an agent call your APIs on a user's behalf

By the end of this guide you will be able to:

  • Have an agent call your own APIs as "on behalf of one specific user" instead of through a shared service account
  • Know, on the API side, exactly who authorized this, which agent is executing it, and what it is allowed to do
  • Put every one of those calls into the audit chain, traceable after the fact

Prerequisites

  • You have completed First delegation in 30 minutes (this guide is its production-grade version)
  • Your API validates, or is about to validate, access tokens issued by GenAuth (checklist: Protect your APIs)
  • Your access key lives in server-side environment variables

The complete chain

First, get the four responsibilities clear:

PartyWhat it does in this chain
Your applicationStarts the delegation, handles the callback, exchanges for an access token, hands the token to the agent
The userMakes the decision on the consent page
GenAuthValidates, issues the delegate token, performs token exchange
The agentCalls your API with the access token

Step 1 · Start the delegation (your app → GenAuth)

typescript
import { Qoni, AnimaScopes } from "@eazo/anima";

const anima = new Qoni({
  host: "https://api.eak.eazo.ai",
  accessKey: process.env.ANIMA_ACCESS_KEY!,
  secretKey: process.env.ANIMA_SECRET_KEY!,
});

const { data } = await anima.delegateToken({
  mode: "interactive",
  agent: "report-agent",
  scopes: [AnimaScopes.WEB_SEARCH_RUN, AnimaScopes.WEB_SEARCH_READ],
  redirectUri: "https://yourapp.example.com/eak/callback",
  state: signedState,          // put signed business context here
  user: { id: currentUser.id },
  expiresIn: 7200,             // seconds, 60-86400. Size it to the task, not to the max
});

redirect(data.authorizationUrl);

How to pick scopes: the smallest set the current task needs. Prefer read over run; prefer a single scope over a bundle. The user sees exactly the list you request on the consent page — the broader you ask, the more they hesitate.

Step 2 · The user consents (user → GenAuth)

The user sees the agent, the scope and the lifetime on the consent page and makes a decision. You do nothing here, but you must handle both outcomes: approval (which carries a code) and rejection or timeout.

You must verify state

The state coming back on the callback must match the value you sent, otherwise reject the request. This is basic hygiene against authorization hijacking.

Step 3 · Exchange for a delegate token (your app → GenAuth)

typescript
// GET /eak/callback?grantId=...&code=...&state=...
verifySignedState(query.state);   // verify state first

const { data: grant } = await anima.completeDelegateToken({
  grantId: query.grantId,
  code: query.code,
  state: query.state,
});

// grant.token / grant.expiresIn / grant.grantId / grant.auditId / grant.grantedScopes

The grantedScopes you get back may be smaller than the scopes you asked for — trust what was actually granted, never assume a request is granted in full.

Step 4 · Exchange for an access token (agent or your app → GenAuth)

A delegate token cannot reach a resource; its aud only accepts the token exchange endpoint. Exchange it for an access token bound to the target resource:

typescript
// Use the SDK's raw request channel to call the token exchange endpoint
const { data: exchanged } = await anima.request<{
  token: string;
  tokenType: string;
  expiresIn?: number;
}>({
  method: "POST",
  path: "/api/v3/eak/token-exchange",
  body: {
    subjectToken: grant.token,
    resource: "webagent",
    scopes: [AnimaScopes.WEB_SEARCH_RUN],
  },
});
// exchanged.token / exchanged.tokenType / exchanged.expiresIn
bash
curl -X POST https://api.eak.eazo.ai/api/v3/eak/token-exchange \
  -H "Authorization: <AK/SK signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "subjectToken": "<delegate token>",
    "resource": "webagent",
    "scopes": ["webagent.web_search:run"]
  }'

The scopes in an exchange request must be a subset of what the delegate token already grants — this is where the attenuation rule is enforced (how it works).

Step 5 · Call your API (agent → your API)

The agent puts the access token in Authorization: Bearer <token> and calls your API. What your API side must check:

  1. Verify the signature
  2. Verify aud is your resource
  3. Read sub for the user being represented, and run your business authorization against it
  4. Read act (act.type === "eak_delegation") for the executing agent, and log it
  5. Verify the scope covers this operation

Full checklist and pseudocode: Protect your APIs. Token field structure: Token and claim reference.

Verify

Three checkpoints, one at a time:

typescript
// (1) The delegate token really is valid, and the authorization facts match expectations
const { data: info } = await anima.genauth.introspectDelegationToken({ token: grant.token });
console.assert(info.active === true);
console.assert(info.sub === currentUser.id);
console.assert(info.agent_id === "report-agent");

(2) Overreach must fail: use a read-only token against a write endpoint — your API must reject it. If this passes, your resource-side validation is not doing its job.

(3) The audit chain is complete: look up grant.auditId and you should see three kinds of event — authorization, exchange and access (see Audit and accountability chain).

FAQ

Can I skip user consent? You can use organization-level consent (trusted server-side integration), but that is a different authorizing party with extra hardening requirements — read Consent and approval and Security considerations first.

Can a delegate token be cached and reused? Within its lifetime, yes — but store it isolated per user and start a new delegation on a token.expired error. Never reuse across users; that breaks the delegation boundary.

What if the user revokes mid-task? The next call fails with the corresponding error code. Your application should catch AnimaPermissionDeniedError / AnimaTokenExpiredError and guide the user through re-authorization (error types: SDK reference).

Next steps