Agent Runs
An agent run executes a configured agent headlessly — no chat UI, no human at the keyboard. Runs are the programmatic core of NexSpace Headless AI: the same durable path backs API-initiated runs, automations (schedule/event triggers), and thenexspace agents run CLI
command.
Every run requires the agents:run scope on the API key.
Lifecycle
Runs are persisted durably (
headless_agent_runs) — state survives server
restarts and is readable across instances. Orphaned runs are reconciled and old
runs are pruned on a schedule.
Start a run
202 Accepted — the run is enqueued, not finished. Poll or
stream it (below) for the result.
Request body
Those two fields are the whole body — unknown keys are stripped, not
rejected. A missing or empty
message returns 400 VALIDATION_ERROR with the
per-field validation errors in error.details.
seedMessage is a different field on a different endpoint. It belongs to
triggers (POST /api/agent-builder/{id}/triggers), where it stores the
instruction an unattended schedule/event run will be seeded with. The run API
takes message.409 AGENT_BUSY — the one-run-per-agent guard prevents overlapping runs.
Run identity
A run always executes as the user your credential is bound to. There is norunAs parameter on this endpoint and no way to run as somebody else over this
API. That user’s RBAC permissions and suite entitlements are the ceiling: a run
can never do more than the bound user could do in the app.
Consequences worth designing around:
- Org-only keys cannot use this API. An API key with no bound user
(
user_id IS NULL) is accepted by the authenticator but rejected by every/api/agents/{id}/runs…route with401 API_KEY_REQUIRED. Use a PAT or a user-bound key/OAuth token. - Foreign agents return
404, never403. If the agent definition belongs to another facility or another org, you get404 NOT_FOUND— the same response as an id that does not exist. This is deliberate: it keeps agent ids unprobeable. Do not read404as “wrong id” without also checking tenancy. - The run executes under your facility. The facility comes from the API key’s facility lock, else the bound user’s facility — never from the agent record you looked up. A key locked to facility A cannot inherit facility B by naming an agent that lives there.
- Runs are scoped to you on read, too.
GET/DELETEon a run owned by a different user answer404, not403.
There is no headless endpoint that lists agent definitions —
/api/agents only
serves /{agentId}/runs…. Take the agent id from the Agent Builder dashboard or
from the CLI (nexspace agents runs list <agentId> and friends all take the id
as an argument).Poll, list, and cancel
Stream a run
Stream run events as they happen over Server-Sent Events:id. If the connection drops, reconnect with
a Last-Event-ID header to replay the frames you missed — the same resumable
transport described in MCP.
The CLI does this for you: nexspace agents run 42 --output-format stream-json.
Approvals
When a run invokes a tool that requires approval (see autonomy), it transitions topending_approval and exposes
pendingApprovalIds instead of executing unattended. List the pending actions,
then approve or reject — approving resumes the agent loop, it does not merely
run the one tool.
reason is optional on both (max 2,000 characters). It is stored on the
approval (approvalReason / rejectionReason) and written to the decision
audit record.
Approving is a two-part authorization
Theagents:approve scope is necessary but not sufficient. Resolving an
approval over this API requires the caller to own the run that raised it — and
owning the run means you are the requester, so approving it is self-approval.
That needs a second, independent grant: the ai.approve_actions RBAC permission
on the credential’s bound user.
Rejection is deliberately ungated: withdrawing your own pending action can only
reduce what runs, so it stays available as a fail-safe even when you cannot
approve.
Internal operators with cross-facility scope are the one exception — their
decision is recorded as
internal and skips the self-approval check.
Responses you should handle
Because approval is a separate grant, the practical pattern for unattended fleets
is: let the agent run under a credential that cannot self-approve, subscribe to
agent_run.pending_approval, and have a human (or a second credential whose user
does hold ai.approve_actions) make the call.
Autonomy
An agent definition’s own autonomy setting (defaultAutonomy, which defaults to
require_approval) decides what happens on a risky write:
require_approval— pause atpending_approvaland wait for a human (or an approver workflow wired to the webhook below).notify_after— perform the write, then notify.auto_execute— perform the write unattended (use only when the writes are safe to run without review).
Lifecycle webhooks
Subscribe to run lifecycle events instead of polling (see Webhooks):agent_run.pending_approvalagent_run.completedagent_run.failed
See also
- Automations — fire runs on a schedule or business event.
- MCP — the tool layer runs invoke.
- CLI commands —
nexspace agents run.

