Skip to content

Authentication and grants

The platform has three access tiers: full access (owner/admin), read-only scoped tokens, and agent tokens — the tier this page is about. An agent token is a JWT giving broad read access (secrets always redacted) and write access only to an explicitly granted pool of contracts.

Only a superuser (the owner) can mint agent tokens — POST /api/v1/auth/agent-token rejects everyone else with 403. An agent can never mint or extend its own token. The owner’s request looks like:

{
"email": "my-agent@example.dev",
"expires_days": 90,
"revoke_prior": true,
"grants": [
{ "salescontract": 12345, "capabilities": ["submit", "launch", "cancel", "edit_config"] }
]
}
  • grants is required and non-empty — a token with no pool is refused (400).
  • expires_days (1–365, default 90) sets the JWT’s own lifetime.
  • revoke_prior: true (the default) immediately invalidates all previously issued tokens for that agent identity.

The response contains the access_token (send it as Authorization: Bearer <token>), its lifetime in seconds, and the agent’s user id.

Write permissions live in one table: one row per (agent, contract) with a capability list — a subset of submit, launch, cancel, edit_config, publish. Default deny: no row means no access, and publish is withheld by default on production contracts — it must be granted explicitly. Task/defect management is a separate global capability (manage_task), not tied to any contract.

Minting a token deletes the agent’s entire grant pool and inserts exactly what the request listed. Re-issuing a token “to add one contract” while listing only that contract silently drops every other grant the agent had. Two safe ways to extend a pool:

  1. List the full pool in the mint request: fetch the current grants first, then pass old + new together.
  2. Additive grant without re-minting: the sanctioned path is inserting the single new grant row directly (owner operation) — the existing token picks it up immediately, nothing else changes.

After any re-mint, an agent should re-check my_capabilities before acting.

ResponseCauseAction
401 with a token-format detailSignature invalid, token expired, or wrong token typeRequest a fresh token from the owner
401 token revokedA newer token was minted for your identity with revoke_prior: true — yours is dead even if its expiry date is in the futureUse the newest token; there can be only one
401 user not found or inactiveThe agent identity was deactivatedEscalate to the owner
403 superuser requiredYou called an owner-only route (e.g. token minting)Don’t — ask the owner
403 naming a contract or capabilityYour token is valid, but the write targets a contract outside your pool, or needs a capability you don’t holdCheck my_capabilities; request the specific grant
403 on a read routeSome read surfaces (e.g. the human task board REST reads) are team-only by designUse the MCP read tools instead — that is the supported agent path

Two layers, both active: a coarse route allowlist for agent writes (only a small set of write routes accept agent tokens at all), and a per-object check inside each handler that verifies the target contract is in your pool with the right capability. MCP tools re-run the same checks in their own bodies — the MCP mount additionally requires a valid token for every call.