HTTP API reference
For people who script against Agent Taskbay or build on it: every route under /api, with its method, who may call it, what it accepts and what it
> For people who script against Agent Taskbay or build on it: every route under /api, with its method, who may call it, what it accepts and what it
> returns. Examples marked "Observed" were run against a local development server.
There is no stable API yet. The routes exist to serve the console. They are not versioned (no /api/v1), have no deprecation policy, and may change in
any minor release while the project is pre-1.0 (package version 0.2.0). A versioned, token-authenticated client API is proposed in
#31. Until then, pin a version if you depend on a route.
The "Use" column in the route index is a reading aid, not a promise: Integration routes are the ones a script is most likely to want and that the console uses as plain request/response APIs; Console routes are UI plumbing (sign-in, live updates, access management) and are the most likely to change.
On this page: Authentication and roles · Conventions · Route index · Sign-in routes · Agents · Sending messages and commands · Tasks · Task workflow · Inbox · Approvals · Notifications · Audit · Access management · Artifacts · Push webhook · Limits
Authentication and roles
Every route except the three sign-in routes and the push webhook requires a signed-in person.
-
Session cookie only. There is no API key, bearer token or service-token authentication for these routes. In
oidcmode the session cookie (__Host-a2a-session,HttpOnly,Secure,SameSite=Lax) is issued after browser sign-in. Indevelopmentmode every request is the local administrator and needs no cookie. See Sign in and roles. -
Roles. Each route is guarded by one of three permissions:
Permission Allowed roles Meaning readadmin,operator,viewerLook at agents, tasks, approvals, inbox, audit for one's own scope operateadmin,operatorSend messages, claim and assign work, open and decide approvals administeradminRegister agents, manage teams and grants, escalation policy, notification channel, organization audit -
Per-agent grants. Roles are a ceiling. Administrators see every agent. For
operatorandviewer, an agent (or one skill of it) is visible only through an enabled grant whose subject is the organization, the person or one of their teams.readneeds any grant;operateneeds anoperategrant and a role that can operate. A resource outside the caller's grants answers as if it does not exist (404), not403. A skill-scoped grant never authorizes a generic send. -
Order of checks. Sign-in (
401), role (403), rate limit (429), then, for non-GET requests, the origin check, then the handler.
Origin check (CSRF)
Requests other than GET, HEAD and OPTIONS are rejected with 403 (This action is not permitted.) when:
- the
Sec-Fetch-Siteheader iscross-site, or - in
oidcmode theOriginheader is missing or is not exactlyA2A_AUTH_ORIGIN, or - in
developmentmode anOriginheader is present that is neither the request's own origin nor a loopback origin with the same scheme and port.
A script that is not a browser must therefore send an Origin header equal to the configured origin, and it can only do so with a valid session cookie
copied from a browser. That is an awkward workflow, and the reason a token API is planned.
Conventions
| Topic | Behavior |
|---|---|
| Format | JSON in, JSON out, Content-Type: application/json. Request bodies larger than 16 MiB are rejected with 400. A missing body is 400 Request body is required.; invalid JSON is 400 Invalid request JSON. |
| Caching | Responses carry Cache-Control: no-store. |
| IDs | Local IDs are UUIDs generated by Agent Taskbay. A task has a local ID (localId, used in URLs) and a remote ID (taskId from the agent); they are different. Remote IDs are only unique within (agent, tenant). |
| Timestamps | ISO 8601 UTC strings. |
| Idempotency | Mutations that start durable work take an Idempotency-Key header (1 to 255 characters). Reusing a key with identical content returns the original result; with different content it returns 409. |
| Pagination | Inbox, notifications, audit and task views use opaque or UUID cursors and return next (null on the last page). The task list uses limit and offset. |
| Unknown JSON keys | Most request schemas are strict: an unrecognized key is a 400. |
Error shapes
Errors use one of these shapes. Messages are written for people and may be reworded; match on the status code.
| Shape | Where |
|---|---|
{"error":{"message":"..."}} | Sign-in, role and rate-limit failures; many validation and not-found errors. |
{"error":{"name":"Error","message":"..."}} | Errors raised by services and caught by the route helper. |
{"error":"..."} | The push webhook only. |
Plain text Not found | GET /api/artifacts/{digest}. |
Common statuses:
| Status | Meaning |
|---|---|
400 | Validation failure (message names the problem). |
401 | Sign in to continue. No valid session. |
403 | This action is not permitted. Role, grant or origin check failed; some services return a specific reason. |
404 | Unknown or not visible. |
409 | State conflict: idempotency key reuse with different content, closed or expired approval, task already finished, task already owned. |
422 | Well-formed but not allowed: assignee or delegate cannot operate the agent, an edit that changes nothing. |
429 | Rate limit. Too many requests. Try again shortly. |
502 | Upstream agent error on routes that call the agent directly (for example agent discovery). |
503 | Identity service unavailable. (sign-in or database problem). |
504 | A blocking route stopped waiting; the work continues (see /messages). |
Rate limits
Fixed 60-second windows in the database, so they hold across web replicas.
| Bucket | Limit per window |
|---|---|
Per person, read routes | 900 |
Per person, operate routes | 120 |
Per person, administer routes | 60 |
GET /api/auth/login and /api/auth/callback, all callers together | 240 each |
POST /api/webhooks/a2a/{id}, all callers together | 1200 |
When a limit is hit the route answers 429. The webhook adds Retry-After: 60. The limits are not configurable.
Route index
| Method and path | Permission | Use | Summary |
|---|---|---|---|
GET /api/auth/login | public | Console | Start sign-in (redirect) |
GET /api/auth/callback | public | Console | OIDC redirect target |
POST /api/auth/logout | read | Console | End the session |
GET /api/auth/session | read | Console | Who am I, and the auth mode |
GET /api/agents | read | Integration | Catalog with discovered cards |
POST /api/agents | administer | Integration | Register an agent by Agent Card URL |
POST /api/agents/preview | administer | Console | Fetch and validate a card without registering |
GET /api/agents/{agentId} | read | Integration | One agent with its card and trust status |
DELETE /api/agents/{agentId} | administer | Integration | Remove a managed agent |
POST /api/agents/{agentId}/commands | operate | Integration | Send a message or cancel (durable, returns 202) |
GET /api/commands/{commandId} | read | Integration | Command status and result |
POST /api/agents/{agentId}/messages | operate | Integration | Send and wait up to 50 s |
POST /api/agents/{agentId}/stream | operate | Console | Send or resubscribe, as server-sent events |
POST /api/agents/{agentId}/tasks/{taskId}/cancel | operate | Integration | Cancel by remote task ID, and wait |
POST /api/agents/{agentId}/ag-ui | operate | Integration | AG-UI run (off unless enabled); see AG-UI |
GET /api/tasks | read | Integration | Task list |
GET /api/tasks/{taskId} | read | Integration | Task detail |
GET /api/task-views | read | Console | Paged task content for chat and flows |
GET /api/tasks/events | read | Console | Live "something changed" signal (SSE) |
GET /api/tasks/{taskId}/workflow | read | Integration | Owner, due time, history, notes |
POST /api/tasks/{taskId}/workflow/claim | operate | Integration | Take an unowned task |
POST /api/tasks/{taskId}/workflow/release | operate | Integration | Give a task up |
POST /api/tasks/{taskId}/workflow/assign | operate | Integration | Assign to a person |
POST /api/tasks/{taskId}/workflow/due | operate | Integration | Set or clear the due time |
POST /api/tasks/{taskId}/workflow/notes | operate | Integration | Add an internal note |
GET /api/inbox | read | Integration | Tasks and approvals the caller may act on |
GET /api/decisions | read | Integration | List approval requests |
POST /api/decisions | operate | Integration | Open an approval request |
GET /api/decisions/{decisionId} | read | Integration | Request with revisions, decisions, executions |
POST /api/decisions/{decisionId}/revisions | operate | Integration | Propose a new revision |
POST /api/decisions/{decisionId}/decisions | operate | Integration | Approve, reject, edit, request changes or delegate |
GET /api/reviewers | operate | Console | People who could review a task |
GET /api/escalation-policies | administer | Console | Escalation targets |
PUT /api/escalation-policies | administer | Console | Set an escalation target |
GET /api/notifications | read | Integration | The caller's in-app notifications |
POST /api/notifications/read | read | Console | Mark notifications read |
GET /api/notifications/channel | administer | Console | Webhook channel status |
POST /api/notifications/test | administer | Console | Queue a test notification |
GET /api/audit | read | Integration | Audit trail |
GET /api/audit/export | administer | Integration | One CSV page of the audit trail |
GET /api/admin/security | administer | Console | Teams, grants, credential metadata |
POST /api/admin/security | administer | Console | Create teams, grant and revoke access |
GET /api/artifacts/{digest} | read | Integration | Download a stored artifact |
POST /api/webhooks/a2a/{registrationId} | push credential | Agents only | A2A push notification receiver |
Route handlers run on the Node.js runtime. Task and event content shown here is what Agent Taskbay has stored, not a live read from the agent.
Sign-in routes
GET /api/auth/login
Public. In development mode it answers 303 to /tasks. In oidc mode it stores encrypted login state for 10 minutes, sets the __Host-a2a-login
cookie and answers 303 to the provider. Query returnTo is honored only if it is a relative path starting with a single / (otherwise /tasks).
Errors: 429 Too many sign-in requests.; 503 Sign-in service unavailable. (misconfiguration or provider unreachable).
Observed (development mode): 303, location: http://localhost:<port>/tasks.
GET /api/auth/callback
Public. Completes the authorization-code flow with PKCE, requires an enabled membership for the configured organization, rotates any previous session and
answers 303 to the validated returnTo on the configured origin with the session cookie set. Every failure, including an expired or reused attempt,
an unknown person and a provider error, is the same 401 Sign-in failed. Start a new sign-in attempt.; provider responses are never reflected.
Rate limited (429).
POST /api/auth/logout
read. Revokes the session, records session.ended and clears the cookie. Response 200 {"signedOut":true}.
GET /api/auth/session
read. Observed (development mode):
{ "user": { "displayName": "Local operator", "role": "admin" }, "mode": "development" }mode is development or oidc. The launcher polls this route to know the server is up.
Agents
GET /api/agents
read. Fetches every visible agent's Agent Card (this makes outbound requests and records a card snapshot per agent) and returns
{ "agents": [ ... ] }. Each entry has id, cardUrl, source (env or managed) and either name, description, skills, card, trust or, when discovery
failed, error. A failing agent stays in the list. trust is unsigned, verified, untrusted or invalid. Non-administrators receive a reduced card for agents
they only partly hold (see Authentication and roles).
Calling this route is also what stores the card snapshot that extension negotiation reads; see the note there.
POST /api/agents
administer. Body { "cardUrl": "<string>" }. Registers the agent; it does not fetch the card. Response 201 { "agent": { "id", "cardUrl", "source": "managed" } }.
Registering the same URL again returns the same agent (and re-enables it if it was removed). 400 for a missing cardUrl, a URL that is not http(s), or one with credentials, a query string or a fragment. The network policy (origin allowlist, private addresses) is applied when the card is first fetched, not at registration. The audit trail records agent.registered.
Observed: {"agent":{"id":"e6e890ae-...","cardUrl":"http://127.0.0.1:4011/showcase/card.json","source":"managed"}}.
POST /api/agents/preview
administer. Body { "cardUrl": "<string>" }. Fetches and validates a card without credentials and without registering it. Response
{ "card", "resolvedCardUrl", "report" } where report is the compliance report (version 1.0, 0.3 or unknown, a score and a list of issues).
Does not record a snapshot. The card URL may be the card itself (a path ending in .json) or a base URL, in which case /.well-known/agent-card.json is tried.
GET /api/agents/{agentId}
read. { "id", "cardUrl", "source", "card", "trust", "report" }. 404 if unknown or not visible. Failures talking to the agent surface as 502 (or the
status of the underlying error). Records a card snapshot.
DELETE /api/agents/{agentId}
administer. 200 { "removed": true }. 409 with This agent comes from A2A_REGISTERED_AGENTS and can't be removed from the UI. for environment-seeded agents.
Sending messages and commands
All sends are recorded as a durable command first, then dispatched by a worker. The routes below differ in how long they wait.
Command body
POST /api/agents/{agentId}/commands, /messages and /stream accept the same strict object:
| Field | Type | Notes |
|---|---|---|
action | "send" or "cancelTask" | Default send. |
text | string | Plain text. Sent as a text/plain part before any parts. |
parts | array of objects | A2A v1 part objects, for example { "text": "...", "mediaType": "text/plain" } or { "data": { ... }, "mediaType": "application/json" }. A send needs non-empty text or at least one part. |
taskId | string | Remote task ID. Required for cancelTask; on send it continues that task. |
contextId | string | Remote context ID. Must match the task's context when taskId is given (403 Task context mismatch.). |
tenant | string, at most 255 | Default empty. Part of the task's identity. |
messageId | string, 1 to 255 | Becomes the A2A messageId; generated if omitted. |
skillId | string, 1 to 255 | Restrict the send to one skill. Needs a skill-routing agent; see Skill routing. |
config | object | returnImmediately, historyLength, acceptedOutputModes, referenceTaskIds, extensions, metadata, requestMetadata. Strict. Referenced tasks must be known (404 Referenced task not found.). |
Server rules: the console always asks the agent to return immediately and observes progress separately; extensions the agent advertises and Agent Taskbay understands are negotiated automatically; a task's skill and context cannot be changed by a later message; a scoped caller cannot start a send in an existing context.
POST /api/agents/{agentId}/commands
operate. Requires the Idempotency-Key header (400 An idempotency key of 1–255 characters is required. without it). Returns immediately.
curl -X POST http://127.0.0.1:3002/api/agents/AGENT_ID/commands \
-H 'Content-Type: application/json' -H 'Idempotency-Key: demo-1' \
-d '{"text":"hello"}'Observed: 202 Accepted, header Location: /api/commands/<id>, body:
{ "command": { "id": "69c44ee5-700f-47ab-aafd-193b19fc94d2", "agentId": "e6e890ae-...", "tenant": "", "action": "send",
"messageId": "27a02477-...", "status": "pending", "result": null, "error": null,
"createdAt": "2026-10-06T20:04:40.363Z", "updatedAt": "2026-10-06T20:04:40.363Z" } }Repeating the request with the same key and body returns 202 with the same command and its current state. Same key with different content:
409 This idempotency key already belongs to different command content. Unknown agent: 404 Unknown agent. Unknown keys or an empty send: 400 with the validation message.
Command status values: pending, dispatching, succeeded, failed, uncertain. uncertain means the request may have reached the agent and
the result is unknown; Agent Taskbay does not resend automatically. When succeeded, result is { event, localId, taskId, tenant }, where localId
is the task's local ID and event is the agent's reply (task or message).
GET /api/commands/{commandId}
read. { "command": { ... } } as above. 404 for an unknown or invisible command. Poll this after a 202.
POST /api/agents/{agentId}/messages
operate. Compatibility route: submits the command, waits up to 50 seconds, and returns the result. The idempotency key defaults to the messageId when
the body has one (so retries are safe), otherwise to a random value (retries are not safe).
Success 200: { "result": <task or message>, "localId", "commandId", "sessionId", "requestId", "telemetry": [] }.
409 when the command failed or ended uncertain (the message carries the reason). 504 when still pending after 50 seconds:
Command accepted; waiting stopped. Query /api/commands/<id> for its outcome before submitting again. The command continues; do not resubmit.
POST /api/agents/{agentId}/tasks/{taskId}/cancel
operate. {taskId} here is the remote task ID. Optional query ?tenant=. Optional Idempotency-Key (random if absent, so a retry is not de-duplicated).
Waits like /messages and returns the same shape; the result is the agent's updated task.
POST /api/agents/{agentId}/stream
operate. Server-sent events (Content-Type: text/event-stream, Cache-Control: no-store, no-transform). Body is a command body, or
{ "resubscribe": true, "taskId": "<remote id>", "tenant": "" } to follow an existing task. Events come from committed state, not from the agent's own stream, so a
dropped browser connection never loses or repeats work.
| Event | Data |
|---|---|
accepted | { "commandId" } (new sends only) |
persisted | { "localId", "taskId", "tenant" } |
snapshot | The task view, sent on first read and whenever the task version changes |
meta | Protocol metadata for the next a2a event, when recorded |
a2a | One stored protocol event from the agent |
sideband | A sideband event decoded from a negotiated extension |
error | { sessionId, requestId, message } |
end | { sessionId, requestId } |
The stream ends with end when the task is paused (input required, auth required) or finished, after about 50 seconds, or on error. The task keeps running; reconnect
with resubscribe. Pre-stream problems are ordinary HTTP errors (400, 403, 404). Observed event order for a new send: accepted, persisted, snapshot.
Tasks
GET /api/tasks
read. Query:
| Parameter | Default | Values |
|---|---|---|
filter | all | all, active, needs-input, done, mine, overdue, unassigned. active is any non-terminal state; needs-input is input-required or auth-required; done is terminal; the last three use ownership. |
limit | 50 | Integer 1 to 100. |
offset | 0 | Integer 0 or more. |
Response { "tasks": [ ... ] }, newest first. Rows carry localId, taskId, tenant, agentId, agentName, state (TASK_STATE_*), kind,
title, contextId, createdAt, updatedAt, referenceLinks, workflow (null or { assigneeMembershipId, assigneeName, dueAt, escalationLevel }), and empty
messages and artifacts arrays; use the detail route for content. Observed: {"tasks":[{"localId":"7470c984-...","taskId":"form-task-0","state":"TASK_STATE_INPUT_REQUIRED","kind":"task","title":"hello", ...,"workflow":null}]}.
Bad filter: 400 Invalid task filter.
GET /api/tasks/{taskId}
read. {taskId} is the local ID. { "task": <view> } with messages (each with id, role user or agent, normalized parts, timestamp,
fromStatus for the agent's question), artifacts, transitions, version and the fields above. 404 Task not found.
GET /api/task-views
read. Pages durable content by local task ID, for chat and flow views. limit 1 to 100 (default 100) and after (a local UUID) are validated:
400 limit must be 1–100 and after must be a local UUID. Response { "tasks": [ <view> ], "next": "<local id>" | null }.
GET /api/tasks/events
read. A long-lived SSE signal that "something changed for this organization". Frames carry no task data (data: {}); clients re-query. Event names:
ready (first frame), freshness (a change was committed), resync (sent after 15 seconds of silence so clients refresh anyway). Each frame is preceded by
retry: 1000. The server closes the stream after 55 seconds; reconnect.
Task workflow
Ownership is local to Agent Taskbay and independent of the remote task state. All routes need operate on the task's agent and skill, and apply only to A2A tasks
(not direct-message threads: 409 Only A2A tasks can be assigned.). Responses are { "assignment": { assigneeMembershipId, claimedAt, dueAt, escalationLevel, escalatedAt, updatedAt } }
(or null fields). Rules: anyone with access may claim an unowned task; only the owner or an administrator may release, reassign or change the due time of an
owned task (403); a claim on a task owned by someone else is 409 This task is already owned by someone else.; claiming, assigning and setting a due time need an unfinished
task (409 The task is already finished.).
| Route | Body | Notes |
|---|---|---|
GET /api/tasks/{taskId}/workflow (read) | none | { assignment, events, notes, people, viewer }. Events are newest first (at most 50), notes at most 100. Invalid UUID or unknown task: 404. |
POST .../workflow/claim | none | Idempotent for the current owner. |
POST .../workflow/release | none | No-op when unowned. |
POST .../workflow/assign | { "assigneeMembershipId": "<uuid>" } | 422 The assignee cannot operate this agent. when the person lacks an operate grant. Notifies the assignee. |
POST .../workflow/due | { "dueAt": "<ISO datetime>" or null } | Must be in the future and within one year (400). A new due time re-arms escalation. |
POST .../workflow/notes | { "body": "<1-4000 chars>", "noteKey": "<1-255 chars>" } | 201 { "note": { id, authorUserId, body, createdAt } }. Repeating a noteKey with the same body returns the same note; a different body is 409. Notes are internal and never sent to the agent. |
Every change is recorded in the audit trail. Observed: claim returned {"assignment":{"assigneeMembershipId":"89254850-...","claimedAt":"...","dueAt":null,"escalationLevel":0,"escalatedAt":null,"updatedAt":"..."}}.
Escalation policy
GET /api/escalation-policies (administer) returns { "policies": [ { id, agentId, targetMembershipId, enabled, updatedAt } ], "people": { ... } }.
PUT takes { "agentId": "<uuid>" or null, "targetMembershipId": "<uuid>", "enabled": true|false } (agentId: null applies to every agent) and answers
{ "policy": { ... } }. The target must be an active operator or administrator (422). When a task passes its due time and a policy exists, a worker
reassigns it to the target once per due time. With no policy it only shows as overdue.
Inbox
GET /api/inbox
read. One page of tasks and approval requests the caller may read, newest update first.
| Parameter | Default | Values |
|---|---|---|
view | all | all, active, needs-input, assigned, overdue, done |
kind | any | task, approval |
agentId | any | UUID |
skillId | any | At most 255 characters |
risk | any | low, medium, high (approvals) |
status | any | Task state or approval status, at most 64 characters |
updatedAfter | none | ISO timestamp |
cursor | none | The next value of the previous page |
limit | 30 | Integer 1 to 100 |
Response { "items": [ ... ], "next": "<cursor>" | null }. Each item: kind, id (task local ID or request ID), taskId, agentId, agentName, skillId,
status, open, title, risk, assigneeMembershipId, assigneeName, dueAt, expiresAt, escalationLevel, updatedAt. Errors are 400 (Unknown inbox view., Invalid cursor., ...).
No event payloads are read, so the route stays cheap on large histories.
Approvals
An approval request asks a reviewer to approve one exact action on a task. The decision is bound to the revision the reviewer saw. Concepts: Human in the loop.
GET /api/decisions
read. Query status (pending, approved, rejected, changes_requested, expired, superseded; else 400 Unknown status.), taskId, and assigned=me. Returns up to 100 requests,
{ "decisions": [ <request> ] }, each with the agent and task context (agentName, taskTitle, taskState). There is no paging.
A request has id, requesterUserId (null when the agent asked), taskId, agentId, tenant, skillId, kind (send_message or send_data), status, title, summary, risk,
policy ({ allowedOutcomes, separationOfDuties }), assignedMembershipId, currentRevision, expiresAt, createdAt, updatedAt.
POST /api/decisions
operate. Opens a request (a person proposing an action for review).
| Field | Required | Notes |
|---|---|---|
taskId | yes | Local task UUID. |
requestKey | one of this or the Idempotency-Key header | 1 to 255 characters. The body field wins. |
title | yes | 1 to 300 characters. |
summary | no | At most 4000 characters. |
risk | no | low, medium (default), high. |
action | yes | { "kind": "send_message", "text": "<1-20000 chars>", "data": { } } or { "kind": "send_data", "form": { ... }, "values": { ... } }. For send_data the form must be in the structured-form subset (at most 64 KB) and values must validate against it. Message data is at most 64 KB. |
expiresAt | yes | ISO datetime in the future and at most 30 days ahead. |
assignedMembershipId | no | A person who can operate the agent (422 otherwise). |
policy | no | { "allowedOutcomes": [...], "separationOfDuties": true }. Outcomes are approve, reject, edit, request_changes, delegate; at least approve or edit must be allowed. separationOfDuties defaults to true. |
Opening a request marks any older open request on the same task superseded: one approval is live per task. Response 201 { "decision": <request> } with Location: /api/decisions/<id>; 200 when the same key and content already created it. Errors: 404 unknown task, 409 task
finished or not an A2A task or key reused for a different request, 422 assignee.
Observed: a minimal body (taskId, title, expiresAt, action) produced status: "pending", risk: "medium", currentRevision: 1 and the default policy.
GET /api/decisions/{decisionId}
read. { "request", "revisions", "decisions", "executions", "people", "viewer" }. Revisions carry number, action, digest, authorType (agent, user, system).
Decisions carry outcome, rationale, revisionId, revisionDigest, reviewer and delegate IDs. Executions correlate each approval with the command it produced
(status, observedTaskState). Reading refreshes execution status from the task. A non-UUID or invisible ID is 404 Decision request not found.
POST /api/decisions/{decisionId}/revisions
operate. { "action": <action>, "expectedRevision": <int >= 1> }. Adds a revision (same action kind), resets the request to pending and voids earlier reviews. 201 { "decision": <request>, "revision": { id, number, action, digest, authorType, authorUserId, createdAt } }.
409 when closed, expired or expectedRevision is stale; 422 for a different kind.
POST /api/decisions/{decisionId}/decisions
operate. Records one decision. Requires Idempotency-Key. Body:
| Field | Required | Notes |
|---|---|---|
outcome | yes | approve, reject, edit, request_changes, delegate. |
expectedRevision | yes | Must equal the request's current revision (409 A newer revision exists; review it before deciding.). |
rationale | required except for approve | At most 4000 characters. |
edit | with edit only | The edited action. May change values but not the form; must differ from the current proposal (422). |
delegateMembershipId | with delegate only | Not yourself; must be able to operate the agent (422). |
Rules: the request must be pending and unexpired; the policy must allow the outcome (403); with separationOfDuties the requester cannot decide
(403 The requester cannot decide their own request.); an assigned request can be decided only by the assignee or an administrator. approve and edit send the exact
revision to the agent once, as a command with metadata.approval (requestId, decisionId, revision, revisionDigest); reject and request_changes send nothing.
Approving a request whose task has finished, or deciding after expiry, closes it (superseded or expired) and answers 409 This request is <reason> and can no longer authorize an action.
Response 201 { "decision", "request", "execution", "replay": false }; a replay of the same key returns 200 with "replay": true. A key reused with a
different decision is 409. Observed: self-deciding with the default policy returned 403, and a reject without a rationale returned 400 A rationale is required for this decision.
GET /api/reviewers
operate. Query taskId (local UUID). { "reviewers": [ { membershipId, displayName, role, self } ] }: active admins and operators who may operate that task's agent and skill. 404 Task not found.
Notifications
GET /api/notifications
read. The caller's own notifications. Query unread (true/false, default false), limit (1 to 100, default 30), cursor. Response
{ "items": [ { id, kind, title, body, link, taskId, createdAt, readAt } ], "next", "unread" }. Items about tasks the person can no longer read are hidden. Kinds:
approval.requested, approval.revised, approval.assigned, approval.decided, approval.expiring, approval.expired, approval.superseded, task.assigned, task.escalated,
task.needs_input, task.finished, task.failed, test. Unknown query keys are 400.
POST /api/notifications/read
read. Body { "ids": [ "<uuid>", ... ] } (1 to 100) or { "all": true }, not both (400 Provide either ids or all.). Response { "updated": <count> }. Only the caller's own rows are affected.
GET /api/notifications/channel
administer. Webhook status: { configured, enabledForOrganization, host, counts: { pending, processing, delivered, failed }, lastFailure: { at, message } | null }. Observed with no webhook set: configured: false.
POST /api/notifications/test
administer. Queues a test notification through the real pipeline, including the webhook. 202 { "queued": true }.
Audit
GET /api/audit
read. Query taskId (UUID), group (all, approvals, ownership, commands, access), actor (user UUID), since and until (ISO datetimes), cursor, limit (1 to 200, default 50).
Without taskId the caller must be an administrator (403). With taskId any member who can read that task's agent sees that task's trail, without identity and access events (404 if not visible).
Response { entries: [ { key, at, kind, actorUserId, taskId, subjectId, data } ], people, context, next, viewer }. since after until is 400. Unknown query keys are 400.
GET /api/audit/export
administer. Same query; limit defaults to 200. Returns text/csv with columns time, kind, actor, task, subject, detail as an attachment named audit-trail.csv. When more rows exist the header
X-Next-Cursor carries the cursor to pass as cursor. Cells that start with =, +, -, @, tab or carriage return are prefixed with ' so spreadsheets do not execute them.
The audit trail is append-only: database triggers reject updates and deletes.
Access management
GET /api/admin/security
administer. { organizationId, teams, memberships, teamMembers, grants, credentials }. Credential entries show only agentId, kind, enabled and updatedAt; secrets are never returned.
POST /api/admin/security
administer. A strict, discriminated body on action; success is { "updated": true }, a malformed body is 400 Invalid access management request., and other failures are 400 with a message.
action | Fields |
|---|---|
createTeam | name (1 to 200) |
addTeamMember, removeTeamMember | teamId, membershipId |
disableTeam | teamId |
grant | agentId, skillId (string or null for the whole agent), subjectType (organization, membership, team), subjectId, permission (read, operate) |
revokeGrant | grantId |
All changes are audited. Credential values are managed with npm run security:credentials, not through the API.
Artifacts
GET /api/artifacts/{digest}
read. {digest} is a 64-character lowercase hex SHA-256. Returns the stored bytes only if the caller can read a task that references that artifact; otherwise 404 Not found (plain text). The
response is always application/octet-stream with Content-Disposition: attachment; filename="artifact", X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none'; sandbox, so stored
content is never rendered by the browser from this route. Objects are limited to 16 MiB each.
Push webhook (agents only)
POST /api/webhooks/a2a/{registrationId}
Not for people. This is the callback that Agent Taskbay registers with agents that support A2A push notifications when A2A_PUSH_CALLBACK_ORIGIN is set. It does not use a session. The caller must send
Authorization: Bearer <token> where the token is the 43-character value Agent Taskbay derived for that registration. The body must be JSON with content type application/json or application/a2a+json. An
optional X-A2A-Delivery-Id header (printable ASCII, at most 255 characters) de-duplicates repeats.
| Status | Meaning |
|---|---|
204 | Accepted (also returned for late deliveries to a finished task, which change nothing). |
400 | Invalid body or delivery ID. |
401 | Invalid push authentication. Wrong, missing or revoked credential, or a deleted/failed registration. |
415 | Push requires a JSON content type. |
429 | Rate limited (Retry-After: 60). |
503 | Push delivery is not configured. (observed when A2A_PUSH_CALLBACK_ORIGIN is unset) or Push receipt unavailable; retry later. |
Limits
- No versioning, deprecation policy, OpenAPI document or typed client. See #31.
- Session-cookie authentication only; scripts need a browser-issued cookie and a matching
Origin. User-delegated and service-token access: #1, #31. - No search, saved views or bulk actions on lists (#5). Approval lists are capped at 100 and are not paged.
- Task content is what Agent Taskbay has observed. It can lag the agent by the worker's polling interval when streaming and push are unavailable.
- Real-time updates are a "something changed" signal followed by a re-query, not a data stream.
Related
Created and maintained by Shashi Kanth G S at allsrc.dev. Visit shashikanth.me.
CLI reference
For anyone who needs the exact commands, flags and exit behavior: the agent-taskbay launcher, every npm run script, and the operational
Extensions reference
For agent authors and operators: the A2A extensions Agent Taskbay defines or understands, how an agent opts in, and what happens when it does not.