Skip to Content
Agent computeREST: deployments
Raw

Aetherfy deployments 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 an agent UUID or its name throughout.

Deploying an Aetherfy agent

POST /api/v1/agents/{agent}/deploy → 202

This is a multipart upload, not JSON. One part, code_archive, carrying a gzipped tarball of your agent directory.

tar czf agent.tar.gz -C ./my-agent . curl -s -X POST "https://agents.aetherfy.com/api/v1/agents/reporter/deploy" \ -H "Authorization: Bearer $AETHERFY_API_KEY" \ -F "code_archive=@agent.tar.gz"
ParameterInNotes
code_archivemultipart formRequired. Gzipped tarball of the agent directory
confirm_overagequeryDefault false. Set true to accept a deploy that adds usage

If aetherfy.yaml sits at the archive root, Aetherfy parses it and applies it to the agent before building — runtime, memory, regions, schedule, spawn configuration. Your uploaded configuration wins over what the agent record already held, so the archive is the source of truth for a deployed agent. See the aetherfy.yaml reference.

The 202 response is a deployment object (below). The build has been queued, not finished — poll the deployment to follow it.

Cost confirmation on an Aetherfy deploy

A deploy that would push the account into paid usage is answered 402 OVERAGE_CONFIRM_REQUIRED the first time, naming the additional monthly amount in dollars. This is a disclosure, not a block: repeat the request with ?confirm_overage=true and it proceeds.

curl -s -X POST "https://agents.aetherfy.com/api/v1/agents/reporter/deploy?confirm_overage=true" \ -H "Authorization: Bearer $AETHERFY_API_KEY" \ -F "code_archive=@agent.tar.gz"

Errors on an Aetherfy deploy

Aetherfy checks account-level gates before per-agent ones, so a frozen account is told that rather than being sent to delete an agent that would not unblock it.

CodeHTTPMeaning
SOFT_CAP_EXCEEDED403A spend limit was reached. Existing agents keep running; new deploys are refused
DUNNING_FROZEN403A payment failed and the account is in its grace period
OVERAGE_CONFIRM_REQUIRED402Confirm the added cost — see above
PLAN_LIMIT_EXCEEDED403Agent count, memory, regions or always-on exceeds the plan
DEPLOY_REGIONS_NOT_IN_SCOPE403Requested regions are outside the workspace’s region set
DEPLOY_REGIONS_NOT_IN_COLLECTION_SCOPE403The declared collection is not replicated to every deploy region
INVALID_REGION400A regions: entry is not a region Aetherfy runs in. The body’s regions array names the bad ones
AGENT_PAUSED_CANNOT_DEPLOY409Start the agent first
DEPLOYMENT_IN_PROGRESS409A build is already running. Transient — retry
DEPLOYMENT_ARCHIVE_TOO_LARGE413The upload exceeded the archive cap
DEPLOYMENT_CONFIG_PARSE_ERROR422aetherfy.yaml is malformed or invalid
RUNTIME_IMMUTABLE422An agent’s runtime cannot change after its first deploy
AGENT_SCHEDULE_NOT_ALLOWED_ON_WORKER422A spawned worker cannot carry a schedule:
COLLECTION_NOT_FOUND404database_collection does not resolve
WORKER_NOT_FOUND404A name in spawn.workers does not resolve
DEPLOYMENT_UPLOAD_FAILED500Aetherfy could not store the archive. Retry

The agent-count limit is enforced here, not at agent creation — creating an agent is free, because the limit counts deployed agents and a new agent is a draft. So PLAN_LIMIT_EXCEEDED on the first deploy of an agent that created cleanly is expected behaviour and not a contradiction: the create never claimed a slot for it. Either every slot was already taken when you created the draft, or one was free then and is not now, because a downgrade landed or another agent took the last one in between.

Reading Aetherfy deployments

GET /api/v1/deployments → 200, every deployment on the account, newest first. Accepts ?agent_id=<uuid> to narrow. GET /api/v1/agents/{agent}/deployments → 200, one agent’s deployments. GET /api/v1/deployments/{deployment_id} → 200, one deployment.

curl -s https://agents.aetherfy.com/api/v1/agents/reporter/deployments \ -H "Authorization: Bearer $AETHERFY_API_KEY"
{ "id": "0b7d1f66-3a9e-4c25-9f10-77c2b4e51a08", "agent_id": "6f1c2b7e-0a2d-4f8e-9c31-2b0d5a7e4411", "version": 7, "state": "active", "image_size_mb": 143, "regions": ["iad", "fra"], "pending_regions": [], "regions_total": 2, "regions_ready": 2, "queue_position": null, "is_serving": true, "is_degraded": false, "is_ephemeral": false, "can_redeploy": true, "can_rollback": true, "cancellation_requested": false, "cancellation_reason": null, "pending_region_alert_stage": null, "created_at": "2026-08-19T09:04:11Z", "started_at": "2026-08-19T09:04:19Z", "deployed_at": "2026-08-19T09:06:02Z", "error_message": null }

Aetherfy deployment states

StateMeaning
queuedWaiting for a builder. queue_position is set
buildingBuilding the image
deployingBuilt; machines coming up region by region
activeServing
failedTerminal. Read error_message
rolled_backSuperseded by a rollback to an earlier version
supersededA newer deployment replaced it
completedTerminal, and not a failure — an ephemeral run that finished

completed only appears on ephemeral deployments: the one-shot runs produced by a scheduled task, a manual run, or a spawn. A long-lived service deployment never reaches it.

Watch is_serving rather than state alone when you need “is traffic being answered”. A multi-region deployment can be deploying with some regions already live, which is what regions_ready against regions_total tells you, and is_degraded with pending_region_alert_stage says a region is taking too long.

CodeHTTPMeaning
DEPLOYMENT_NOT_FOUND404Unknown id, or it belongs to another account
DEPLOYMENT_ACCESS_DENIED403The deployment’s agent is not yours

Rolling back an Aetherfy agent

POST /api/v1/agents/{agent}/deployments/{version}/rollback → 202

{version} is the integer version you want to return to, not a deployment id.

curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/deployments/6/rollback \ -H "Authorization: Bearer $AETHERFY_API_KEY"

Rollback re-deploys a previously built image, so it does not rebuild and does not need the original source. It creates a new version rather than reviving the old number. Only versions whose image still exists are valid targets; the rejection names that as the reason.

CodeHTTPMeaning
DEPLOYMENT_ROLLBACK_TARGET_INVALID422That version is not a rollback target — most often its image is no longer available. can_rollback on the deployment object answers this before you call
DEPLOYMENT_NOT_FOUND404No such version for this agent
DEPLOYMENT_IN_PROGRESS409A build is running. Transient — retry
AGENT_PAUSED_CANNOT_ROLLBACK409Start the agent first
SOFT_CAP_EXCEEDED / DUNNING_FROZEN403The account is frozen

See Rollback for the operational guide.

Redeploying an Aetherfy agent

POST /api/v1/agents/{agent}/deployments/{version}/redeploy → 202

{version} is the integer version whose source you want rebuilt, not a deployment id.

curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/deployments/6/redeploy \ -H "Authorization: Bearer $AETHERFY_API_KEY"

Redeploy runs a fresh build from that version’s stored code archive, which is what separates it from rollback: rollback re-deploys an image that already exists, so it cannot pick up a secret written since. Secrets are injected while the machine is built, so redeploy is how a stored secret reaches a running service. It creates a new version.

The agent keeps its current configuration — the archive is not re-read for memory, regions or any other setting, so redeploying an old version never reverts a change you made after it shipped.

Archives are not kept forever: Aetherfy stores them for the three most recent successful deployments, and deletes one when its build fails. can_redeploy on the deployment object tells you whether a given version still has its source; read it rather than inferring from the state.

This applies to GitHub-deployed versions too: a version whose archive has aged out is not redeployable even though the commit is still in your repository. Aetherfy does not re-clone the commit, because re-reading its aetherfy.yaml would revert configuration you changed afterwards. Push again instead — a push deploys with current secrets.

Redeploy is not a retry for a failed build. A build that fails has its archive deleted immediately, so there is nothing left to rebuild — and re-running an identical build would fail identically. To recover from a failed build, deploy your code again with afy deploy, or push to the linked repository. What redeploy is for is taking a deployment that built successfully and running its build again against current secrets. The one failure it does cover is a deployment that built but failed to launch, which keeps its archive.

CodeHTTPMeaning
DEPLOYMENT_REDEPLOY_SOURCE_UNAVAILABLE422That version’s stored code archive is gone, so it cannot be rebuilt
DEPLOYMENT_NOT_FOUND404No such version for this agent
DEPLOYMENT_IN_PROGRESS409A build is running. Transient — retry
AGENT_PAUSED_CANNOT_REDEPLOY409Start the agent first
SOFT_CAP_EXCEEDED / DUNNING_FROZEN403The account is frozen

Cancelling an in-flight Aetherfy deployment

POST /api/v1/agents/{agent}/deployments/{version}/cancel → 202, returning the deployment object with cancellation_requested: true.

Cancellation is cooperative: Aetherfy flags the deployment and the builder stops at its next checkpoint. A deployment that already reached a terminal state cannot be cancelled.

CodeHTTPMeaning
DEPLOYMENT_TERMINAL_CANNOT_CANCEL409Already active, failed, completed, superseded or rolled_back
DEPLOYMENT_NOT_FOUND404

Reading the payload a run was started with

GET /api/v1/deployments/{deployment_id}/payload → 200 {"payload": { … }}

There are two ways to reach this route, and they differ only in where the {deployment_id} comes from.

From inside the agent. A spawned or scheduled type: job agent calls it on itself. Aetherfy injects AETHERFY_SPAWN_ID and AETHERFY_API_KEY into the machine’s environment, and AETHERFY_SPAWN_ID is the deployment id to use:

curl -s "https://agents.aetherfy.com/api/v1/deployments/$AETHERFY_SPAWN_ID/payload" \ -H "Authorization: Bearer $AETHERFY_API_KEY"

From outside, to inspect a run that already happened. The id of a row in run history is the deployment id — the same value — so list the runs and pass one straight through:

# The id of the most recent run. RUN_ID=$(curl -s "https://agents.aetherfy.com/api/v1/agents/reporter/runs?limit=1" \ -H "Authorization: Bearer $AETHERFY_API_KEY" | jq -r '.[0].id') curl -s "https://agents.aetherfy.com/api/v1/deployments/$RUN_ID/payload" \ -H "Authorization: Bearer $AETHERFY_API_KEY"

A spawn’s id comes back as spawn_id on the spawn response instead, since a spawned run does not appear in the child’s run history.

payload is whatever the spawner sent, or {} if it sent nothing.

CodeHTTPMeaning
DEPLOYMENT_NOT_EPHEMERAL404A normal deployment has no payload — only spawned and scheduled runs do
DEPLOYMENT_NOT_FOUND404

Build logs are not available from the Aetherfy API

There is no endpoint for build output. Aetherfy does not currently expose one, and no path under /deployments/ serves build logs.

To follow a build, poll GET /api/v1/deployments/{deployment_id} and read state, queue_position and error_message; a failed build puts its reason in error_message. For an agent’s runtime output, which is a different thing entirely and does have a route, see GET /agents/{agent}/logs.

Linking an Aetherfy agent to a GitHub repository

Push-to-deploy is configured per agent, on top of an account-level GitHub connection made once at /api/v1/auth/github.

POST /api/v1/agents/{agent}/github → 201

curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/github \ -H "Authorization: Bearer $AETHERFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"repo":"acme/agents","branch":"main","root_dir":"reporter"}'
FieldTypeDefaultNotes
repostringRequired. owner/repo
branchstringmainPushes to this branch deploy
root_dirstring | nullnullSubdirectory holding this agent’s code and aetherfy.yaml. Omit for a repository root

The 201 response carries repo, branch, root_dir, webhook_id and webhook_secret:

{ "repo": "acme/agents", "branch": "main", "root_dir": "reporter", "webhook_id": "0000000", "webhook_secret": "<shown once — store it now>" }

webhook_secret is returned in this response and nowhere else. Aetherfy has no route that reads it back. Capture it when you link, and if you lose it, unlink and link again to mint a new one.

GET /api/v1/agents/{agent}/github → 200 returns {linked, repo, branch, root_dir, webhook_id} — no secret. DELETE /api/v1/agents/{agent}/github → 204 unlinks and removes the webhook.

CodeHTTPMeaning
GITHUB_NOT_CONNECTED422Connect the account first — see Account and usage
GITHUB_REPO_NOT_FOUND404The repository is not visible to the installation
GITHUB_API_ERROR503GitHub was unreachable. Retry
GITHUB_WEBHOOK_CREATE_FAILED422GitHub rejected the webhook — most often one already exists on the repository
GITHUB_PERSISTENCE_FAILED500The link could not be stored

See GitHub integration for the workflow.

Last updated on