API Reference
The Iron.sh REST API lets you manage sandboxes, agents, environment variables, egress rules, and audit logs programmatically.
Base URL: https://api.iron.sh
Authentication: Pass your API key in the Authorization header:
Authorization: Bearer <api_key>All responses use JSON. All timestamps are ISO 8601 UTC.
Conventions
Resource IDs
All resources use prefixed, opaque IDs:
| Resource | Prefix | Example |
|---|---|---|
| VM | vm_ | vm_k3mf9xvw2p |
| Egress Rule | egr_ | egr_9xm2kfp4 |
| Egress Event | ee_ | ee_w8n3vqx1 |
| Secret | sec_ | sec_m4xk9wp2 |
| Snapshot | snap_ | snap_abc123 |
| Agent | agt_ | agt_x9k2mf4p |
| Env Variable | env_ | env_m4xk9wp2 |
| User | usr_ | usr_w3n8vqx1 |
| API Key | key_ | key_p4t7xm2k |
Pagination
All list endpoints return a consistent envelope:
{
"data": [],
"has_more": false,
"cursor": null
}When has_more is true, pass the returned cursor as a query parameter on the next request to fetch the next page.
Errors
Error responses include a machine-readable code and a human-readable message:
{
"error": {
"code": "vm_not_found",
"message": "No VM with ID vm_k3mf9xvw2p"
}
}| HTTP Status | Meaning |
|---|---|
400 | Bad request / validation error |
401 | Missing or invalid API key |
404 | Resource not found |
409 | Conflict (e.g. VM already running) |
422 | Unprocessable entity |
429 | Rate limited |
500 | Internal server error |
VMs
VM Status
The API exposes simplified statuses derived from internal VM states. The status field is stable and safe to branch on. The status_detail field exposes the internal state for debugging and observability.
status | status_detail values | Description |
|---|---|---|
pending | queued, creating, starting | VM is being provisioned |
running | running, ready | VM is up and accepting connections |
stopped | stopping, stopped | VM is shut down |
destroyed | destroying, destroyed | VM has been terminated |
failed | failed | VM encountered an error |
Create a VM
POST /v1/vmsRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable name. Must be unique among non-destroyed VMs within the account. Can be reused once the previous VM with that name is destroyed. |
public_key | string | yes | SSH public key for access. |
snapshot_id | string | no | Snapshot ID to restore from. The VM will boot from this snapshot instead of the default base image. |
curl -X POST https://api.iron.sh/v1/vms \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-dev-env",
"public_key": "ssh-ed25519 AAAA..."
}'Response: 201 Created
{
"id": "vm_k3mf9xvw2p",
"name": "my-dev-env",
"status": "pending",
"status_detail": "creating",
"created_at": "2026-02-28T12:00:00Z",
"updated_at": "2026-02-28T12:00:00Z"
}Once the VM is created, you can retrieve its details using the GET /v1/vms/{id} endpoint. You will need to poll that endpoint until the status is running and the status_detail is ready prior to connecting via SSH.
List VMs
GET /v1/vmsQuery parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by simplified status: pending, running, stopped, destroyed, failed |
status_detail | string | — | Filter by internal status: queued, creating, starting, running, ready, stopping, stopped, destroying, destroyed, failed |
name | string | — | Filter by exact name match |
cursor | string | — | Pagination cursor from previous response |
limit | integer | 20 | Results per page. Max 100. |
sort | string | created_at | Sort field: created_at, updated_at, name |
order | string | desc | Sort order: asc, desc |
curl https://api.iron.sh/v1/vms?status=running&limit=10 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": [
{
"id": "vm_k3mf9xvw2p",
"name": "my-dev-env",
"status": "running",
"status_detail": "ready",
"created_at": "2026-02-28T12:00:00Z",
"updated_at": "2026-02-28T12:01:00Z"
}
],
"has_more": false,
"cursor": null
}Get a VM
GET /v1/vms/{vm_id}curl https://api.iron.sh/v1/vms/vm_k3mf9xvw2p \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"id": "vm_k3mf9xvw2p",
"name": "my-dev-env",
"status": "running",
"status_detail": "ready",
"created_at": "2026-02-28T12:00:00Z",
"updated_at": "2026-02-28T12:01:00Z"
}Destroy a VM
DELETE /v1/vms/{vm_id}curl -X DELETE https://api.iron.sh/v1/vms/vm_k3mf9xvw2p \
-H "Authorization: Bearer $IRONS_API_KEY"The VM must be stopped before it can be destroyed. Returns 409 Conflict if the VM is still running:
{
"error": {
"code": "vm_not_stopped",
"message": "VM must be stopped before it can be destroyed"
}
}Response: 204 No Content
Start a VM
POST /v1/vms/{vm_id}/startcurl -X POST https://api.iron.sh/v1/vms/vm_k3mf9xvw2p/start \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
Returns the full VM object with status: "running".
Stop a VM
POST /v1/vms/{vm_id}/stopcurl -X POST https://api.iron.sh/v1/vms/vm_k3mf9xvw2p/stop \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
Returns the full VM object with status: "stopped".
Get SSH Connection Info
GET /v1/vms/{vm_id}/sshcurl https://api.iron.sh/v1/vms/vm_k3mf9xvw2p/ssh \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"host": "203.0.113.10",
"port": 2222,
"username": "root",
"command": "ssh -p 2222 root@203.0.113.10"
}Egress Policy (Account-Level)
Account-level egress settings apply to all VMs unless overridden at the VM level.
Get Default Policy
GET /v1/egress/policycurl https://api.iron.sh/v1/egress/policy \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"mode": "enforce"
}| Mode | Behavior |
|---|---|
enforce | Block all egress traffic except to hosts/CIDRs in the allowlist. |
warn | Allow all traffic but log requests to non-allowlisted destinations. |
Update Default Policy
PUT /v1/egress/policyRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | yes | enforce or warn |
curl -X PUT https://api.iron.sh/v1/egress/policy \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode": "enforce"}'Response: 200 OK
Returns the updated policy object.
List Default Egress Rules
GET /v1/egress/rulescurl https://api.iron.sh/v1/egress/rules \
-H "Authorization: Bearer $IRONS_API_KEY"Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
host | string | — | Filter by exact host match |
cidr | string | — | Filter by exact CIDR match |
name | string | — | Filter by exact name match |
cursor | string | — | Pagination cursor |
limit | integer | 50 | Results per page. Max 200. |
sort | string | created_at | Sort field: created_at, host |
order | string | desc | Sort order: asc, desc |
Response: 200 OK
{
"data": [
{
"id": "egr_9xm2kfp4",
"name": "github-api",
"host": "api.github.com",
"cidr": null,
"comment": "GitHub API access for code pulls",
"created_at": "2026-02-28T12:00:00Z"
},
{
"id": "egr_t4wn8xk2",
"name": "internal-network",
"host": null,
"cidr": "10.0.0.0/8",
"comment": "Internal network access",
"created_at": "2026-02-28T12:01:00Z"
}
],
"has_more": false,
"cursor": null
}Create Default Egress Rule
POST /v1/egress/rulesRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | Unique human-readable identifier for the rule. Must be unique across all rules in the account. |
host | string | no | Domain to match (e.g. api.github.com, *.npmjs.org). Provide host or cidr, not both. |
cidr | string | no | CIDR range to match (e.g. 10.0.0.0/8). Provide host or cidr, not both. |
comment | string | no | Optional human-readable note about the rule’s purpose. |
curl -X POST https://api.iron.sh/v1/egress/rules \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "github-api",
"host": "api.github.com",
"comment": "GitHub API access for code pulls"
}'Response: 201 Created
Returns the created rule object.
Delete Default Egress Rule
DELETE /v1/egress/rules/{rule_id}curl -X DELETE https://api.iron.sh/v1/egress/rules/egr_9xm2kfp4 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 204 No Content
Audit Logs
List Egress Audit Events
GET /v1/audit/egresscurl "https://api.iron.sh/v1/audit/egress?vm_id=vm_k3mf9xvw2p&verdict=denied&limit=20" \
-H "Authorization: Bearer $IRONS_API_KEY"Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
vm_id | string | — | Filter by VM ID (e.g. vm_k3mf9xvw2p) |
verdict | string | — | Filter by verdict: allowed, denied |
allowed | boolean | — | Filter by whether the request was actually permitted |
mode | string | — | Filter by egress mode at time of event: enforce, warn |
host | string | — | Filter by exact host match |
cidr | string | — | Filter by CIDR match |
protocol | string | — | Filter by protocol: http, tls, tcp |
since | string (ISO 8601) | — | Only events after this timestamp |
until | string (ISO 8601) | — | Only events before this timestamp |
cursor | string | — | Pagination cursor |
limit | integer | 50 | Results per page. Max 500. |
order | string | desc | Sort order: asc, desc (always sorted by timestamp) |
Response: 200 OK
{
"data": [
{
"id": "ee_w8n3vqx1",
"timestamp": "2026-02-28T12:05:30Z",
"vm_id": "vm_k3mf9xvw2p",
"host": "api.github.com",
"cidr": null,
"protocol": "tls",
"verdict": "allowed",
"allowed": true,
"mode": "enforce"
}
],
"has_more": true,
"cursor": "ee_p3xn7vm2"
}Secrets
Secrets let you store credentials that the egress proxy injects into outbound requests on behalf of workloads, so the workload never sees the actual credential. Instead, workloads use a proxy_value placeholder in environment variables, and the proxy replaces it with the real secret at the network level.
The proxy is generic: it scans all HTTP headers on every outbound request for known proxy values and replaces them. There is no per-provider logic. Optionally, secrets can be scoped to specific destination hosts.
Secrets are scoped to the authenticated user. The secret value itself is write-only — it is never returned by any endpoint.
Create a Secret
POST /v1/secretsRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique human-readable identifier. Alphanumeric, hyphens, underscores only. Max 64 characters. |
secret | string | yes | The secret value. Encrypted at rest. Never returned in responses. |
env_var | string | yes | Environment variable name to set inside sandboxes (e.g. GITHUB_TOKEN). Must be a valid env var name: uppercase letters, digits, and underscores, starting with a letter. Must be unique within the user’s secrets. |
hosts | array of strings | no | Hosts where this secret may be injected. Supports exact hostnames (e.g. api.github.com), wildcards (e.g. *.github.com, single subdomain level), and * (any host). Default: ["*"]. |
comment | string | no | Human-readable note about the secret’s purpose. |
curl -X POST https://api.iron.sh/v1/secrets \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "github-main",
"secret": "ghp_abc123def456...",
"env_var": "GITHUB_TOKEN"
}'Response: 201 Created
{
"id": "sec_m4xk9wp2",
"name": "github-main",
"env_var": "GITHUB_TOKEN",
"hosts": ["*"],
"proxy_value": "IRONSH_PROXY_github-main",
"comment": null,
"created_at": "2026-03-04T12:00:00Z",
"updated_at": "2026-03-04T12:00:00Z"
}The proxy_value field is the placeholder that gets set as the value of the environment variable inside sandboxes. The egress proxy scans all outbound HTTP headers for this value and replaces it with the real secret. The proxy_value is safe to expose — it is meaningless outside the proxy.
By default, secrets are injected on requests to any host allowed by the egress policy. To restrict a secret to specific destinations, provide a hosts array. If a request goes to a host not in the list, the proxy value is left as-is and the upstream receives a meaningless placeholder.
Example with host scoping:
curl -X POST https://api.iron.sh/v1/secrets \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "github-main",
"secret": "ghp_abc123def456...",
"env_var": "GITHUB_TOKEN",
"hosts": ["api.github.com", "*.github.com"]
}'Creating a secret with a duplicate name returns 409 Conflict with code secret_name_taken. Creating a secret with a duplicate env_var returns 409 Conflict with code env_var_taken. Invalid host entries return 422 with code validation_error.
List Secrets
GET /v1/secretsQuery parameters:
| Param | Type | Default | Description |
|---|---|---|---|
name | string | — | Filter by exact name match |
cursor | string | — | Pagination cursor from previous response |
limit | integer | 50 | Results per page. Max 200. |
sort | string | created_at | Sort field: created_at, name |
order | string | desc | Sort order: asc, desc |
curl https://api.iron.sh/v1/secrets \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": [
{
"id": "sec_m4xk9wp2",
"name": "github-main",
"env_var": "GITHUB_TOKEN",
"hosts": ["*"],
"proxy_value": "IRONSH_PROXY_github-main",
"comment": null,
"created_at": "2026-03-04T12:00:00Z",
"updated_at": "2026-03-04T12:00:00Z"
}
],
"has_more": false,
"cursor": null
}Get a Secret
GET /v1/secrets/{secret_id}curl https://api.iron.sh/v1/secrets/sec_m4xk9wp2 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
Same shape as the create response.
Update a Secret
PATCH /v1/secrets/{secret_id}All fields are optional. Only provided fields are updated. name cannot be changed after creation — delete and recreate instead.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | no | New secret value. |
env_var | string | no | Updated environment variable name. |
hosts | array of strings | no | Updated host scoping. Replaces existing hosts. |
comment | string | no | Updated comment. |
curl -X PATCH https://api.iron.sh/v1/secrets/sec_m4xk9wp2 \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"secret": "ghp_newtoken789..."}'Response: 200 OK
Returns the updated secret object (without the secret value).
Delete a Secret
DELETE /v1/secrets/{secret_id}curl -X DELETE https://api.iron.sh/v1/secrets/sec_m4xk9wp2 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 204 No Content
Snapshots
Snapshots capture the state of a virtual machine. A snapshot is sourced from either a base image or another snapshot (never both).
Snapshot object
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (prefixed snap_). |
source_virtual_machine_id | string | ID of the VM this snapshot was taken from. |
source_base_image | string | null | Base image name the VM was running when snapshotted. Present if the VM had no prior snapshot. |
source_snapshot_id | string | null | ID of the snapshot the VM was restored from when this snapshot was taken. Present if the VM was snapshot-based. |
label | string | null | Optional user-provided label. |
status | string | One of pending, uploading, ready, failed. |
r2_key | string | null | Storage key for the snapshot data. Present once uploaded. |
size_bytes | integer | null | Size of the snapshot in bytes. Present once uploaded. |
created_at | string | ISO 8601 timestamp. |
Create a snapshot
POST /v1/vms/{vm_id}/snapshotsCreates a snapshot of a running or ready VM. To create a new VM from a snapshot, pass the snapshot ID as snapshot_id when creating a VM.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
vm_id | string | The VM ID. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
label | string | no | Optional label for the snapshot. |
curl -X POST https://api.iron.sh/v1/vms/vm_xyz789/snapshots \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label": "before-refactor"}'Response: 201 Created
{
"data": {
"id": "snap_abc123",
"source_virtual_machine_id": "vm_xyz789",
"source_base_image": "ubuntu-24.04",
"source_snapshot_id": null,
"label": "before-refactor",
"status": "pending",
"r2_key": null,
"size_bytes": null,
"created_at": "2026-03-10T12:00:00Z"
}
}Errors:
| Code | Status | Description |
|---|---|---|
vm_not_found | 404 | VM does not exist or does not belong to you. |
vm_not_running | 409 | VM must be in running or ready status. |
validation_error | 422 | Snapshot validation failed. |
List snapshots
GET /v1/snapshots
GET /v1/vms/{vm_id}/snapshotsReturns snapshots owned by the authenticated user. Optionally scoped to a single VM.
Query parameters:
| Param | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Max number of results to return. |
cursor | string | no | Cursor for pagination (snapshot ID). |
curl https://api.iron.sh/v1/snapshots?limit=10 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": [
{
"id": "snap_abc123",
"source_virtual_machine_id": "vm_xyz789",
"source_base_image": "ubuntu-24.04",
"source_snapshot_id": null,
"label": "before-refactor",
"status": "ready",
"r2_key": "snapshots/snap_abc123.raw",
"size_bytes": 1073741824,
"created_at": "2026-03-10T12:00:00Z"
}
],
"has_more": false,
"cursor": null
}Results are ordered by created_at descending (newest first).
Get a snapshot
GET /v1/snapshots/{id}Path parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | The snapshot ID. |
curl https://api.iron.sh/v1/snapshots/snap_abc123 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": {
"id": "snap_abc123",
"source_virtual_machine_id": "vm_xyz789",
"source_base_image": "ubuntu-24.04",
"source_snapshot_id": null,
"label": "before-refactor",
"status": "ready",
"r2_key": "snapshots/snap_abc123.raw",
"size_bytes": 1073741824,
"created_at": "2026-03-10T12:00:00Z"
}
}Errors:
| Code | Status | Description |
|---|---|---|
snapshot_not_found | 404 | Snapshot does not exist or does not belong to you. |
Delete a snapshot
DELETE /v1/snapshots/{id}Soft-deletes a snapshot. It will no longer appear in list or get responses.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | The snapshot ID. |
curl -X DELETE https://api.iron.sh/v1/snapshots/snap_abc123 \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 204 No Content
Errors:
| Code | Status | Description |
|---|---|---|
snapshot_not_found | 404 | Snapshot does not exist or does not belong to you. |
Signup
Create a User
POST /v1/usersCreates a new user account and returns an API key. This endpoint does not require authentication.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | yes | User’s first name. |
last_name | string | yes | User’s last name. |
email | string | yes | Email address. Must be unique. |
password | string | yes | Account password. |
curl -X POST https://api.iron.sh/v1/users \
-H "Content-Type: application/json" \
-d '{
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"password": "s3cureP@ssword"
}'Response: 201 Created
{
"api_key": {
"id": "key_p4t7xm2k",
"name": "CLI",
"token": "iron_live_a1b2c3d4e5f6...",
"created_at": "2026-03-15T12:00:00Z"
},
"user": {
"id": "usr_w3n8vqx1",
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com",
"email_verified": false,
"created_at": "2026-03-15T12:00:00Z"
}
}A verification email is sent automatically. The returned token can be used immediately as a Bearer token to authenticate subsequent API requests.
Errors:
| Code | Status | Description |
|---|---|---|
email_taken | 409 | An account with this email already exists. |
validation_error | 422 | One or more fields failed validation. |
Environment Variables
Environment variables let you store key-value configuration that is available inside your sandboxes.
List Environment Variables
GET /v1/envQuery parameters:
| Param | Type | Default | Description |
|---|---|---|---|
cursor | string | — | Pagination cursor from previous response |
limit | integer | 50 | Results per page. Max 200. |
curl https://api.iron.sh/v1/env \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": [
{
"id": "env_m4xk9wp2",
"key": "NODE_ENV",
"value": "production",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:00:00Z"
}
],
"has_more": false,
"cursor": null
}Get an Environment Variable
GET /v1/env/{key}curl https://api.iron.sh/v1/env/NODE_ENV \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"id": "env_m4xk9wp2",
"key": "NODE_ENV",
"value": "production",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:00:00Z"
}Errors:
| Code | Status | Description |
|---|---|---|
env_not_found | 404 | Environment variable not found. |
Set an Environment Variable
PUT /v1/env/{key}Creates or updates an environment variable. If a variable with the given key already exists, its value is replaced.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
value | string | yes | The value for the variable. |
curl -X PUT https://api.iron.sh/v1/env/NODE_ENV \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "production"}'Response: 200 OK
{
"id": "env_m4xk9wp2",
"key": "NODE_ENV",
"value": "production",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:00:00Z"
}Errors:
| Code | Status | Description |
|---|---|---|
validation_error | 422 | One or more fields failed validation. |
Delete an Environment Variable
DELETE /v1/env/{key}curl -X DELETE https://api.iron.sh/v1/env/NODE_ENV \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 204 No Content
Errors:
| Code | Status | Description |
|---|---|---|
env_not_found | 404 | Environment variable not found. |
Agents
Agents are long-running AI coding agents backed by a dedicated VM. Each agent clones a repository and executes a prompt using the specified harness (e.g. claude).
Agent Status
status | Description |
|---|---|
provisioning | The agent’s VM is being created and configured. |
running | The agent is actively executing its prompt. |
idle | The agent has finished its work and is waiting. |
Create an Agent
POST /v1/agentsRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique name for the agent. Must be unique among active agents. |
repo | string | yes | Git repository URL to clone (e.g. https://github.com/org/repo). |
branch | string | no | Branch to check out. Defaults to the repository’s default branch. |
harness | string | no | Agent harness to use. Default: claude. |
prompt | string | yes | The prompt or task description for the agent to execute. |
agent_args | string | no | Additional arguments to pass to the agent harness. |
curl -X POST https://api.iron.sh/v1/agents \
-H "Authorization: Bearer $IRONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "fix-auth-bug",
"repo": "https://github.com/org/repo",
"branch": "main",
"prompt": "Fix the authentication bug in src/auth.ts"
}'Response: 201 Created
{
"id": "agt_x9k2mf4p",
"name": "fix-auth-bug",
"repo": "https://github.com/org/repo",
"branch": "main",
"harness": "claude",
"prompt": "Fix the authentication bug in src/auth.ts",
"agent_args": null,
"status": "provisioning",
"vm_id": "vm_k3mf9xvw2p",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:00:00Z"
}Errors:
| Code | Status | Description |
|---|---|---|
agent_name_taken | 409 | An active agent with this name already exists. |
validation_error | 422 | One or more fields failed validation. |
List Agents
GET /v1/agentsReturns active agents (not destroyed).
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status: provisioning, running, idle |
name | string | — | Filter by exact name match |
cursor | string | — | Pagination cursor from previous response |
limit | integer | 20 | Results per page. Max 100. |
curl https://api.iron.sh/v1/agents?status=running \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
{
"data": [
{
"id": "agt_x9k2mf4p",
"name": "fix-auth-bug",
"repo": "https://github.com/org/repo",
"branch": "main",
"harness": "claude",
"prompt": "Fix the authentication bug in src/auth.ts",
"agent_args": null,
"status": "running",
"vm_id": "vm_k3mf9xvw2p",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:01:00Z"
}
],
"has_more": false,
"cursor": null
}Get an Agent
GET /v1/agents/{agent_id}curl https://api.iron.sh/v1/agents/agt_x9k2mf4p \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 200 OK
Same shape as the create response.
Errors:
| Code | Status | Description |
|---|---|---|
agent_not_found | 404 | Agent not found or has been destroyed. |
Destroy an Agent
DELETE /v1/agents/{agent_id}Destroys the agent and its backing VM.
curl -X DELETE https://api.iron.sh/v1/agents/agt_x9k2mf4p \
-H "Authorization: Bearer $IRONS_API_KEY"Response: 204 No Content
Errors:
| Code | Status | Description |
|---|---|---|
agent_not_found | 404 | Agent not found or has been destroyed. |
Authentication
Request Device Code
POST /v1/auth/device/codecurl -X POST https://api.iron.sh/v1/auth/device/codeResponse: 200 OK
{
"data": {
"code": "ABCD-1234",
"verification_uri": "https://app.iron.sh/activate",
"expires_at": "2026-02-28T12:15:00Z"
}
}Poll Device Authorization
GET /v1/auth/device/pollQuery parameters:
| Param | Type | Required | Description |
|---|---|---|---|
code | string | yes | The device code from the code request |
curl "https://api.iron.sh/v1/auth/device/poll?code=ABCD-1234"Response: 200 OK
{
"data": {
"status": "authorized",
"token": "iron_live_a1b2c3d4e5f6..."
}
}| Status | Meaning |
|---|---|
pending | User hasn’t authorized yet. Continue polling. |
authorized | Authorization successful. token is present. |
expired | Code expired. Request a new one. |