orbotodocs
Agents & AI

External agent tokens

Issue scoped, read-only credentials for third-party AI agents that aren't full workspace members.

What an external agent token is

Most AI clients that talk to orboto do so as a workspace member - a human's own account, or a bot identity created for automation. Either way, the connection acts with a real project membership and a real permission set, exactly like a person using the app (see Connecting an AI client to your workspace).

An external agent token is a different, narrower kind of credential, for a case that doesn't fit that model: a third-party AI agent harness - a background coding agent built into an external tool, for example - that you want to give limited read access to specific orboto data, without making it a member of any project. It has no project membership and no role. What it can read is defined entirely by an explicit allowlist of resource patterns an administrator grants when the token is created.

How it differs from a bot API key

Bot API key (orb_*)External agent token (xag_*)
IdentityA real user account with project membershipNo user account, no project membership
AccessWhatever its role's permissions allow, across every project it's a member ofOnly the specific orboto:// resource patterns listed on the token
Can write?Yes, if its role permits itNo - read-only by design
Issued fromThe bot user's own Profile → API keysThe admin API, by a super-admin, naming an agent and its allowed scopes directly
Typical useAn automation that should act like a team member - create tickets, comment, log timeA third-party client you want to hand a narrow, revocable read window into your workspace, without onboarding it as a user

If the integration should behave like a teammate - creating tickets, commenting, moving work through statuses - use a bot identity and a regular API key instead, the same as the n8n integration or the MCP server does. Reach for an external agent token only when you specifically want to hand out scoped, read-only visibility to a client you don't want as a full member.

The token format

An external agent token is a random secret prefixed xag_ (distinct from the orb_ prefix on ordinary API keys, so orboto's authentication layer can tell the two apart at a glance). Only its SHA-256 hash is stored - the plaintext secret is shown to the administrator who created it exactly once, at creation time, and never again.

Managing tokens

Token management is an administrator action, gated behind the admin:system:write permission (list-only access needs admin:system:read). There is currently no admin UI page for this - manage tokens through the REST API directly, for example with curl.

Grant a token

curl -X POST https://your-orboto-host.example.com/api/admin/external-agents \
  -H "Authorization: Bearer orb_<an admin's API key>" \
  -H "Content-Type: application/json" \
  -d '{
    "agentName": "ACME support bot",
    "allowedScopes": ["orboto://ticket/ACME-*", "orboto://project/ACME"]
  }'
{ "id": "5b1a...", "token": "xag_3f9c2a1e..." }

Copy the token value somewhere safe immediately - this response is the only time orboto ever returns the plaintext secret. Hand it to whatever external client will use it (however that client expects a bearer credential configured).

List tokens

curl https://your-orboto-host.example.com/api/admin/external-agents \
  -H "Authorization: Bearer orb_<an admin's API key>"

Each entry shows the agent's name, its scope list, when it was created and last used, and whether it's been revoked - but only the first 12 characters of the token (apiKeyPrefix), enough to recognize which secret a client is presenting without ever exposing the full value again.

Revoke a token

curl -X DELETE https://your-orboto-host.example.com/api/admin/external-agents/5b1a... \
  -H "Authorization: Bearer orb_<an admin's API key>"

Revocation is immediate and permanent. There's no un-revoke - grant a fresh token if the integration needs to reconnect later.

The scope model: what a token can and cannot reach

allowedScopes is a list of orboto:// resource patterns - the same URI scheme used by MCP resources elsewhere in orboto. Each pattern is either an exact resource address or ends in a single trailing * wildcard:

  • orboto://project/ACME - exactly that one project.
  • orboto://ticket/ACME-42 - exactly that one ticket.
  • orboto://ticket/ACME-* - every ticket in the ACME project, current and future, without listing them one by one.

Anything not covered by at least one pattern on the token is invisible to it - there's no implicit access to "the rest of the workspace" the way a project member's role would grant. A token scoped to orboto://ticket/ACME-* can't see WEB project tickets, docs, milestones, or anything else outside what its patterns literally cover, no matter how narrow or broad you make the list.

Current status

Provisioning and revoking external agent tokens through the admin API above is available today. Connecting a specific external client to consume a token - the piece where the client actually presents an xag_* token against a live orboto endpoint and gets back the scoped data - is still being finalized on orboto's side; there's no client-facing connection guide to publish yet. If you're evaluating this for an integration, provision a token now to reserve the scope you want, and check back for the connection steps once that path ships.

Troubleshooting

SymptomLikely cause
POST /admin/external-agents returns 403The calling account doesn't hold admin:system:write - only a super-admin (or an admin role with that specific permission) can create or revoke tokens.
allowedScopes rejected with a validation errorEvery entry must start with orboto://. A bare resource name without the scheme prefix (e.g. ticket/ACME-42) is rejected, not silently corrected.
Lost the plaintext token after creationIt can't be recovered - the stored value is a one-way hash. Revoke the old token and grant a new one.
A token still shows as active after you meant to remove itConfirm you deleted the right row - GET /admin/external-agents lists every token's id; match on agentName or apiKeyPrefix before calling DELETE.

On this page