SpedySpedy Docs

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/widerrufenBerechtigung agents:manage oder Eigentümer des Agenten sein
Agent anlegen, Token erzeugenagents:manage und das Plan-Feature AGENT_USERS (ab Pro)
Übersicht, Policies ändernNur 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}/agents

Mit 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}/agents

Legt einen Agenten-User ohne Seat mit der Rolle TEAM_MEMBER an. Antwortet mit 201 und derselben Struktur wie oben.

Request-Body

FeldTypPflichtBeschreibung
namestringJaAnzeigename, erscheint überall dort, wo ein Mitglied erscheint (max. 100 Zeichen)
descriptionstring | nullNeinWas der Agent tut / welcher Loop ihn fährt (max. 2.000 Zeichen)
ownerIdstringNeinMenschlicher Eigentümer (Standard: der Aufrufer)
permissionGroupIdstringNeinINTERNAL-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

StatusCode / Grund
403agents:manage fehlt, oder der Plan enthält AGENT_USERS nicht
403AGENT_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.

FeldTypBeschreibung
namestringAnzeigename
descriptionstring | nullFreitext
ownerIdstringNeuer menschlicher Eigentümer (aktives Mitglied, kein Agent). Der Wechsel braucht agents:manage
timeFactornumber | nullMCP-Zeitfaktor für diesen Agenten, 1,0–10,0. null = Organisationsfaktor
weeklyHoursCapnumber | nullWochenlimit für per MCP erfasste Stunden (freigegeben + ausstehend), 1–168. null = kein Limit
maxConcurrentClaimsnumber | nullWie viele Tickets der Agent gleichzeitig claimen darf, 1–50. null = Organisations-Standard (3)
gitIdentitystring | nullGitHub-/GitLab-Login oder Commit-E-Mail, an der seine Pull Requests erkannt werden
isActivebooleanfalse 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/policies

Lesen 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

FeldTypBeschreibung
statusTransitionsRequireApprovalstring[]Status-Keys, in die ein Agent ein Ticket nicht allein bewegen darf (Standard ["DONE"]). BACKLOG wird nicht akzeptiert
prsRequireHumanReviewbooleanPull Requests, deren Autor zur Git-Identität eines Agenten passt, als „braucht menschliches Review" markieren. Rein informativ — Spedy kann keinen Merge blockieren
gateableStatusKeysstring[]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}/tokens

Gibt 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}/tokens

Antwortet mit 201 und dem Klartext-Token genau einmal — später ist es nicht mehr abrufbar.

Request-Body

FeldTypPflichtBeschreibung
namestringJaLesbare Bezeichnung (max. 200 Zeichen)
expiresInDaysnumber | nullNeinLaufzeit in Tagen (1–3650). Weglassen oder null = läuft nie ab
allowedBoardIdsstring[]NeinAuf diese Projekte begrenzen, unabhängig von den Mitgliedschaften des Agenten (max. 100). Leer = alle Projekte, in denen der Agent Mitglied ist
readOnlybooleanNeinNur Lese-Tools und GET-Requests (Standard false)
denyDeletebooleanNeinSperrt jede Lösch-Fähigkeit und jeden DELETE-Request. Standard true für Agenten-Tokens
clientLabelstring | nullNeinFreitext-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

StatusGrund
403Plan 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/overview

Eine 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.