API tokens
Two token kinds decide execution (ADR 0027 §1); the client — CLI, MCP, raw HTTP — is irrelevant.
| kind | TR | execution |
|---|---|---|
agent | Ajan | propose-only: every live mutation is recorded into a proposal; bound to exactly one workspace (writing main is refused with agent_workspace_only); can never apply, publish, or hold secret/token-admin scopes |
developer | Geliştirici | direct: executes with rev-guarded CAS semantics (409 on drift, never merged) |
Scope model
scope_registry.grantable_to ∈ {any, developer, session}; GET /v1/scopes serves the registry with TR labels and sensitivity. A token holds at most 10 scopes.
Session-only — never mintable on ANY token (the admin wildcard included):
| scope |
|---|
admin |
agents:admin |
proposals:approve |
checkout |
Ajan-forbidden — an agent token can never be minted with these:
| scope |
|---|
admin |
agents:admin |
proposals:approve |
checkout |
secrets:write |
design:publish |
Defaults at mint: Ajan = catalog:read, design:read, catalog:write, design:write, proposals:write, sandbox:use; Geliştirici = catalog:read, design:read, catalog:write, design:write. orders:* is opt-in on both (sensitive).
Owner-policy commands a developer token may still run with an explicit opt-in grant (never in defaults; the mint dialog warns):
| command | required scope |
|---|---|
design.publish | design:publish |
design.rollback | design:publish |
secret.set | secrets:write |
secret.delete | secrets:write |
Secrets can NEVER be set by agents. secret.set/secret.delete are owner-policy commands; secrets:write is developer-grantable only, and Ajan tokens can never hold it — extensions.ops.get reports missing_secret:<key> until the owner (or a secrets:write developer) enters the value.
Lifecycle
- Minted by the owner (
tokens.mint, or the Ajanlar ve geliştiriciler admin page); themzk_plaintext is returned exactly once and never audited. - Default expiry 90 days (
tokenDefaultExpiryDays). - Per-token throttle, default 120/min → HTTP 429 with
Retry-After. token.revokekills it immediately and withdraws the token's open proposal.tokens.whoami(GET /v1/tokens/me) tells a credential its store, kind, scopes, expiry and workspace — the first call an agent should make.