SpedySpedy Docs

Agents

Create seat-free agent users, configure their guardrails, mint scoped access tokens, and read the organization-wide agent overview.

An agent is a member of your organization with no login of any kind. It authenticates exclusively with access tokens, costs no seat, and receives no e-mail. Everything a person can be attributed with — a ticket, a comment, a time entry, a status change — an agent can be attributed with too.

These endpoints are not reachable with a personal access token. They require a session (JWT), so an agent can never mint tokens for itself. They are also blocked while impersonating another user.

Who may call them

Requirement
List, get, update, delete, list/revoke tokensThe agents:manage permission, or being the agent's owner
Create an agent, mint a tokenagents:manage and the AGENT_USERS plan feature (Pro and up)
Overview, update policiesagents:manage only — these carry organization-wide numbers and org-wide rules

Creating and minting are the only plan-gated operations. Reading, deactivating, deleting and revoking stay open on every plan, so an organization that moves down can always see and cut off the machine users it already owns.

List Agents

GET /api/v1/organizations/{orgId}/agents

With agents:manage, every agent in the organization. Without it, only the agents the caller owns.

Example Response

[
  {
    "id": "usr_agent123",
    "name": "Nightly Refactor",
    "description": "Runs the housekeeping loop on ci-1",
    "email": "[email protected]",
    "isActive": true,
    "owner": { "id": "usr_abc123", "name": "Alex Smith", "email": "[email protected]" },
    "timeFactor": null,
    "weeklyHoursCap": 20,
    "maxConcurrentClaims": 3,
    "gitIdentity": "nightly-refactor[bot]",
    "tokenCount": 1,
    "lastSeenAt": "2026-09-04T22:11:00Z",
    "permissionGroups": [{ "id": "grp_agents", "name": "Agents", "color": "#8b8b8b" }],
    "createdAt": "2026-08-01T09:00:00Z"
  }
]

email is synthetic and undeliverable — agents never receive mail. lastSeenAt is the most recent use of any of the agent's tokens.

Create Agent

POST /api/v1/organizations/{orgId}/agents

Creates a seat-free agent user with the TEAM_MEMBER role. Returns 201 with the same shape as above.

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name, shown wherever a member appears (max 100 characters)
descriptionstring | nullNoWhat this agent does / which loop drives it (max 2,000 characters)
ownerIdstringNoHuman member owning the agent (defaults to the caller)
permissionGroupIdstringNoINTERNAL permission group to place the agent in (defaults to the system Agents group)

An organization may hold at most 25 live agents. Beyond that the call fails with AGENT_LIMIT_REACHED — "seat-free" must not mean "unbounded".

Errors

StatusCode / reason
403Missing agents:manage, or the plan does not include AGENT_USERS
403AGENT_LIMIT_REACHED — the organization already holds 25 agents

Get Agent

GET /api/v1/organizations/{orgId}/agents/{id}

Returns one agent. 404 when the id is not an agent of this organization.

Update Agent

PATCH /api/v1/organizations/{orgId}/agents/{id}

All fields optional; send only what changes. Send null to clear a nullable field.

FieldTypeDescription
namestringDisplay name
descriptionstring | nullFree text
ownerIdstringNew human owner (must be an active, non-agent member). Changing the owner needs agents:manage
timeFactornumber | nullPer-agent MCP time factor, 1.0–10.0. null falls back to the organization factor
weeklyHoursCapnumber | nullWeekly cap on MCP-tracked hours (approved + pending), 1–168. null = no cap
maxConcurrentClaimsnumber | nullHow many tickets this agent may hold a claim on at once, 1–50. null = organization default (3)
gitIdentitystring | nullGitHub/GitLab login or commit e-mail its pull requests are recognised by
isActivebooleanfalse deactivates: the tokens stay, but stop working

Delete Agent

DELETE /api/v1/organizations/{orgId}/agents/{id}

Soft-deletes the agent and revokes all of its tokens. Returns 204 No Content.

Agent Policies

Organization-wide rules that apply to every agent. Humans are unaffected by them.

GET   /api/v1/organizations/{orgId}/agents/policies
PATCH /api/v1/organizations/{orgId}/agents/policies

Reading needs agents:manage or agent ownership; writing needs agents:manage — a policy is the ceiling for everyone's agents, not for your own.

Body / Response

FieldTypeDescription
statusTransitionsRequireApprovalstring[]Status keys an agent may not move a ticket into on its own (default ["DONE"]). BACKLOG is not accepted
prsRequireHumanReviewbooleanFlag pull requests authored by an agent's git identity as needing a human review. Informational — Spedy cannot block a merge
gateableStatusKeysstring[]Read-only: which status keys can be gated at all

When an agent tries to move a ticket into a gated status, the move does not happen: Spedy records a pending suggestion for a human instead and answers 403 with AGENT_APPROVAL_REQUIRED. This is enforced identically on PATCH /boards/{boardId}/tickets/{ticketId}/status and on the MCP tools — there is no second door. If the board has Explainable Status turned off no suggestion can be recorded, and the move is refused outright rather than silently dropped.

The per-agent time factor, the weekly hours cap and the PENDING approval state of agent-tracked time apply to agent users over REST as well, not only over MCP.

List Access Tokens

GET /api/v1/organizations/{orgId}/agents/{id}/tokens

Returns the agent's tokens without their secrets.

{
  "tokens": [
    {
      "id": "tok_abc123",
      "name": "ci-1 loop",
      "tokenPrefix": "pat_1a2b3c4d",
      "expiresAt": "2027-01-01T00:00:00Z",
      "lastUsedAt": "2026-09-04T22:11:00Z",
      "createdAt": "2026-08-01T09:05:00Z",
      "allowedBoardIds": ["brd_web"],
      "readOnly": false,
      "denyDelete": true,
      "clientLabel": "nightly-refactor on ci-1"
    }
  ]
}

Mint an Access Token

POST /api/v1/organizations/{orgId}/agents/{id}/tokens

Returns 201 and the plaintext token exactly once — it cannot be retrieved later.

Request Body

FieldTypeRequiredDescription
namestringYesHuman-readable label (max 200 characters)
expiresInDaysnumber | nullNoLifetime in days (1–3650). Omit or null for a token that never expires
allowedBoardIdsstring[]NoRestrict to these projects regardless of the agent's memberships (max 100). Empty = every project the agent is a member of
readOnlybooleanNoRead tools and GET requests only (default false)
denyDeletebooleanNoBlock every delete capability and DELETE request. Defaults to true for agent tokens
clientLabelstring | nullNoFree-text label of the client that will use the token (max 200 characters)

Minting is rate-limited to 20 calls per hour. The caller must hold every permission the agent's groups grant — you cannot mint a credential more capable than yourself.

Errors

StatusReason
403Plan does not include AGENT_USERS, or the agent holds permissions the caller does not

See Authentication for how the scopes behave at request time.

Revoke an Access Token

DELETE /api/v1/organizations/{orgId}/agents/{id}/tokens/{tokenId}

Cuts the loop off immediately. Returns 204 No Content. Never plan-gated — a downgraded organization must always be able to stop a running loop.

Agents Overview

GET /api/v1/organizations/{orgId}/agents/overview

A read-only aggregate over data other modules already own: are the loops working, and is a human still in the loop. Requires agents:manage, because the response carries organization-wide figures.

Example Response

{
  "generatedAt": "2026-09-05T08:00:00Z",
  "monthStart": "2026-09-01",
  "totals": {
    "agentCount": 3,
    "activeAgentCount": 2,
    "ticketsClosedThisMonth": 48,
    "ticketsClosedByAgentsThisMonth": 11,
    "agentClosedShare": 0.229,
    "minutesBookedThisMonth": 12600,
    "agentMinutesThisMonth": 2100,
    "humanMinutesThisMonth": 10500,
    "agentMinutesShare": 0.167,
    "pendingAgentMinutes": 480
  },
  "agents": [
    {
      "id": "usr_agent123",
      "name": "Nightly Refactor",
      "isActive": true,
      "ownerName": "Alex Smith",
      "lastSeenAt": "2026-09-04T22:11:00Z",
      "gitIdentity": "nightly-refactor[bot]",
      "openClaims": [
        {
          "ticketId": "tkt_abc123",
          "displayId": "WEB-42",
          "title": "Fix login page styling",
          "boardId": "brd_web",
          "claimExpiresAt": "2026-09-05T08:25:00Z"
        }
      ],
      "claimsExpiredLast7Days": 1,
      "reportedLast7Days": { "total": 9, "done": 5, "blocked": 1, "needsReview": 3, "partial": 0, "unknown": 0 },
      "reportedLast30Days": { "total": 31, "done": 19, "blocked": 4, "needsReview": 7, "partial": 1, "unknown": 0 },
      "pendingTime": { "count": 4, "minutes": 480 },
      "approvedMinutesThisMonth": 1620,
      "pullRequests": { "total": 12, "requiresHumanReview": 3 }
    }
  ]
}

agentClosedShare and agentMinutesShare are null when there is nothing to divide — a share with no denominator is never reported as 0.