# Actions und Formulare

Links, Webhooks, Eingaben und Ergebnisprüfung konfigurieren.

Actions werden im `actions`-Array eines Events definiert. Erstellung und Updates
verwenden dasselbe Schema. Bestehende Buttons mit Webhook-Schritten bleiben gültig.

## Formular mit Submit

```json
{
  "type": "form",
  "maxRuns": 2,
  "allowRetryOnFailure": true,
  "stopOnFailure": true,
  "confirmation": "Release mit diesen Eingaben starten?",
  "expiresAt": "2027-01-01T00:00:00Z",
  "properties": {
    "buttonText": "Release starten",
    "fields": [
      {
        "name": "comment",
        "type": "text",
        "label": "Kommentar",
        "required": true
      },
      {
        "name": "owner",
        "type": "input",
        "label": "Verantwortlich"
      },
      {
        "name": "date",
        "type": "date",
        "label": "Datum",
        "format": "DD.MM.YYYY"
      },
      {
        "name": "time",
        "type": "time",
        "label": "Uhrzeit"
      },
      {
        "name": "hour",
        "type": "hour",
        "label": "Stunde",
        "defaultValue": 9
      },
      {
        "name": "count",
        "type": "number",
        "label": "Anzahl",
        "numberType": "int",
        "min": 1
      },
      {
        "name": "amount",
        "type": "number",
        "label": "Betrag",
        "numberType": "float"
      },
      {
        "name": "environment",
        "type": "select",
        "label": "Umgebung",
        "required": true,
        "options": [
          {
            "label": "Staging",
            "value": "staging"
          },
          {
            "label": "Produktion",
            "value": "production"
          }
        ]
      },
      {
        "name": "services",
        "type": "select",
        "label": "Dienste",
        "multiple": true,
        "options": [
          {
            "label": "API",
            "value": "api"
          },
          {
            "label": "Frontend",
            "value": "frontend"
          }
        ]
      }
    ]
  },
  "action": [
    {
      "type": "webhook",
      "properties": {
        "url": "https://your-service.example/releases",
        "method": "POST",
        "bodyType": "json",
        "headers": {
          "Authorization": "Bearer <target-service-token>"
        },
        "timeout": 10000,
        "expects": {
          "code": [
            200,
            201,
            202
          ],
          "json": [
            {
              "path": "success",
              "operator": "equals",
              "value": true
            },
            {
              "path": "release.id",
              "operator": "exists"
            },
            {
              "path": "release.id",
              "operator": "type",
              "value": "string"
            }
          ]
        }
      }
    }
  ]
}
```

`text` und `input` sind Texteingaben. `hour` ist eine ganze Stunde von 0 bis 23,
`time` eine Uhrzeit in `HH:mm`. Zahlen werden als JSON-Zahlen und Mehrfachauswahlen
als Arrays übermittelt. `required`, `placeholder`, `defaultValue` sowie bei Zahlen
`min` und `max` sind optional. Das Datum wird mit einem Datumseingabefeld ausgewählt;
die API formatiert es vor der Übermittlung als `YYYY-MM-DD` (Standard), `DD.MM.YYYY`
oder `MM/DD/YYYY`. Die API akzeptiert ISO-Daten oder das konfigurierte Datumsformat.
Unbekannte Eingaben und ungültige Werte werden vor der Ausführung abgelehnt.

## Ausführung und Limits

```http
POST /v1/event/<event-id>/actions/0/execute
Authorization: Bearer <user-access-token>
Content-Type: application/json

{
  "confirmed": true,
  "inputs": {
    "comment": "Freigegeben",
    "count": 2,
    "environment": "staging"
  }
}
```

- `maxRuns`: optionale positive Obergrenze je Action-Gruppe und Event, gemeinsam
  für alle Benutzer. Ohne Angabe unbegrenzt. Parallel eintreffende Requests werden
  atomar begrenzt. Bei ausgeschöpftem Limit oder Ablauf antwortet die API mit 409.
- `allowRetryOnFailure`: standardmäßig `false`. Bei `true` gibt eine nicht vollständig
  erfolgreiche Ausführung ihren Platz im Limit wieder frei. Dies gilt auch für
  teilweise erfolgreiche Gruppen: ein erneuter Submit wiederholt die ganze Gruppe.
  Es gibt keine automatische Wiederholung.
- `runs`: vom Server verwaltete Anzahl verbrauchter bzw. laufender Plätze;
  `attempts`: Anzahl aller tatsächlich gestarteten Versuche. Beide sind nur lesbar.
  Abgewiesene Eingaben oder fehlende Bestätigung verbrauchen keinen Lauf.
- `confirmation`: optionaler Bestätigungstext. Das Frontend zeigt vor dem Versand
  einen Dialog; die API verlangt dann `confirmed: true`.
- `expiresAt`: optionaler ISO-Zeitpunkt. Das Frontend deaktiviert die Action nach
  Ablauf; die API prüft den Ablauf auch bei direkten Aufrufen.
- `stopOnFailure`: standardmäßig `false`. Bei `true` werden nach dem ersten Fehler
  alle folgenden Schritte als `skipped` protokolliert und nicht ausgeführt.

Eine Gruppe gilt nur dann als erfolgreich (`success: true`), wenn alle Schritte
erfolgreich sind. Request, Response, Laufzeit, einzelne Erwartungen und übersprungene
Schritte werden protokolliert und im Frontend angezeigt. Authentifizierungswerte
in üblichen Auth-/Cookie-/API-Key-/Token-Headern werden in den Request-Logs maskiert.

## Request-Bodies und Ergebnisprüfung

POST, PATCH, PUT und DELETE unterstützen `bodyType`:

| Wert | Übermittlung |
| --- | --- |
| `json` (Standard) | JSON-Body, inklusive Zahlen, Arrays und boolescher Werte |
| `urlencoded` | `application/x-www-form-urlencoded`; Arrays als wiederholte Schlüssel |
| `multipart` | Multipart-Formularfelder mit automatisch erzeugter Boundary; Arrays als wiederholte Schlüssel |
| `text` | Klartext mit `text/plain` |

Bei Formular-Actions ohne expliziten `body` werden die validierten Eingaben zum Body.
GET-Formulare übermitteln die Werte als Query-Parameter. Ein expliziter `body` ersetzt
dieses Standardverhalten; Werte lassen sich mit `{{inputs.count}}` einfügen:

```json
{
  "body": {
    "quantity": "{{inputs.count}}",
    "note": "Kommentar: {{inputs.comment}}"
  }
}
```

Ein Platzhalter als ganzer Wert behält den JSON-Typ, eingebettete Platzhalter werden
zu Text. URL-Platzhalter werden URL-kodiert. Fehlende referenzierte Eingaben führen
vor der Ausführung zu 400. Header werden serverseitig an den Zieldienst geschickt.

Ohne `expects` gelten HTTP 200–399 als Erfolg. Mit `expects.code` kann ein einzelner
Status oder eine Liste erlaubter Statuscodes festgelegt werden. `expects.body`
vergleicht den gesamten Antwortinhalt exakt. `expects.json` prüft einzelne Pfade
mit Punktnotation, etwa `release.id` oder `items.0.id`: `equals` (Standard), `exists`
oder `type` (`string`, `number`, `boolean`, `object`, `array`, `null`). Alle
konfigurierten Erwartungen müssen erfüllt sein; Abweichungen ergeben
`errorType: "expectation_failed"` und bleiben im Verlauf einzeln nachvollziehbar.

## Externer Link

```json
{
  "type": "link",
  "properties": {
    "buttonText": "Bestellung öffnen",
    "url": "https://your-service.example/orders/123"
  }
}
```

Links enthalten keine Ausführungsschritte. Sie öffnen als normale externe Links
in einem neuen Tab, ohne API-Ausführung oder Laufzähler. Erlaubt sind HTTP und HTTPS.
Ein Event ausschließlich mit Links wird als abgeschlossen angelegt.


## Actions in Pipelines

Bei Pipeline-Events gehören Actions an den jeweiligen Step unter `steps[].actions`. Die Ausführung benötigt ein Benutzer-Access-JWT und erfüllte Vorgänger. Ein verfügbarer Step führt einen Webhook nicht automatisch aus. Siehe [Step-Action ausführen](/api/execute-step-action).