Aetherfy runs, schedules and spawns 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.
Running an Aetherfy agent on demand
POST /api/v1/agents/{agent}/run → 202
curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/run \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"payload":{"date":"2026-08-19"}}'The body is optional; {"payload": { … }} passes input the agent reads back through
the payload route. Omit it entirely to run with no input.
{
"deployment_id": "3c9a5f10-7b2e-4d81-a6f4-19e0c8b7d224",
"version": 12,
"job_id": "8e41b0c7-2d15-4a93-b7e2-5f0c6a91d338"
}Only type: job agents can be run this way — a type: service agent is always on and
has nothing to trigger. The agent must already be deployed; run does not build.
A manual run and a scheduled task occurrence produce the
same kind of ephemeral run, and both appear in the history below. A manual run does
not touch the schedule: it does not move cron_next_run_at, and it does not
count as that schedule having fired.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_RUN_REQUIRES_JOB_TYPE | 422 | Set type: job in aetherfy.yaml and redeploy |
AGENT_NOT_DEPLOYED | 422 | Deploy the agent first |
AGENT_RUN_INELIGIBLE_STATE | 409 | The status does not allow a run. Carries current_state |
AGENT_RUN_IN_PROGRESS | 409 | A run is already in flight. Transient — retry |
SOFT_CAP_EXCEEDED | 403 | A spend limit was reached; new runs are frozen |
AGENT_RUN_INELIGIBLE_STATE names what to do in its message: a paused agent needs
starting, a usage_paused one needs the spend limit raised, an archived one needs
restoring.
Listing Aetherfy run history
GET /api/v1/agents/{agent}/runs → 200, newest first.
curl -s "https://agents.aetherfy.com/api/v1/agents/reporter/runs?limit=50&trigger_source=cron" \
-H "Authorization: Bearer $AETHERFY_API_KEY"| Parameter | Default | Notes |
|---|---|---|
limit | 20 | Clamped to 1–100 |
trigger_source | both | cron or manual |
before | — | ISO-8601 cursor; returns runs created strictly before it |
[
{
"id": "3c9a5f10-7b2e-4d81-a6f4-19e0c8b7d224",
"trigger_source": "cron",
"state": "completed",
"created_at": "2026-08-19T02:00:00Z",
"error_message": null,
"machine_started_at": "2026-08-19T02:00:07Z",
"machine_stopped_at": "2026-08-19T02:01:31Z",
"duration_seconds": 84
}
]state is the deployment state, so a finished run reads completed and a failed one
failed with error_message set. The timing fields are the machine’s real
wall-clock, and duration_seconds is null until the run has both started and
stopped.
A run’s id is also its deployment id, which is what makes the rest of the run
inspectable: pass it as deployment_id to the logs route
to read just that run’s output, or to
the payload route to see the input it was started with.
Spawned runs are deliberately excluded. Only runs this agent triggered itself —
cron and manual — appear here. A run created by another agent spawning this one
belongs to the parent’s story and trigger_source=spawn is not addressable on this
route.
Page with before, not with an offset: pass the created_at of the last row you saw.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_RUNS_INVALID_TRIGGER_SOURCE | 422 | Carries allowed: ["cron","manual"] |
AGENT_RUNS_INVALID_BEFORE | 422 | before was not an ISO-8601 timestamp |
Pausing and resuming an Aetherfy scheduled task
POST /api/v1/agents/{agent}/schedule/pause → 200
POST /api/v1/agents/{agent}/schedule/resume → 200
curl -s -X POST https://agents.aetherfy.com/api/v1/agents/reporter/schedule/pause \
-H "Authorization: Bearer $AETHERFY_API_KEY"{"cron_paused": true, "cron_next_run_at": "2026-08-20T02:00:00Z", "changed": true}Both are idempotent: pausing an already-paused schedule answers 200 with
changed: false and alters nothing.
This is an operational pause, like stopping an agent — not a configuration change.
The schedule stays declared in aetherfy.yaml, so deploying again re-applies what
that file says, and deploying schedule: null clears the pause along with the
schedule.
Resuming never backfills. Aetherfy recomputes the next occurrence from the moment you resume, so occurrences that elapsed while paused are simply not run — a task paused for a week runs once when resumed, not seven times. Resuming a schedule that was already live leaves the existing cursor untouched, so a redundant resume cannot shift the next fire.
To change when a task runs, edit the schedule: expression and redeploy; these
routes only pause and resume it. See Scheduled tasks.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_SCHEDULE_NOT_SET | 422 | The agent has no schedule to pause or resume |
AGENT_NOT_FOUND | 404 | |
AGENT_OPERATION_IN_PROGRESS | 409 | A worker holds the agent. Transient — retry |
Spawning an Aetherfy worker agent
POST /api/v1/agents/{agent}/spawn → 202
{agent} is the parent. The body names the child to start.
curl -s -X POST https://agents.aetherfy.com/api/v1/agents/orchestrator/spawn \
-H "Authorization: Bearer $AETHERFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"child_agent_id":"reporter","payload":{"customer":"acme"}}'| Field | Type | Notes |
|---|---|---|
child_agent_id | string | Required. UUID or name of the type: job agent to spawn |
payload | object | Input the child reads back at GET /deployments/{spawn_id}/payload |
{
"spawn_id": "b41e77c9-58a0-4de6-9a72-3c1f8e05d6b7",
"job_id": "d0f2a613-9c47-4b85-8e30-71a5c9f24e10",
"child_agent_id": "9a3f0c58-6b21-4e79-bd04-8f2e1c73a065",
"workspace": "research",
"region": "iad",
"status": "queued",
"estimated_start": "2026-08-19T09:15:02Z"
}spawn_id is the child’s deployment id — the value Aetherfy injects into the child as
AETHERFY_SPAWN_ID, and the one to use when reading its payload or looking the run
up. The child starts in the same region as its parent.
Spawning requires configuration on both sides: the parent needs spawn_enabled, the
child must be type: job and already deployed, and if the parent lists
allowed_workers the child must be on that list. Chains are bounded by a maximum
spawn depth.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_NOT_SPAWN_ENABLED | 403 | Set spawn_enabled on the parent |
AGENT_WORKER_NOT_ALLOWED | 403 | The child is not in the parent’s allowed_workers |
AGENT_CHILD_NOT_JOB_TYPE | 400 | Only type: job agents can be spawned |
AGENT_SPAWN_DEPTH_INVALID | 400 | The chain is too deep |
AGENT_PARENT_NOT_SPAWNABLE | 409 | The parent’s state does not allow spawning |
AGENT_PARENT_PAUSED | 409 | Start the parent |
AGENT_WORKER_PAUSED | 409 | Start the child |
AGENT_CHILD_NOT_DEPLOYED | 400 | Deploy the child first |
AGENT_PARENT_NO_DEPLOYMENT | 400 | The parent has never deployed |
AGENT_SPAWN_CONCURRENCY_LIMIT_EXCEEDED | 429 | Too many spawns in flight for this account |
AGENT_SPAWN_RATE_LIMITED | 503 | A deploy of the same agent held its version lock too long. Transient — retry |
SOFT_CAP_EXCEEDED | 403 | A spend limit was reached |
The two throttles are different and want different handling. The 429 is a hard cap on concurrent spawns across your account — wait for some to finish. The 503 means another deploy of the same agent was holding its version lock when this spawn asked for one. Aetherfy serialises that allocation, so this is not a collision and nothing was corrupted — the spawn simply gave up waiting. Retry it; if the same spawn keeps being refused, a deploy on that agent is stuck rather than slow.
Reading Aetherfy agent logs
GET /api/v1/agents/{agent}/logs → 200, an array of log entries.
curl -s "https://agents.aetherfy.com/api/v1/agents/reporter/logs?tail=200&level=ERROR" \
-H "Authorization: Bearer $AETHERFY_API_KEY"| Parameter | Default | Notes |
|---|---|---|
tail | 50 | Lines to return, up to 1000 |
since | — | Relative window, N followed by s, m, h or d: 45s, 30m, 1h, 7d |
level | — | One of INFO, WARN, ERROR, DEBUG, SYSTEM |
stream | — | stdout, stderr or system |
search | — | Case-insensitive substring match on the message |
deployment_id | — | Narrow to one deployment — the way to read a single run’s output |
after_id / before_id | — | Keyset cursors on the integer id |
The sort order depends on after_id. Without it you get newest-first
(descending id), which is what you want for “show me the last 200 lines”. Pass
after_id and the order flips to oldest-first (ascending), so that following a live
agent moves forward through time. A client that assumes one order will silently
reverse when it starts paging.
[
{
"id": 918273,
"timestamp": "2026-08-19T02:00:41Z",
"stream": "stdout",
"level": "INFO",
"message": "report generated for acme"
}
]level buckets several stored spellings: WARN covers WARNING, ERROR covers
CRITICAL and FATAL, and DEBUG covers TRACE. Those five bucket names are the
only accepted values.
These are the agent’s runtime logs. Build output is a different thing and is not available through the API — see Deployments.
To read one scheduled or spawned run’s output, take the run’s id from the run
history above and pass it as deployment_id.
Retention is 7 days on every plan. See Limits.
| Code | HTTP | Meaning |
|---|---|---|
AGENT_LOGS_INVALID_SINCE | 400 | since was not a recognised duration |
AGENT_LOGS_INVALID_FILTER | 422 | level or stream was not one of the accepted values |
AGENT_LOGS_INVALID_DEPLOYMENT_ID | 422 | deployment_id was not a UUID |