AGNTDocs

Authentication

API keys and their levels, acting as a user with X-User-Email, certificate JWTs for the Node SDK, what each credential can do, errors and rate limits.

Every request to https://api.agnt.ai carries one credential in Authorization: Bearer <credential>. The credential decides which account the request belongs to and, for anything a user owns, which single user it acts as. That binding keeps each end user's data away from every other end user of your app.

How it works

CredentialFormatUse it for
API keyak_live_ followed by 64 hex charactersServer-to-server calls from your backend, curl and the agnt CLI. Covers the full public API.
Certificate JWTAn RS256 JWT with a kid header, signed with your private key@agnt-sdk/client
 request --> credential --> account --> user (for user resources)
                 |
                 +-- account key ........ account-wide; add X-User-Email to act as a user
                 +-- organization key ... one organization, for its admin operations
                 +-- user key ........... exactly one user, always

Data a user owns (tasks, chats, memories, contacts, calendars, files, pending approvals) is isolated per user, not just per app. A user-scoped key acts only as its own user for approvals and edits, and an account key with X-User-Email acts as that user. An account key without the header is your server's trusted, account-wide credential: it cannot act as a user, but it can list, read and delete tasks and chats across all users.

Create an API key

In the console, open Settings > API Keys and generate a key. The full key is shown once. AGNT stores only a hash of it, so a lost key cannot be recovered: revoke it and create another.

You can also create keys through the API:

curl https://api.agnt.ai/api-keys \
  -X POST \
  -H "Authorization: Bearer $AGNT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "harborview-billing", "expiresAt": "2027-11-02T00:00:00.000Z" }'
{
  "ok": true,
  "apiKey": {
    "id": "6702cb37e8f9a0b1c2d3e4f5",
    "account": "66f1a2b3c4d5e6f708192a3b",
    "organization": null,
    "user": null,
    "label": "harborview-billing",
    "prefix": "ak_live_3f9c2a1b",
    "scopes": [],
    "status": "active",
    "lastUsedAt": null,
    "expiresAt": "2027-11-02T00:00:00.000Z",
    "createdAt": "2026-11-02T16:24:11.000Z",
    "key": "ak_live_..."
  }
}

key appears in this response only. Afterwards the key is identified by id and prefix.

NameTypeRequiredDescription
labelstringYesA name for your records
userIdstringNoCreate a user key for this user. Creating one for someone other than yourself requires an organization or account admin.
expiresAtstringNoISO 8601. The key stops authenticating at this time.
scopesstring[]NoStrings stored on the key and returned on reads

Key levels

LevelCreated byReaches
AccountA developer on your account (console, or an account key)The whole account: users, teams, organizations, assistants, skills. Acts as any user with X-User-Email.
OrganizationAn admin of one of your customer organizations, from their organization settingsAdministrative operations for that organization
UserThe user, or an admin on their behalfActs only as that user for approvals and edits

The level is fixed when the key is created. Use the narrowest level that works: a backend job that serves one user holds a user key, not an account key.

Act as a user

User resources need a user. With an account key, name the user in X-User-Email:

curl "https://api.agnt.ai/tasks?perPage=20" \
  -H "Authorization: Bearer $AGNT_API_KEY" \
  -H "X-User-Email: [email protected]"
  • The email must belong to a user in your account (any of the user's identifiers). Otherwise the request fails with 401. It never falls back to account-wide access.
  • Without the header, an account key reaches account resources. Calls that need a user, such as POST /tasks, fail.
  • A user key always acts as its own user. Do not send X-User-Email with it.
  • Before a key can act as a user for the first time, the user needs a timezone and working hours, confirmed with POST /onboarding/mark-onboarded. Until then user calls return 428 onboarding_required. The Quickstart shows the three calls.

Inspect a key

GET /me returns what the key resolves to:

curl https://api.agnt.ai/me \
  -H "Authorization: Bearer $AGNT_API_KEY"
{ "ok": true, "accountId": "66f1a2b3c4d5e6f708192a3b", "email": null, "orgId": null, "userId": null, "scopes": [] }

An account key has orgId and userId set to null. An organization key has orgId. A user key has userId and email.

Revoke and delete a key

GET /api-keys lists the keys you can manage, never the secret. Revocation takes effect immediately: the next request with the key fails with 401.

curl -X POST https://api.agnt.ai/api-keys/6702cb37e8f9a0b1c2d3e4f5/revoke \
  -H "Authorization: Bearer $AGNT_API_KEY"

Only a revoked key can be deleted:

curl -X DELETE https://api.agnt.ai/api-keys/6702cb37e8f9a0b1c2d3e4f5 \
  -H "Authorization: Bearer $AGNT_API_KEY"

Revoking a key that is already revoked returns 400. A key cannot be deleted while it is active, or while your hosted app uses it.

Rotate a key

  1. Create the new key.
  2. Deploy it to every caller.
  3. Revoke the old key. Its lastUsedAt in GET /api-keys shows whether anything still uses it.

Certificate JWTs for the SDK

The Node SDK, @agnt-sdk/client, authenticates by signing short-lived JWTs with a private key that never leaves your servers. Register the matching public key in the AGNT console to get its key id (kid). AGNT holds only the public key.

Install the SDK (ESM, Node.js 18 or later):

npm install @agnt-sdk/client

Configure it in agnt.config.js at your project root. AgntClient.create() searches up from the working directory for this file.

export default {
  privateKey: process.env.AGNT_PRIVATE_KEY, // PKCS#8 PEM
  kid: process.env.AGNT_KID,
  apiUrl: 'https://api.agnt.ai',
};

AgntClient.create() returns a management client with an account-level token. client.as(email) returns a delegated client whose token carries that user's email. Calls through it currently act account-wide.

import { AgntClient, AgntApiError } from '@agnt-sdk/client';

const client = await AgntClient.create();
const jane = client.as('[email protected]', { firstName: 'Jane', lastName: 'Rivera' });

try {
  const assistants = await client.assistants.list();
} catch (err) {
  if (err instanceof AgntApiError) console.error(err.status, err.errorCode, err.message);
  else throw err;
}

Token contents

PartValue
Header{ "alg": "RS256", "kid": "<your kid>" }
Claimsiat, exp (one hour after iat)
Delegated claimsemail, plus firstName and lastName when passed to client.as(email, { firstName, lastName })

The SDK caches each token per kid and email and signs a new one when less than five minutes remain.

  • A certificate authenticates only its own account. The account comes from the registered certificate, never from claims in the token.
  • Tokens with a type claim are refused.
  • The certificate must be active. A revoked certificate fails with 401.

What each credential can do

OperationAccount keyAccount key + X-User-EmailUser key
Provision users, manage rosters, teams and organizationsYesNot neededNo
Create and run tasks and chats; read and write memoriesNo: name a userYes, as that userYes, as its user
List, read and delete tasks and chats across all usersYes (GET /tasks, GET /tasks/:taskId, DELETE /tasks/:taskId, GET /chats, GET /chats/:chatId, DELETE /chats/:chatId; narrow a list to one user with ownerEmail on tasks or userEmail on chats)No, only that user'sNo, only its user's
Approve, edit or cancel a pending actionNoYes, relaying that user's decisionYes

These limits apply whatever the credential:

  • Approvals need a person. Only the user a pending action belongs to can approve it: from their own signed-in session, or through your backend relaying their decision with an API key acting as them. A certificate token cannot approve, edit or cancel one (403), and neither can a console session acting on a user's behalf.
  • Some account settings are console-only. Account-wide defaults such as the default model tiers (Settings > Model) are changed in the console by an account admin.
  • A blocked user is blocked everywhere. When a user's status is inactive or suspended, every request acting as them fails with 403 account_blocked.
  • A frozen account accepts no new work. While the account-wide kill switch is engaged, creating, running or messaging tasks returns 423 with error_code: "KILL_SWITCH_ENGAGED".

Errors

API errors share one shape:

{ "status": 404, "error_code": "not_found", "error": "Task not found" }

An invalid API key and a rate-limited request are refused before they reach an endpoint, with this shape:

{ "ok": false, "error": "Too many requests", "retryAfter": 1793636711 }
Statuserror_codeWhenWhat to do
400bad_request (or a specific code)Validation failed; error lists each field and problemFix the request
401unauthorizedMissing or malformed credential; expired token; revoked or unknown certificate; X-User-Email that matches no user in the accountCheck the credential and the email
401none ({ "ok": false } body)Unknown, revoked or expired API keyUse a valid key
403forbiddenAuthenticated but not allowed: wrong role, assistant not on the user's roster, approval from a certificate token or from an account key with no X-User-EmailUse a credential with the right level
403account_blockedThe user is inactive or suspendedUnblock the user, or stop acting as them
404not_foundThe resource does not exist, or is not visible to this userCheck the id and the user
409conflict (or a specific code)Conflicts with current state, such as a concurrent changeRe-read, then retry
423KILL_SWITCH_ENGAGEDThe account is frozen by the kill switchA person signed in to your hosted app releases the freeze
428onboarding_requiredThe user has not finished setupSet timezone and working hours, then POST /onboarding/mark-onboarded
429none ({ "ok": false } body)Rate limitedWait Retry-After seconds, then retry
500server_errorUnexpected failureRetry with exponential backoff

A resource that belongs to another user returns 404 rather than 403, so its existence is not disclosed.

Errors on every user call

Any call made as a user (an account key with X-User-Email, or a user key) can return these, whatever the endpoint. The API reference does not repeat them on each operation.

Statuserror_codeWhen
401unauthorizedX-User-Email matches no user in the account
403account_blockedThe user's status is inactive or suspended
428onboarding_requiredThe user has not finished setup. Only the calls that finish it still work: GET and PATCH /profile, GET and PUT /users/:userId/preferences/scheduling, POST /onboarding/mark-onboarded, and listing, revoking and deleting API keys.
423KILL_SWITCH_ENGAGEDCreating or running a task (POST /tasks, POST /tasks/:taskId/process) while the account is frozen

An endpoint that needs a user, called with an account key and no X-User-Email, returns the error listed on that operation: usually 401 unauthorized, or 403 or 404 where the call checks the task's owner or the caller's access.

Rate limits

API-key traffic is rate limited per account, client IP and path: 1,000 requests a minute for reads (GET) and 50 a minute for writes by default. A limited request returns 429 with these headers. The body's retryAfter is the Unix time at which you may retry.

HeaderDescription
Retry-AfterSeconds until you may retry
X-RateLimit-LimitThe limit for this window
X-RateLimit-Remaining0 on a limited response
X-RateLimit-ResetUnix time, in seconds, when the window resets

Read the headers rather than hard-coding the numbers.

Endpoints

MethodPathPurpose
GET/meWhat the API key resolves to
GET/api-keysList keys (never the secret)
POST/api-keysCreate a key; the secret is returned once
POST/api-keys/:keyId/revokeRevoke immediately
DELETE/api-keys/:keyIdDelete a revoked key

Best practices

  • Send credentials only in the Authorization header, never in a URL or query string.
  • Unknown fields in a request body are ignored. Check field names against the API reference.
  • Keep account keys on your servers. If your app calls AGNT from a browser or mobile client, route the call through your backend, which holds the key and sets X-User-Email from your own session, never from client input.

See also