# Einen Request erstellen

Entdecke die Feldschlüssel eines Formulars und erstelle dann einen Request mit vorausgefüllten Werten, gesperrten Feldern und Kontext.

## Einen Request erstellen

Zwei API-Aufrufe: frag das Formular, was ihm mitgeteilt werden kann, und weise es dann mit den Werten, die du bereits kennst, einer Person zu.

<h2 id="start-in-the-share-sheet">Starte im Share-Sheet</h2>

<p>
  Öffne dein veröffentlichtes Formular, klicke auf <strong>Teilen</strong> und wähle den Tab <strong>Anfragen</strong>. Die Karte dort gibt
  dir alles, was du für den ersten Aufruf brauchst:
</p>

<ul>
  <li>
    Die <strong>Formular-ID</strong>, mit Kopieren-Button.
  </li>
  <li>
    Ein <strong>curl</strong>-Snippet und einen <strong>MCP</strong>-Prompt, beide aus den echten Feldschlüsseln deines Formulars gebaut —
    das Beispiel spricht also bereits die Felder an, die dieses Formular tatsächlich hat.
  </li>
  <li>
    Einen Tab <strong>Manuell</strong>, der von Hand einen Request erstellt, und <strong>Selbst ausprobieren</strong>, das aus dem, was du
    dort ausgefüllt hast, einen Request im <a href="#test-mode">Testmodus</a> macht und dir dessen Link übergibt.
  </li>
  <li>
    Einen Link zur <a href="/de/requests/managing-requests">Requests-Seite</a>, gefiltert auf dieses Formular.
  </li>
</ul>

> ⚠️ **Erst veröffentlichen**
> <p>
>     Ein unveröffentlichtes Formular kann nicht per Request versendet werden, und die Snippets bleiben deaktiviert, bis du veröffentlichst.
>     Feldschlüssel frieren bei der ersten Veröffentlichung ein — deshalb kann deine Automation <code>company_name</code> auch noch ein Jahr
>     später ansprechen. Siehe <a href="/de/requests/field-keys">Feldschlüssel</a>.
>   </p>

<h2 id="discover-fields">Schritt 1 — Die Felder entdecken</h2>

<p>
  <code>fields.list</code> liefert jedes Feld der aktuell veröffentlichten Version des Formulars, mit dem Schlüssel, über den es
  angesprochen wird, der Wertform, die es erwartet, und dem Bucket, zu dem es gehört.
</p>

```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer $FORMSTEP_TOKEN" \
  -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": "case_id", "type": "hidden", "title": "Case", "required": false, "prefillable": false, "context": true },
      { "key": "total", "type": "number", "title": "Total", "required": false, "prefillable": false, "calculated": true },
      { "key": "signature", "type": "signature", "title": "Sign here", "required": true, "prefillable": false }
    ],
    "hasMore": false
  }
}
```

<ul>
  <li>
    <code>context: true</code> markiert ein verstecktes Feld. Sein Wert gehört in <code>context</code>, niemals in <code>prefill</code>; der
    Schlüssel eines versteckten Felds in <code>prefill</code> wird mit <code>UNKNOWN_FIELD_KEY</code> abgelehnt.
  </li>
  <li>
    <code>calculated: true</code> markiert ein berechnetes Feld. Das Formular ermittelt seinen Wert selbst, also kann niemand einen liefern;
    du liest ihn unter seinem Schlüssel in <code>answers</code> zurück.
  </li>
  <li>
    <code>prefillable: false</code> markiert ein Feld, für das niemand einen Wert liefern kann: Datei-Upload, Signatur, Zahlung,
    Terminbuchung und Dokumente-Blöcke. Die Fragen füllt die empfangende Person selbst aus. Versteckte Felder und berechnete Felder zeigen
    ebenfalls <code>prefillable: false</code>: versteckte Felder übernehmen <code>context</code>, berechnete Felder übernehmen nichts.
  </li>
  <li>
    <code>options</code> listet die Auswahlmöglichkeiten einer Auswahlfrage. Sende den <strong>Schlüssel</strong> der Option, nicht ihr
    Label; das Label ist nur dazu da, damit du die dir bekannte Option auf ihren Schlüssel abbilden kannst. Eine Matrix listet ihre{' '}
    <code>rows</code> und <code>columns</code> auf dieselbe Weise auf.
  </li>
  <li>
    Wiederholungsgruppen kommen als ein Eintrag mit <code>type: "group"</code>, <code>repeating: true</code> und einer Liste von{' '}
    <code>members</code> zurück.
  </li>
</ul>

<h2 id="create">Schritt 2 — Den Request erstellen</h2>

```
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer $FORMSTEP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"method":"requests.create","params":{
        "formId":"j57...",
        "recipient":{"email":"ada@acme.com","name":"Ada"},
        "context":{"case_id":"CASE-9"},
        "prefill":{"company_name":"Acme","company_size":"51_200"},
        "readonly":["company_name"],
        "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
  }
}
```

<p>
  <code>deliveryStatus</code> ist <code>"queued"</code>, wenn Formstep die Einladung per E-Mail versendet, und <code>"not_requested"</code>,
  wenn du den Link selbst zustellst.
</p>

<h2 id="three-buckets">Prefill, gesperrte Felder und Kontext</h2>

<p>Drei verschiedene Dinge lassen sich an einen Request anhängen, und sie zu verwechseln ist der häufigste erste Fehler.</p>

<h3 id="prefill">Prefill</h3>

<p>
  Anfangsantworten für die sichtbaren Fragen, sodass die empfangende Person prüft und korrigiert, statt von vorne zu tippen. Alles, was du
  bereits über sie weißt, gehört hierher — der Firmenname aus deinem CRM, der Betrag aus der Rechnung, die Antworten vom letzten Jahr.
</p>

<h3 id="locked-fields">Gesperrte Felder</h3>

<p>
  Liste einen vorausgefüllten Schlüssel in <code>readonly</code> auf, und die empfangende Person sieht den Wert, kann ihn aber nicht ändern.
  Nutze das für Fakten, die bestätigt statt geliefert werden — die Vertragsnummer, den vereinbarten Preis. Die Sperrung gilt pro Request:
  Das Formular selbst bleibt unberührt, und dasselbe Feld ist beim nächsten Request wieder frei bearbeitbar.
</p>

<p>
  Jeder gesperrte Schlüssel muss auch vorausgefüllt sein, und ein gesperrtes <em>Pflichtfeld</em> muss mit etwas Nicht-Leerem vorausgefüllt
  sein — sonst stünde die empfangende Person vor einem Formular, das sie nie absenden könnte, und Formstep lehnt den Aufruf ab, statt diese
  Falle zu erstellen.
</p>

<h3 id="context">Kontext</h3>

<p>
  Vertrauenswürdige Werte für die <a href="/de/building-forms/hidden-fields">versteckten Felder</a> des Formulars — eine Fallnummer, eine
  Workflow-Run-ID, ein Betrag. Kontext speist Variablen, bedingte Logik, berechnete Felder und E-Mail-Texte, kommt unverändert im Callback
  zurück, und die empfangende Person kann ihn nicht ändern. Das ist der Unterschied zum Befüllen eines versteckten Felds über eine URL bei
  einem öffentlichen Link, wo jeder die Query-String bearbeiten kann; Request-Links ignorieren URL-Query-Parameter vollständig. Kontextwerte
  müssen ein String, eine Zahl oder ein Boolean sein.
</p>

<p>
  Kontext ist nicht frei formulierbar: Jeder Schlüssel muss ein verstecktes Feld auf der veröffentlichten Version des Formulars sein, und
  jeder andere Schlüssel wird mit <code>UNKNOWN_FIELD_KEY</code> abgelehnt. Buchhaltung ohne passendes verstecktes Feld, wie eine
  Ausführungs-ID, gehört in <a href="#metadata">metadata</a>.
</p>

<p>
  Versteckte Felder werden im Formular nicht angezeigt, aber ein Kontextwert ist vor der empfangenden Person nicht geheim. Sie sieht ihn
  überall dort, wo das Formular oder die Einladung ihn zeigt: als <a href="/de/building-forms/answer-piping">Erwähnung</a> im Formularinhalt
  oder im E-Mail-Text, oder als sichtbare Frage, die dieses versteckte Feld als{' '}
  <a href="/de/building-forms/field-configuration#default-values">Standardwert</a> verwendet. In diesem letzten Fall sieht die empfangende
  Person den Kontextwert in dieser Frage vorausgefüllt und kann die Antwort bearbeiten. Der Kontextwert selbst bleibt unverändert. Ein{' '}
  <code>prefill</code> für den eigenen Schlüssel dieser Frage hat Vorrang vor dem Standardwert.
</p>

<h3 id="metadata">Metadata</h3>

<p>
  Deine eigene Buchhaltung — eine Ausführungs-ID, eine CRM-Datensatz-ID. Sie erreicht das Formular gar nicht, kann also nicht in Texte
  eingespeist oder von Logik gelesen werden; sie fährt einfach mit und kommt in jedem Callback und jeder Statusabfrage zurück.
</p>

<h2 id="value-shapes">Wertformen</h2>

<p>
  Sende Werte in der Form, die der <code>type</code> aus <code>fields.list</code> verlangt. Eine falsche Form kommt als Validierungsfehler
  zurück, der den Schlüssel, den erwarteten Typ und — bei Auswahlfragen — die akzeptierten Werte nennt.
</p>

<h2 id="documents">Dokumente</h2>

<p>
  Ein <a href="/de/building-forms/documents-block">Dokumentenblock</a> reicht der ausfüllenden Person Dateien. Seine hinterlegten Dateien
  sind für alle gleich und bleiben immer erhalten; ein Request fügt Dateien für seine eine empfangende Person darunter hinzu — den eigenen
  Mietvertrag der Kundin, eine Ausweiskopie zur Prüfung. Bytes wandern nie durch den API-Aufruf selbst: erst hochladen, dann referenzieren.
</p>

```
"documents": [
  { "documentId": "kn7...", "name": "Your lease contract" },
  { "documentId": "kn8..." }
]
```

<p>
  <code>name</code> überschreibt den auf dem Upload gespeicherten Anzeigenamen. Hat das Formular mehr als einen Dokumente-Block, benennst du
  das Ziel mit <code>field</code>, dem Feldschlüssel des Blocks (<code>fields.list</code> führt ihn auf, zusammen mit den hinterlegten
  Dokumenten, die alle Antwortenden ohnehin erhalten). Die Dateien erscheinen unterhalb dieser hinterlegten Dokumente — ein Request fügt
  Dateien hinzu, er ersetzt nie eine. Ein Upload kann von so vielen Requests referenziert werden, wie du willst — eine einmal hochgeladene
  Preisliste bedient fünfhundert Requests.
</p>

<p>
  Limits: nur PDF und Bilder, 25 MB pro Dokument, 100 MB pro Request (<code>DOCUMENTS_TOO_LARGE</code>), und höchstens 20 Dokumente pro
  Block einschließlich der hinterlegten (<code>DOCUMENTS_TOO_MANY</code>). Die Dateien werden auf dein Workspace-Speicherkontingent
  angerechnet und werden freigegeben, sobald die Requests, die auf sie verweisen, aus dem Aufbewahrungszeitraum des Formulars fallen. Die
  Einreichung protokolliert die Liste, die die empfangende Person unter dem Feldschlüssel des Blocks gesehen hat, sodass der Callback dir
  genau sagt, welche Dateien dieser Person gegeben wurden.
</p>

<h2 id="options">Die übrigen Optionen</h2>

<h3 id="test-mode">Testmodus</h3>

<p>
  Übergib <code>test: true</code>, um die gesamte Verkabelung vor einem echten Lauf durchzuspielen. Eine Testanfrage ist in jeder Hinsicht,
  die für die Verkabelung zählt, echt: Der Link öffnet sich und lässt sich abschließen, der <a href="/de/requests/callbacks">Callback</a>{' '}
  feuert wie gewohnt, und <code>requests.get</code> liefert die Antworten. Was sie nie tut, ist jemanden oder etwas zu erreichen, das du
  danach wieder aufräumen müsstest:
</p>

<ul>
  <li>
    Es wird weder eine Einladung noch eine Erinnerung versendet, egal was <code>delivery</code> sagt. <strong>Erinnerung senden</strong>{' '}
    wird dafür verweigert, und sie verbraucht nichts von deinem monatlichen Kontingent.
  </li>
  <li>
    Der Callback trägt <code>"test": true</code>, sodass deine Automation danach verzweigen oder das Ereignis ignorieren kann.
  </li>
  <li>
    Die Einreichung wird gespeichert, zählt aber nicht: nicht gegen dein monatliches Kontingent (ein Test wird auch abgeschlossen, wenn das
    Kontingent aufgebraucht ist), und sie erscheint nie in den Einreichungszahlen des Formulars, im Einreichungen-Tab, in Exporten oder in
    deinen Integrationen. Niemand wird benachrichtigt.
  </li>
  <li>
    Der Request wird auf der <a href="/de/requests/managing-requests">Requests-Seite</a> hinter <strong>Testanfragen anzeigen</strong>{' '}
    versteckt, im Request-Funnel in Analytics ausgelassen, und aus <code>requests.list</code> ausgelassen, sofern du nicht{' '}
    <code>includeTest: true</code> übergibst.
  </li>
  <li>
    Der Link schließt sich innerhalb von 24 Stunden, auch wenn <code>expiresAt</code> mehr verlangt; das <code>expiresAt</code> in der
    Antwort nennt den Zeitpunkt. Auf Free darf ein Workspace 10 Testanfragen pro Tag erstellen. Die nächste schlägt mit{' '}
    <code>RATE_LIMITED</code> und dem Grund <code>TEST_REQUEST_LIMIT_REACHED</code> fehl, und <code>retryAfterMs</code> sagt, wann du es
    erneut versuchen kannst. Pro und Business haben kein Tageslimit.
  </li>
</ul>

<p>
  <strong>Selbst ausprobieren</strong> im Share-Sheet ist dieser Modus per Knopfdruck: Er übernimmt den Entwurf aus dem Tab Manuell —
  Prefill, Sperren, Kontext, Callback, Ablauf —, adressiert den Request an dein eigenes Konto, verschickt keine E-Mail und übergibt dir den
  Link zum selbst Öffnen.
</p>

<h3 id="allowance">Was ein Request kostet</h3>

<p>
  Jeder Plan hat ein <strong>monatliches Kontingent</strong>, das sich beide Kanäle teilen: Eine Einreichung über einen Freigabelink
  verbraucht eine Einheit, und jeder Request, den du erstellst, ebenfalls — egal ob die empfangende Person antwortet, ihn ignoriert, oder du
  ihn stornierst. Die Einreichung, die ein Request einsammelt, ist bereits bezahlt und zählt nirgends. Free enthält 1.000 Einheiten pro
  Monat, Pro und Business 50.000; der Zähler wird am 1. jedes Monats zurückgesetzt, UTC. Am Limit schlägt <code>requests.create</code> mit{' '}
  <code>UPGRADE_REQUIRED</code> und dem Grund <code>MONTHLY_ALLOWANCE_REACHED</code> fehl; bereits erstellte Requests bleiben beantwortbar.
</p>

<p>
  Im Free-Plan verbraucht ein Request, der mit <code>"delivery": "email"</code> erstellt wird, zusätzlich eine der{' '}
  <a href="/de/subscription-billing/limits-quotas#free-invitations">10 kostenlosen Einladungen</a> des Kontos. Sie werden nie zurückgesetzt;
  sind sie aufgebraucht, schlägt die E-Mail-Zustellung mit <code>UPGRADE_REQUIRED</code> und dem Grund <code>FREE_INVITATIONS_USED</code>{' '}
  fehl.
</p>

<h3 id="idempotency">Idempotenz</h3>

<p>
  Sende denselben <code>idempotencyKey</code> mit demselben Body und du bekommst den ursprünglichen Request zurück, mit{' '}
  <code>deduplicated: true</code> und dem ursprünglichen Link — kein zweiter Request, keine zweite E-Mail. Verwendest du den Schlüssel mit
  einem <em>anderen</em> Body erneut, lehnt Formstep mit <code>IDEMPOTENCY_CONFLICT</code> ab, statt zu raten, welchen du meintest.
  Schlüssel sind an den Workspace gebunden und gelten 30 Tage; danach startet derselbe Schlüssel einen neuen Request.
</p>

<p>
  In einem Workflow-Tool ist die Ausführungs-ID der natürliche Schlüssel: Ein Lauf, der nach einem Netzwerkaussetzer wiederholt wird, greift
  den Request auf, den er bereits erstellt hat.
</p>

<h3 id="rate-limit">Ratenbegrenzung</h3>

<p>
  <code>requests.create</code> und <code>documents.create</code> teilen sich ein Budget von <strong>60 Aufrufen pro Minute</strong>, gezählt
  pro API-Token (oder pro Nutzer, bei einem Aufruf ohne Token). Ein Rückstand, den du abarbeitest, sollte sich selbst drosseln; ein Schub
  über das Budget hinaus wird abgelehnt und kann wiederholt werden.
</p>

<h3 id="custom-domains">Benutzerdefinierte Domains</h3>

<p>
  Ist das Formular bereits auf einer deiner <a href="/de/branding-domains/custom-domains">benutzerdefinierten Domains</a> veröffentlicht,
  werden Request-Links dort automatisch erstellt — <code>https://forms.deinunternehmen.com/r/rq_…</code>. Nenne <code>domainId</code>{' '}
  explizit, wenn das Formular auf mehr als einer veröffentlicht ist. Die Domain muss zum selben Workspace wie das Formular gehören.
</p>

<h2 id="what-the-recipient-sees">Was die empfangende Person sieht</h2>

<p>
  Genau das Formular, das du erstellt hast — gleiches Theme, gleiches Logo, gleiche Sprache — mit ihren Werten an Ort und Stelle, gesperrten
  Feldern schreibgeschützt, und ohne ein Captcha lösen zu müssen. Beim Absenden bekommt sie deine Dankesseite. Kehrt sie danach zum Link
  zurück, bekommt sie die Ergebnisseite statt eines leeren Formulars.
</p>

<p>
  Auf der Seite steht keine Nachricht von deiner Automation. Alles, was der empfangenden Person mitgeteilt werden muss, gehört ins Formular
  selbst, wo du es personalisieren kannst, indem du einen Kontextwert oder ein vorausgefülltes Feld{' '}
  <a href="/de/building-forms/answer-piping">erwähnst</a>.
</p>

<p>
  Ein KI-Agent führt dieselben zwei Schritte aus wie <code>fields_list</code> und <code>request_create</code>, mit denselben Optionen —
  einschließlich documents und <code>domainId</code>. Siehe <a href="/de/developers/mcp-server#requests">Requests auf dem MCP-Server</a>.
</p>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Feldschlüssel](/de/requests/field-keys) — Woher diese Schlüssel kommen und wie du sie stabil hältst.
  - [Callbacks & Signierung](/de/requests/callbacks) — Was ankommt, wenn die empfangende Person fertig ist.
  - [Fehlerbehebung](/de/requests/troubleshooting) — Jeder Ablehnungsgrund und was dagegen zu tun ist.
  - [API-Referenz](/de/developers/rest-api) — Vollständige Parameterliste für jede Request-Methode.
</div>
