Aetherfy agent lifecycle API
Base URL https://agents.aetherfy.com/api/v1, bearer authentication, and the shared
error envelope are described on the Agent REST API index. {agent}
accepts a UUID or a name throughout.
Creating an Aetherfy agent
POST /api/v1/agents → 201
curl -s -X POST https://agents.aetherfy.com/api/v1/agents \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"reporter","runtime":"python3.12","memory_mb":512,"agent_type":"job"}'| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Required. 1–64 characters, unique per account |
description | string | null | null | Human-readable |
agent_type | enum | service | service or job |
runtime | enum | python3.11 | See the runtime list below |
memory_mb | integer | 256 | Must be within your plan’s maximum |
idle_timeout_minutes | integer | 5 | Within your plan’s maximum |
keep_alive | boolean | false | Always-on. Counts as usage |
entrypoint | string | null | null | e.g. main.py, index.js |
database_collection | string | null | null | Collection this agent declares |
model_name | string | null | null | Free-form |
spawn_enabled | boolean | false | Allow this agent to spawn type: job agents |
allowed_workers | string[] | [] | Names this agent may spawn. Empty means no restriction |
workspace_name | string | null | null | Workspace to place the agent in |
tier | enum | free | Accepted but authoritative plan comes from your subscription |
Runtimes: python3.11, python3.12, python3.13, node20, node22, node20-ts,
node22-ts, bun, dockerfile.
Creating an agent does not deploy it — it reserves the name and the configuration. See Deployments.
Your agent quota is not checked here. The quota counts deployed agents, and a freshly created agent is a draft, so you can keep as many drafts as you like on any plan — including building a replacement for an agent that is currently serving traffic, before you take the old one down. It is checked when you first deploy an agent, and when you restore one from the archive.
Which agents hold a slot follows from that. A deployed agent holds one, and
stopping it does not give it back: a stopped agent is paused, which keeps it
deployed. Archiving or deleting an agent releases its slot. Drafts never held one.
The per-agent caps are checked here — memory, idle timeout, always-on and the
dockerfile runtime are all validated against your plan before the record is
written, and an over-cap create is refused with PLAN_LIMIT_EXCEEDED.
Checking the quota at deploy rather than at create is not merely a deferral, and the deploy-time check is not redundant with anything: an agent created under your limit can reach its first deploy after a downgrade, or after another agent took the last slot.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_NAME_TAKEN | 409 | Another agent on this account already has that name |
WORKSPACE_NOT_FOUND | 404 | workspace_name does not resolve |
PLAN_LIMIT_EXCEEDED | 403 | Memory, idle timeout, always-on or dockerfile runtime exceeds your plan. Not agent count — that is checked at deploy |
AGENT_COLLECTION_REGION_MISMATCH | 400 | database_collection lives in regions this agent could not reach |
SERVICE_UNAVAILABLE | 503 | Your plan could not be read, so no limit could be checked. Transient — retry |
Listing and reading Aetherfy agents
GET /api/v1/agents → 200, an array of agent objects.
GET /api/v1/agents/{agent} → 200, one agent object.
curl -s https://agents.aetherfy.com/api/v1/agents/reporter \
-H "Authorization: Bearer $AETHERFY_API_KEY"The agent object carries the creation fields above plus:
| Field | Type | Notes |
|---|---|---|
id | uuid | |
user_id | uuid | |
status | enum | See the status table below |
parent_agent_id | uuid | null | Set on a spawned worker |
regions | string[] | Regions the agent currently occupies |
pending_regions | string[] | Regions a change is still moving into |
regions_total / regions_ready | integer | Convergence counters |
is_degraded | boolean | Some regions are not serving |
degraded_reason | string | null | Why, when is_degraded is true |
failure_code | string | null | Why the agent is failed, when nothing else on the record says. Branch on this |
failure_message | string | null | The same reason as prose, for display. Wording may change between releases |
cron_schedule | string | null | The 5-field UTC cron expression, when a schedule is set |
cron_next_run_at | timestamp | null | Next scheduled fire |
cron_paused | boolean | Whether the schedule is paused |
cron_last_run_at | timestamp | null | |
cron_last_status | string | null | |
cron_last_reason | string | null | Why the last occurrence did not fire, when it did not |
created_at / updated_at | timestamp |
failure_code is set only where the reason cannot be recovered from anything else.
Today it carries one value, app_lost_on_provider: the compute plane no longer has
an app for the agent. Your code, your secrets and the agent’s address are all kept,
and afy deploy <agent> brings it back. A null failure_code is not a claim that
the agent is healthy — an agent whose build did not compile has null here and
explains itself through its deployment’s error instead.
failure_code is separate from is_degraded on purpose. is_degraded means the
agent is serving and something about it is wrong; a failed agent is not serving at
all, so the two never describe the same condition.
The cron_* fields describe a scheduled task. cron names
the expression format only.
Aetherfy agent statuses
| Status | Meaning |
|---|---|
pending | Created, nothing built yet |
building | A deployment is building |
deploying | Built, machines coming up |
running | Serving |
failed | The last deployment failed |
paused | You stopped it — see stop below |
stopped | Aetherfy stopped it because the account was suspended |
usage_paused | Aetherfy paused it because a spend limit was reached |
suspended | Account-level suspension |
archived | Torn down, configuration and code retained |
deleting / deleted | Terminal |
paused and stopped are different states with different causes, and the
distinction matters when you are diagnosing an agent that is not serving. paused is
something you did and can undo with start. stopped means the account was
suspended — settle it and Aetherfy returns the agent to running on its own. See
Billing and spend caps.
Updating an Aetherfy agent
PATCH /api/v1/agents/{agent} → 200. Every field is optional; omitted fields are
untouched. Setting workspace_name to null moves the agent out of any workspace.
curl -s -X PATCH https://agents.aetherfy.com/api/v1/agents/reporter \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"memory_mb":1024,"keep_alive":true}'Configuration changes here take effect on the next deploy. To change what is running now, deploy again.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_NOT_FOUND | 404 | |
AGENT_TOGGLE_RATE_LIMITED | 429 | Always-on was toggled too rapidly |
PLAN_LIMIT_EXCEEDED | 403 | The new configuration exceeds your plan |
AGENT_COLLECTION_REGION_MISMATCH | 400 | The declared collection’s regions are unreachable from the agent’s |
WORKSPACE_NOT_FOUND | 404 | The target workspace_name does not resolve |
AGENT_DEPLOYMENT_OUTSIDE_NEW_WORKSPACE_SCOPE | 400 | The agent’s live deployment sits outside the target workspace’s region set. Carries a violations array naming the deployment version, its regions and the cap |
SOFT_CAP_EXCEEDED / DUNNING_FROZEN | 403 | Configuration changes are frozen — see Billing |
Stopping and starting an Aetherfy agent
POST /api/v1/agents/{agent}/stop → 202 {"status": "paused", "machines": "stopping", "agent_id": "…"}
POST /api/v1/agents/{agent}/start → 202 {"status": "running", "agent_id": "…"}
curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/stop \
-H "Authorization: Bearer $AETHERFY_API_KEY"Stop moves the agent to paused — note the endpoint is named for the verb and the
resulting status is paused, not stopped. The agent keeps its slot against your
plan’s agent count, its configuration and its code. Start brings the same machines
back without rebuilding.
The pause is in force the moment the call returns: the status is paused, billing
for machine uptime stops at that instant, and a deploy or rollback against the agent
is already refused. The machines field reports the remaining work — the machines
wind down in the background, and the machine states you see under
GET /api/v1/agents/{agent}/status catch up afterwards rather than immediately.
Nothing you can do with the agent waits on that. start works straight away, even
on machines still shown as stopping, and the pause is already reflected everywhere
it matters. Treat the machine states as a view that trails, not as the pause itself.
Because a paused agent still holds its quota slot, you cannot pause your way under an agent limit. Archive instead.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_ALREADY_PAUSED | 409 | Already paused |
AGENT_NOT_PAUSED | 409 | start on an agent that was not paused |
AGENT_ALREADY_ARCHIVED | 409 | Restore it first |
AGENT_HAS_PENDING_DEPLOYMENTS | 409 | Wait for the build to finish or fail |
AGENT_NOT_PAUSEABLE_SYSTEM_STATE | 409 | Aetherfy owns the current state, e.g. usage_paused |
AGENT_OPERATION_IN_PROGRESS | 409 | A worker holds the agent. Transient — retry |
AGENT_NO_MACHINES | 400 | Nothing to start; deploy first |
AGENT_PAUSE_FAILED / AGENT_RESUME_FAILED | 503 | The machine host was unreachable. The status is unchanged — retry |
AGENT_OPERATION_IN_PROGRESS is the only 409 here that is safe to retry blindly; the
others describe a state you have to change first.
Archiving and restoring an Aetherfy agent
POST /api/v1/agents/{agent}/archive → 202 {"status": "archiving", …}
POST /api/v1/agents/{agent}/restore → 202 {"status": "restoring", …}
Archiving tears the agent’s infrastructure down and releases its quota slot, keeping its configuration and stored code. It is the way to get under your agent limit without deleting anything.
Restoring rebuilds it — and re-checks quota at that moment, because other agents may have taken the slot meanwhile. An archived agent is therefore not guaranteed to come back if you have filled your plan since.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_ALREADY_ARCHIVED | 409 | |
AGENT_NOT_ARCHIVED | 409 | restore on an agent that was not archived |
AGENT_NOT_ARCHIVABLE_SYSTEM_STATE | 409 | Aetherfy owns the current state |
AGENT_HAS_PENDING_DEPLOYMENTS | 409 | |
AGENT_HAS_WORKER_DEPENDENTS | 409 | Another agent lists this one in spawn.workers. Carries dependents |
PLAN_LIMIT_EXCEEDED | 403 | On restore: no quota slot free any more |
Deleting an Aetherfy agent
DELETE /api/v1/agents/{agent} → 202 {"status": "deleting", "agent_id": "…"}
Deletion is permanent and asynchronous: the agent moves to deleting and a worker
tears down machines and stored code. It is refused while something still depends on
the agent.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_HAS_PENDING_DEPLOYMENTS | 409 | Carries pending_deployments. Wait for active or failed |
AGENT_HAS_WORKER_DEPENDENTS | 409 | Carries dependents — the agents naming this one in spawn.workers |
Deleting is not how you free a quota slot temporarily. Archive does that reversibly.
Reading live Aetherfy machine status
GET /api/v1/agents/{agent}/status → 200
curl -s https://agents.aetherfy.com/api/v1/agents/reporter/status \
-H "Authorization: Bearer $AETHERFY_API_KEY"{
"agent_id": "6f1c2b7e-0a2d-4f8e-9c31-2b0d5a7e4411",
"name": "reporter",
"status": "running",
"url": "https://reporter-k3m7x2.aetherfy.dev",
"machines": [
{
"id": "1857a3d4f21e08",
"region": "iad",
"state": "started",
"source": "live",
"health": "passing",
"last_check": "2026-08-19T09:12:44Z",
"instance_id": "01J9…"
}
]
}This route differs from GET /agents/{agent} in one important way: it queries the
machine host directly, so machines[].state is real-time rather than whatever
Aetherfy last recorded.
Read source before trusting state. It is "live" when that machine was in
the host’s response, and "cached" when Aetherfy fell back to its own last-known
value because the host read failed. A cached reading can be up to about a minute
stale. If your automation waits for a machine to reach a state, require
source == "live" — otherwise a stale reading can satisfy the wait before the state
is actually true.
url is null until the agent has machines. Destroyed machines are omitted.
Exporting an Aetherfy agent’s aetherfy.yaml
GET /api/v1/agents/{agent}/yaml → 200, Content-Type: application/yaml
curl -s https://agents.aetherfy.com/api/v1/agents/reporter/yaml \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-o aetherfy.yamlThe response body is YAML, not JSON. It is the declarative subset of the agent’s current configuration — the same content the dashboard’s download button produces — so it round-trips: write it into a project directory and deploy, and you get the agent back. See the aetherfy.yaml reference.
Returns 404 AGENT_NOT_FOUND for an agent being deleted.