Authentication
The platform API supports both machine-oriented bearer tokens and the cookie-backed browser session used by the dashboard.
That split matters because Agirunner has different callers:
- operators in the dashboard
- external automation or SDK clients
- runtimes and workers
Route Family
Section titled “Route Family”Authentication routes live under:
/api/v1/auth/...Main routes:
POST /api/v1/auth/tokenPOST /api/v1/auth/loginGET /api/v1/auth/mePOST /api/v1/auth/refreshPOST /api/v1/auth/logout
Token Exchange
Section titled “Token Exchange”POST /api/v1/auth/token and POST /api/v1/auth/login use the same
handler.
Request body
Section titled “Request body”{ "api_key": "ab_admin_defreplace-with-at-least-20-characters", "persistent_session": true}Accepted fields:
api_keyorapiKey: required API key valuepersistent_session: optional, defaults totrue
Request fields
Section titled “Request fields”| Field | Required | Contract | Meaning |
|---|---|---|---|
api_key / apiKey | Yes | string | The raw API key being exchanged for an access token. Both names are accepted for client convenience, but they map to the same handler input. |
persistent_session | No | boolean | When true, the server sets cookies with explicit expiry dates so the browser session survives restarts. When false, the cookies behave more like session cookies. |
Response
Section titled “Response”{ "data": { "token": "jwt-access-token", "expires_at": "2026-03-31T19:55:00.000Z", "refresh_expires_at": "2026-04-30T18:55:00.000Z", "scope": "admin", "tenant_id": "00000000-0000-0000-0000-000000000000" }}Response fields
Section titled “Response fields”| Field | Contract | Meaning |
|---|---|---|
token | opaque | Bearer access token used on normal authenticated API calls. |
expires_at | server-generated | ISO timestamp when the access token stops being valid. |
refresh_expires_at | server-generated | ISO timestamp when the refresh session expires and the client must log in again. |
scope | enum | Identity scope embedded into the token, such as admin, agent, worker, or service. |
tenant_id | UUID | Tenant boundary the token is valid for. |
Side effects
Section titled “Side effects”The handler also sets these cookies:
agirunner_access_tokenagirunner_refresh_tokenagirunner_csrf_token
That is what enables the dashboard to use browser sessions instead of manually managing bearer headers on every request.
Identity Check
Section titled “Identity Check”GET /api/v1/auth/me validates the current session or bearer token.
Response
Section titled “Response”{ "data": { "authenticated": true, "scope": "admin", "tenant_id": "00000000-0000-0000-0000-000000000000", "owner_type": "user", "owner_id": "11111111-1111-1111-1111-111111111111" }}Identity fields
Section titled “Identity fields”| Field | Contract | Meaning |
|---|---|---|
authenticated | boolean | Boolean success marker for the current session or bearer token. |
scope | enum | Effective scope after token validation. |
tenant_id | UUID | Tenant boundary associated with the current caller. |
owner_type | enum-like string | Owner category behind the API key, such as user, agent, or worker. |
owner_id | UUID or null | Concrete owner record id when one exists. |
Use this route when a client needs to confirm current identity without reissuing a token.
Refresh
Section titled “Refresh”POST /api/v1/auth/refresh rotates the refresh session and returns a
new access token.
Request
Section titled “Request”The route reads the refresh token from:
- the
agirunner_refresh_tokencookie, or - a bearer token header
If you use the cookie flow, the request must also include a matching CSRF token:
- cookie:
agirunner_csrf_token - header:
x-csrf-token
Response
Section titled “Response”{ "data": { "token": "new-jwt-access-token", "expires_at": "2026-03-31T19:55:00.000Z", "refresh_expires_at": "2026-04-30T18:55:00.000Z" }}The refresh route rotates the refresh session, not just the access token. Clients should treat the newly returned token and cookies as the current canonical auth state.
Logout
Section titled “Logout”POST /api/v1/auth/logout revokes the refresh session when possible and
clears the auth cookies.
Response
Section titled “Response”{ "data": { "logged_out": true }}Logout is intentionally idempotent. If the refresh token is already gone or invalid, the route still clears cookies and returns success.
Scope Model
Section titled “Scope Model”The token exchange returns identity with one of the platform scopes in current use:
adminagentworkerservice
Those scopes are enforced at route registration time, so route access is not just a UI concern. It is part of the platform contract.
Example
Section titled “Example”curl -X POST http://localhost:8080/api/v1/auth/token \ -H 'content-type: application/json' \ -d '{ "api_key": "ab_admin_defreplace-with-at-least-20-characters", "persistent_session": true }'Then use the returned access token:
curl http://localhost:8080/api/v1/auth/me \ -H "Authorization: Bearer $TOKEN"