# Een formulier en een aanvraag in één aanroep maken

Stuur de blokken mee met de aanvraag: Formstep bouwt het formulier, publiceert het en geeft de link terug, zonder editor ertussen.

## Wanneer je het gebruikt

Een aanvraag verwijst normaal naar een formulier dat iemand in de editor heeft gebouwd en gepubliceerd. Hangen de vragen van de zaak af, dan stuurt de aanroeper ze mee: `requests.create` neemt `form` in plaats van `formId`. Formstep bouwt van de blokken een gewoon formulier, publiceert het en maakt de aanvraag in één stap, dus ze bestaan allebei of geen van beide. Dat noemen we een **inline formulier**.

Blijf `formId` gebruiken voor een formulier dat je in dezelfde vorm naar veel mensen stuurt en met de hand in de editor wilt vormgeven. Gebruik `form` wanneer een automatisering bepaalt wat er gevraagd wordt.

## De aanroep

```bash
curl -X POST https://api.formstep.io/api/v1 \
  -H "Authorization: Bearer $FORMSTEP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"method":"requests.create","params":{
        "form":{
          "name":"Documents and fee for your mortgage application",
          "logo":{"type":"image","url":"https://acme.example/logo.png"},
          "cover":{"type":"color","color":"#0f4c81"},
          "emoji":"🏦",
          "blocks":[
            {"type":"paragraph","content":"Upload the two documents and pay the processing fee."},
            {"type":"file","key":"payslips","title":"Your last two payslips","maxFiles":2},
            {"type":"payment","key":"fee","title":"Processing fee","amount":40,"currency":"EUR"},
            {"type":"scheduleAppointment","key":"call","title":"Pick a time for our call","eventType":"15 min call"}]},
        "recipient":{"email":"ada@example.com","name":"Ada"},
        "delivery":"email",
        "idempotencyKey":"call-8841"}}'
```

Al het andere aan de aanvraag houdt zijn betekenis: `recipient`, `prefill`, `context`, `readonly`, `delivery`, herinneringen, verloop, `callbackUrl`, `test` en `idempotencyKey` worden tegen het nieuwe formulier gecontroleerd, precies zoals tegen een formulier dat met de hand is gebouwd. De respons bevat ook het formulier, met de sleutels waaronder de antwoorden terugkomen:

```json
{
  "id": "kd7...",
  "status": "pending",
  "url": "https://form.formstep.io/r/rq_...",
  "form": {
    "id": "j58...",
    "name": "Documents and fee for your mortgage application",
    "reused": false,
    "fields": [
      { "key": "payslips", "type": "file" },
      { "key": "fee", "type": "payment" },
      { "key": "call", "type": "schedule-appointment" }
    ]
  }
}
```

Om een keuzevraag in dezelfde aanroep vooraf in te vullen, stuur je optiesleutels, zoals bij elk formulier. De sleutel van een optie wordt afgeleid van het label, dus `Self-employed` wordt `self_employed`. Een label in plaats van een sleutel faalt met `INVALID_PREFILL_VALUE`, met de geldige sleutels in de foutmelding.

## Het formulierobject

| Veld | Wat het doet |
| - | - |
| name | Verplicht, 1 tot 100 tekens. De naam op het dashboard en in het browsertabblad, voor elke ontvanger dezelfde. Personaliseer via prefill en context, nooit via de naam. |
| blocks | Verplicht, 1 tot 200 blokken, in de volgorde waarin het formulier ze toont. De blokken in de kolommen van een rij en de velden van een herhalende groep tellen ook mee. |
| language | De interfacetaal van het formulier. Standaard: Engels. |
| logo, cover, emoji | Je huisstijl, meegestuurd met elke aanvraag. logo: { "type": "icon", "name": "HeartIcon" } of { "type": "image", "url": "https\://…" }. cover: { "type": "color", "color": "#0f4c81" } of { "type": "image", "url": "https\://…", "offsetY": 50 }. emoji: één emoji naast de naam. Zie hieronder. |
| theme | Een uiterlijk per kleurmodus: { "light": "solarized-light", "dark": "dracula" }. Elke modus neemt een preset-id, gratis op elk abonnement, of een volledig thema-object zoals de MCP-tool formTheme\_set dat neemt, waarvoor het Pro-abonnement nodig is. |
| settings | Het formuliergedrag dat de instellingenpagina regelt: huisstijl, een geverifieerd e-maildomein (emailDomainId), e-mails aan de eigenaar en de invuller met hun tekst, het herinneringsschema, de uitnodigingsmail, de doorverwijzing en de bewaartermijn. Velden per sleutel: respondentNotificationTo is de sleutel van een e-mailvraag, redirectQueryParams koppelt een parameter aan een veldsleutel. E-mailtekst is platte tekst met {{key}}-plaatshouders. Geen wachtwoord- of inlogdrempel en geen deellinkinstelling zoals botbeveiliging of bewerken na verzenden: een aanvraag heeft een eigen link. Elke andere sleutel wordt geweigerd. |
| translations | Het formulier in meer talen, met een taalkiezer voor de ontvanger: { "de": { "Your name": "Ihr Name" } }, elke tekst van het formulier met als sleutel de tekst zoals de blokken hem schreven. Zie hieronder. |

## Blokken

Een blok neemt de parameters van de bijbehorende `editor_insert*`-tool op de [MCP-server](https://docs.formstep.io/nl/developers/mcp-server/) , zonder `formId` en `after`, plus een optionele `key`. Zonder sleutel wordt die uit de titel afgeleid, dus “Full name” wordt `full_name`. Een parameter die het blok niet kent, wordt geweigerd in plaats van genegeerd.

| Soort | Typen |
| - | - |
| Vragen | text, contact, number, date, time, radio, checkbox, select, switch, rating, linearScale, matrix, ranking, pictureChoice, file, signature, decision |
| Documenten | documents, om bestanden te tonen die de ontvanger leest of ondertekent |
| Betalingen en afspraken | payment, scheduleAppointment |
| Inhoud | header, paragraph, image (src-URL), embedded, table (cells vult hem), list, row (columns: de blokken van elke kolom, 2 tot 6 kolommen, en widths in procenten). Met een optionele key kan logica het blok tonen of verbergen. |
| Structuur | pageDivider, repeatingGroup (fields: de vragen van de groep als blokken, elk met een eigen key), hiddenField (key verplicht: dat is de contextsleutel), calculatedField (de name is de sleutel), logic |

Tekst in header en paragraph, lijstitems en tabelcellen begrijpen `bold`, `italic`, `struck`, `<u>underlined</u>`, `code`, `$$equation$$`, `text`, `<span color=“red” background=“yellow” font=“font-georgia” size=“1.25”>…</span>` met de kleuren, lettertypen en groottes van de werkbalk van de editor, en `{{key}}`, dat de waarde toont van het veld met die sleutel. Markeringen kunnen in elkaar zitten; een markering zonder tegenhanger blijft tekst. Een kolom van een rij bevat elk blok behalve pageDivider, logic, hiddenField, calculatedField, repeatingGroup, row en scheduleAppointment. Een `decision`-blok neemt zijn `title` en `labels` voor goedkeuren, afwijzen en wijzigingen in de taal van het formulier.

Blokken verwijzen naar elkaar via de sleutel, nooit via een element-id: een `logic`-blok neemt zijn `rule` zoals `editor_insertLogic` dat doet, met veldsleutels waar die ids verwacht, optielabels als voorwaardewaarden en paginanamen voor `setNextPage`.

### Betalingen

Een `payment`-blok neemt `amount` en `currency`, of `amountFromField`: de sleutel van een verborgen veld of een getalvraag. Met een verborgen veld geeft elke aanvraag haar eigen bedrag mee in `context`, dus één formulier rekent bij elke klant een eigen bedrag af:

```json
{
  "form": {
    "name": "Pay your invoice",
    "blocks": [
      { "type": "hiddenField", "key": "invoice_total" },
      { "type": "payment", "key": "invoice", "title": "Invoice", "currency": "EUR", "amountFromField": "invoice_total" }
    ]
  },
  "recipient": { "email": "ada@example.com" },
  "context": { "invoice_total": 1200 }
}
```

Een getalvraag die het bedrag bepaalt, moet verplicht zijn en een `min` boven 0 hebben, zodat niemand met een leeg, nul- of negatief bedrag bij de checkout komt.

Het formulier rekent af via het standaard Stripe-account van de werkruimte, of via het account dat `connection` noemt. Een persoon [verbindt Stripe één keer voor de werkruimte](https://docs.formstep.io/nl/building-forms/payment-collection/#connect-stripe); een automatisering kan dat niet. Zonder bruikbaar account mislukt de aanroep en krijg je te horen waar je het verbindt.

### Afspraken

Een `scheduleAppointment`-blok neemt `eventType`, de titel of het id van een eventtype op de [Cal.com](https://docs.formstep.io/nl/integrations/cal-com/)-verbinding van de werkruimte, en `connection` als de werkruimte er meerdere heeft. Formstep zoekt het eventtype op wanneer de aanvraag wordt gemaakt. `prefill: { name, email }` noemt de vragen waarvan de antwoorden de afspraak invullen.

## Je huisstijl

Een automatisering kan je uitstraling niet verzinnen, dus geef je die één keer mee en stuurt ze die met elke aanvraag mee: `logo`, `cover` en `emoji` naast de blokken, en `theme` voor de kleuren en lettertypen.

| Veld | Neemt |
| - | - |
| logo | Een icoon uit de builder, { "type": "icon", "name": "HeartIcon" }, of je eigen afbeelding, { "type": "image", "url": "https\://…/logo.png" }. |
| cover | Een effen kleur, { "type": "color", "color": "#0f4c81" }, of een afbeelding, { "type": "image", "url": "https\://…/cover.jpg", "offsetY": 50 }, waarbij offsetY van 0 tot 100 kiest welk deel van een hoge afbeelding zichtbaar is. |
| emoji | Eén emoji, naast de formuliernaam getoond. |

Elke afbeelding, ook de `src` van een afbeeldingsblok, moet een openbare JPEG, PNG of WebP zijn. Formstep haalt elke afbeelding één keer op wanneer het formulier wordt gemaakt en bewaart een eigen kopie, zodat het formulier en de pdf’s van de inzendingen de bron overleven. Een URL die niet op te halen is, laat de aanroep mislukken met `INLINE_FORM_IMAGE_UNREACHABLE`, met het veld erbij. Zonder huisstijl ziet het formulier eruit als een nieuw formulier vanaf het dashboard.

## Vertalingen

`translations` publiceert het formulier meteen in meer talen, en de ontvanger krijgt de taalkiezer die een vertaald formulier heeft: `{ "de": { "Your name": "Ihr Name", "Order for **{{customer}}**": "Bestellung für **{{customer}}**" } }`. Elke tekst van het formulier moet erin staan, met als sleutel de tekst precies zoals de blokken hem schreven: vraagtitels, opties, plaatshouders, headers, alinea’s, lijstitems, tabelcellen, groepsnamen en knoplabels. Opmaak en `{{key}}` blijven zoals in de bron. Ontbreekt er een tekst, dan mislukt de aanroep met `INLINE_FORM_TRANSLATION_MISSING`, die de toe te voegen teksten opsomt. E-mailtekst wordt niet vertaald.

## Eén definitie, één formulier

Een latere aanroep met hetzelfde `form`-object hergebruikt hetzelfde formulier, en de respons zegt `“reused”: true`. Honderd aanvragen met één definitie leveren één formulier op, met één set antwoorden om te exporteren. Verander je een blok, de naam, de huisstijl of een vertaling, dan ontstaat er een nieuw formulier; aanvragen die al verstuurd zijn, houden het formulier waarmee ze zijn gemaakt. Een formulier dat je sluit of depubliceert, wordt niet hergebruikt: de volgende aanroep met dezelfde definitie maakt een nieuw formulier.

> **Personaliseer via prefill en context**
>
> Zet de naam van de klant, het factuurnummer of het bedrag in `prefill` en `context`, nooit in de formuliernaam, een titel of een vast bedrag. Een definitie die per klant verandert, maakt per klant een nieuw formulier. Zet `key` op elke vraag: een afgeleide sleutel volgt de titel, dus een vraag met een nieuwe titel antwoordt onder een nieuwe sleutel. Een werkruimte maakt hooguit 300 nieuwe inline formulieren per dag; daarna mislukt een aanroep die een nieuw formulier zou maken met `RATE_LIMITED`, terwijl aanroepen die een formulier hergebruiken blijven werken.

Maakt een aanroep een nieuw formulier terwijl een open inline formulier dezelfde bloktypen in dezelfde volgorde heeft, dan noemt het antwoord dat formulier in `form.similarForm`. Meestal kwam dat door een gewijzigde naam of titel. Wil je alle antwoorden op één inline formulier lezen, vraag dan de inzendingen op in plaats van elke aanvraag te lezen.

## Op het dashboard

Een inline formulier hoort bij zijn aanvraag en staat daarom op de pagina van de aanvraag in plaats van op de formulierenpagina. De pagina toont de antwoorden per veldsleutel, en **Voorbeeld** opent het formulier zoals de ontvanger het zag, ingevuld met hun antwoorden. Een link naar de editor of de inzendingen van een inline formulier opent in plaats daarvan de pagina Aanvragen, gefilterd op dat formulier. Agents zien inline formulieren nog steeds in `form_list`, en de API weigert ze te wijzigen.

Een inline formulier heeft geen deellink, en je kunt er geen toevoegen: de aanvragen zijn de enige weg naar binnen, en de callback bevat elk antwoord. Het heeft ook geen integraties.

Omdat het geen eigen pagina heeft, verwijder je een inline formulier nooit met de hand. Het verdwijnt met zijn laatste aanvraag: als je [een aanvraag verwijdert](https://docs.formstep.io/nl/requests/managing-requests/#delete), of de bewaartermijn van het formulier die opruimt, en geen andere aanvraag het formulier nog gebruikt, wordt het formulier ook verwijderd.

## Wanneer de aanroep wordt geweigerd

Elke weigering is een `VALIDATION_ERROR` met een `details.reason` waarop je automatisering kan vertakken. Er wordt niets aangemaakt.

| Reden | Wat je doet |
| - | - |
| FORM\_SOURCE\_MISSING / FORM\_SOURCE\_AMBIGUOUS | Stuur precies één van formId en form. |
| INLINE\_FORM\_INVALID | Een blok of een veld van form voldoet niet aan het schema. details.issues noemt elk ervan. |
| INLINE\_FORM\_DUPLICATE\_KEY | Twee blokken komen op dezelfde sleutel uit. Geef een van beide een eigen key. |
| INLINE\_FORM\_UNKNOWN\_KEY | Een logicaregel, amountFromField of een prefill van een afspraak noemt een sleutel die geen enkel blok heeft. |
| INLINE\_FORM\_AMOUNT\_MISSING | Geef in context een positief getal mee voor het bedragveld. |
| INLINE\_FORM\_CONNECTION\_MISSING | Stripe of Cal.com is niet verbonden. details.connectUrl is de pagina die een persoon één keer opent om het te verbinden. |
| INLINE\_FORM\_EVENT\_TYPE\_NOT\_FOUND | Geen enkel Cal.com-eventtype komt overeen. De fout noemt de eventtypen die er wel zijn. |
| INLINE\_FORM\_IMAGE\_UNREACHABLE | Een afbeeldings-URL is geen openbare JPEG, PNG of WebP. details.field noemt hem. Gebruik een URL die iedereen kan openen, of laat de afbeelding weg. |
| INLINE\_FORM\_TRANSLATION\_MISSING | Een vertaling mist teksten die het formulier heeft. details.missing somt ze op, zoals ze in de blokken staan. |
| INLINE\_FORM\_PUBLISH\_ISSUES | Publiceren weigerde het formulier, bijvoorbeeld door een eventtype dat niet vanuit een formulier te boeken is. |

## Vanuit een agent

Op de MCP-server neemt `request_create` hetzelfde `form`. Voordat een agent een formulier met een betaling of een afspraak bouwt, leest die `workspaceConnection_list`: die toont de verbonden Stripe- en Cal.com-accounts en, als er een ontbreekt, de link die een persoon één keer moet openen. `scheduler_listEventTypes` somt de eventtypen op die een afspraak kan noemen, en `paymentConnection_setDefault` wijzigt het standaard Stripe-account. Via de REST API heten dezelfde drie `connections.list`, `scheduler.listEventTypes` en `paymentConnections.setDefault`.

[Een aanvraag maken](https://docs.formstep.io/nl/requests/creating-requests/) — Vooraf invullen, vergrendelen en context voor elk formulier.

[Veldsleutels](https://docs.formstep.io/nl/requests/field-keys/) — Hoe sleutels worden afgeleid en stabiel blijven.

[Callbacks & ondertekening](https://docs.formstep.io/nl/requests/callbacks/) — Ontvang de antwoorden zodra de aanvraag eindigt.

[Betalingen verzamelen](https://docs.formstep.io/nl/building-forms/payment-collection/) — Verbind Stripe voor de werkruimte.
