Agenten
Agenten-User ohne Seat anlegen, Leitplanken konfigurieren, eingeschränkte Access-Tokens erzeugen und die organisationsweite Agenten-Übersicht lesen.
Ein Agent ist ein Mitglied deiner Organisation ohne jede Anmeldemöglichkeit. Er authentifiziert sich ausschließlich mit Access-Tokens, kostet keinen Seat und bekommt keine E-Mail. Alles, was einem Menschen zugeschrieben werden kann — ein Ticket, ein Kommentar, ein Zeiteintrag, ein Statuswechsel — kann auch einem Agenten zugeschrieben werden.
Diese Endpunkte sind nicht mit einem Personal Access Token erreichbar. Sie brauchen eine Session (JWT), damit ein Agent sich nie selbst Tokens ausstellen kann. Während einer Impersonation sind sie ebenfalls gesperrt.
Wer sie aufrufen darf
| Voraussetzung | |
|---|---|
| Auflisten, lesen, ändern, löschen, Tokens auflisten/widerrufen | Berechtigung agents:manage oder Eigentümer des Agenten sein |
| Agent anlegen, Token erzeugen | agents:manage und das Plan-Feature AGENT_USERS (ab Pro) |
| Übersicht, Policies ändern | Nur agents:manage — hier stehen organisationsweite Zahlen und Regeln |
Nur Anlegen und Token-Erzeugen hängen am Plan. Lesen, Deaktivieren, Löschen und Widerrufen bleiben auf jedem Plan offen: Eine Organisation, die herunterstuft, muss die Maschinen-User, die sie schon hat, immer sehen und abschalten können.
Agenten auflisten
GET /api/v1/organizations/{orgId}/agentsMit agents:manage alle Agenten der Organisation, sonst nur die eigenen.
Beispiel-Antwort
[
{
"id": "usr_agent123",
"name": "Nightly Refactor",
"description": "Fährt den Housekeeping-Loop auf 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": "Agenten", "color": "#8b8b8b" }],
"createdAt": "2026-08-01T09:00:00Z"
}
]email ist synthetisch und nicht zustellbar — Agenten empfangen keine Mail. lastSeenAt ist die letzte Nutzung irgendeines Tokens dieses Agenten.
Agent anlegen
POST /api/v1/organizations/{orgId}/agentsLegt einen Agenten-User ohne Seat mit der Rolle TEAM_MEMBER an. Antwortet mit 201 und derselben Struktur wie oben.
Request-Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| name | string | Ja | Anzeigename, erscheint überall dort, wo ein Mitglied erscheint (max. 100 Zeichen) |
| description | string | null | Nein | Was der Agent tut / welcher Loop ihn fährt (max. 2.000 Zeichen) |
| ownerId | string | Nein | Menschlicher Eigentümer (Standard: der Aufrufer) |
| permissionGroupId | string | Nein | INTERNAL-Berechtigungsgruppe (Standard: Systemgruppe Agenten) |
Eine Organisation darf höchstens 25 aktive Agenten halten. Darüber hinaus schlägt der Aufruf mit AGENT_LIMIT_REACHED fehl — „ohne Seat" darf nicht „unbegrenzt" heißen.
Fehler
| Status | Code / Grund |
|---|---|
| 403 | agents:manage fehlt, oder der Plan enthält AGENT_USERS nicht |
| 403 | AGENT_LIMIT_REACHED — die Organisation hält bereits 25 Agenten |
Agent lesen
GET /api/v1/organizations/{orgId}/agents/{id}Gibt einen Agenten zurück. 404, wenn die ID kein Agent dieser Organisation ist.
Agent ändern
PATCH /api/v1/organizations/{orgId}/agents/{id}Alle Felder optional; schicke nur, was sich ändert. null löscht ein nullbares Feld.
| Feld | Typ | Beschreibung |
|---|---|---|
| name | string | Anzeigename |
| description | string | null | Freitext |
| ownerId | string | Neuer menschlicher Eigentümer (aktives Mitglied, kein Agent). Der Wechsel braucht agents:manage |
| timeFactor | number | null | MCP-Zeitfaktor für diesen Agenten, 1,0–10,0. null = Organisationsfaktor |
| weeklyHoursCap | number | null | Wochenlimit für per MCP erfasste Stunden (freigegeben + ausstehend), 1–168. null = kein Limit |
| maxConcurrentClaims | number | null | Wie viele Tickets der Agent gleichzeitig claimen darf, 1–50. null = Organisations-Standard (3) |
| gitIdentity | string | null | GitHub-/GitLab-Login oder Commit-E-Mail, an der seine Pull Requests erkannt werden |
| isActive | boolean | false deaktiviert: Die Tokens bleiben, funktionieren aber nicht mehr |
Agent löschen
DELETE /api/v1/organizations/{orgId}/agents/{id}Löscht den Agenten weich und widerruft alle seine Tokens. Antwortet mit 204 No Content.
Agenten-Policies
Organisationsweite Regeln, die für jeden Agenten gelten. Menschen sind davon nicht betroffen.
GET /api/v1/organizations/{orgId}/agents/policies
PATCH /api/v1/organizations/{orgId}/agents/policiesLesen braucht agents:manage oder Eigentümerschaft, Schreiben agents:manage — eine Policy ist die Obergrenze für die Agenten aller, nicht nur für die eigenen.
Body / Antwort
| Feld | Typ | Beschreibung |
|---|---|---|
| statusTransitionsRequireApproval | string[] | Status-Keys, in die ein Agent ein Ticket nicht allein bewegen darf (Standard ["DONE"]). BACKLOG wird nicht akzeptiert |
| prsRequireHumanReview | boolean | Pull Requests, deren Autor zur Git-Identität eines Agenten passt, als „braucht menschliches Review" markieren. Rein informativ — Spedy kann keinen Merge blockieren |
| gateableStatusKeys | string[] | Nur lesend: welche Status-Keys überhaupt gesperrt werden können |
Versucht ein Agent, ein Ticket in einen gesperrten Status zu bewegen, passiert der Wechsel nicht: Spedy legt stattdessen einen Vorschlag für einen Menschen an und antwortet mit 403 und dem Code AGENT_APPROVAL_REQUIRED. Das gilt identisch für PATCH /boards/{boardId}/tickets/{ticketId}/status und für die MCP-Tools — es gibt keine zweite Tür. Ist Explainable Status auf dem Board aus, kann kein Vorschlag entstehen; dann wird der Wechsel offen abgelehnt statt stillschweigend verworfen.
Zeitfaktor pro Agent, Wochenlimit und der PENDING-Freigabestatus von Agenten-Zeiten gelten für Agenten-User auch über REST, nicht nur über MCP.
Access-Tokens auflisten
GET /api/v1/organizations/{orgId}/agents/{id}/tokensGibt die Tokens des Agenten ohne Secrets zurück.
{
"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 auf ci-1"
}
]
}Access-Token erzeugen
POST /api/v1/organizations/{orgId}/agents/{id}/tokensAntwortet mit 201 und dem Klartext-Token genau einmal — später ist es nicht mehr abrufbar.
Request-Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
| name | string | Ja | Lesbare Bezeichnung (max. 200 Zeichen) |
| expiresInDays | number | null | Nein | Laufzeit in Tagen (1–3650). Weglassen oder null = läuft nie ab |
| allowedBoardIds | string[] | Nein | Auf diese Projekte begrenzen, unabhängig von den Mitgliedschaften des Agenten (max. 100). Leer = alle Projekte, in denen der Agent Mitglied ist |
| readOnly | boolean | Nein | Nur Lese-Tools und GET-Requests (Standard false) |
| denyDelete | boolean | Nein | Sperrt jede Lösch-Fähigkeit und jeden DELETE-Request. Standard true für Agenten-Tokens |
| clientLabel | string | null | Nein | Freitext-Label des Clients, der das Token nutzt (max. 200 Zeichen) |
Das Erzeugen ist auf 20 Aufrufe pro Stunde begrenzt. Der Aufrufer muss jede Berechtigung besitzen, die die Gruppen des Agenten gewähren — man kann kein mächtigeres Credential ausstellen, als man selbst hat.
Fehler
| Status | Grund |
|---|---|
| 403 | Plan enthält AGENT_USERS nicht, oder der Agent hat Berechtigungen, die der Aufrufer nicht hat |
Wie sich die Einschränkungen zur Laufzeit verhalten, steht unter Authentifizierung.
Access-Token widerrufen
DELETE /api/v1/organizations/{orgId}/agents/{id}/tokens/{tokenId}Schneidet den Loop sofort ab. Antwortet mit 204 No Content. Nie plan-abhängig — eine heruntergestufte Organisation muss einen laufenden Loop immer stoppen können.
Agenten-Übersicht
GET /api/v1/organizations/{orgId}/agents/overviewEine rein lesende Aggregation über Daten, die andere Module ohnehin führen: Laufen die Loops, und ist noch ein Mensch dabei? Erfordert agents:manage, weil die Antwort organisationsweite Zahlen enthält.
Beispiel-Antwort
{
"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": "Login-Seite: Styling reparieren",
"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 und agentMinutesShare sind null, wenn es nichts zu teilen gibt — ein Anteil ohne Nenner wird nie als 0 gemeldet.