REST API

Gentity exposes a small REST surface for instance lifecycle, logs, terminal, and catalog browsing. The same endpoints back the dashboard and the CLI— there's no separate internal API.

Base URL

https://gentity.ai. Override with GENTITY_API_URL when targeting a non-prod control plane.

Authentication

Every endpoint except /api/catalog/agents requires either:

  • A NextAuth session cookie (set automatically when you sign in to the dashboard).
  • A Bearer token: Authorization: Bearer gn_live_…. Mint at /dashboard/settings/tokens.

Bearer wins when both are present. A missing or revoked token returns 401 Unauthorized.

Endpoints

GET /api/catalog/agents

Public. Returns the agent catalog.

200 OK
{
  "agents": [
    {
      "id": "claude-code",
      "name": "Claude Code",
      "description": "Anthropic's agentic coding CLI…",
      "defaultCpus": 1,
      "defaultMemoryMb": 2048,
      "defaultVolumeGb": 5,
      "models": [
        { "provider": "anthropic", "name": "claude-sonnet-4-5", "label": "…" }
      ]
    }
  ]
}

GET /api/instances

List your instances.

200 OK
{
  "instances": [
    {
      "id": "cmp8w6wf...",
      "name": "Claude Code - claude-sonnet-4-5",
      "subdomain": "cl-x7k2m9",
      "agentType": "claude-code",
      "modelProvider": "anthropic",
      "modelName": "claude-sonnet-4-5",
      "status": "running",
      "errorMessage": null,
      "createdAt": "...",
      "updatedAt": "..."
    }
  ]
}

POST /api/instances

Create a new instance. Blocks until the machine is started.

Body
{
  "name": "optional label",
  "agentType": "claude-code",
  "modelProvider": "anthropic",
  "modelName": "claude-sonnet-4-5",
  "apiKey": "sk-ant-...",
  "region": "iad"   // optional
}

200 OK
{ "id": "cmp...", "subdomain": "cl-x7k2m9", "status": "running" }

GET /api/instances/<id>

Fetch one instance.

DELETE /api/instances/<id>

Destroy machine + volume.

POST /api/instances/<id>/action

Drive a lifecycle transition.

Body
{ "action": "start" | "stop" | "restart" }

200 OK
{ "ok": true }

GET /api/instances/<id>/logs

One-shot logs (last ?lines=N, default 200, max 1000):

200 OK
{
  "logs": [
    { "timestamp": "...", "level": "info", "message": "..." },
    ...
  ]
}

With ?follow=1, returns a text/event-stream response: one Fly log line per data: frame, until the client disconnects.

GET /api/instances/<id>/terminal (WebSocket)

Upgrade to a WebSocket and pipe to flyctl ssh console inside the machine. The CLI's gentity compute ssh wraps this.

  • Authentication: ?token=gn_live_...query parameter (WebSocket clients can't set Authorization headers portably).
  • Protocol (JSON messages):
# Client → Server
{ "type": "input",  "data": "<keystrokes>" }
{ "type": "resize", "cols": N, "rows": N }

# Server → Client
{ "type": "output", "data": "..." }
{ "type": "exit",   "exitCode": N, "signal": "..." }

GET /api/tokens

List your active (non-revoked) tokens.

POST /api/tokens

Mint a new token. The plaintext is returned once — we only store its sha256 hash.

Body
{ "name": "laptop" }

200 OK
{
  "token": "gn_live_...",   ← shown once, save it now
  "record": { "id": "...", "name": "laptop", "prefix": "gn_live_abc12345", "createdAt": "..." }
}

DELETE /api/tokens/<id>

Revoke a token. Soft-deleted (kept for audit, but reads fail with 401).

Errors

StatusBodyWhen
400{ "error": "..." }Validation failure (missing fields, bad action).
401{ "error": "Unauthorized" }Missing or revoked token.
404{ "error": "Not Found" }Instance / token belongs to another user, or doesn't exist.
409{ "error": "..." }Instance is in a state that doesn't allow the action (e.g. stop on a never-booted row).
5xx{ "error": "<upstream message>" }Fly Machines API or Cloudflare API failures bubbled up.