Aegis Orchestrator
Reference

Edge REST API

HTTP reference for the /v1/edge/* surface — enrollment tokens, hosts, tags, groups, fleet preview, fleet invoke, fleet cancel, and run history.

Edge REST API

The /v1/edge/* namespace is the HTTP surface for managing edge daemons, tags, groups, and fleet operations. Every operation here is mirrored by an aegis edge CLI command and, where appropriate, by a system-tier MCP tool.

This page assumes:

  • You have a user JWT (issued by Keycloak via Zaru) in the Authorization: Bearer <token> header.
  • Your tenant is resolved via the standard mechanism (X-Tenant-Id header for service accounts, JWT claims for users).
  • All responses are JSON unless noted.

For broader API context, see REST API reference.


Enrollment tokens

POST /v1/edge/enrollment-tokens

Issue a new enrollment token bound to the caller's effective_tenant.

Request body:

{
  "name": "workstation-east",
  "ttl_seconds": 900,
  "team_tenant_id": "t-..."
}
FieldTypeRequiredDefaultDescription
namestringnoFriendly display name to associate with the resulting daemon.
ttl_secondsintno900Token lifetime in seconds. Must be ≤ 900 (15 minutes).
team_tenant_idstringnoTeam tenant id to issue the token under. Caller must have admin rights on that team. Defaults to the caller's personal tenant.

Response:

{
  "token": "eyJhbGc...3Q",
  "expires_at": "2026-04-28T14:47:11Z",
  "controller_endpoint": "relay.example.com:443",
  "qr_payload": "aegis-edge-enroll://eyJhbGc...3Q"
}

Errors:

CodeMeaning
400Invalid ttl_seconds (>900) or unknown team_tenant_id.
401Missing or invalid auth.
403Caller lacks aegis.edge.enroll scope or admin rights on the team.

Hosts

GET /v1/edge/hosts

List edge hosts in the caller's tenant.

Query parameters:

ParameterTypeDescription
tagstring (repeatable)Filter to hosts with all listed tags.
labelstring K=V (repeatable)Filter to hosts with the given label.
connectedboolOnly Connected hosts.
cursorstringPagination cursor.
limitintPage size; default 50, max 200.

Response:

{
  "items": [
    {
      "node_id": "n-7a3b2f...",
      "name": "workstation-east",
      "tenant_id": "u-8d1c...",
      "status": "Connected",
      "since": "2026-04-28T14:32:11Z",
      "last_heartbeat_at": "2026-04-28T14:51:43Z",
      "tags": ["prod", "team-platform"],
      "capabilities": {
        "os": "linux",
        "arch": "x86_64",
        "local_tools": ["shell", "docker", "kubectl"],
        "labels": { "region": "us-east-1" }
      }
    }
  ],
  "next_cursor": null
}

GET /v1/edge/hosts/:id

Get a single host's full record, including recent command history.

Response:

{
  "node_id": "n-7a3b2f...",
  "name": "workstation-east",
  "tenant_id": "u-8d1c...",
  "status": "Connected",
  "enrolled_at": "2026-03-15T10:12:08Z",
  "since": "2026-04-28T14:32:11Z",
  "last_heartbeat_at": "2026-04-28T14:51:43Z",
  "tags": ["prod", "team-platform"],
  "capabilities": { "...": "..." },
  "recent_commands": [
    {
      "command_id": "c-...",
      "tool": "cmd.run",
      "dispatched_at": "2026-04-28T14:50:11Z",
      "outcome": "ok",
      "duration_ms": 1234
    }
  ]
}

PATCH /v1/edge/hosts/:id

Update a host's display name and/or tags.

Request body:

{
  "name": "workstation-east-renamed",
  "tags": ["prod", "team-platform", "db-host"]
}

The tags array is a complete replacement — to add or remove individual tags atomically, use the tags add/remove subresource:

POST /v1/edge/hosts/:id/tags
{ "add": ["db-host"], "remove": ["staging"] }

DELETE /v1/edge/hosts/:id

Revoke an edge host. Marks the daemon Revoked, blacklists its NodeSecurityToken, drops the active gRPC stream.

Response: 204 No Content.


Groups

GET /v1/edge/groups

List groups in the tenant.

{
  "items": [
    {
      "id": "g-a3f4...",
      "name": "production-db-hosts",
      "selector": {
        "tags": [{ "AllOf": ["prod", "db"] }],
        "tools": ["docker"]
      },
      "pinned_members": ["n-7a3b2f..."],
      "resolved_member_count": 4,
      "created_at": "2026-04-01T09:00:00Z"
    }
  ]
}

POST /v1/edge/groups

Create a group.

Request body:

{
  "name": "production-db-hosts",
  "selector": {
    "os": "linux",
    "tools": ["docker"],
    "tags": [{ "AllOf": ["prod", "db"] }],
    "labels": [{ "Equals": { "region": "us-east-1" } }]
  },
  "pinned_members": []
}

GET /v1/edge/groups/:id

Fetch a single group, with the resolved member list at request time.

{
  "id": "g-a3f4...",
  "name": "production-db-hosts",
  "selector": { "...": "..." },
  "pinned_members": [],
  "resolved_members": [
    {
      "node_id": "n-7a3b2f...",
      "name": "workstation-east",
      "status": "Connected"
    }
  ]
}

PATCH /v1/edge/groups/:id

Update a group's name, selector, or pinned members.

{
  "name": "production-db-hosts-renamed",
  "selector": { "tags": [{ "AllOf": ["prod", "db", "team-platform"] }] }
}

DELETE /v1/edge/groups/:id

Delete a group.


Fleet operations

POST /v1/edge/fleet/preview

Resolve a target to a node list without dispatching.

Request body:

{
  "target": {
    "Selector": {
      "tags": [{ "Has": "prod" }]
    }
  }
}

The target field is a tagged-union over Node, Group, Selector, All:

{ "Node": "n-7a3b2f..." }
{ "Group": "g-a3f4..." }
{ "Selector": { "...": "..." } }
{ "All": null }

Response:

{
  "resolved": [
    {
      "node_id": "n-7a3b2f...",
      "name": "workstation-east",
      "status": "Connected"
    },
    {
      "node_id": "n-1c8d4e...",
      "name": "workstation-west",
      "status": "Connected"
    }
  ],
  "skipped": [{ "node_id": "n-9f2a31...", "reason": "Disconnected" }]
}

POST /v1/edge/fleet/invoke

Dispatch a tool against a target with a fleet dispatch policy. Server-streamed — the response is text/event-stream with one event per CommandProgress chunk and a terminal FleetExecutionResult.

Request body:

{
  "target": { "Group": "g-a3f4..." },
  "tool": "service.restart",
  "args": { "name": "nginx" },
  "policy": {
    "mode": { "Rolling": { "batch": 5 } },
    "max_concurrency": 5,
    "failure_policy": { "StopAfter": 1 },
    "require_min_targets": null,
    "per_target_deadline_secs": 30
  }
}

Response (server-streamed events):

event: command_progress
data: {"node_id":"n-7a3b2f...","command_id":"c-...","chunk":{"stdout":"Reloading systemd..."}}

event: command_progress
data: {"node_id":"n-7a3b2f...","command_id":"c-...","chunk":{"stdout":"nginx.service restarted"}}

event: command_result
data: {"node_id":"n-7a3b2f...","command_id":"c-...","result":{"ok":{"exit_code":0,"duration_ms":1234}}}

...

event: fleet_result
data: {
  "fleet_command_id": "f-a3f4...",
  "targets_resolved": ["n-7a3b2f...","n-1c8d4e..."],
  "targets_skipped": [],
  "summary": {"ok": 6, "err": 1, "timed_out": 0, "cancelled": 2, "not_started": 3}
}

Errors (returned as event: error):

ReasonMeaning
RequireMinNotSatisfiedResolved target count below require_min_targets; no per-node calls dispatched.
EdgeUnavailableA Node target is offline or not found.
Haltedfailure_policy triggered a halt. The terminal fleet_result carries the breakdown.

POST /v1/edge/fleet/:fleet_command_id/cancel

Broadcast Cancel to every in-flight per-node command in a fleet operation.

Response:

{
  "fleet_command_id": "f-a3f4...",
  "cancelled": 12,
  "already_terminal": 5
}

GET /v1/edge/fleet/runs

List historical fleet runs in the tenant.

Query parameters:

ParameterDescription
cursorPagination cursor.
limitPage size; default 50, max 200.
toolFilter by tool name.
outcomeFilter by outcome (complete, halted, cancelled).

Response:

{
  "items": [
    {
      "fleet_command_id": "f-a3f4...",
      "tool": "service.restart",
      "target": { "Group": "g-..." },
      "policy": { "...": "..." },
      "started_at": "2026-04-28T14:32:11Z",
      "ended_at": "2026-04-28T14:34:07Z",
      "outcome": "halted",
      "summary": { "ok": 6, "err": 1, "cancelled": 2, "not_started": 3 }
    }
  ],
  "next_cursor": null
}

GET /v1/edge/fleet/runs/:fleet_command_id

Fetch a single run with per-node detail.

{
  "fleet_command_id": "f-a3f4...",
  "tool": "service.restart",
  "target": { "...": "..." },
  "policy": { "...": "..." },
  "started_at": "2026-04-28T14:32:11Z",
  "ended_at": "2026-04-28T14:34:07Z",
  "outcome": "halted",
  "per_node": [
    {
      "node_id": "n-7a3b2f...",
      "command_id": "c-...",
      "outcome": "ok",
      "exit_code": 0,
      "duration_ms": 1234,
      "stdout_tail": "...",
      "stderr_tail": ""
    }
  ]
}

Authentication and authorization

OperationRequired scope
Issue enrollment tokensaegis.edge.enroll
List/get hostsaegis.edge.hosts.read
Update/revoke hostsaegis.edge.hosts.write
Manage groupsaegis.edge.groups.write
Fleet previewaegis.edge.fleet.read
Fleet invoke / cancelaegis.edge.fleet.manage
Fleet run historyaegis.edge.fleet.read

These scopes are mapped from Keycloak roles by the API gateway and enforced at the handler boundary. A user with insufficient scope receives 403 Forbidden.

Tenant resolution

Every endpoint is filtered through effective_tenant. Service accounts using X-Tenant-Id resolve to the delegated tenant; users resolve via JWT claims; team-tenant access is granted by the team-membership claim. Cross-tenant access is impossible at the router boundary.


What's next

On this page