Skip to content

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

Authentication routes live under:

/api/v1/auth/...

Main routes:

  • POST /api/v1/auth/token
  • POST /api/v1/auth/login
  • GET /api/v1/auth/me
  • POST /api/v1/auth/refresh
  • POST /api/v1/auth/logout

POST /api/v1/auth/token and POST /api/v1/auth/login use the same handler.

{
"api_key": "ab_admin_defreplace-with-at-least-20-characters",
"persistent_session": true
}

Accepted fields:

  • api_key or apiKey: required API key value
  • persistent_session: optional, defaults to true
FieldRequiredContractMeaning
api_key / apiKeyYesstringThe 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_sessionNobooleanWhen true, the server sets cookies with explicit expiry dates so the browser session survives restarts. When false, the cookies behave more like session cookies.
{
"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"
}
}
FieldContractMeaning
tokenopaqueBearer access token used on normal authenticated API calls.
expires_atserver-generatedISO timestamp when the access token stops being valid.
refresh_expires_atserver-generatedISO timestamp when the refresh session expires and the client must log in again.
scopeenumIdentity scope embedded into the token, such as admin, agent, worker, or service.
tenant_idUUIDTenant boundary the token is valid for.

The handler also sets these cookies:

  • agirunner_access_token
  • agirunner_refresh_token
  • agirunner_csrf_token

That is what enables the dashboard to use browser sessions instead of manually managing bearer headers on every request.

GET /api/v1/auth/me validates the current session or bearer token.

{
"data": {
"authenticated": true,
"scope": "admin",
"tenant_id": "00000000-0000-0000-0000-000000000000",
"owner_type": "user",
"owner_id": "11111111-1111-1111-1111-111111111111"
}
}
FieldContractMeaning
authenticatedbooleanBoolean success marker for the current session or bearer token.
scopeenumEffective scope after token validation.
tenant_idUUIDTenant boundary associated with the current caller.
owner_typeenum-like stringOwner category behind the API key, such as user, agent, or worker.
owner_idUUID or nullConcrete owner record id when one exists.

Use this route when a client needs to confirm current identity without reissuing a token.

POST /api/v1/auth/refresh rotates the refresh session and returns a new access token.

The route reads the refresh token from:

  • the agirunner_refresh_token cookie, 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
{
"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.

POST /api/v1/auth/logout revokes the refresh session when possible and clears the auth cookies.

{
"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.

The token exchange returns identity with one of the platform scopes in current use:

  • admin
  • agent
  • worker
  • service

Those scopes are enforced at route registration time, so route access is not just a UI concern. It is part of the platform contract.

Terminal window
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:

Terminal window
curl http://localhost:8080/api/v1/auth/me \
-H "Authorization: Bearer $TOKEN"