Reference
Operations & plans
What the platform enforces on the workspace's behalf when you run it for real: monthly run quotas, data retention windows, operational alerts, a full audit trail, role-based access control, API keys, and production authentication.
Everything on this page is already enforced in the local and self-hosted builds — the plan tier is just a field on the workspace. Wiring up a billing provider changes who may switch plans, not what the enforcement layer does.
Plans & run quotas
Every run-trigger surface — manual, webhook, MCP, and the scheduler — counts against a single monthly quota set by the workspace plan:
| Field | Type | Default | Description |
|---|---|---|---|
| free | 1,000 / mo | — | Solo and trial workspaces. |
| starter | 10,000 / mo | — | Small teams running in production. |
| pro | 200,000 / mo | — | High-volume automation. |
| enterprise | unlimited | — | No monthly cap — the quota check short-circuits. |
The quota window is the calendar month in UTC, resetting at 00:00 on the 1st. It counts every run created in the current month regardless of final status — a failed or cancelled run still consumes quota, because the work was attempted. The check runs before graph validation, so a workspace at its limit is refused even for a valid workflow.
At the limit, triggers return 402 Payment Required with a plain, itemised explanation — no runs are silently dropped:
HTTP/1.1 402 Payment Required
{
"detail": "Monthly run limit reached (1,000/1,000 on the free plan) — upgrade the workspace plan to keep running"
}
Behaviour per trigger surface
| Field | Type | Default | Description |
|---|---|---|---|
| Manual | 402 | — | POST /api/workflows/{id}/runs raises 402. Checked after an idempotent replay returns the prior run, but before graph validation. |
| Webhook | 402 | — | POST /api/hooks/{workflow_id}/{secret} raises 402 after the secret is verified. |
| MCP | tool error | — | The run_workflow tool returns the same message as a tool error (isError), not an HTTP 402. |
| Scheduled | skip + log | — | The scheduler tick is skipped and logged, never queued. Cadence resumes when quota frees up. |
GET /api/usage and in Settings → Usage, where runs_used is the exact count the 402 gate compares against the limit.Managing the plan
/api/workspace/planSwitch tiers: { plan: free | starter | pro | enterprise }. Requires the manage_workspace capability (admin). Quotas and retention windows apply immediately; the change is audit-logged as plan_change with { from, to }.
{ "plan": "pro" }
// →
{ "id": "0af31c92bd07", "name": "Acme", "plan": "pro", "member_count": 5, "created_at": "2026-06-01T00:00:00Z" }
Where billing plugs in
Locally and self-hosted, plan switching is an unmetered admin action. In the hosted product, checkout lands on exactly this seam — the enforcement layer (quotas, retention, 402s) is already real, so a payment integration changes who may call this endpoint, not what it does.Budgets & cost enforcement
Quotas cap the number of runs; block budgets cap the dollars. A budget is now enforced on every trigger surface — manual POST /api/workflows/{id}/runs, replay, webhook, schedule, and MCP — so no path can quietly spend past the cap. A trigger that would exceed the budget is refused with 402 Payment Required and never queued.
Atomic reservations
A budget can carry a per-run reservation_estimate_usd. On each trigger the engine places a hold for that amount under a per-budget row lock, so concurrent triggers cannot race past the limit — at the boundary exactly one of N simultaneous triggers is admitted and the rest get 402. Each hold settles to the run's actual spend when it finishes, so an overestimate is released rather than lost.
Budget status reports the full ledger, so a dashboard can show headroom before a run is even created:
| Field | Type | Default | Description |
|---|---|---|---|
| spent_usd | number | — | Settled cost of finished runs in the window. |
| reserved_usd | number | — | Sum of open holds for in-flight runs, not yet settled. |
| committed_usd | number | — | spent_usd + reserved_usd — what is already promised against the cap. |
| remaining_usd | number | — | Budget limit minus committed_usd; the headroom a new trigger is checked against. |
| pct | percent | — | committed_usd as a share of the limit. |
| over | boolean | — | True once committed_usd meets or exceeds the limit — the state that returns 402. |
| projected_usd | number | — | Forward projection of end-of-window spend from the current burn rate. |
reservation_estimate_usd when you create or update a budget — POST /api/budgets and PUT /api/budgets/{id} both accept it. Leave it unset to enforce on settled spend alone, without per-run holds.Override with approval
A run that failed on budget can be resumed past the cap, but only by an admin — the authorization is the approval, and the act is fully audited.
/api/runs/{id}/budget-overrideLift or raise the per-run cost cap and resume a budget-failed run. Body { approved: true, cap_usd?, reason? }. Requires the manage_workspace capability. Writes a budget_override audit entry and an approval_override_granted governance event.
{ "approved": true, "cap_usd": 5.00, "reason": "One-off backfill approved by finance" }
An override is a spending decision on the record
Because it can push a single run past the workspace budget, the override requiresmanage_workspace and leaves two trails: the tamper-evident budget_override audit entry (who, when, the cap) and an approval_override_granted governance event on the run.Data retention
A background sweeper runs at startup and every 24 hours thereafter, deleting execution and audit data older than the plan window. Deleting a run cascades to its steps and checkpoints. A sweep that removes anything logs an audit entry (retention_sweep on the workspace) whose details are the counts it removed; a sweep that removes nothing is silent.
| Field | Type | Default | Description |
|---|---|---|---|
| free | runs 7d | audit 30d | Runs (with steps and checkpoints) kept 7 days; audit log kept 30 days. |
| starter | runs 30d | audit 30d | Runs kept 30 days; audit log kept 30 days. |
| pro | runs 90d | audit 365d | Runs kept 90 days; audit log kept a year. |
| enterprise | runs kept | audit kept | Both windows are unbounded — nothing is swept. |
Windows are measured from each record's creation time. Runs and the audit log have independent windows, which is why an audit trail can outlive the runs it describes on the pro plan.
Retention is destructive by design
Swept runs — including their step traces and checkpoints — are gone. Export anything you need long-term before its window closes, or run a plan whose window matches your compliance requirements.Failure & approval alerts
When a run fails or pauses at a human gate, the backend notifies your team. Delivery is entirely env-gated — unset means silent, so local development stays quiet. Slack and email fire independently: you can enable either, both, or neither.
| Field | Type | Default | Description |
|---|---|---|---|
| SLACK_WEBHOOK_URL | string | unset | An incoming-webhook URL. When set, posts one Slack message per failure or pending gate. |
| RESEND_API_KEY | string | unset | Enables email delivery (shared with the send_email tool and member invites). |
| ALERT_EMAIL | string | unset | Recipient address for alert emails; requires RESEND_API_KEY to take effect. |
Two events trigger an alert. A failedevent carries the run's error message; a paused event carries the human-gate prompt.
[Ballast] Run af772b3602ef of 'Support Triage Crew' failed: Node 'Fetch CRM' failed: tool 'crm' returned HTTP 500
[Ballast] Run b91c04d2ee01 of 'Support Triage Crew' is paused at a human gate: Review the drafted support reply before it is sent.
Subject: [Ballast] Run failed — Support Triage Crew
Body: Run af772b3602ef of 'Support Triage Crew' failed: Node 'Fetch CRM' failed: tool 'crm' returned HTTP 500
Subject: [Ballast] Approval needed — Support Triage Crew
Body: Run b91c04d2ee01 of 'Support Triage Crew' is paused at a human gate: Review the drafted support reply before it is sent.
- Alerts are fire-and-forget: the executor schedules them without awaiting, and any delivery problem is logged and never affects the run itself
- The email body is the same one-line text as the Slack message, minus the
[Ballast]prefix that only Slack carries - Member invites reuse the same email path — with
RESEND_API_KEYset, invited members receive their invite code by email; the code stays visible in Settings → Members either way
Audit log
Every state-changing action is appended to an immutable trail with the actor, target, timestamp, and a small details object. Reads are never recorded. The actor is resolved from the request credential to a member name or email; actions taken by an unlinked API key (or, in open mode, by no credential at all) resolve to the workspace owner or to no actor.
/api/auditAppend-only audit trail, newest first. Params: limit (default 50, capped at 200), offset. Returns { logs, total }.
{
"logs": [
{
"id": "77b21c09aa04",
"action": "approve",
"actor_id": "3c1a9f22bd10",
"actor_name": "Dana Ortiz",
"target_type": "run",
"target_id": "af772b3602ef",
"details": { "node_id": "review" },
"created_at": "2026-07-07T14:12:40Z"
}
],
"total": 128
}
| Field | Type | Default | Description |
|---|---|---|---|
| create · update · delete | workflow | — | Authoring changes; update bumps the version when the graph changes. |
| run · schedule_run | workflow | — | Manual and scheduler-fired triggers; details carry the run_id (schedule_run also carries schedule_id and cron). |
| approve · reject · resume · cancel | run | — | Human-gate decisions and run recovery. |
| schedule_create · schedule_update · schedule_delete | workflow | — | Schedule lifecycle; details carry schedule_id and cron. |
| update · plan_change | workspace | — | Rename and tier switch; plan_change details are { from, to }. |
| invite · approve_member · role_change · permissions_change · remove | member | — | Membership and per-member RBAC changes. |
| create · revoke | api_key | — | Key issuance and revocation. |
| retention_sweep | workspace | — | A sweep that removed data; details are the { runs, audit } counts. |
Metrics
/api/metrics/overviewDashboard aggregates computed across all runs in the workspace.
{
"total_runs": 412,
"success_rate": 91.4,
"total_cost_usd": 1.83271,
"active_workflows": 3,
"runs_by_day": [
{ "date": "2026-06-24", "count": 28, "cost_usd": 0.1142 }
],
"eval_pass_rate": 87.5,
"evaluated_runs": 64
}
| Field | Type | Default | Description |
|---|---|---|---|
| total_runs | integer | — | Every run ever recorded in the workspace. |
| success_rate | percent | — | Succeeded ÷ finished (success + failed) runs, to one decimal; 0.0 when nothing has finished. |
| total_cost_usd | number | — | Summed model + tool cost across all runs, to six decimals. |
| active_workflows | integer | — | Count of workflows with status published. |
| runs_by_day | array | — | The last 14 days, each { date, count, cost_usd }. |
| eval_pass_rate | percent | null | — | Passed checks ÷ total checks across every evaluated run; null when no checks have run. |
| evaluated_runs | integer | — | Runs that produced at least one evaluation check. |
Members & RBAC
Access is governed by ten granular capabilities. A member's role supplies a default set; per-member overrides create exceptions in either direction; the effective permission is the override if one is set, else the role default. API keys linked to a member act with that member's effective permissions.
| Field | Type | Default | Description |
|---|---|---|---|
| view | all roles | — | View workflows, runs, and the audit log. |
| run_workflows | admin · editor | — | Trigger runs. |
| approve_gates | admin · editor | — | Approve or reject paused human gates. |
| cancel_resume | admin · editor | — | Cancel and resume runs. |
| create_edit_workflows | admin · editor | — | Create and edit workflows, custom tools, and schedules. |
| publish_workflows | admin · editor | — | Publish a workflow (draft to published). |
| delete_workflows | admin | — | Delete workflows and their run history. |
| manage_members | admin | — | Invite, approve, re-role, and remove members. |
| manage_api_keys | admin | — | Create and revoke API keys. |
| manage_workspace | admin | — | Workspace settings and plan changes. |
- admin holds all ten capabilities; editor holds the six above through publish_workflows; viewer holds only
view - Members carry a status:
active,invited(holds an invite code), orpending(self-serve signup awaiting an admin's approval) - The last active admin is protected: you cannot demote or remove them, nor deny them
manage_membersormanage_workspace— promote another admin first
How requests map to capabilities
The auth middleware maps each request to a required capability and returns 403 — "This action requires the 'X' permission" — when the credential lacks it.
| Field | Type | Default | Description |
|---|---|---|---|
| GET / HEAD / OPTIONS | view | — | All reads, plus POST .../validate (a read-only check). |
| POST /api/workflows/{id}/runs | run_workflows | — | Triggering a run. |
| POST .../approve | approve_gates | — | Approving or rejecting a gate. |
| POST .../cancel · .../resume | cancel_resume | — | Cancelling or resuming a run. |
| POST/PUT /api/workflows · /api/templates | create_edit_workflows | — | Authoring and instantiating workflows. |
| DELETE /api/workflows/{id} | delete_workflows | — | Deleting a workflow. |
| /api/schedules · /api/tools | create_edit_workflows | — | Managing schedules and custom tools. |
| /api/members | manage_members | — | Any member mutation. |
| /api/keys | manage_api_keys | — | Any API-key mutation. |
| /api/workspace | manage_workspace | — | Workspace and plan changes. |
| /api/mcp | view | — | Gateway is view-level; each tool enforces its own capability inside the MCP handler. |
/api/membersList members, ordered by join time. Invite codes are shown only for members still in the invited state.
/api/members/inviteInvite a member: { name, email, role }. 400 if the email already belongs to a member. With RESEND_API_KEY set, the invite code is emailed. Returns 201.
/api/members/{id}Member detail including every capability with its default, override, and effective value, plus the count of non-revoked API keys linked to them.
/api/members/{id}Change a member's role: { role }. Guarded against demoting the last active admin. Audit-logged as role_change.
/api/members/{id}/permissionsSet per-member overrides: { overrides: { capability: true | false | null } }. null clears an override. Unknown capabilities return 400; last-admin denials are blocked.
{ "overrides": { "delete_workflows": true, "publish_workflows": false } }
/api/members/{id}/approveApprove a self-serve signup: moves a pending member to active. 400 unless the member is pending.
/api/members/{id}/statsPer-member activity: runs triggered, approvals, rejections, workflows created, total cost, a 30-day activity series, and the 15 most recent actions.
/api/members/{id}Remove a member. Guarded against removing the last active admin. Returns 204.
API keys
API keys are role-scoped bearer credentials sent in the X-API-Keyheader. A key linked to a member acts with that member's effective permissions; an unlinked key uses its own role's defaults. The full secret (prefixed aos_) is returned once at creation — only a display prefix is stored. Keys are revoked (flagged), never hard deleted, and last_used_at updates on each authenticated request.
X-API-Key wins over Authorization: Bearer <session token>. With neither, AUTH_MODE=open acts as the workspace owner and AUTH_MODE=required returns 401./api/keysList keys, newest first: name, display prefix, role, linked member_id, created_at, last_used_at, revoked.
/api/keysCreate a key: { name, role, member_id? }. When member_id is set, the key adopts that member's role. The plaintext secret is returned once, in this response only.
{
"id": "5f2ab9013c77",
"name": "CI pipeline",
"prefix": "aos_1a2b3c4d…",
"role": "editor",
"member_id": null,
"revoked": false,
"secret": "aos_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"
}
/api/keys/{id}Revoke a key: it stops authenticating immediately. Returns 204.
Usage
/api/usageCurrent billing period at a glance — the same run count the 402 gate uses.
{
"plan": "free",
"runs_used": 274,
"runs_limit": 1000,
"cost_usd": 0.83104,
"period_start": "2026-07-01T00:00:00Z",
"period_end": "2026-07-31T23:59:59Z"
}
runs_limit is null on the enterprise plan. The period spans the first and last day of the current UTC month.
Authentication modes
Auth is a single environment switch. Local development runs open; set it to required in production and hand out session tokens or API keys.
| Field | Type | Default | Description |
|---|---|---|---|
| AUTH_MODE | open | required | open | open: no login; unauthenticated /api requests act as the workspace owner. required: every /api request needs a session token or API key, else 401. |
| SECRET_KEY | string | per-boot | Signs HMAC session tokens (7-day TTL). Unset means a random per-boot secret, so sessions do not survive a restart — set it in production. |
| OWNER_PASSWORD | string | generated | In required mode, if the owner admin has no password at boot, one is set from this env var or generated and logged once. |
| RATE_LIMIT_PER_MINUTE | integer | 0 | Above 0, enables a per-IP request ceiling that returns 429. Off by default. |
| CORS_ORIGINS | csv | http://localhost:3000 | Comma-separated browser origins allowed by CORS. |
- Exempt from auth even in required mode:
/api/health,/api/auth/login,/api/auth/accept-invite,/api/auth/signup, and/api/hooks/*(a webhook's secret is its own credential) - On restart, runs left
runningorpendingbecome resumable failures — their last checkpoint is intact, soPOST /api/runs/{id}/resumepicks up where they left off