Authentication tells Robots Center which agent is calling and what it is allowed to do. Each service agent belongs to a workspace and uses an agk_ API key for HTTP requests. You can optionally exchange that key for a 30-day access token; realtime connections use a separate 10-minute socket token.
Four credentials, four jobs
An agk_ key and its optional 30-day access token authenticate the ordinary machine API. An enrollment token only bootstraps an identity and cannot call protected routes. A short-lived socket_token is a Phoenix connection parameter: redact WebSocket upgrade URLs and logs that expose it. Never place long-lived secrets in URLs. Operator /api/v1/operator routes instead require a signed-in browser session and do not accept these machine credentials.
Direct key use versus exchange
Protected routes accept Authorization: Bearer $AGENT_API_KEY or x-agent-api-key; exchange is optional. POST /api/v1/agent_tokens is different: it accepts the API key in x-agent-api-key or JSON api_key, and deliberately rejects an Authorization bearer value with 400.
Workspace and scope gates
Authentication pins every request to the credential's workspace and service agent. Missing scopes return 403 with extra.required_scopes; frozen, paused, or archived workspaces return 403 with a top-level error naming that workspace state.
Before you begin
The command-line examples require curl and jq; any exception is identified in its introduction.
Choose a credential source: an owner/admin-managed workspace or service-agent key, a one-time enrollment claim, or API registration for a genuinely new account.
Store every returned agk_ secret immediately; registration, enrollment claim, and operator key creation or rotation show the new secret only in that response.
Know the exact scopes needed by the routes the agent will call; an exchanged or delegated token cannot add scopes the source credential lacks.
How it works
Obtain and secure the primary credential
For an existing workspace, have an owner/admin create or rotate the appropriate workspace/service-agent key or mint an enrollment token to claim once. Use registration only for a new account, and keep the returned key out of source control and browser URLs.
Prove the identity with a scoped read
Call a small endpoint such as GET /api/v1/agents/me with the agk_ key. A successful response identifies the service agent; 401 means the credential is invalid or expired, while 403 usually identifies a scope or workspace-state problem.
Mint narrower transport credentials when useful
Exchange the API key for a selected scope subset when a 30-day bearer token fits deployment policy. For realtime, use a REST credential carrying sockets:connect to mint the 600-second socket token immediately before opening /socket.
Examples
These examples are illustrative. Replace the sample values with your own.
Keep credentials in environment variables or a secret manager, never in client-side code.
Verify an existing workspace credential first
Most integrations already have an owner/admin-created or enrollment-issued key. Use it directly for a small read before adding exchange or realtime layers.
HTTP 200 returns the authenticated Agent object when the existing credential carries agents:read. No token exchange is required.
Optionally register a new account and make its first read
This call creates a durable user and workspace. Use it only for an intentional new account, not as the default way to add a machine to an established workspace, and set both guarded variables yourself before running it.
HTTP 200 returns socket_token, expires_in: 600, socket_path: "/socket", the service-agent and workspace IDs, and the socket-authorized scopes.
Troubleshooting
The exchange returns 400
Do not put the key in Authorization on /agent_tokens. Send the literal agk_ value as x-agent-api-key or JSON api_key; underscore variants of the header are not read.
A known key returns 401
Check for an expired, revoked, or archived credential and an inactive service agent. The exchange endpoint exposes stable details such as api_key_expired and api_key_revoked; ordinary protected routes intentionally use a less specific invalid-token response.
A valid credential returns 403
Compare extra.required_scopes with the credential's scopes, then inspect the top-level error for workspace_frozen, workspace_paused, or workspace_archived. Exchanging the key cannot bypass either gate.
The socket disconnects or rejects the token
Mint a fresh socket token, pass it as socket_token rather than as an API key, use the configured HTTPS origin in production, and keep each WebSocket frame at or below 64 KB.
Endpoint reference
Ready to integrate? Open an endpoint for its required permissions, request fields,
response format, and status codes. The examples above explain the workflow;
these details define each individual request.
POST
/api/v1/agent_tokens
Exchange API key for access token
Description
Unauthenticated exchange: send a primary workspace or service-agent API key (agk_<base64>.<base64>) in the x-agent-api-key header or as api_key in the JSON body. Returns a 30-day access token. This route is optional — protected /api/v1 routes also accept the same API key as Authorization: Bearer agk_… or in x-agent-api-key. A Bearer token on this exchange returns 400.
Auth
None
Request Body
Field
Type
Required
Description
scopes
array<string>
No
Optional subset of the credential's scopes to request
api_key
string
No
API key in format agk_<base64>.<base64> (alternative to x-agent-api-key)
Parameters
Name
In
Type
Required
Description
x-agent-api-key
header
string
No
API key in format agk_<base64>.<base64>. Underscore names are not read.
Authorization: Bearer was sent. This endpoint accepts an API key (agk_...), not a Bearer token. Use x-agent-api-key or api_key. Also returned with detail invalid_scopes when scopes is present but not an array.
401
Missing, invalid, expired, archived, or revoked API key. Detail is api_key_required, invalid_api_key, api_key_expired, api_key_archived, or api_key_revoked. A wrong secret always gets invalid_api_key; the specific states are only reported to callers holding the key's secret.
Credential is recognized but refused. Detail is service_agent_inactive or insufficient_scopes. Frozen, paused, or archived workspaces also return 403 with a top-level error of workspace_frozen, workspace_paused, or workspace_archived.
Example
{
"detail": "This workspace is frozen. Machine traffic is refused until the freeze is lifted.",
"error": "workspace_frozen",
"status": 403,
"title": "Forbidden",
"type": "https://robotscenter.net/problems/forbidden"
}
POST
/api/v1/register
Register new user with API access
Description
Creates a new user account, workspace, service agent, and API credentials in a single request. Returns everything needed for immediate API access. Registration is throttled per client IP (20 per hour by default) and returns 429 when the budget is spent.
Auth
None
Request Body
Field
Type
Required
Description
name
string
No
Optional display name (2–120 characters). When omitted, register_via_api derives it from the email local part.
password
string
Yes
Password (12–72 characters)
email
string
Yes
User email address
agent_name
string
Yes
Name for the default service agent
workspace_name
string
Yes
Name for the new workspace
Responses
201
Account, workspace, service agent, agk_ API key, and 30-day access token issued
Response Body
Field
Type
Required
Description
user
object
Yes
Created user
user.id
string
Yes
User UUID
user.name
string
No
Display name
user.email
string
Yes
Normalized email
user.confirmed_at
datetime (ISO 8601)
No
Confirmation timestamp
api_key
object
Yes
Primary long-lived workspace API key (shown once)
api_key.type
string (api_key)
Yes
Always api_key
api_key.key
string
Yes
agk_ secret (prefix.secret)
api_key.scopes
array<string>
Yes
Granted scopes
api_key.credential_id
string
Yes
API credential UUID
api_key.service_agent_id
string
Yes
Service agent UUID
access_token
object
Yes
Optional 30-day bearer token for the same credential
Too many registrations from this client IP (default 20 per hour, IPv6 bucketed per /64). Problem JSON with a Retry-After header and extra.retry_after_seconds; the account was not created.
POST
/api/v1/socket_tokens
API key / access token
Issue short-lived socket token
sockets:connect
Description
Returns a short-lived socket token for websocket authentication. Authenticate this request with a service-agent bearer token or API key, then pass the returned token as socket_token in Phoenix connection params instead of exposing a long-lived API credential on the websocket URL.
Transport notes: /socket rejects frames above 65 536 bytes (64 KB). Ready, heartbeat, and command handlers also validate decoded payload size. Unrecognized agent events receive %{reason: "unknown_event"}. Production accepts WebSocket upgrades only from the configured HTTPS origin.
Auth
API key / access token
Required Scopes
sockets:connect
Responses
200
Socket token issued
Returns: SocketTokenResponse
Response Body
Field
Type
Required
Description
scopes
array<string>
Yes
Granted scopes carried into the socket connection
socket_path
string
Yes
Phoenix socket path to connect to. Service-agent socket identity is based on credential_id; operator socket identity is tied to the authenticated browser session.
expires_in
integer
Yes
Seconds until the socket token expires
service_agent_id
string
Yes
Service agent UUID bound to the token
workspace_id
string
Yes
Workspace UUID bound to the token
socket_token
string
Yes
Short-lived socket token for websocket authentication
Credential is valid but lacks sockets:connect (detail: The token does not have sufficient scope). The :api_auth plug also refuses frozen, paused, or archived workspaces before the controller runs, with a top-level error of workspace_frozen, workspace_paused, or workspace_archived.
Example
{
"detail": "This workspace is frozen. Machine traffic is refused until the freeze is lifted.",
"error": "workspace_frozen",
"status": 403,
"title": "Forbidden",
"type": "https://robotscenter.net/problems/forbidden"
}
POST
/api/v1/enrollments/claim
Claim an agent or fleet enrollment token
Description
Claims an enrollment token. A single-use token returns the canonical service-agent identity created with it; a fleet claim token (`max_claims` > 1) mints a fresh identity per claim, binds to the pre-registered `pending` robot carrying `serial_number` when one exists, and applies the token's cohort and default tags. Every claim returns its own agk_ API key and 30-day access token, returned only once. The claim token itself is never usable as an API credential.
Auth
None
Request Body
Field
Type
Required
Description
token
string
Yes
One-time enrollment token in age_prefix.secret format
serial_number
string
No
Serial of the claiming unit. Required for fleet claim tokens: it selects the pre-registered robot to bind to and is checked against the token's serial allowlist.
agent
object
No
Optional runtime metadata advertised during claim
Responses
201
Enrollment claimed and machine credentials issued
400
Serial not on the token's allowlist, serial already claimed, or no serial supplied for a fleet claim token
401
Invalid enrollment token
403
The workspace is not accepting machine traffic, with a machine-readable `error` of `workspace_frozen`, `workspace_paused` or `workspace_archived`. Nothing is created: the claim is refused before the service agent, robot, credential and access token are issued, and the token keeps its remaining claims.
Example
{
"detail": "This workspace is frozen. Machine traffic is refused until the freeze is lifted.",
"error": "workspace_frozen",
"status": 403,
"title": "Forbidden",
"type": "https://robotscenter.net/problems/forbidden"
}
409
Enrollment was already claimed, exhausted or revoked
410
Gone. type problems/enrollment-expired when the token is past expires_at. type problems/robot-decommissioned when the claimed serial belongs to a retired robot — that claim is refused and claims_count is not incremented.