# API-Methoden

Vollständige Referenz für alle REST-API-Methoden mit Parametern, Beispielen und Antworten.

## API-Methoden

Vollständige Referenz für alle Methoden der Formstep REST API. Jede Methode zeigt ihre Parameter, Beispielanfragen und Antwortstrukturen.

> ℹ️ **Ein Endpunkt, viele Methoden**
> <p>
>     Jede Methode ist <code>POST https://api.formstep.io/api/v1</code> mit einem JSON-Body{' '}
>     <code>{`{"method": "...", "params": {...}}`}</code> und einem <code>Authorization: Bearer fb_...</code>-Header. Siehe{' '}
>     <a href="/de/developers/overview">API-Übersicht</a> für Authentifizierung und Fehlerbehandlung, und{' '}
>     <a href="/de/developers/api-tokens">API-Tokens</a> für das Token selbst.
>   </p>

<h2 id="conventions">Konventionen</h2>

<ul>
  <li>
    <code>params</code> kann weggelassen werden; es ist standardmäßig <code>{`{}`}</code>. Eine unbekannte Methode ergibt{' '}
    <code>404 METHOD_NOT_FOUND</code>.
  </li>
  <li>
    Ein Token ist an <strong>einen Workspace</strong> gebunden. Einen anderen Workspace zu nennen, oder ein Formular darin, ergibt{' '}
    <code>403 FORBIDDEN</code> — selbst wenn du zu beiden gehörst.
  </li>
  <li>
    <strong>Paginierung.</strong> List-Methoden liefern <code>{`{ items, nextCursor, hasMore }`}</code>; die meisten liefern außerdem{' '}
    <code>canPaginate</code>, das <code>false</code> ist, wenn <code>hasMore</code> zwar <code>true</code> ist, sich aber mit keinem Cursor
    fortsetzen lässt (unscharfe Suche). Gib <code>nextCursor</code> als <code>cursor</code> zurück. <code>limit</code> liegt bei 1–100,
    Standard 20 — außer bei <code>requests.list</code>, dessen Standard 25 ist.
  </li>
  <li>
    <strong>Rate-Limits.</strong> 120 Aufrufe pro Minute pro Token, gemeinsam genutzt mit dem{' '}
    <a href="/de/developers/mcp-server">MCP-Server</a>; <code>requests.create</code> hat ein eigenes Limit von 60 pro Minute.
    Fehlgeschlagene Authentifizierung wird separat begrenzt, 30 pro 15 Minuten pro IP, danach sehen ungültige Token{' '}
    <code>RATE_LIMITED</code> statt <code>UNAUTHORIZED</code>.
  </li>
  <li>
    <strong>Body-Größe.</strong> 1 MiB. Größere Bodys werden mit <code>VALIDATION_ERROR</code> abgelehnt.
  </li>
  <li>
    <strong>Versionierung.</strong> Der Pfad trägt die Version. Breaking Changes erscheinen als <code>/api/v2</code>; neue Methoden und neue
    Antwortfelder nicht.
  </li>
</ul>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="forms">Formulare</h2>

{/* ── forms.list ──────────────────────────────────────────────────────────── */}

Formulare in einem Workspace auflisten. Unterstützt Cursor-Paginierung und optionale unscharfe Namenssuche.

  
    Workspace-ID.
  
  
    Nach Ordner filtern. <code>null</code> übergeben für nur Formulare auf Root-Ebene. Weglassen, um alle aufzulisten.
  
  
    Unscharfe Namenssuche. Ergebnisse auf <code>limit</code> begrenzt; keine Cursor-Paginierung.
  
  
    Seitengröße (1–100).
  
  
    Paginierungs-Cursor aus einer vorherigen Antwort.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.list",
    "params": { "workspaceId": "ws_abc123" }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "frm_abc123",
        "name": "Contact Form",
        "folderId": null,
        "workspaceId": "ws_abc123",
        "isPublished": true,
        "publishedAt": 1714041851000,
        "unpublishedAt": null,
        "createdAt": 1714041800000,
        "lastEditedAt": 1714042000000,
        "emoji": null
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── forms.get ───────────────────────────────────────────────────────────── */}

Vollständige Details für ein einzelnes Formular abrufen, einschließlich Fragen, Cover, Logo und einer Vorschau-URL.

  
    Formular-ID.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.get",
    "params": { "formId": "frm_abc123" }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Event Feedback",
    "folderId": null,
    "workspaceId": "ws_abc123",
    "isPublished": true,
    "publishedAt": 1714041851000,
    "unpublishedAt": null,
    "createdAt": 1714041800000,
    "lastEditedAt": 1714042000000,
    "emoji": null,
    "questions": [
      {
        "id": "q_1",
        "type": "email-input",
        "inputType": "email",
        "title": "Your email",
        "metadata": null
      },
      {
        "id": "q_2",
        "type": "rating-input",
        "inputType": "rating",
        "title": "Overall experience",
        "metadata": { "kind": "rating", "maxStars": 5 }
      }
    ],
    "hasContent": true,
    "cover": null,
    "logo": null,
    "isDeleted": false,
    "trashedAt": null,
    "previewUrl": "https://formstep.io/preview/abc..."
  }
}
```

  

{/* ── forms.create ────────────────────────────────────────────────────────── */}

Ein neues leeres Formular erstellen. Gibt das Formular und eine Vorschau-URL zurück.

  
    Formularname (1–255 Zeichen).
  
  
    Workspace-ID.
  
  
    Das Formular in einem Ordner ablegen. Weglassen, um es im Workspace-Root zu erstellen.
  

  
    
      
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.create",
    "params": {
      "name": "Contact",
      "workspaceId": "ws_abc123"
    }
  }'
```

    
    
      
```
const res = await fetch('https://api.formstep.io/api/v1', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FORMSTEP_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    method: 'forms.create',
    params: { name: 'Contact', workspaceId: 'ws_abc123' },
  }),
})
const { ok, data } = await res.json()
```

    
    
      
```
import os, requests
res = requests.post(
  "https://api.formstep.io/api/v1",
  headers={"Authorization": f"Bearer {os.environ['FORMSTEP_TOKEN']}"},
  json={
    "method": "forms.create",
    "params": {
      "name": "Contact",
      "workspaceId": "ws_abc123",
    },
  },
)
data = res.json()
```

  

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_new123",
    "name": "Contact",
    "workspaceId": "ws_abc123",
    "folderId": null,
    "isPublished": false,
    "createdAt": 1714041851000,
    "previewUrl": "https://formstep.io/preview/abc..."
  }
}
```

  

{/* ── forms.update ────────────────────────────────────────────────────────── */}

Formular-Metadaten aktualisieren: Name, Ordner, Emoji, Cover oder Logo. Aktualisiert nicht den Formularinhalt (dafür die Editor-Tools verwenden).

  
    Formular-ID.
  
  
    Neuer Formularname (1–255 Zeichen).
  
  
    Formular in einen Ordner verschieben. <code>null</code> übergeben, um es in den Workspace-Root zu verschieben.
  
  
    Formular-Emoji (max. 10 Zeichen). <code>null</code> übergeben, um es zu entfernen.
  
  
    Cover. <code>{`{"type": "color", "color": "#ffffff"}`}</code>, <code>{`{"type": "image", "url": "https://...", "offsetY": 50}`}</code> (
    <code>offsetY</code> 0–100, Standard 50), oder <code>{`{"type": "none"}`}</code> zum Entfernen. Bild-URLs müssen <code>http(s)</code>{' '}
    oder eine <code>data:image</code>-URI sein.
  
  
    Logo. <code>{`{"type": "icon", "name": "HeartIcon"}`}</code>, <code>{`{"type": "image", "url": "https://..."}`}</code> oder{' '}
    <code>{`{"type": "none"}`}</code> zum Entfernen. Icon-Namen sind fest: <code>QuestionMarkIcon</code>, <code>ListBulletsIcon</code>,{' '}
    <code>ChartBarIcon</code>, <code>ClockCountdownIcon</code>, <code>HeartIcon</code>, <code>LightbulbIcon</code>,{' '}
    <code>CheckCircleIcon</code>, <code>MagnifyingGlassIcon</code>, <code>TrendUpIcon</code>, <code>EnvelopeIcon</code>,{' '}
    <code>PhoneIcon</code>, <code>CalendarIcon</code>, <code>LinkIcon</code>, <code>UsersIcon</code>.
  

<p>Übergib mindestens eines der fünf aktualisierbaren Felder. Das ändert nicht den Formularinhalt — nutze dafür die MCP-Editor-Tools.</p>

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.update",
    "params": {
      "formId": "frm_abc123",
      "name": "Updated Name",
      "emoji": "📋"
    }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Updated Name",
    "folderId": null,
    "emoji": "📋"
  }
}
```

  

<p>
  <code>cover</code> und <code>logo</code> kommen nur zurück, wenn du sie mitgeschickt hast. Ein Payload, der bei jedem skalaren Feld dem
  aktuellen Zustand entsprach, ergänzt <code>noChange: true</code>.
</p>

{/* ── forms.publish ───────────────────────────────────────────────────────── */}

Ein Formular veröffentlichen, damit es Antworten entgegennehmen kann, und seine [Feldschlüssel](/de/requests/field-keys) in einem neuen
Snapshot einfrieren. Idempotent: ein bereits veröffentlichtes Formular liefert Erfolg mit `alreadyPublished: true`, und ein
depubliziertes Formular wird aus seinem letzten Snapshot erneut veröffentlicht.

  
    Formular-ID.
  

<p>
  Ein Formular mit Inhaltsblöcken, aber ohne Fragen, wird mit einer Warnung veröffentlicht. Ein Formular ganz ohne Inhalt lässt sich nicht
  veröffentlichen. Veröffentlichen erzeugt keine öffentliche URL — rufe dafür <a href="#share-links-create">shareLinks.create</a> auf.
</p>

{/* ── forms.unpublish ─────────────────────────────────────────────────────── */}

Ein Formular offline nehmen. Ausfüllende können es nicht mehr öffnen. Idempotent — ein Formular, das nicht veröffentlicht ist, liefert
`alreadyUnpublished: true`. Umkehrbar mit `forms.publish`.

  
    Formular-ID.
  

{/* ── forms.delete ────────────────────────────────────────────────────────── */}

Ein Formular in den Papierkorb verschieben. Seine aktiven Freigabelinks werden widerrufen, ihre öffentlichen URLs liefern also nichts
mehr aus.

  
    Formular-ID.
  

> ⚠️ **Wiederherstellen bringt die Links nicht zurück**
> <p>
>     <code>forms.restore</code> liefert das Formular zurück, aber die Freigabelinks, die es widerrufen hat, bleiben widerrufen. Erstelle neue
>     mit <code>shareLinks.create</code>. Ein Formular, das schon im Papierkorb liegt, liefert <code>alreadyTrashed: true</code> und behält
>     sein ursprüngliches Papierkorb-Datum.
>   </p>

{/* ── forms.restore ───────────────────────────────────────────────────────── */}

Ein Formular aus dem Papierkorb wiederherstellen.

  
    Formular-ID.
  
  
    Wohin es wiederhergestellt wird. Weglassen für den ursprünglichen Ordner, <code>null</code> für den Workspace-Root, oder eine Ordner-ID.
  

<p>
  Ein Formular, das nicht im Papierkorb liegt, liefert <code>alreadyRestored: true</code>.
</p>

{/* ── formSettings.get ────────────────────────────────────────────────────── */}

Die Verhaltenseinstellungen eines Formulars lesen.

  
    Formular-ID.
  

<p>
  Liefert <code>{`{ settings, isDefault, availableEmailDomains, defaultFromAddress, payment }`}</code>. <code>isDefault</code> ist wahr,
  wenn das Formular noch keine gespeicherte Einstellungszeile hat und du die Standardwerte siehst. <code>availableEmailDomains</code>{' '}
  enthält die verifizierten Domain-IDs, die du als <code>emailDomainId</code> übergeben kannst, und <code>payment</code> meldet, ob Stripe
  verbunden ist (das Verbinden selbst ist ein Dashboard-Schritt).
</p>

{/* ── formSettings.update ─────────────────────────────────────────────────── */}

Die Verhaltenseinstellungen eines Formulars aktualisieren. Ein partielles Update: nur die gesendeten Felder werden geschrieben.

  
    Formular-ID.
  
  
    <code>language</code> (BCP-47, Standard <code>"en"</code>), <code>requireAuthentication</code>, <code>showBranding</code>,{' '}
    <code>captchaEnabled</code>, <code>passwordEnabled</code>, <code>password</code> (4 Zeichen oder mehr; ein String setzt implizit{' '}
    <code>passwordEnabled: true</code>, <code>null</code> entfernt die Sperre).
  
  
    <code>notifyOnSubmission</code>, <code>notificationEmails</code> (Array), <code>selfNotificationSubject</code>,{' '}
    <code>selfNotificationEmailBody</code>, <code>pdfGenerationEnabled</code>, <code>showViewSubmissionButton</code>. E-Mails an den
    Formularinhaber sind nicht übersetzbar — schreibe sie in der Sprache, die du willst.
  
  
    <code>respondentNotificationEnabled</code>, <code>respondentNotificationTo</code> (die Feld-ID einer E-Mail-Frage, oder{' '}
    <code>null</code>), <code>respondentNotificationSubject</code>, <code>respondentNotificationBody</code>,{' '}
    <code>respondentNotificationPdfEnabled</code>.
  
  
    <code>respondentReminderEnabled</code>, <code>respondentReminderTo</code>, <code>respondentReminderSubject</code>,{' '}
    <code>respondentReminderBody</code>, <code>respondentReminderRequiredFieldIds</code>, und <code>reminderSteps</code> — Leerlauf-Abstände
    wie <code>["1d","3d","1w"]</code>, maximal 5, beim Speichern sortiert und dedupliziert, <code>[]</code> für keine. Der Zeitplan gilt
    gleichermaßen für abgebrochene Antworten über den öffentlichen Link und für Requests. Pro.
  
  
    <code>redirectUrl</code> (<code>http(s)</code>; <code>null</code> oder <code>""</code> entfernt sie), <code>redirectQueryParams</code> (
    <code>{`[{ paramName, fieldId }]`}</code>), <code>allowAnotherResponse</code> (schließt sich mit einer Weiterleitung gegenseitig aus),{' '}
    <code>maxSubmissionsPerRespondent</code> (0 = unbegrenzt, max. 1000), <code>editAfterSubmit</code>, <code>maxEdits</code> (max. 3; 0
    bedeutet unbegrenzt bei Pro und Business, 3 bei Free).
  
  
    <code>draftRetentionDays</code> und <code>submissionRetentionDays</code> (0–36500, <code>null</code> setzt auf den Standard zurück).
    Aufbewahrung von Einreichungen ist Business, und sie zu setzen entfernt ein festes Löschdatum, das im Builder konfiguriert wurde.
  
  
    Eine verifizierte E-Mail-Domain-ID aus <code>formSettings.get</code>, für eine benutzerdefinierte Absenderadresse. <code>null</code>{' '}
    setzt auf den Standardabsender zurück.
  

<p>
  Betreffzeilen und Texte sind Klartext und akzeptieren <code>{`{{variable}}`}</code>-Platzhalter; Zeilenumbrüche werden zu Absätzen. Einen
  Betreff oder Text für die ausfüllende Person anzupassen macht ihn übersetzbar, seine Schlüssel erscheinen also sofort in{' '}
  <code>translations.listEntries</code>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="submissions" class="border-t border-border pt-8">
  Einreichungen
</h2>

{/* ── submissions.list ────────────────────────────────────────────────────── */}

Die Einreichungen eines Formulars auflisten, neueste Seite zuerst, mit Cursor-Paginierung.

  
    Formular-ID.
  
  
    Antworten einschließen, die begonnen, aber nie abgesendet wurden. Entwürfe sind eine Pro-Funktion: im Free-Plan werden nur
    abgeschlossene Einreichungen aufgelistet.
  
  
    Gespeicherte KI-Übersetzungen der Antworten unter <code>items[].translation.display</code> anhängen, mit denselben Schlüsseln wie{' '}
    <code>display</code>. <code>items[].answers</code> und <code>items[].display</code> bleiben immer das Original.
  
  
    Seitengröße (1–100).
  
  
    Paginierungs-Cursor aus einer vorherigen Antwort.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "formName": "Event Feedback",
    "items": [
      {
        "id": "sub_xyz789",
        "submittedAt": "2026-05-18 18:02:28",
        "isCompleted": true,
        "createdAt": "2026-05-18 18:02:19",
        "answers": {
          "email": "user@example.com",
          "plan": "pro",
          "contacts": [{ "name": "Ada" }, { "name": "Grace" }]
        },
        "display": {
          "email": "user@example.com",
          "plan": "Pro",
          "contacts": "Ada, Grace"
        }
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

> ℹ️ **Dieselben Antworten wie bei Webhooks und Callbacks**
> <p>
>     Jedes Element trägt <code>answers</code>, indiziert nach <a href="/de/requests/field-keys">Feldschlüssel</a>, und <code>display</code>{' '}
>     mit denselben Schlüsseln in lesbarem Text — die Form, die ein <a href="/de/developers/webhooks-reference">Webhook-Payload</a>, ein{' '}
>     <a href="/de/requests/callbacks">Request-Callback</a> und <code>requests.get</code> tragen. Eine Auswahlantwort ist ihr
>     Optionsschlüssel, eine Wiederholungsgruppe ein Array von Instanzen. Ruf <code>fields.list</code> für Titel und Optionsbeschriftungen
>     jedes Schlüssels auf. Diese Methode liefert keine Summen.
>   </p>

{/* ── submissions.pdf ─────────────────────────────────────────────────────── */}

Einen Link zum PDF einer Einreichung abrufen. Für den Zapier-Connector gebaut: Es liefert nur dann ein Ergebnis, wenn das Formular eine
aktive Zapier-Integration hat, die so konfiguriert ist, dass sie das PDF einschließt, und das PDF aufbewahrt wurde.

  
    Formular-ID.
  
  
    Einreichungs-ID. Muss zu diesem Formular gehören und abgeschlossen sein.
  

  
    
```
{
  "ok": true,
  "data": {
    "url": "https://api.formstep.io/api/storage/...",
    "filename": "formstep-submission-sub_xyz789.pdf",
    "contentType": "application/pdf",
    "byteLength": 148213
  }
}
```

  

{/* ── submissions.sample ──────────────────────────────────────────────────── */}

Ein Beispiel-Payload für die Einreichung eines Formulars bauen, ohne echte Daten. Es hat exakt die Form, die die Zustellung einer
Einreichung über den öffentlichen Link trägt, Connectors nutzen es also für die Felderkennung; eine aus einem Request entstandene
Einreichung erreicht ein Abonnement stattdessen als <code>request.completed</code> — das Beispiel dafür liefert{' '}

<a href="#requests-sample">requests.sample</a>. <code>data.form.snapshotId</code> ist die aktuell veröffentlichte Version des Formulars,
dieselbe ID, die Live-Events tragen, oder <code>null</code>, solange das Formular unveröffentlicht ist.

  
    Formular-ID.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "submission.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}
```

  

<p>
  Feld- und Payload-Semantik sind einmalig dokumentiert, in der <a href="/de/developers/webhooks-reference#payload">Webhooks-Referenz</a>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="share-links" class="border-t border-border pt-8">
  Freigabe-Links
</h2>

{/* ── shareLinks.list ─────────────────────────────────────────────────────── */}

Freigabe-Links für ein Formular auflisten.

  
    Formular-ID.
  
  
    Widerrufene Links in das Ergebnis einschließen.
  
  
    Seitengröße (1–100).
  
  
    Paginierungs-Cursor.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "sl_abc123",
        "code": "RPjNes52",
        "url": "https://formstep.io/RPjNes52",
        "customDomainUrl": null,
        "formId": "frm_abc123",
        "createdAt": 1714041851000,
        "expiresAt": null,
        "maxClaims": null,
        "claimedCount": 7,
        "isRevoked": false,
        "revokedAt": null,
        "customDomainId": null,
        "customSlug": null
      }
    ],
    "availableCustomDomains": [],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── shareLinks.create ───────────────────────────────────────────────────── */}

Einen Freigabe-Link für ein Formular erstellen. Das Formular muss zuerst veröffentlicht sein.

  
    Formular-ID. Ein Formular, das depubliziert ist oder das veröffentlicht und dann depubliziert wurde, wird abgelehnt — rufe zuerst{' '}
    <code>forms.publish</code> auf.
  
  
    Ablauf als zukünftiger Unix-Zeitstempel in Millisekunden. Anders als beim Update wird <code>0</code> hier nicht akzeptiert.
  
  
    Maximale Anzahl der Verwendungen dieses Links. Muss positiv sein; nutze <code>shareLinks.update</code>, um es später zu entfernen.
  

<p>
  Die Antwort ist der Freigabe-Link (dieselbe Form wie ein <code>shareLinks.list</code>-Eintrag) plus <code>availableCustomDomains</code>,
  sodass du mit <code>shareLinks.update</code> nachfassen kannst, um eine zu verknüpfen.
</p>

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "shareLinks.create",
    "params": {
      "formId": "frm_abc123",
      "maxClaims": 100
    }
  }'
```

{/* ── shareLinks.update ───────────────────────────────────────────────────── */}

Einen Freigabe-Link aktualisieren. Ablauf, maximale Nutzungen, benutzerdefinierte Domain, Slug oder Widerruf des Links können geändert werden.

  
    Freigabe-Link-ID.
  
  
    Neuer Ablaufzeitstempel in Millisekunden. <code>0</code> übergeben, um den Ablauf zu entfernen.
  
  
    Neue maximale Nutzungen. <code>-1</code> übergeben, um das Limit aufzuheben.
  
  
    Eine benutzerdefinierte Domain verknüpfen. <code>null</code> übergeben, um die Verknüpfung aufzuheben.
  
  
    Benutzerdefinierter URL-Slug (3–64 Zeichen, Kleinbuchstaben, alphanumerisch und Bindestriche). Erforderlich zusammen mit{' '}
    <code>customDomainId</code>; übergib beide als <code>null</code>, um die Verknüpfung aufzuheben. <code>login</code>,{' '}
    <code>auth-callback</code>, <code>preview</code>, <code>payment</code>, <code>api</code>, <code>admin</code> und <code>health</code>{' '}
    sind reserviert.
  
  
    Auf <code>true</code> setzen, um den Link dauerhaft zu widerrufen. Kann nicht mit anderen Feldern kombiniert werden, und lässt sich
    nicht rückgängig machen — das ist der einzige Löschweg für einen Freigabe-Link.
  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="fields" class="border-t border-border pt-8">
  Felder
</h2>

{/* ── fields.list ─────────────────────────────────────────────────────────── */}

Jedes Feld der aktuell veröffentlichten Version eines Formulars auflisten, mit dem Schlüssel, über den es angesprochen wird. Vor
`requests.create` aufrufen, statt Schlüssel fest im Code zu hinterlegen. Siehe [Feldschlüssel](/de/requests/field-keys).

  
    Formular-ID. Ein Formular, das noch nie veröffentlicht wurde, hat noch keine Feldschlüssel und liefert <code>published: false</code>
    ohne Einträge zurück.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "fields.list",
    "params": { "formId": "j57..." }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "published": true,
    "items": [
      { "key": "company_name", "type": "text", "title": "Company", "required": true, "prefillable": true },
      {
        "key": "company_size", "type": "select", "title": "Company size", "required": false, "prefillable": true,
        "options": [
          { "key": "1_50", "label": "1–50" },
          { "key": "51_200", "label": "51–200" }
        ]
      },
      {
        "key": "satisfaction", "type": "matrix", "title": "How did we do?", "required": false, "prefillable": true,
        "rows": [{ "key": "delivery_speed", "label": "Delivery speed" }],
        "columns": [{ "key": "very_good", "label": "Very good" }, { "key": "poor", "label": "Poor" }]
      },
      { "key": "case_id", "type": "hidden", "title": "Case", "required": false, "prefillable": false, "context": true },
      { "key": "total", "type": "number", "title": "Total", "required": false, "prefillable": false, "calculated": true }
    ],
    "hasMore": false
  }
}
```

  

> ℹ️ **Die Flags lesen**
> <p>
>     <code>context: true</code> ist ein verstecktes Feld — sein Wert gehört in <code>context</code>, niemals in <code>prefill</code>.{' '}
>     <code>prefillable: false</code> markiert ein Feld, für das niemand einen Wert liefern kann (Datei, Signatur, Zahlung, Termin,
>     Dokumente). Bei einer Auswahlfrage den <strong>Schlüssel</strong> der Option senden, nicht ihr Label; eine Matrix listet ihre{' '}
>     <code>rows</code> und <code>columns</code> auf dieselbe Weise auf und nimmt <code>{'{ "row_key": "column_key" }'}</code>.{' '}
>     <code>calculated: true</code> ist ein berechnetes Feld: Das Formular ermittelt seinen Wert, du liest ihn in <code>answers</code> zurück,
>     und nichts kann ihn senden.
>   </p>

<p>
  Eine Wiederholungsgruppe ist <code>type: "group"</code> mit <code>repeating: true</code> und einem <code>members</code>-Array. Ein
  Dokumente-Block ist <code>type: "documents"</code> und trägt <code>documents: [{`{ name }`}]</code>, die vom Autor hinterlegten Dateien,
  die jede ausfüllende Person bereits sieht.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="requests" class="border-t border-border pt-8">
  Requests
</h2>

Ein Request weist ein veröffentlichtes Formular einer Person zu und ruft dich zurück, wenn er endet. Die konzeptionelle Anleitung steht in
[Eine Anfrage erstellen](/de/requests/creating-requests); hier folgt die Parameterliste.

{/* ── requests.create ─────────────────────────────────────────────────────── */}

Einen Request erstellen. Verbraucht eine Einheit des monatlichen Kontingents des Workspace, unabhängig davon, ob die empfangende Person
antwortet.

  
    Das zu vergebende veröffentlichte Formular.
  
  
    <code>{`{ email?, name? }`}</code>. Eine E-Mail ist erforderlich, wenn <code>delivery</code> gleich <code>"email"</code> ist;
    andernfalls identifiziert sie die Person nur auf der Requests-Seite und bei ihren Antworten.
  
  
    Erste Antworten nach Feldschlüssel. Die empfangende Person sieht sie und kann sie ändern.
  
  
    Vorausgefüllte Schlüssel, die die empfangende Person nicht ändern kann. Jeder Schlüssel hier muss auch in <code>prefill</code>{' '}
    erscheinen, und ein gesperrtes Pflichtfeld muss mit einem nicht-leeren Wert vorausgefüllt sein.
  
  
    Werte für die versteckten Felder des Formulars, nach Feldschlüssel. Vertrauenswürdig, unveränderlich und im Callback zurückgegeben. Ein
    unbekannter Schlüssel wird mit <code>UNKNOWN_FIELD_KEY</code> abgelehnt.
  
  
    Deine eigene Buchführung. Erreicht das Formular nie; kommt in Callbacks und Abfragen zurück.
  
  
    Eine der veröffentlichten Sprachen des Formulars. Standardmäßig die Standardsprache des Formulars.
  
  
    <code>"email"</code>, damit Formstep die Einladung sendet (braucht eine E-Mail der empfangenden Person sowie Pro oder Business oder eine
    der 10 kostenlosen Einladungen eines Free-Kontos), oder <code>"none"</code>, um den Link selbst auszuliefern.
  
  
    Überschreibt den Erinnerungsplan des Formulars für diesen Request. Ein leeres Array schaltet Erinnerungen ab.
  
  
    Epoch-Millisekunden. Standardmäßig 30 Tage später; 365 Tage ist das Maximum.
  
  
    Wohin Formstep den Callback per POST sendet, wenn der Request endet. Nur HTTPS, und der Host muss auf eine öffentliche Adresse auflösen.
  
  
    Deine eigene ID für diesen Request. Filterbar in <code>requests.list</code>.
  
  
    Wiederholung mit demselben Body gibt den ursprünglichen Request mit <code>deduplicated: true</code> zurück. Ein anderer Body wird
    abgelehnt. Schlüssel leben 30 Tage.
  
  
    Den Link auf einer deiner benutzerdefinierten Domains erzeugen. Nur über die REST API.
  
  
    <code>{`[{ documentId, field?, name? }]`}</code> — Dateien, die dieser einen empfangenden Person übergeben werden, zuvor mit{' '}
    <a href="#documents-create">documents.create</a> hochgeladen.
  
  
    Ein Testlauf: Es wird nichts per E-Mail gesendet, der Callback trägt <code>"test": true</code>, und die Einreichung zählt nirgends. Der
    Link schließt sich innerhalb von 24 Stunden, und auf Free darf ein Workspace 10 Testanfragen pro Tag erstellen.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "requests.create",
    "params": {
      "formId": "j57...",
      "recipient": { "email": "ada@acme.com", "name": "Ada" },
      "prefill": { "company_name": "Acme" },
      "readonly": ["company_name"],
      "context": { "case_id": "CASE-9" },
      "delivery": "email",
      "externalId": "run-42",
      "callbackUrl": "https://automation.example/webhook/resume-abc",
      "idempotencyKey": "run-42"
    }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "status": "pending",
    "url": "https://form.formstep.io/r/rq_...",
    "deliveryStatus": "queued",
    "expiresAt": 1794787200000,
    "createdAt": 1789379200000,
    "externalId": "run-42",
    "deduplicated": false
  }
}
```

  

> ⚠️ **Behalte die url**
> <p>
>     <code>url</code> trägt das einmalige Token. <code>requests.get</code> kann sie meist rekonstruieren, aber sie kommt als{' '}
>     <code>null</code> zurück für einen Request, der erstellt wurde, bevor das Deployment einen Request-Token-Schlüssel hatte. Wenn du den
>     Link selbst ausgibst, speichere ihn bei der Erstellung.
>   </p>

<p>
  <code>deliveryStatus</code> ist <code>not_requested</code>, bis eine Einladung eingereiht wird, dann <code>queued</code> →{' '}
  <code>sent</code> oder <code>failed</code>, und <code>bounced</code>, sobald der Mail-Anbieter einen Hard Bounce oder eine Beschwerde
  meldet.
</p>

{/* ── requests.get ────────────────────────────────────────────────────────── */}

Einen Request vollständig abrufen: Status, Ergebnis, was vorausgefüllt wurde, seine Timeline und — sobald abgeschlossen — <code>answers</code> und <code>display</code> nach Feldschlüssel, dieselben zwei Maps, die auch der Callback trägt.

  
    Request-ID.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "formId": "j57...",
    "status": "completed",
    "outcome": "approve",
    "isTest": false,
    "recipient": { "email": "ada@acme.com", "name": "Ada" },
    "language": "en",
    "externalId": "run-42",
    "metadata": null,
    "context": { "case_id": "CASE-9" },
    "prefill": { "company_name": "Acme" },
    "readonlyKeys": ["company_name"],
    "delivery": "email",
    "deliveryStatus": "sent",
    "hasCallback": true,
    "callbackFailedAt": null,
    "submissionId": "kp2...",
    "url": "https://form.formstep.io/r/rq_...",
    "answers": { "company_name": "Acme", "decision": "approve" },
    "display": { "company_name": "Acme", "decision": "Approve" },
    "timeline": [
      { "id": "kd7...:created", "type": "created", "at": 1789379200000 },
      { "id": "kd7...:completed", "type": "completed", "at": 1789465600000 }
    ],
    "expiresAt": 1794787200000,
    "completedAt": 1789465600000
  }
}
```

  

> ℹ️ **outcome vs. status**
> <p>
>     <code>status</code> sagt, ob der Request abgeschlossen ist; <code>outcome</code> sagt, wie die empfangende Person entschieden hat —{' '}
>     <code>approve</code>, <code>decline</code>, <code>changes</code>, oder <code>null</code> bei allem außer einem abgeschlossenen Request,
>     dessen empfangende Person eine der drei Optionen gewählt hat — auch bei einem Formular ohne{' '}
>     <a href="/de/requests/decisions-and-approvals">Entscheidungsfrage</a>. Die Callback-URL selbst wird nie zurückgegeben;{' '}
>     <code>hasCallback</code> sagt nur, ob eine gesetzt ist.
>   </p>

<p>
  Das Beispiel oben ist gekürzt. Eine vollständige Antwort trägt außerdem <code>workspaceId</code>, <code>formSnapshotId</code>,{' '}
  <code>createdVia</code>, <code>documents</code>, <code>reminderStep</code>, <code>remindersSent</code>, <code>reminderDueAt</code>,{' '}
  <code>dataPurgedAt</code> und den Rest der Zeitstempel (<code>updatedAt</code>, <code>openedAt</code>, <code>startedAt</code>,{' '}
  <code>lastActivityAt</code>, <code>expiredAt</code>, <code>canceledAt</code>, <code>canceledBy</code>, <code>cancelReason</code>).
</p>
<p>
  Zwei Felder sagen dir, wann die Kopie vor dir die einzige ist. <code>callbackFailedAt</code> ist gesetzt, während der Callback dieses
  Requests keine Versuche mehr übrig hat, und wird geleert, sobald einer durchkommt oder du ihn erneut sendest. <code>dataPurgedAt</code>{' '}
  ist gesetzt, sobald die Aufbewahrung den Request gestrippt hat: <code>context</code>, <code>prefill</code> und <code>metadata</code>{' '}
  kommen leer zurück, <code>readonlyKeys</code> und <code>documents</code> sind <code>[]</code>, und <code>submissionId</code>,{' '}
  <code>answers</code> und <code>display</code> sind <code>null</code>.
</p>
<p>
  <code>timeline</code> ist abgeleitet, älteste zuerst. Jeder Eintrag hat eine <code>id</code>, ein <code>at</code> und einen{' '}
  <code>type</code> — <code>created</code>, <code>invitation</code>, <code>reminder</code>, <code>opened</code>, <code>started</code>,{' '}
  <code>completed</code>, <code>expired</code>, <code>canceled</code>, <code>callback</code>. Zustellungs-Einträge ergänzen{' '}
  <code>deliveryStatus</code> und <code>attemptCount</code>, und Callbacks ergänzen <code>eventType</code>. Zustellungszeilen bleiben 30
  Tage erhalten, ältere Timelines dünnen also auf die Zeitstempel aus.
</p>

{/* ── requests.list ───────────────────────────────────────────────────────── */}

Requests in einem Workspace oder zu einem Formular auflisten, neueste zuerst. Testanfragen bleiben außen vor, sofern du nicht danach
fragst.

  
    Auf einen Workspace eingrenzen. Dies oder <code>formId</code> angeben.
  
  
    Auf ein Formular eingrenzen.
  
  
    <code>pending</code>, <code>completed</code>, <code>expired</code> oder <code>canceled</code>.
  
  
    <code>approve</code>, <code>decline</code> oder <code>changes</code>. Impliziert nur abgeschlossene Requests.
  
  
    Deine eigene ID, um den Request zu finden, den ein Lauf erstellt hat.
  
  
    Mit <code>test: true</code> erstellte Requests einschließen.
  
  
    Seitengröße (1–100).
  
  
    Paginierungs-Cursor aus einer vorherigen Antwort.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "kd7...",
        "formId": "j57...",
        "status": "pending",
        "outcome": null,
        "recipient": { "email": "ada@acme.com", "name": "Ada" },
        "externalId": "run-42",
        "deliveryStatus": "sent",
        "expiresAt": 1794787200000,
        "createdAt": 1789379200000
      }
    ],
    "nextCursor": null,
    "hasMore": false
  }
}
```

  

<p>
  Listeneinträge tragen dieselben Felder wie <code>requests.get</code>, minus <code>url</code>, <code>answers</code>, <code>display</code>{' '}
  und <code>timeline</code>, und jeder trägt <code>isTest</code>. Gib <code>workspaceId</code> oder <code>formId</code> an — keines von
  beiden ergibt <code>400 VALIDATION_ERROR</code> mit dem Grund <code>SCOPE_REQUIRED</code>. <code>outcome</code> hat Vorrang vor{' '}
  <code>status</code>, denn nur ein abgeschlossener Request hat ein Urteil.
</p>

{/* ── requests.cancel ─────────────────────────────────────────────────────── */}

Einen ausstehenden Request zurückziehen. Der Link funktioniert nicht mehr, die empfangende Person sieht einen Hinweis auf den Rückzug, und
ein `request.canceled`-Callback wird ausgelöst.

  
    Request-ID.
  
  
    Deine Notiz zum Warum, wird am Request gespeichert und im Callback gesendet.
  

{/* ── requests.remind ─────────────────────────────────────────────────────── */}

Die empfangende Person jetzt per E-Mail erreichen, ohne den Erinnerungsplan anzurühren. Braucht eine E-Mail der empfangenden Person und
einen Pro- oder Business-Plan.

  
    Request-ID. Muss noch ausstehend sein und darf keine Testanfrage sein.
  

<p>
  Zwei Untergrenzen gelten: mindestens 10 Minuten zwischen manuellen Erinnerungen, und höchstens 8 Erinnerungen pro Request insgesamt,
  manuelle und geplante zusammengerechnet. Der automatische Zeitplan bleibt unberührt — <code>reminderStep</code> und{' '}
  <code>reminderDueAt</code> bleiben, wo sie waren.
</p>

{/* ── requests.replayCallback ─────────────────────────────────────────────── */}

Den Callback, den ein Request bei seinem Ende ausgelöst hat, erneut senden — gleiche Payload, gleiche Event-ID, sodass ein Empfänger, der
ihn bereits verarbeitet hat, dedupliziert. Nach dem Beheben eines defekten Endpunkts verwenden.

  
    Request-ID. Muss abgeschlossen, abgelaufen oder storniert sein.
  

  
    
```
{
  "ok": true,
  "data": { "dispatchId": "kf9...", "eventId": "evt_kf9..." }
}
```

  

{/* ── requests.sample ─────────────────────────────────────────────────────── */}

Ein Beispiel-Request-Ereignis für ein Formular bauen, ohne echten Request. Es ist genau der Umschlag, den ein mit{' '}

<code>webhooks.create</code> erstelltes <code>request\_\*</code>-Abonnement empfängt, Connectors nutzen es also für die Felderkennung. Ein
abgeschlossenes Beispiel trägt dieselben Beispielantworten, die <a href="#submissions-sample">submissions.sample</a> zeigt; ein abgelaufenes
oder storniertes Beispiel trägt nur den Request-Block.

  
    Formular-ID.
  
  
    Welches Ende als Beispiel dient, in der Schreibweise von <code>webhooks.create</code>. Das <code>type</code> des Umschlags ist die
    gepunktete Form.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "request.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "request": {
        "id": "req_example000000000000",
        "externalId": "order-1234",
        "status": "completed",
        "language": "en",
        "recipient": { "email": "recipient@example.com", "name": "Sample Recipient" },
        "metadata": { "source": "sample" },
        "context": {},
        "createdAt": "2026-05-18T18:00:00.000Z",
        "completedAt": "2026-05-18T19:00:00.000Z"
      },
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}
```

  

<p>
  Der Request-Block und das Ergebnis sind auf der <a href="/de/requests/callbacks#payload">Callbacks-Seite</a> dokumentiert; die
  Einreichungshälfte in der <a href="/de/developers/webhooks-reference#payload">Webhooks-Referenz</a>. Beispiel-IDs sind die oben gezeigten
  festen Platzhalter, und <code>test</code> ist <code>true</code>, sodass ein Empfänger ein Beispiel von einem Live-Ereignis unterscheiden
  kann.
</p>

{/* ── documents.create ────────────────────────────────────────────────────── */}

Einen Upload für eine Datei reservieren, die du einer empfangenden Person über den [Dokumente-Block](/de/building-forms/documents-block) des
Formulars übergibst. Bytes wandern nie durch diese API: Du bekommst ein vorsigniertes `PUT`, lädst hoch, und `requests.create` prüft das
Objekt, bevor der Request existiert.

  
    Das Formular, dessen Dokumente-Block die Datei zeigt. Bindet den Upload an diesen Workspace.
  
  
    Anzeigename, den die empfangende Person sieht (1–200 Zeichen). Pro Request überschreibbar.
  
  
    <code>application/pdf</code> oder ein Bildtyp: <code>image/png</code>, <code>image/jpeg</code>, <code>image/webp</code>,{' '}
    <code>image/gif</code>, <code>image/svg+xml</code>, <code>image/avif</code>, <code>image/bmp</code>, <code>image/tiff</code>.
    Office-Dokumente werden nicht akzeptiert.
  
  
    Exakte Byte-Länge. Maximum 25 MB (26.214.400).
  
  
    Hex-Digest der Bytes. Wird nach dem Upload verifiziert, wenn angegeben.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "kn7...",
    "name": "Lease contract draft",
    "contentType": "application/pdf",
    "size": 412000,
    "uploadUrl": "https://...",
    "expiresAt": 1792198800000
  }
}
```

  

<p>
  <code>PUT</code>e die rohen Bytes innerhalb einer Stunde an <code>uploadUrl</code>, mit <code>Content-Type</code> auf den deklarierten Typ
  gesetzt, und referenziere dann die ID von <code>requests.create</code> aus:
</p>

```
{
  "method": "requests.create",
  "params": {
    "formId": "j57...",
    "documents": [
      { "documentId": "kn7...", "name": "Your lease contract" },
      { "documentId": "kn8...", "field": "attachments" }
    ]
  }
}
```

<ul>
  <li>
    <code>field</code> ist der Feldschlüssel des Dokumente-Blocks. Optional, wenn das Formular genau einen Block hat; erforderlich bei zwei
    oder mehr.
  </li>
  <li>Die vom Autor hinterlegten Dokumente des Blocks bleiben; deine erscheinen darunter, nur für diese eine empfangende Person.</li>
  <li>
    Obergrenzen: 25 MB pro Dokument, 100 MB Dokumente pro Request, 20 pro Block angezeigte Dokumente einschließlich der vom Autor
    hinterlegten.
  </li>
  <li>
    Ein Upload kann von beliebig vielen Requests referenziert werden. Ein Upload, den niemand referenziert, läuft irgendwann aus. Bytes
    zählen gegen den Speicher des Workspace-Inhabers, bis der letzte Request, der sie referenziert, von der Aufbewahrung gestrippt wird.
  </li>
</ul>

<p>
  Jeder Fehler hier ist <code>400 VALIDATION_ERROR</code> mit einem <code>details.reason</code>: <code>DOCUMENT_TYPE_NOT_ALLOWED</code>,{' '}
  <code>DOCUMENT_TOO_LARGE</code>, <code>INVALID_DOCUMENT_NAME</code> oder <code>INVALID_DOCUMENT_SHA256</code> von dieser Methode, und{' '}
  <code>DOCUMENT_NOT_FOUND</code>, <code>DOCUMENT_NOT_UPLOADED</code> (du hast das <code>PUT</code> übersprungen),{' '}
  <code>DOCUMENT_INVALID</code>, <code>INVALID_DOCUMENT_TARGET</code>, <code>DOCUMENTS_TOO_LARGE</code> oder <code>DOCUMENTS_TOO_MANY</code>{' '}
  von <code>requests.create</code>.
</p>

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="webhooks" class="border-t border-border pt-8">
  Webhooks
</h2>

{/* ── webhooks.list ───────────────────────────────────────────────────────── */}

Webhook-Abonnements für ein Formular auflisten.

  
    Formular-ID.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "subscriptionId": "int_abc123",
        "formId": "frm_abc123",
        "provider": "zapier",
        "targetUrl": "https://hooks.zapier.com/...",
        "eventType": "submission_created",
        "status": "active",
        "createdAt": 1714041851000
      }
    ],
    "hasMore": false
  }
}
```

  

{/* ── webhooks.create ─────────────────────────────────────────────────────── */}

Eine URL für Formularereignisse abonnieren: neue oder abgebrochene Einreichungen, oder Requests, die auf dem Formular enden. Die URL muss
HTTPS verwenden.

  
    Formular-ID.
  
  
    HTTPS-URL zum Empfangen von Webhook-Payloads.
  
  
    Zu welchem Tool das Abonnement gehört. Das ist nur ein Label für deine eigene Buchführung — es gibt keine Marketplace-App zu
    installieren, und jeder Anbieter verhält sich gleich.
  
  
    Ereignistyp, der abonniert werden soll. Die drei <code>submission_*</code>-Typen liefern das Einreichungs-Payload:{' '}
    <code>submission_created</code> für eine erste Einreichung, <code>submission_updated</code> für eine Bearbeitung durch die empfangende
    Person, und <code>submission_abandoned</code> für einen inaktiven Entwurf. Die drei <code>request_*</code>-Typen liefern das passende{' '}
    <a href="/de/requests/callbacks#payload">Request-Ereignis</a>, wann immer ein Request auf dem Formular so endet, signiert mit dem
    Geheimnis dieses Abonnements; Testanfragen erreichen kein Abonnement.
  
  
    Erforderlich, wenn <code>eventType</code> gleich <code>submission_abandoned</code> ist; abgelehnt bei jedem anderen Typ.
  
  
    Optionales HMAC-Signing-Secret, 32–255 Zeichen. Wenn angegeben, enthalten Zustellungen <code>X-Formstep-Signature</code>. Das Secret
    wird gespeichert, aber nie von der API zurückgegeben.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "webhooks.create",
    "params": {
      "formId": "frm_abc123",
      "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
      "provider": "zapier",
      "eventType": "submission_created",
      "signingSecret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  }'
```

  
    
```
{
  "ok": true,
  "data": {
    "subscriptionId": "int_new123",
    "formId": "frm_abc123",
    "provider": "zapier",
    "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
    "eventType": "submission_created"
  }
}
```

  

<p>
  Abonnements für abgebrochene Einreichungen liefern das gewählte <code>idleWindow</code> sowohl von <code>webhooks.create</code> als auch
  von <code>webhooks.list</code> zurück. Jedes andere Abonnement lässt es aus.
</p>

<p>
  Ein Request-Abonnement hört dieselben Ereignisse wie ein <a href="/de/requests/callbacks">Callback</a>, aber als eigene Zustellung: eigene
  Event-ID, eigene Signatur und ein eigenes Wiederholungsbudget von fünf Versuchen, nach dem das Abonnement pausiert. Ein Request, der mit
  einer <code>callbackUrl</code> auf einem Formular mit einem <code>request_completed</code>-Abonnement erstellt wurde, feuert deshalb
  zweimal, einmal an jeden Empfänger. <code>requests.replayCallback</code> sendet nur den Callback erneut. Verwende{' '}
  <a href="#requests-sample">requests.sample</a>, um das Payload zu sehen, bevor irgendein Request geendet hat.
</p>

{/* ── webhooks.delete ─────────────────────────────────────────────────────── */}

Ein Webhook-Abonnement entfernen.

  
    Abonnement-ID aus <code>webhooks.list</code> oder <code>webhooks.create</code>.
  

  
    
```
{
  "ok": true,
  "data": {
    "subscriptionId": "int_abc123",
    "deleted": true
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="analytics" class="border-t border-border pt-8">
  Analysen
</h2>

{/* ── analytics.get ───────────────────────────────────────────────────────── */}

Aggregierte Analysekennzahlen für ein Formular abrufen. Unterstützt Datumsbereich, Gerät, Verkehrsquelle und Länderfilter.

Analysen sind eine Pro-Funktion, und die Regel richtet sich nach dem Tarif des **Workspace-Eigentümers**, genau wie der Analytics-Tab im Dashboard. Ist der Eigentümer nicht auf Pro, gibt der Aufruf `UPGRADE_REQUIRED` zurück — auch für Verlaufsdaten aus der Zeit davor. Ein Free-Mitglied im Workspace eines Pro-Eigentümers erhält die Daten.

  
    Formular-ID.
  
  
    Beginn des Datumsbereichs als Unix-Zeitstempel in Millisekunden. Muss kleiner oder gleich <code>to</code> sein, wenn beide gesetzt sind.
  
  
    Ende des Datumsbereichs als Unix-Zeitstempel in Millisekunden. Beide weglassen für die gesamte Zeit — <code>period</code> kommt dann als{' '}
    <code>{`{ "from": null, "to": null }`}</code> zurück.
  
  
    Nach Gerätetyp filtern.
  
  
    Nach Verkehrsquelle filtern (z. B. <code>"Direct"</code>, <code>"Google"</code>).
  
  
    Nach 2-Buchstaben-Ländercode filtern (z. B. <code>"US"</code>, <code>"DE"</code>).
  
  
    Auch die bereinigten Analyse-Events hinter den Kennzahlen zurückgeben, für deine eigene Auswertung. Keine Besucher-IDs.
  

<p>
  Raten sind Zahlen von 0 bis 100, Zählungen sind Ganzzahlen, und <code>totalEvents</code> ist die rohe Event-Zeilenzahl vor der
  Deduplizierung zu eindeutigen Besuchern.
</p>

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "period": { "from": null, "to": null },
    "totalEvents": 17,
    "metrics": {
      "views": 7,
      "uniqueVisitors": 7,
      "engaged": 6,
      "submissions": 4,
      "engagementRate": 86,
      "completionRate": 57,
      "bounceRate": 14
    },
    "breakdown": {
      "byBrowser": { "Chrome": 17 },
      "byCountry": { "US": 10, "DE": 7 },
      "byDevice": { "desktop": 14, "mobile": 3 },
      "bySource": { "Direct": 12, "Google": 5 }
    }
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="workspaces" class="border-t border-border pt-8">
  Workspaces
</h2>

{/* ── workspaces.list ─────────────────────────────────────────────────────── */}

Die Workspaces auflisten, die dein Token erreichen kann. Keine Parameter.

Ein API-Token ist an einen Workspace gebunden, liefert also genau diesen einen zurück — auch wenn dein Konto zu mehreren gehört.

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "ws_abc123",
        "name": "Acme Inc",
        "createdAt": 1714041800000,
        "role": "owner"
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── workspaces.createInvite ─────────────────────────────────────────────── */}

Einen Einladungslink für einen Workspace erstellen.

  
    Workspace-ID.
  
  
    Ablauf als zukünftiger Unix-Zeitstempel in Millisekunden.
  
  
    Maximale Anzahl der Verwendungen der Einladung.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "inv_abc123",
    "workspaceId": "ws_abc123",
    "code": "aBcDeFgH",
    "expiresAt": null,
    "maxUses": null,
    "uses": 0,
    "createdAt": 1714041851000
  }
}
```

  

{/* ── workspaces.getInvite ────────────────────────────────────────────────── */}

Eine einzelne Workspace-Einladung abrufen. Liefert dieselbe Form wie `workspaces.createInvite`.

  
    Einladungs-ID.
  

{/* ── workspaces.updateInvite ─────────────────────────────────────────────── */}

Eine bestehende Workspace-Einladung aktualisieren. Gib mindestens eines von <code>expiresAt</code> oder <code>maxUses</code> an, sonst
wird der Aufruf abgelehnt. Liefert die aktualisierte Einladung.

  
    Einladungs-ID.
  
  
    Neuer Ablaufzeitstempel in Millisekunden.
  
  
    Neues maximales Nutzungslimit.
  

{/* ── workspaces.revokeInvite ─────────────────────────────────────────────── */}

Eine Workspace-Einladung dauerhaft widerrufen.

  
    Einladungs-ID.
  

  
    
```
{
  "ok": true,
  "data": {
    "inviteId": "inv_abc123",
    "revoked": true
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="folders" class="border-t border-border pt-8">
  Ordner
</h2>

{/* ── folders.list ────────────────────────────────────────────────────────── */}

Ordner in einem Workspace auflisten.

  
    Workspace-ID.
  
  
    Seitengröße (1–100).
  
  
    Paginierungs-Cursor.
  

  
    
```
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "fld_abc123",
        "name": "Customer Feedback",
        "workspaceId": "ws_abc123",
        "parentId": null,
        "createdAt": 1714041800000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── folders.create ──────────────────────────────────────────────────────── */}

Einen Ordner in einem Workspace erstellen. Idempotent — gibt den bestehenden Ordner zurück, wenn bereits ein Ordner mit demselben Namen existiert.

  
    Workspace-ID.
  
  
    Ordnername (1–255 Zeichen).
  
  
    Übergeordnete Ordner-ID für Verschachtelung. Für Root-Ebene weglassen.
  

  
    
```
{
  "ok": true,
  "data": {
    "id": "fld_new123",
    "name": "Customer Feedback",
    "workspaceId": "ws_abc123",
    "parentId": null,
    "createdAt": 1714041851000,
    "alreadyExisted": false
  }
}
```

  

{/* ── folders.update ──────────────────────────────────────────────────────── */}

Einen Ordner umbenennen oder in einen anderen übergeordneten Ordner verschieben.

  
    Ordner-ID.
  
  
    Neuer Ordnername (1–255 Zeichen).
  
  
    Neuer übergeordneter Ordner. <code>null</code> übergeben, um in den Root zu verschieben.
  

{/* ── folders.delete ──────────────────────────────────────────────────────── */}

Einen Ordner und seinen gesamten Inhalt (Unterordner und Formulare) dauerhaft löschen.

  
    Ordner-ID.
  

> ⚠️ **Destruktive Aktion**
> <p>Diese Aktion löscht dauerhaft alle Unterordner und Formulare im Ordner. Sie kann nicht rückgängig gemacht werden.</p>

  
    
```
{
  "ok": true,
  "data": {
    "folderId": "fld_abc123",
    "deletedFolderIds": ["fld_abc123"],
    "deletedFormIds": ["frm_in_folder"],
    "message": "Folder and 1 form deleted."
  }
}
```

  

<h2 id="translations" class="border-t border-border pt-8">
  Übersetzungen
</h2>

{/* ── translations.listLanguages ──────────────────────────────────────────── */}

Alle für ein Formular konfigurierten Sprachen auflisten.

  
    Formular-ID.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "items": [
      {
        "language": "es",
        "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
        "lastUpdatedAt": 1714041851000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
```

  

{/* ── translations.addLanguage ────────────────────────────────────────────── */}

Eine Sprache auf einem Formular registrieren. Jede andere Übersetzungsmethode schlägt mit `404 NOT_FOUND` fehl, bis du das getan hast.

  
    Formular-ID.
  
  
    BCP-47-Sprachtag (z. B. <code>"es"</code>, <code>"pt-BR"</code>).
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "rowId": "tl_new123"
  }
}
```

  

{/* ── translations.removeLanguage ─────────────────────────────────────────── */}

Eine Sprache und alle ihre Übersetzungen aus einem Formular entfernen.

  
    Formular-ID.
  
  
    BCP-47-Sprachtag.
  

{/* ── translations.listEntries ────────────────────────────────────────────── */}

Jeden Quellschlüssel für eine Sprache in einem Formular auflisten, mit seinem aktuellen Zustand. So findest du die `key`-Werte, die
`translations.setEntry` erwartet.

  
    Formular-ID.
  
  
    BCP-47-Sprachtag. Muss bereits auf dem Formular sein.
  

  
    
```
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
    "items": [
      {
        "key": "block_q_1.title",
        "status": "current",
        "value": "[{\"text\":\"Tu correo electrónico\"}]",
        "sourceFragment": "[{\"text\":\"Your email\"}]",
        "updatedAt": 1714041851000,
        "block": { "id": "q_1", "type": "email-input", "label": "Your email" }
      }
    ],
    "hasMore": false
  }
}
```

  

<p>
  <code>status</code> ist <code>missing</code> (nichts gespeichert), <code>outdated</code> (die Quelle hat sich seither geändert),{' '}
  <code>current</code>, oder <code>suggested</code> (ein KI-Vorschlag, vorgemerkt, aber nicht übernommen). Schlüssel decken Formularinhalt
  ab (<code>block_&lt;id&gt;.*</code>) und, sobald ein Autor sie angepasst hat, die Bestätigungs- und Erinnerungs-E-Mails an die ausfüllende
  Person (<code>email.confirmation.*</code>, <code>email.reminder.*</code>).
</p>

{/* ── translations.setEntry ───────────────────────────────────────────────── */}

Einen einzelnen Übersetzungseintrag setzen. Die Sprache muss zuvor über <code>translations.addLanguage</code> hinzugefügt worden sein.

  
    Formular-ID.
  
  
    BCP-47-Sprachtag.
  
  
    Ein Schlüssel aus <code>translations.listEntries</code>. Nicht von Hand konstruieren.
  
  
    Das übersetzte Fragment, JSON-stringifiziert. Seine Markup-Struktur muss der des Quellfragments entsprechen.
  

  
```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "translations.setEntry",
    "params": {
      "formId": "frm_abc123",
      "language": "es",
      "key": "block_q_1.title",
      "value": "[{\"text\":\"Tu correo electrónico\"}]"
    }
  }'
```

> ⚠️ **Schreiben hier ist live**
> <p>
>     Die API hat keinen Entwurf-dann-Veröffentlichen-Schritt: Ein <code>setEntry</code> oder <code>deleteEntry</code> erreicht Befragte
>     sofort. Das Dashboard und die MCP-Übersetzungstools nutzen stattdessen einen Entwurf.
>   </p>

<p>
  Liefert <code>{`{ formId, language, key }`}</code>.
</p>

{/* ── translations.deleteEntry ────────────────────────────────────────────── */}

Einen einzelnen Übersetzungseintrag löschen und diesen Schlüssel zur Standardsprache des Formulars zurücksetzen. Idempotent. Verschwindet
der letzte Eintrag einer Sprache, fällt die Sprache aus den veröffentlichten Sprachen des Formulars.

  
    Formular-ID.
  
  
    BCP-47-Sprachtag.
  
  
    Zu löschender Übersetzungsschlüssel.
  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="me" class="border-t border-border pt-8">
  Konto
</h2>

{/* ── me.get ──────────────────────────────────────────────────────────────── */}

Informationen über den authentifizierten Benutzer abrufen.

  
    
```
{
  "ok": true,
  "data": {
    "id": "usr_abc123",
    "email": "you@example.com",
    "name": "Jane Doe"
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="meta" class="border-t border-border pt-8">
  Meta
</h2>

Jeden Methodennamen auflisten, den dieses Deployment bereitstellt, sortiert. Die maßgebliche Antwort, wenn diese Seite und der Server
sich widersprechen.

  
    
```
{
  "ok": true,
  "data": {
    "methods": [
      "methods.list",
      "analytics.get",
      "folders.create",
      "folders.delete",
      "folders.list",
      "folders.update",
      "formSettings.get",
      "formSettings.update",
      "forms.create",
      "forms.delete",
      "forms.get",
      "forms.list",
      "..."
    ]
  }
}
```

  

{/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */}

<h2 id="error-reference" class="border-t border-border pt-8">
  Fehlerreferenz
</h2>

Jede Fehlerantwort hat dieselbe Form. Das Set der obersten `code`-Werte ist absichtlich geschlossen: Ein neuer Fehlerfall fügt nie einen
Code hinzu, sondern einen `reason`. Verzweige nach `code` für das Ergebnis auf HTTP-Ebene und nach `details.reason` für die Behebung.

```
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "No field with key \"company\" on this form.",
    "details": { "reason": "UNKNOWN_FIELD_KEY", "field": "prefill.company", "validKeys": ["company_name", "contacts"] }
  }
}
```

<p>
  <code>details</code> ist immer vorhanden, wenn der Server die Ursache benennen kann. Neben <code>reason</code> kann es <code>field</code>{' '}
  tragen (der problematische Parameter, mit Punkten für Verschachtelung), <code>validKeys</code>, <code>validValues</code> (die{' '}
  <strong>Werte</strong> der Optionen, die eine Auswahlfrage akzeptiert), <code>expectedType</code>, <code>feature</code> (bei{' '}
  <code>UPGRADE_REQUIRED</code>) und <code>retryAfterMs</code> (bei einem gedrosselten Aufruf). Gründe für die jeweilige Anfrage stehen bei
  jeder Methode oben.
</p>

<p>Das sind alle Codes:</p>

  
    Ungültige oder fehlende Parameter in der Anfrage.
  
  
    Fehlendes oder ungültiges API-Token.
  
  
    Token hat keinen Zugriff auf die angeforderte Ressource.
  
  
    Ressource existiert nicht.
  
  
    Unbekannter Methodenname. <code>methods.list</code> verwenden, um verfügbare Methoden zu sehen.
  
  
    Die Ressource ist nicht in einem Zustand, der diesen Aufruf erlaubt — ein Request, der nicht mehr aussteht, ein Idempotenzschlüssel, der
    mit einem anderen Body wiederverwendet wurde.
  
  
    Über 120 Aufrufe pro Minute auf diesem Token, über 60 <code>requests.create</code>-Aufrufe pro Minute, oder zu viele fehlgeschlagene
    Authentifizierungen von dieser IP.
  
  
    Funktion erfordert einen höheren Abonnement-Tarif, der Workspace hat sein monatliches Kontingent aufgebraucht (Grund{' '}
    <code>MONTHLY_ALLOWANCE_REACHED</code>), oder ein Free-Konto hat seine 10 kostenlosen Einladungen aufgebraucht (Grund{' '}
    <code>FREE_INVITATIONS_USED</code>).
  
  
    Unerwarteter Serverfehler. Später erneut versuchen.
  

<h2 id="next-steps">Nächste Schritte</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [API-Tokens](/de/developers/api-tokens) — Tokens erstellen und verwalten
  - [MCP-Server](/de/developers/mcp-server) — Formstep aus KI-Agenten verwenden
  - [Webhooks-Referenz](/de/developers/webhooks-reference) — Payload-Schema und Signierung
</div>
