# Créer une demande

Découvrez les clés de champ d'un formulaire, puis créez une demande avec des valeurs préremplies, des champs verrouillés, et du contexte.

## Créer une demande

Deux appels API : demandez au formulaire ce qu'on peut lui dire, puis assignez-le à une personne avec les valeurs que vous connaissez déjà.

<h2 id="start-in-the-share-sheet">Commencer dans le panneau Partager</h2>

<p>
  Ouvrez votre formulaire publié, cliquez sur <strong>Partager</strong>, et choisissez l'onglet <strong>Demandes</strong>. La carte qui s'y
  trouve vous donne tout ce dont vous avez besoin pour faire le premier appel :
</p>

<ul>
  <li>
    L'<strong>id du formulaire</strong>, avec un bouton de copie.
  </li>
  <li>
    Un extrait <strong>curl</strong> et une invite <strong>MCP</strong>, tous deux construits à partir des vraies clés de champ de votre
    formulaire — l'exemple s'adresse donc déjà aux champs que ce formulaire possède réellement.
  </li>
  <li>
    Un onglet <strong>Manuel</strong> qui crée une demande à la main, et <strong>Essayez par vous-même</strong>, qui transforme ce que vous
    y avez saisi en une demande en <a href="#test-mode">mode test</a> et vous remet son lien.
  </li>
  <li>
    Un lien vers la <a href="/fr/requests/managing-requests">page Demandes</a>, filtrée sur ce formulaire.
  </li>
</ul>

> ⚠️ **Publiez d'abord**
> <p>
>     Un formulaire non publié ne peut pas recevoir de demande, et les extraits restent désactivés jusqu'à ce que vous publiiez. Les clés de
>     champ sont figées à la première publication — c'est ce qui permet à votre automatisation de continuer à s'adresser à{' '}
>     <code>company_name</code> un an plus tard. Voir <a href="/fr/requests/field-keys">Clés de champ</a>.
>   </p>

<h2 id="discover-fields">Étape 1 — Découvrir les champs</h2>

<p>
  <code>fields.list</code> renvoie chaque champ de la version actuellement publiée du formulaire, avec la clé pour l'adresser, la forme de
  valeur qu'il attend, et le compartiment auquel il appartient.
</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> marque un champ caché. Sa valeur va dans <code>context</code>, jamais dans <code>prefill</code> ; la clé d'un
    champ caché dans <code>prefill</code> est rejetée avec <code>UNKNOWN_FIELD_KEY</code>.
  </li>
  <li>
    <code>calculated: true</code> marque un champ calculé. Le formulaire détermine sa valeur lui-même, donc rien ne peut en envoyer une ;
    vous la relisez sous sa clé dans <code>answers</code>.
  </li>
  <li>
    <code>prefillable: false</code> marque un champ pour lequel personne ne peut fournir de valeur : téléversement de fichier, signature,
    paiement, prise de rendez-vous, et blocs Documents. Le destinataire remplit ces questions. Les champs cachés et les champs calculés
    affichent eux aussi <code>prefillable: false</code> : les champs cachés reçoivent leur valeur via <code>context</code>, les champs
    calculés n'en reçoivent aucune.
  </li>
  <li>
    <code>options</code> liste les choix d'une question à choix. Envoyez la <strong>clé</strong> de l'option, pas son libellé ; le libellé
    est là pour vous permettre de faire correspondre le choix que vous connaissez à sa clé. Une question matricielle liste ses{' '}
    <code>rows</code> et <code>columns</code> de la même façon.
  </li>
  <li>
    Les groupes répétables reviennent comme une seule entrée avec <code>type: "group"</code>, <code>repeating: true</code> et une liste de{' '}
    <code>members</code>.
  </li>
</ul>

<h2 id="create">Étape 2 — Créer la demande</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> vaut <code>"queued"</code> quand Formstep envoie l'invitation par e-mail, et <code>"not_requested"</code>{' '}
  quand vous livrez le lien vous-même.
</p>

<h2 id="three-buckets">Préremplissage, champs verrouillés, et contexte</h2>

<p>Trois choses différentes peuvent être attachées à une demande, et les confondre est l'erreur la plus fréquente au départ.</p>

<h3 id="prefill">Préremplissage</h3>

<p>
  Des réponses initiales pour les questions visibles, pour que le destinataire relise et corrige plutôt que de tout retaper. Tout ce que
  vous savez déjà sur lui a sa place ici — le nom de l'entreprise depuis votre CRM, le montant de la facture, les réponses de l'an dernier.
</p>

<h3 id="locked-fields">Champs verrouillés</h3>

<p>
  Listez une clé préremplie dans <code>readonly</code> et le destinataire voit la valeur mais ne peut pas la modifier. Utilisez ceci pour
  les faits qu'il confirme plutôt qu'il ne fournit — le numéro de contrat, le prix convenu. Le verrouillage est propre à chaque demande : le
  formulaire lui-même n'est pas touché, et le même champ reste librement modifiable sur la prochaine demande.
</p>

<p>
  Chaque clé verrouillée doit aussi être préremplie, et un champ verrouillé <em>obligatoire</em> doit être prérempli avec une valeur non
  vide — sinon le destinataire se retrouverait face à un formulaire qu'il ne pourrait jamais soumettre, et Formstep refuse l'appel plutôt
  que de créer ce piège.
</p>

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

<p>
  Des valeurs de confiance pour les <a href="/fr/building-forms/hidden-fields">champs cachés</a> du formulaire — un numéro de dossier, un id
  d'exécution de workflow, un montant. Le contexte alimente les variables, la logique conditionnelle, les champs calculés, et le texte des
  e-mails, revient inchangé dans le callback, et le destinataire ne peut pas le modifier. C'est la différence avec le fait d'initialiser un
  champ caché via une URL sur un lien public, où n'importe qui peut modifier la chaîne de requête ; les liens de demande ignorent
  entièrement les paramètres de requête de l'URL. Les valeurs de contexte doivent être une chaîne, un nombre ou un booléen.
</p>

<p>
  Le contexte n'est pas de forme libre : chaque clé doit être un champ caché sur la version publiée du formulaire, et toute autre clé est
  rejetée avec <code>UNKNOWN_FIELD_KEY</code>. Un suivi qui n'a pas de champ caché correspondant, comme un id d'exécution, a sa place dans{' '}
  <a href="#metadata">metadata</a>.
</p>

<p>
  Les champs cachés ne sont pas affichés sur le formulaire, mais une valeur de contexte n'est pas secrète pour le destinataire. Il la voit
  partout où le formulaire ou l'invitation la montre : une <a href="/fr/building-forms/answer-piping">mention</a> dans le contenu du
  formulaire ou le texte de l'e-mail, ou une question visible qui utilise ce champ caché comme{' '}
  <a href="/fr/building-forms/field-configuration#default-values">valeur par défaut</a>. Dans ce dernier cas, le destinataire voit la valeur
  de contexte préremplie dans cette question et peut modifier la réponse. La valeur de contexte elle-même reste inchangée. Un{' '}
  <code>prefill</code> pour la clé propre de cette question l'emporte sur la valeur par défaut.
</p>

<h3 id="metadata">Métadonnées</h3>

<p>
  Votre propre suivi interne — un id d'exécution, un id d'enregistrement CRM. Cela n'atteint jamais le formulaire, donc ça ne peut pas être
  intégré dans du texte ni lu par la logique ; ça voyage simplement et revient dans chaque callback et chaque lecture de statut.
</p>

<h2 id="value-shapes">Formes de valeur</h2>

<p>
  Envoyez les valeurs dans la forme demandée par <code>type</code> issu de <code>fields.list</code>. Une forme incorrecte revient comme une
  erreur de validation nommant la clé, le type attendu, et — pour les questions à choix — les valeurs qui auraient été acceptées.
</p>

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

<p>
  Un <a href="/fr/building-forms/documents-block">bloc Documents</a> remet des fichiers au répondant. Ses fichiers créés sont les mêmes pour
  tout le monde et restent toujours ; une demande ajoute des fichiers pour son seul destinataire en dessous — le propre contrat de bail du
  client, une copie de pièce d'identité à vérifier. Les octets ne transitent jamais par l'appel API lui-même : téléversez d'abord, puis
  référencez.
</p>

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

<p>
  <code>name</code> remplace le nom d'affichage enregistré avec l'envoi. Si le formulaire compte plusieurs blocs Documents, désignez la
  cible avec <code>field</code>, la clé de champ du bloc (<code>fields.list</code> la liste, avec les documents de l'auteur que chaque
  répondant reçoit déjà). Les fichiers apparaissent sous ces documents de l'auteur : une demande ajoute des fichiers, elle n'en remplace
  jamais. Un envoi peut être référencé par autant de demandes que vous voulez — une liste de prix envoyée une fois sert cinq cents demandes.
</p>

<p>
  Limites : PDF et images uniquement, 25 Mo par document, 100 Mo par demande (<code>DOCUMENTS_TOO_LARGE</code>), et au maximum 20 documents
  par bloc en comptant ceux de l'auteur (<code>DOCUMENTS_TOO_MANY</code>). Les fichiers comptent dans le quota de stockage de votre espace
  de travail et sont libérés dès que les demandes qui les référencent sortent de la période de conservation du formulaire. La soumission
  enregistre la liste que le destinataire a vue sous la clé de champ du bloc, si bien que le callback vous indique exactement quels fichiers
  ont été donnés à cette personne.
</p>

<h2 id="options">Le reste des options</h2>

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

<p>
  Passez <code>test: true</code> pour éprouver tout le circuit avant une exécution réelle. Une demande test est réelle sur tout ce qui
  compte pour le câblage : le lien s'ouvre et peut être complété, le <a href="/fr/requests/callbacks">callback</a> se déclenche comme
  d'habitude, et <code>requests.get</code> renvoie les réponses. Ce qu'elle ne fait jamais, c'est atteindre quelqu'un ou quelque chose que
  vous devriez ensuite nettoyer :
</p>

<ul>
  <li>
    Aucune invitation ni aucun rappel n'est envoyé, quoi que dise <code>delivery</code>. <strong>Envoyer un rappel</strong> est refusé
    dessus, et elle ne consomme rien de votre allocation mensuelle.
  </li>
  <li>
    Le callback porte <code>"test": true</code>, pour que votre flux de travail puisse bifurquer ou ignorer l'événement.
  </li>
  <li>
    La soumission est stockée mais ne compte pas : ni sur votre quota mensuel (un test se complète même quand le quota est épuisé), et elle
    n'apparaît jamais dans les compteurs de soumissions du formulaire, l'onglet soumissions, les exports, ou vos intégrations. Personne
    n'est notifié.
  </li>
  <li>
    La demande est masquée de la <a href="/fr/requests/managing-requests">page Demandes</a> derrière{' '}
    <strong>Afficher les demandes de test</strong>, exclue de l'entonnoir de demandes dans Analytiques, et exclue de{' '}
    <code>requests.list</code> sauf si vous passez <code>includeTest: true</code>.
  </li>
  <li>
    Le lien se ferme dans les 24 heures, même si <code>expiresAt</code> demande plus ; le <code>expiresAt</code> de la réponse indique
    quand. Sur Free, un workspace peut créer 10 demandes de test par jour. La suivante échoue avec <code>RATE_LIMITED</code> et la raison{' '}
    <code>TEST_REQUEST_LIMIT_REACHED</code>, et <code>retryAfterMs</code> indique quand vous pouvez réessayer. Pro et Business n'ont pas de
    plafond quotidien.
  </li>
</ul>

<p>
  <strong>Essayez par vous-même</strong> sur le panneau Partager est ce mode en un seul clic : il reprend le brouillon de l'onglet Manuel —
  préremplissage, verrous, contexte, callback, expiration — adresse la demande à votre propre compte, n'envoie aucun e-mail, et vous remet
  le lien à ouvrir vous-même.
</p>

<h3 id="allowance">Ce que coûte une demande</h3>

<p>
  Chaque plan dispose d'une seule <strong>allocation mensuelle</strong> partagée par les deux canaux : une soumission par lien de partage
  consomme une unité, et chaque demande que vous créez aussi — que le destinataire y réponde, l'ignore, ou que vous l'annuliez. La
  soumission qu'une demande recueille est déjà payée et ne compte nulle part. Free inclut 1 000 unités par mois, Pro et Business 50 000 ; le
  compteur se réinitialise le 1er de chaque mois, UTC. Une fois la limite atteinte, <code>requests.create</code> échoue avec{' '}
  <code>UPGRADE_REQUIRED</code> et la raison <code>MONTHLY_ALLOWANCE_REACHED</code> ; les demandes déjà créées restent répondables.
</p>

<p>
  Sur Free, une demande créée avec <code>"delivery": "email"</code> consomme aussi l'une des{' '}
  <a href="/fr/subscription-billing/limits-quotas#free-invitations">10 invitations gratuites</a> du compte. Elles ne se réinitialisent
  jamais ; une fois épuisées, la livraison par e-mail échoue avec <code>UPGRADE_REQUIRED</code> et la raison{' '}
  <code>FREE_INVITATIONS_USED</code>.
</p>

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

<p>
  Passez la même <code>idempotencyKey</code> avec le même corps et vous récupérez la demande d'origine, avec <code>deduplicated: true</code>{' '}
  et le lien d'origine — pas de seconde demande, pas de second e-mail. Réutilisez la clé avec un corps <em>différent</em> et Formstep refuse
  avec <code>IDEMPOTENCY_CONFLICT</code> plutôt que de deviner ce que vous vouliez dire. Les clés sont propres à l'espace de travail et
  valables 30 jours ; passé ce délai, la même clé démarre une nouvelle demande.
</p>

<p>
  Dans un outil de workflow, l'id d'exécution est la clé naturelle : une exécution relancée après un incident réseau retrouve la demande
  qu'elle avait déjà créée.
</p>

<h3 id="rate-limit">Limite de débit</h3>

<p>
  <code>requests.create</code> et <code>documents.create</code> partagent un budget de <strong>60 appels par minute</strong>, compté par
  jeton API (ou par utilisateur, pour un appel effectué sans jeton). Un arriéré que vous videz devrait s'autoréguler ; une rafale qui
  dépasse le budget est refusée et peut être retentée.
</p>

<h3 id="custom-domains">Domaines personnalisés</h3>

<p>
  Si le formulaire est déjà publié sur l'un de vos <a href="/fr/branding-domains/custom-domains">domaines personnalisés</a>, les liens de
  demande y sont générés automatiquement — <code>https://forms.votreentreprise.com/r/rq_…</code>. Nommez explicitement <code>domainId</code>{' '}
  quand le formulaire est publié sur plus d'un domaine. Le domaine doit appartenir au même workspace que le formulaire.
</p>

<h2 id="what-the-recipient-sees">Ce que voit le destinataire</h2>

<p>
  Exactement le formulaire que vous avez conçu — même thème, même logo, même langue — avec ses valeurs en place, les champs verrouillés en
  lecture seule, et aucun captcha à résoudre. Quand il soumet, il obtient votre page de remerciement. S'il revient sur le lien après coup,
  il obtient la page de résultat au lieu d'un formulaire vide.
</p>

<p>
  Il n'y a aucun message de votre automatisation sur la page. Tout ce que le destinataire doit savoir a sa place dans le formulaire
  lui-même, où vous pouvez le personnaliser en <a href="/fr/building-forms/answer-piping">mentionnant</a> une valeur de contexte ou un champ
  prérempli.
</p>

<p>
  Un agent IA exécute les deux mêmes étapes que <code>fields_list</code> et <code>request_create</code>, avec les mêmes options — documents
  et <code>domainId</code> compris. Voir <a href="/fr/developers/mcp-server#requests">Demandes sur le serveur MCP</a>.
</p>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Clés de champ](/fr/requests/field-keys) — D'où viennent ces clés et comment les garder stables.
  - [Callbacks et signature](/fr/requests/callbacks) — Ce qui arrive quand le destinataire a terminé.
  - [Dépannage](/fr/requests/troubleshooting) — Chaque motif de refus et quoi faire.
  - [Référence API](/fr/developers/rest-api) — Liste complète des paramètres pour chaque méthode de demande.
</div>
