# Créer un formulaire et une demande en un seul appel

Envoyez les blocs avec la demande : Formstep crée le formulaire, le publie et renvoie le lien, sans passer par l’éditeur.

## Quand l’utiliser

Une demande pointe d’ordinaire vers un formulaire que quelqu’un a créé dans l’éditeur et publié. Quand les questions dépendent du dossier, l’appelant les envoie avec la demande : `requests.create` accepte `form` à la place de `formId`. Formstep crée un formulaire ordinaire à partir des blocs, le publie et crée la demande en une seule étape : soit les deux existent, soit aucun. Nous l’appelons un **formulaire inline**.

Continuez d’utiliser `formId` pour un formulaire que vous envoyez à beaucoup de personnes sous la même forme et que vous voulez concevoir à la main dans l’éditeur. Utilisez `form` quand une automatisation décide de ce qu’il faut demander.

## L’appel

```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"}}'
```

Tout le reste de la demande garde son sens : `recipient`, `prefill`, `context`, `readonly`, `delivery`, les rappels, l’expiration, `callbackUrl`, `test` et `idempotencyKey` sont vérifiés par rapport au nouveau formulaire exactement comme pour un formulaire créé à la main. La réponse ajoute le formulaire, avec les clés sous lesquelles ses réponses reviennent :

```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" }
    ]
  }
}
```

Pour préremplir une question à choix dans le même appel, envoyez des clés d’option, comme pour tout formulaire. La clé d’une option est dérivée de son libellé : `Self-employed` devient `self_employed`. Un libellé à la place d’une clé échoue avec `INVALID_PREFILL_VALUE`, qui liste les clés valides.

## L’objet form

| Champ | Ce que ça fait |
| - | - |
| name | Obligatoire, de 1 à 100 caractères. Le nom affiché dans le tableau de bord et dans l’onglet du navigateur, le même pour chaque destinataire. Personnalisez avec prefill et context, jamais avec le nom. |
| blocks | Obligatoire, de 1 à 200 blocs, dans l’ordre où le formulaire les affiche. Les blocs des colonnes d’une ligne et les champs d’un groupe répétable comptent aussi. |
| language | La langue de l’interface du formulaire. Par défaut : l’anglais. |
| logo, cover, emoji | Votre identité visuelle, envoyée avec chaque demande. logo : { "type": "icon", "name": "HeartIcon" } ou { "type": "image", "url": "https\://…" }. cover : { "type": "color", "color": "#0f4c81" } ou { "type": "image", "url": "https\://…", "offsetY": 50 }. emoji : un emoji à côté du nom. Voir plus bas. |
| theme | Une apparence par mode de couleur : { "light": "solarized-light", "dark": "dracula" }. Chaque mode prend l’id d’un préréglage, gratuit sur tous les plans, ou un objet de thème complet tel que le prend l’outil MCP formTheme\_set, ce qui demande le plan Pro. |
| settings | Le comportement du formulaire que règle la page des paramètres : l’identité visuelle, un domaine d’e-mail vérifié (emailDomainId), les e-mails au propriétaire et au répondant avec leur texte, le calendrier des relances, l’e-mail d’invitation, la redirection et la conservation. Champs par clé : respondentNotificationTo est la clé d’une question e-mail, redirectQueryParams associe un paramètre à une clé de champ. Le texte des e-mails est du texte brut avec des repères {{key}}. Aucune barrière de mot de passe ou de connexion, et aucun réglage de lien de partage comme la protection anti-robots ou les modifications après envoi : une demande porte son propre lien. Toute autre clé est refusée. |
| translations | Le formulaire dans d’autres langues, avec un sélecteur de langue pour le destinataire : { "de": { "Your name": "Ihr Name" } }, chaque texte du formulaire ayant pour clé le texte tel que les blocs l’ont écrit. Voir plus bas. |

## Les blocs

Un bloc prend les paramètres de l’outil `editor_insert*` correspondant du [serveur MCP](https://docs.formstep.io/fr/developers/mcp-server/), sans `formId` ni `after`, plus une `key` facultative. Sans elle, la clé est dérivée du titre : « Full name » devient `full_name`. Un paramètre que le bloc ne connaît pas est refusé plutôt qu’ignoré.

| Catégorie | Types |
| - | - |
| Questions | text, contact, number, date, time, radio, checkbox, select, switch, rating, linearScale, matrix, ranking, pictureChoice, file, signature, decision |
| Documents | documents, pour montrer des fichiers que le destinataire lit ou signe |
| Paiements et rendez-vous | payment, scheduleAppointment |
| Contenu | header, paragraph, image (URL dans src), embedded, table (cells la remplit), list, row (columns : les blocs de chaque colonne, de 2 à 6 colonnes, et widths en pourcentage). Une key facultative permet à la logique d’afficher ou de masquer le bloc. |
| Structure | pageDivider, repeatingGroup (fields : les questions membres sous forme de blocs, chacune avec sa clé), hiddenField (key obligatoire : c’est la clé de context), calculatedField (son name est sa clé), logic |

Le texte de header et paragraph, les éléments de liste et les cellules de tableau acceptent `bold`, `italic`, `struck`, `<u>underlined</u>`, `code`, `$$equation$$`, `text`, `<span color=“red” background=“yellow” font=“font-georgia” size=“1.25”>…</span>` avec les couleurs, polices et tailles de la barre d’outils de l’éditeur, et `{{key}}`, qui affiche la valeur du champ portant cette clé. Les marques s’imbriquent ; un marqueur sans son pendant reste du texte. Une colonne d’une ligne accepte tout bloc sauf pageDivider, logic, hiddenField, calculatedField, repeatingGroup, row et scheduleAppointment. Un bloc `decision` prend son `title` et ses `labels` pour approuver, refuser et demander des modifications, dans la langue du formulaire.

Les blocs se désignent entre eux par leur clé, jamais par un id d’élément : un bloc `logic` prend sa `rule` comme `editor_insertLogic`, avec des clés de champ là où il attend des ids, des libellés d’option comme valeurs de condition et des noms de page pour `setNextPage`.

### Paiements

Un bloc `payment` prend `amount` et `currency`, ou `amountFromField` : la clé d’un champ caché ou d’une question numérique. Avec un champ caché, chaque demande passe son propre montant dans `context`, si bien qu’un seul formulaire facture à chaque client son propre montant :

```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 }
}
```

Une question numérique qui fixe le montant doit être obligatoire et avoir un `min` supérieur à 0, pour que personne n’arrive au checkout avec un montant vide, nul ou négatif.

Le formulaire encaisse par le compte Stripe par défaut de l’espace de travail, ou par celui que `connection` désigne. Une personne [connecte Stripe une fois pour l’espace de travail](https://docs.formstep.io/fr/building-forms/payment-collection/#connect-stripe) ; une automatisation ne le peut pas. Sans compte utilisable, l’appel échoue et vous indique où le connecter.

### Rendez-vous

Un bloc `scheduleAppointment` prend `eventType`, le titre ou l’id d’un type d’événement de la connexion [Cal.com](https://docs.formstep.io/fr/integrations/cal-com/) de l’espace de travail, et `connection` quand l’espace de travail en a plusieurs. Formstep recherche le type d’événement au moment où la demande est créée. `prefill: { name, email }` désigne les questions dont les réponses remplissent la réservation.

## Votre identité visuelle

Une automatisation ne peut pas inventer votre identité visuelle, alors vous la lui donnez une fois et elle l’envoie avec chaque demande : `logo`, `cover` et `emoji` à côté des blocs, et `theme` pour les couleurs et la typographie.

| Champ | Valeur |
| - | - |
| logo | Une icône de l’éditeur, { "type": "icon", "name": "HeartIcon" }, ou votre propre image, { "type": "image", "url": "https\://…/logo.png" }. |
| cover | Une couleur unie, { "type": "color", "color": "#0f4c81" }, ou une image, { "type": "image", "url": "https\://…/cover.jpg", "offsetY": 50 }, où offsetY, de 0 à 100, choisit la partie visible d’une image haute. |
| emoji | Un emoji, affiché à côté du nom du formulaire. |

Chaque image, y compris le `src` d’un bloc image, doit être un JPEG, PNG ou WebP public. Formstep récupère chacune une fois à la création du formulaire et en garde sa propre copie : le formulaire et les PDF de ses soumissions survivent ainsi à la source. Une URL impossible à récupérer fait échouer l’appel avec `INLINE_FORM_IMAGE_UNREACHABLE`, qui nomme le champ. Sans identité visuelle, le formulaire ressemble à un nouveau formulaire créé depuis le tableau de bord.

## Traductions

`translations` publie le formulaire dans d’autres langues en une fois, et le destinataire obtient le sélecteur de langue d’un formulaire traduit : `{ "de": { "Your name": "Ihr Name", "Order for **{{customer}}**": "Bestellung für **{{customer}}**" } }`. Chaque texte du formulaire doit y figurer, avec pour clé le texte exactement tel que les blocs l’ont écrit : titres de question, options, textes indicatifs, en-têtes, paragraphes, éléments de liste, cellules de tableau, noms de groupe et libellés de bouton. Les marques et `{{key}}` restent comme dans la source. Un texte manquant fait échouer l’appel avec `INLINE_FORM_TRANSLATION_MISSING`, qui liste les textes à ajouter. Le texte des e-mails n’est pas traduit.

## Une définition, un formulaire

Un appel ultérieur avec le même objet `form` réutilise le même formulaire, et la réponse indique `“reused”: true`. Cent demandes avec une même définition donnent un seul formulaire, avec un seul ensemble de réponses à exporter. Modifier un bloc, le nom, l’identité visuelle ou une traduction crée un nouveau formulaire ; les demandes déjà envoyées gardent le formulaire avec lequel elles ont été créées. Un formulaire fermé ou dépublié n’est pas réutilisé : l’appel suivant avec la même définition en crée un nouveau.

> **Personnalisez avec prefill et context**
>
> Placez le nom du client, le numéro de facture ou le montant dans `prefill` et `context`, jamais dans le nom du formulaire, un titre ou un montant fixe. Une définition qui change à chaque client crée un nouveau formulaire par client. Définissez `key` sur chaque question : une clé dérivée suit le titre, donc une question renommée répond sous une nouvelle clé. Un espace de travail crée au plus 300 nouveaux formulaires inline par jour ; au-delà, un appel qui créerait un nouveau formulaire échoue avec `RATE_LIMITED`, tandis que les appels qui réutilisent un formulaire continuent de fonctionner.

Quand un appel crée un nouveau formulaire alors qu’un formulaire inline ouvert a les mêmes types de blocs dans le même ordre, la réponse nomme ce formulaire dans `form.similarForm`. Le plus souvent, un nom ou un titre modifié en est la cause. Pour lire toutes les réponses à un formulaire inline, listez ses soumissions au lieu de lire chaque demande.

## Dans le tableau de bord

Un formulaire inline appartient à sa demande : il vit donc sur la page de la demande plutôt que sur la page Formulaires. La page liste les réponses par clé de champ, et **Aperçu** ouvre le formulaire tel que le destinataire l’a vu, rempli avec ses réponses. Un lien vers l’éditeur ou les réponses d’un formulaire inline ouvre à la place la page Demandes filtrée sur ce formulaire. Les agents voient toujours les formulaires inline dans `form_list`, et l’API refuse de les modifier.

Un formulaire inline n’a pas de lien de partage, et on ne peut pas en ajouter : ses demandes sont le seul moyen d’y accéder, et le callback transporte toutes les réponses. Il n’a pas non plus d’intégrations.

Comme il n’a pas de page propre, un formulaire inline n’est jamais supprimé à la main. Il disparaît avec sa dernière demande : quand vous [supprimez une demande](https://docs.formstep.io/fr/requests/managing-requests/#delete), ou que la période de conservation du formulaire la supprime, et qu’aucune autre demande n’utilise encore le formulaire, le formulaire est supprimé lui aussi.

## Quand l’appel est refusé

Chaque refus est une `VALIDATION_ERROR` dont votre automatisation peut lire le `details.reason` pour choisir la suite. Rien n’est créé.

| Motif | Que faire |
| - | - |
| FORM\_SOURCE\_MISSING / FORM\_SOURCE\_AMBIGUOUS | Envoyez exactement l’un des deux : formId ou form. |
| INLINE\_FORM\_INVALID | Un bloc ou un champ de form ne respecte pas son schéma. details.issues les nomme un par un. |
| INLINE\_FORM\_DUPLICATE\_KEY | Deux blocs aboutissent à la même clé. Donnez à l’un d’eux sa propre clé. |
| INLINE\_FORM\_UNKNOWN\_KEY | Une règle de logique, amountFromField ou un prefill de rendez-vous désigne une clé qu’aucun bloc ne porte. |
| INLINE\_FORM\_AMOUNT\_MISSING | Passez un nombre positif pour le champ du montant dans context. |
| INLINE\_FORM\_CONNECTION\_MISSING | Stripe ou Cal.com n’est pas connecté. details.connectUrl est la page qu’une personne ouvre une fois pour le connecter. |
| INLINE\_FORM\_EVENT\_TYPE\_NOT\_FOUND | Aucun type d’événement Cal.com ne correspond. L’erreur liste ceux qui existent. |
| INLINE\_FORM\_IMAGE\_UNREACHABLE | Une URL d’image n’est pas un JPEG, PNG ou WebP public. details.field la nomme. Utilisez une URL que tout le monde peut ouvrir, ou retirez l’image. |
| INLINE\_FORM\_TRANSLATION\_MISSING | Une traduction omet des textes du formulaire. details.missing les liste, tels qu’écrits dans les blocs. |
| INLINE\_FORM\_PUBLISH\_ISSUES | La publication a refusé le formulaire, par exemple à cause d’un type d’événement qu’on ne peut pas réserver depuis un formulaire. |

## Depuis un agent

Sur le serveur MCP, `request_create` accepte le même `form`. Avant de créer un formulaire avec un paiement ou un rendez-vous, un agent lit `workspaceConnection_list` : l’outil montre les comptes Stripe et Cal.com connectés et, quand l’un manque, le lien qu’une personne doit ouvrir une fois. `scheduler_listEventTypes` liste les types d’événement qu’un rendez-vous peut désigner, et `paymentConnection_setDefault` change le compte Stripe par défaut. Avec l’API REST, ces trois outils s’appellent `connections.list`, `scheduler.listEventTypes` et `paymentConnections.setDefault`.

[Créer une demande](https://docs.formstep.io/fr/requests/creating-requests/) — Préremplir, verrouiller et passer du contexte, pour tout formulaire.

[Clés de champ](https://docs.formstep.io/fr/requests/field-keys/) — Comment les clés sont dérivées et restent stables.

[Callbacks et signature](https://docs.formstep.io/fr/requests/callbacks/) — Recevez les réponses quand la demande se termine.

[Collecte de paiements](https://docs.formstep.io/fr/building-forms/payment-collection/) — Connectez Stripe pour l’espace de travail.
