Skip to Content
Agent computeREST API reference
Raw

Aetherfy agent REST API

Base URL and authentication for the Aetherfy control plane

The Aetherfy control plane is served from https://agents.aetherfy.com. Every customer-facing route lives under /api/v1. Authentication is a bearer token:

Authorization: Bearer <api key>
curl -s https://agents.aetherfy.com/api/v1/agents \ -H "Authorization: Bearer $AETHERFY_API_KEY"

This is the same API key that authenticates the Aetherfy vector API and the same one afy uses — see API keys. The key’s prefix selects the environment: afy_live_ keys address your production account and afy_test_ keys address the test environment. You never send an environment header; the credential encodes it.

A request with no Authorization header is refused with 401 MISSING_API_KEY. A key that does not resolve is refused with 401 INVALID_API_KEY — the same two codes the Aetherfy vector API answers with.

The Aetherfy control plane is a different API from the vector API

Aetherfy runs two independent HTTP surfaces, and a client written against one will be subtly wrong against the other. The differences are not cosmetic.

Vector API (vectors.aetherfy.com)Control plane (agents.aetherfy.com)
What it doesCollections, points, searchAgents, deployments, runs, secrets, workspaces
Error envelope{"error": {"code", "message"}}{"detail": {"code", "message"}}
No credentials401 MISSING_API_KEY401 MISSING_API_KEY
Bad credentials401 INVALID_API_KEY401 INVALID_API_KEY
Suspended account403 ACCOUNT_SUSPENDED403 ACCOUNT_SUSPENDED
Rate-limit windowFixed 60-second bucketSliding 60-second window
Retry-After on 429Not sentSent
Request budgetVector API requests/minControl-plane requests/min — a separate budget

The envelope is the trap, not the code. The auth codes are identical across both surfaces, so the string you match on is the same either way — but the field it arrives in is not. A handler that reads error.code finds nothing on a control-plane response, and one that reads detail.code finds nothing on a vector response. Read the field that matches the host you called.

Historical note, for a client written before 2026-08-20: the control plane used to answer these three conditions with AUTH_REQUIRED, AUTH_INVALID_API_KEY and AUTH_ACCOUNT_SUSPENDED. Those strings are retired and nothing returns them now.

The Aetherfy control-plane error envelope

Every error carries a stable code inside a detail object:

{ "detail": { "code": "AGENT_NOT_FOUND", "message": "Agent 'reporter' not found", "agent": "reporter" } }

Codes are append-only, so switching on detail.code is safe. The auth rename noted above is the single exception on record, and it is closed. Individual errors add context fields beside messageagent, field, allowed, pending_deployments, dependents, current_state and others — documented with the endpoint that emits them.

A DIFFERENT ENVELOPE COMES FROM YOUR AGENT’S OWN ADDRESS. Everything above describes agents.aetherfy.com, the control plane. Requests you send to an agent’s own URL do not go through it — they are answered by the edge in front of *.aetherfy.dev, which uses {"error": {"code", "message"}} rather than detail.code, and returns an aetherfy-request-id header on every response. Three codes are its own:

CodeStatusMeans
AGENT_NOT_FOUND404The hostname is not an agent address. Nothing was contacted.
AGENT_NOT_REACHABLE404A well-formed agent address where nothing identified itself as a running agent.
AGENT_UPSTREAM_FAILURE502 or 504A gateway answered before the agent did, so no agent response was produced.

AGENT_NOT_FOUND is spelled the same here and on the control plane and means the same thing; only the envelope differs. AGENT_NOT_REACHABLE has no control plane equivalent — the control plane knows what an agent SHOULD be doing, and the edge only knows what answered. When they disagree, the control plane is authoritative: run afy agents list rather than inferring state from a request that failed.

AGENT_UPSTREAM_FAILURE keeps the gateway’s own status — 502 or 504 — rather than reporting a 404 like the two above. That difference is the useful part: the status is the retry signal, and this condition is usually transient, while a 404 would tell you never to retry and would also claim the address resolves to nothing. Your agent may be perfectly healthy; something in front of it answered first.

It names no cause, deliberately. A request body large enough to be refused produces this, and so does an agent that stopped responding — the edge sees the same thing in both cases and will not guess between them. Retry; if it persists, afy agents logs shows what your agent did with the request.

The envelope covers routed errors only. A request to a path that matches no route never reaches Aetherfy’s error handling, so it gets the framework default — {"detail": "Not Found"} with a plain string, not an object — and detail.code is undefined. If you are reading a code off a 404, check that you called a path that exists before concluding the code is missing. The same gap opens at the other end of the request: the control plane installs error handlers for request validation and for database lock timeouts only, so a server error neither covers falls through to the framework’s fallback, which answers 500 with the plain text Internal Server Error — not JSON at all, so parsing the body throws before you can look for a code. A 500 that does carry one, such as AUTHENTICATION_ERROR below, was raised by a route and kept the envelope.

Request-validation errors keep the envelope. When a request body fails schema validation before reaching the handler, Aetherfy wraps the framework’s per-field errors rather than passing them through, so detail is still an object with a codeVALIDATION_ERROR — and the field-level detail moves into a violations array beside message:

{ "detail": { "code": "VALIDATION_ERROR", "message": "Extra inputs are not permitted", "violations": [ {"type": "extra_forbidden", "loc": ["body", "memorymb"], "msg": "Extra inputs are not permitted", "input": 512} ] } }

So detail.code is safe to read on a 422 like any other error, and field-level handling reads detail.violations. Each violation carries the framework’s own loc / msg / type, verbatim.

Errors every Aetherfy control-plane endpoint can return

These come from middleware and apply to every authenticated route below, so they are stated once here rather than repeated on each endpoint.

CodeHTTPMeaning
MISSING_API_KEY401No Authorization header. Response carries WWW-Authenticate: Bearer
INVALID_API_KEY401The key does not resolve — it may have been revoked
ACCOUNT_SUSPENDED403The account is suspended for unpaid bills. Settle it in billing settings
AUTH_SUBSCRIPTION_INACTIVE403The subscription is not active, trialing or past due. Carries subscription_status. Control-plane only — the vector API does not gate on subscription status, which is why this one keeps the AUTH_ prefix
AUTHENTICATION_ERROR500Authentication itself failed. Retry
RATE_LIMIT_EXCEEDED429The control-plane per-minute budget is exhausted — see below
VALIDATION_ERROR422The request body did not match the schema. Field-level errors are in detail.violations
RESOURCE_BUSY503A background worker held a row lock too long. Transient — retry in a few seconds
SERVICE_UNAVAILABLE503A service this request depended on was unreachable, so Aetherfy could not answer either way. Transient — retry in a few seconds

RESOURCE_BUSY is worth handling explicitly. Aetherfy bounds how long any request may wait on a row a worker is holding, and answers 503 rather than hanging. It is always safe to retry.

SERVICE_UNAVAILABLE is the other transient 503, and the two are worth telling apart when you read logs: RESOURCE_BUSY is Aetherfy’s own database contending on a row, while SERVICE_UNAVAILABLE means a dependency was down. Today it reaches you from any route that reads your plan before acting — creating an agent, deploying, spawning, placing a collection or workspace — because a plan that cannot be read is a limit that cannot be checked, and Aetherfy refuses rather than guessing. Nothing is written when it fires. The vector API at vectors.aetherfy.com answers its own dependency outages with this same code and status, in error.code rather than detail.code.

Rate limiting on the Aetherfy control plane

The control plane has its own per-minute request budget, separate from the vector API’s — saturating one leaves the other untouched. Per-plan numbers are in Limits.

Aetherfy returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on successful responses, so you can pace without provoking a 429. When you do exceed it, the 429 tells you how long to wait:

{ "detail": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Limit: 500 requests/minute. Try again in 23 seconds.", "limit": 500, "retry_after_seconds": 23 } }

with Retry-After: 23 in the headers. Enterprise accounts are unlimited and receive no X-RateLimit-* headers at all, so treat the headers as optional.

GET /api/v1/health is exempt from rate limiting.

Addressing an Aetherfy agent by name or by id

Every route spelled /agents/{agent} below accepts either the agent’s UUID or its name. These are equivalent:

curl -s https://agents.aetherfy.com/api/v1/agents/reporter \ -H "Authorization: Bearer $AETHERFY_API_KEY" curl -s https://agents.aetherfy.com/api/v1/agents/6f1c2b7e-0a2d-4f8e-9c31-2b0d5a7e4411 \ -H "Authorization: Bearer $AETHERFY_API_KEY"

Names are unique per account, so there is no ambiguity. A name that does not resolve returns 404 AGENT_NOT_FOUND — the same answer as an unknown UUID, and the same answer as an agent belonging to another account. Aetherfy does not distinguish “does not exist” from “not yours”.

Asynchronous responses in the Aetherfy control plane

Anything that touches machines is accepted and completed in the background. HTTP 202 means the request was accepted, not that the work finished.

ResponseWhat it means
200Done. The body is the result
201Created. The body is the new resource
202Accepted. Work continues in the background — poll for completion
204Done, no body

The 202 bodies carry a progress label, not a status. POST /agents/{agent}/stop answers {"status": "paused", …} and the agent’s own status field becomes paused; archive answers "archiving" and restore answers "restoring", which are not members of the status enum. Poll GET /agents/{agent} for real state, or GET /operations/{operation_id} for region changes.

Route index for the Aetherfy control plane

Fifty endpoints, grouped by the page that documents them.

Agent lifecycle

MethodPath
POST/api/v1/agents
GET/api/v1/agents
GET/api/v1/agents/{agent}
PATCH/api/v1/agents/{agent}
DELETE/api/v1/agents/{agent}
POST/api/v1/agents/{agent}/stop
POST/api/v1/agents/{agent}/start
POST/api/v1/agents/{agent}/archive
POST/api/v1/agents/{agent}/restore
GET/api/v1/agents/{agent}/status
GET/api/v1/agents/{agent}/yaml

Deployments

MethodPath
POST/api/v1/agents/{agent}/deploy
GET/api/v1/deployments
GET/api/v1/agents/{agent}/deployments
GET/api/v1/deployments/{deployment_id}
POST/api/v1/agents/{agent}/deployments/{version}/rollback
POST/api/v1/agents/{agent}/deployments/{version}/cancel
GET/api/v1/deployments/{deployment_id}/payload
POST/api/v1/agents/{agent}/github
GET/api/v1/agents/{agent}/github
DELETE/api/v1/agents/{agent}/github

Runs, schedules and spawns

MethodPath
POST/api/v1/agents/{agent}/run
GET/api/v1/agents/{agent}/runs
POST/api/v1/agents/{agent}/schedule/pause
POST/api/v1/agents/{agent}/schedule/resume
POST/api/v1/agents/{agent}/spawn
GET/api/v1/agents/{agent}/logs

Secrets

MethodPath
POST/api/v1/agents/{agent}/secrets
GET/api/v1/agents/{agent}/secrets
DELETE/api/v1/agents/{agent}/secrets/{key}
POST/api/v1/agents/{agent}/secrets/{key}/rotate
POST/api/v1/workspaces/{workspace}/secrets
GET/api/v1/workspaces/{workspace}/secrets
DELETE/api/v1/workspaces/{workspace}/secrets/{key}

Workspaces, regions and operations

MethodPath
POST/api/v1/workspaces
GET/api/v1/workspaces
GET/api/v1/workspaces/{workspace}
PATCH/api/v1/workspaces/{workspace}
DELETE/api/v1/workspaces/{workspace}
GET/api/v1/workspaces/{workspace}/agents
PATCH/api/v1/workspaces/{workspace}/regions
PATCH/api/v1/collections/{collection_id}
GET/api/v1/operations/{operation_id}

Account and usage

MethodPath
GET/api/v1/auth/me
PATCH/api/v1/users/me/home-region
GET/api/v1/usage/summary
GET/api/v1/usage/agent/{agent_id}/events
GET/api/v1/auth/github
GET/api/v1/auth/github/status
DELETE/api/v1/auth/github

Aetherfy control-plane routes that are not customer API

Two groups of routes exist on agents.aetherfy.com and are deliberately absent from the index above.

Everything under /internal/ is a private contract between the Aetherfy dashboard and the control plane, authenticated by a shared secret rather than by your API key. Your key will not open it. POST /api/v1/webhooks/github and POST /api/v1/webhooks/github/{agent_id} are called by GitHub and authenticated by webhook signature, not by a bearer token.

The health probes — GET /api/v1/health, /readiness and /liveness — are unauthenticated and exist for Aetherfy’s own monitoring. They are not part of the customer API and carry no compatibility promise.

There is no build-log endpoint. Read build progress from GET /api/v1/deployments/{deployment_id}, whose state and error_message are real. An agent’s runtime logs are a different thing and do have a route — GET /agents/{agent}/logs.

Machine-readable index of the Aetherfy control plane

The routes on this page are also published as OpenAPI 3.1 at https://docs.aetherfy.com/agents-openapi.json. It is a second document, separate from openapi.json, which describes the vector API — different origin, different auth budget, different error envelope. See Aetherfy for AI agents.

The Aetherfy CLI does most of this for you

Every route here is reachable through afy, which handles archiving, auth and polling. The REST API is the right tool when you are building automation that cannot shell out, or a client in a language with no Aetherfy SDK. Start at the CLI reference if you are not.

Last updated on