// Infrastructure

Private Agents

Bring your own hardware under the router. Mint a time-bounded join key, run the XIM agent, watch it transition from pending to active, and the router treats it as a first-class worker for the project that owns it. This page is the practical CRUD: join keys, enrollment, lifecycle, monitoring.

Overview

For a side-by-side of private agents vs. shared agents, see the Agent Types overview.

This page documents the project-scoped Frontend API (authenticated with a project API key or dashboard session). The router also exposes a parallel API-key-authed management surface at /:project_id/v1/management/join-keys and /:project_id/v1/management/agents; see the API reference for details. Defaults may differ between the two surfaces.

Agent Fields

The following fields describe an enrolled agent. They are returned by the agent list and detail API endpoints.

Fields returned by the agent list and detail API endpoints.
Field Type Description
id string (UUID) Unique agent identifier.
name string Human-readable name for the agent. Settable at enrollment and updatable via PATCH.
worker_id string Unique worker identifier used in the mesh protocol.
region string Region where the agent is located (1-24 ASCII characters, inherited from the join key used at enrollment).
max_concurrent_requests integer Maximum number of concurrent inference requests this agent can handle. 0 means auto-configured by the platform.
description string or null Optional description or notes about this agent. Updatable via PATCH.
supported_tiers array of strings or null Service tier IDs this agent supports. When null, the platform uses defaults for the agent's accelerator type. Example: ["gpu_nvidia_shared", "self_hosted"].
storage_limit_enabled boolean or null Whether a storage limit is configured for this agent. When false or null, the agent can use unlimited storage up to disk capacity.
storage_limit_bytes integer (int64) or null Configured storage limit in bytes. Only relevant when storage_limit_enabled is true.
status string Current lifecycle status. See Agent Lifecycle for all values.
last_heartbeat_at string (ISO 8601) or null Timestamp of the most recent heartbeat received from the agent.
enrolled_at string (ISO 8601) or null Timestamp when the agent successfully completed enrollment.
created_at string (ISO 8601) or null Timestamp when the agent record was created.

Join Key Management

Join keys are secure tokens that authorize a backend agent to enroll in your project. Each key is tied to a specific project, carries a region assignment, and expires after a configurable TTL (maximum 1 hour). Keys can be single-use or multi-use.

API Endpoints

REST endpoints for managing join keys.
Method Endpoint Description
POST /api/v1/projects/:projectId/join-keys Create a new join key.
GET /api/v1/projects/:projectId/join-keys List all join keys for the project (paginated).
GET /api/v1/projects/:projectId/join-keys/:keyId Get join key details.
DELETE /api/v1/projects/:projectId/join-keys/:keyId Revoke a join key. Agents already enrolled are not affected. Returns 204 No Content on success.

Join Key Fields

The following fields are accepted when creating a join key (POST).

Request body fields for POST join-keys.
Field Type Required Description
name string Yes Human-readable label for the join key (e.g. "datacenter-rack-3").
region string Yes Region to assign to agents enrolled with this key. Must be 1-24 ASCII characters (e.g. "us-east-1").
expires_in_hours integer or null No Time-to-live in hours. Defaults to the maximum (1 hour). The hard limit is 1 hour regardless of the value supplied. Values less than or equal to 0 are accepted but produce an already-expired key; supply a positive integer.
max_enrollments integer or null No Maximum number of agents that may enroll with this key. Defaults to 1 (single-use). Note: the router-side management endpoint (/v1/management/join-keys) uses a different default (10), so always set max_enrollments explicitly when calling either surface.
router_addresses array of strings or null No Router addresses for agents to connect to. When omitted, platform defaults are used.

The response for a create request includes the full key value exactly once. It cannot be retrieved later because only the SHA-256 hash is stored.

The list and detail responses include the following join key fields:

Fields returned by the join-key list and detail endpoints.
Field Type Description
id string (UUID) Unique join key identifier.
name string Human-readable label.
key_prefix string Visible prefix of the key for identification. The first 8 characters include the xjk_ tag and the leading characters of the project slug (e.g. for project slug myproj, the prefix renders as xjk_mypr****wxyz). The full key is never returned after creation.
region string Region assigned to agents enrolled with this key.
max_enrollments integer Maximum enrollments allowed.
current_enrollments integer Number of agents that have enrolled using this key so far.
expires_at string (ISO 8601) Timestamp when the key expires.
status string Current status: active, used, expired, or revoked.
can_enroll boolean Whether the key can still be used for enrollment (active, not expired, enrollments remaining).
created_at string (ISO 8601) or null Timestamp when the key was created.

Join Key API Examples

In the examples below, replace proj_ABC123 with your project external id (e.g. proj_a1b2c3d4). The route parameter is named :projectId and accepts the project external id.

Create a Join Key

curl
curl -X POST https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys \ -H "Authorization: Bearer xero_my-project_abc123" \ -H "Content-Type: application/json" \ -d '{ "name": "datacenter-rack-3", "region": "us-east-1", "expires_in_hours": 1, "max_enrollments": 1 }'
Python
import requests headers = { "Authorization": "Bearer xero_my-project_abc123", "Content-Type": "application/json" } response = requests.post( "https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys", headers=headers, json={ "name": "datacenter-rack-3", "region": "us-east-1", "expires_in_hours": 1, "max_enrollments": 1 } ) result = response.json() # Save the key value immediately, it cannot be retrieved later print(f"Join Key: {result['key']}")
Node.js
const response = await fetch( "https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys", { method: "POST", headers: { "Authorization": "Bearer xero_my-project_abc123", "Content-Type": "application/json" }, body: JSON.stringify({ name: "datacenter-rack-3", region: "us-east-1", expires_in_hours: 1, max_enrollments: 1 }) } ); const result = await response.json(); // Save the key value immediately, it cannot be retrieved later console.log(`Join Key: ${result.key}`);

List Join Keys

curl
curl https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys \ -H "Authorization: Bearer xero_my-project_abc123"

Get a Join Key

curl
curl https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys/KEY_ID \ -H "Authorization: Bearer xero_my-project_abc123"

Revoke a Join Key

curl
curl -X DELETE https://api.xerotier.ai/api/v1/projects/proj_ABC123/join-keys/KEY_ID \ -H "Authorization: Bearer xero_my-project_abc123"

Agent API

Agents are enrolled automatically using a join key (see Agent Enrollment). Once enrolled, you can list, view, update, and remove agents via the following endpoints.

REST endpoints for managing enrolled agents.
Method Endpoint Description
GET /api/v1/projects/:projectId/agents List all enrolled agents for the project (paginated). Supports ?status= filter.
GET /api/v1/projects/:projectId/agents/:agentId Get full details of a specific agent.
PATCH /api/v1/projects/:projectId/agents/:agentId Update agent name, description, or status (suspend/resume).
DELETE /api/v1/projects/:projectId/agents/:agentId Remove an agent from the project. The stored status is set to disconnected and a disconnected event is emitted with reason=removed_by_user. The agent remains visible in the list under the disconnected status. Returns 204 No Content on success.
GET /api/v1/projects/:projectId/agents/:agentId/events Retrieve the event log for a specific agent.

PATCH Request Body

All fields are optional. Only provided fields are updated.

Optional fields accepted by PATCH agent.
Field Type Description
name string or null New human-readable name for the agent.
description string or null New description or notes for the agent.
status string or null Status change request. Valid values:
  • active: resume a suspended agent.
  • suspended: suspend an active agent. Suspended agents do not receive new requests.

Agent API Examples

List Agents

curl
curl https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents \ -H "Authorization: Bearer xero_my-project_abc123"

Get Agent Details

curl
curl https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents/AGENT_ID \ -H "Authorization: Bearer xero_my-project_abc123"

Suspend an Agent

curl
curl -X PATCH https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents/AGENT_ID \ -H "Authorization: Bearer xero_my-project_abc123" \ -H "Content-Type: application/json" \ -d '{"status": "suspended"}'

Resume an Agent

curl
curl -X PATCH https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents/AGENT_ID \ -H "Authorization: Bearer xero_my-project_abc123" \ -H "Content-Type: application/json" \ -d '{"status": "active"}'

Get Agent Events

curl
curl https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents/AGENT_ID/events \ -H "Authorization: Bearer xero_my-project_abc123"

Remove an Agent

curl
curl -X DELETE https://api.xerotier.ai/api/v1/projects/proj_ABC123/agents/AGENT_ID \ -H "Authorization: Bearer xero_my-project_abc123"

Agent Enrollment

To enroll a private agent:

  1. Create a join key in the dashboard or via the API (see Join Key API Examples).
  2. Save the full key value immediately, it is shown only once.
  3. Run the XIM agent with the enroll subcommand, passing the join key.
  4. The agent authenticates with the router and establishes a secure encrypted connection.
  5. The agent transitions to active status and begins sending heartbeats. It is then ready to receive inference requests.
Shell
# Enroll a XIM node xerotier-xim-agent enroll --join-key "xjk_my-project_..." # Or using the environment variable XEROTIER_AGENT_JOIN_KEY="xjk_my-project_..." xerotier-xim-agent enroll

After enrollment, start the agent in serve mode to begin handling inference requests. See the XIM Deployment guide for detailed setup instructions.

Agent Monitoring

The agent dashboard at /agents provides real-time visibility into your enrolled agents:

  • Heartbeat status: whether each agent is actively communicating with the router.
  • Current load: active request count and capacity.
  • Model information: which model is loaded and its serving state.
  • Event history: per-agent log of significant lifecycle events.

Programmatic Access

For programmatic snapshot access to your fleet, use the documented REST endpoint GET /api/v1/projects/:projectId/agents (see Agent API) with a project API key.

Heartbeat Tracking

The router tracks the last_heartbeat_at timestamp for each agent. An agent with a heartbeat older than the staleness threshold (default 30 seconds; configurable via XEROTIER_FRONTEND_HEARTBEAT_TIMEOUT_SECONDS) is considered disconnected and excluded from routing decisions until it reconnects and resumes sending heartbeats. The effective status shown in the dashboard and API is derived from both the stored status and heartbeat freshness.

Agent Lifecycle

Agents transition through the following status states. The status field in the database reflects the stored lifecycle state. The effective_status is computed at runtime from the stored status and heartbeat freshness.

Stored lifecycle states for an enrolled agent.
Status Description
pending Agent record has been created (join key issued) but the agent has not yet connected and authenticated.
active Agent is connected and sending regular heartbeats. If the most recent heartbeat is older than the heartbeat staleness threshold (default 30 seconds; configurable via XEROTIER_FRONTEND_HEARTBEAT_TIMEOUT_SECONDS), the effective status is disconnected even while the stored status remains active.
disconnected Stored after the heartbeat-timeout worker observes a stale agent or after a DELETE remove call; also computed as the effective_status when the stored status is active but the most recent heartbeat is stale. The router excludes the agent from routing until it reconnects and heartbeats resume. The agent may reconnect and return to active.
suspended Agent has been suspended by the project owner. Suspended agents do not receive new requests. Resume with PATCH status=active.
dead Terminal state. The agent failed to come online within the join key lifespan or was declared dead by the platform. Dead agents do not receive requests and cannot be revived.

Allowed Transitions

Permitted agent state transitions and the trigger that causes each one.
From To Trigger
pending active Agent connects and completes enrollment handshake.
pending dead Join key expired before the agent connected.
active suspended Owner calls PATCH with status=suspended.
active disconnected Heartbeat timeout detected by the router.
active dead Platform declares agent dead after extended absence.
disconnected active Agent reconnects and heartbeats resume.
disconnected suspended Owner suspends a disconnected agent.
disconnected dead Platform declares agent dead after extended absence.
suspended active Owner calls PATCH with status=active.
suspended dead Platform declares agent dead after extended absence.

State Diagram

Single-Agent Queuing

When only one agent is available for an endpoint's tier, the router queues incoming requests instead of immediately returning a 503 error. This matters for XIM deployments with a single backend agent.

The queue timeout is configurable via XEROTIER_SINGLE_AGENT_QUEUE_TIMEOUT_S. If the agent does not become available within the timeout, the request fails with a timeout error.