# API formulaires

Un formulaire se crée dans l’application (voir [Créer un formulaire](/docs/guides/creer-un-formulaire/)). Son adresse d’envoi reçoit les réponses depuis n’importe quelle page, sans clé. Avec une clé à accès complet, l’API liste tes formulaires et lit leurs réponses : c’est ce qu’utilise Host pour proposer un formulaire à un site qu’il héberge.

Pour recevoir chaque réponse sans interroger l’API, utilise un [webhook](/docs/guides/recevoir-les-reponses-par-webhook/).

## POST `/f/:public_id`

Envoie une réponse au formulaire. Pas de clé : l’identifiant public n’ouvre que l’envoi de réponses à ce formulaire.

Le corps est en JSON, en `application/x-www-form-urlencoded` ou en `multipart/form-data`, 64 Ko au plus, une valeur par champ. Les noms sont les variables du formulaire (`prenom`, `email`…). Une case cochée vaut `oui`, `on` ou `true`.

```html
<form action="https://api.mail.hgdc.ch/f/k7m2p9x4q8ab" method="POST">
  <input name="prenom" required>
  <input name="email" type="email" required>
  <textarea name="message"></textarea>
  <label><input type="checkbox" name="consentement" value="oui"> J'accepte de recevoir vos nouvelles</label>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
  <button type="submit">Envoyer</button>
</form>
```

Avec `Accept: application/json`, la réponse est en JSON. Statut `200` :

```json
{ "ok": true, "message": "Merci, votre message a bien été envoyé.", "redirect_url": "https://ton-site.ch/merci" }
```

`redirect_url` n’est là que si le formulaire a une page de remerciement. Sans `Accept: application/json` (formulaire HTML classique), la réponse est une redirection `303` vers cette page, ou vers la page de remerciement de Mail.

### Champs spéciaux

| Champ                   | Rôle                                                                                             |
| ----------------------- | ------------------------------------------------------------------------------------------------ |
| `_gotcha`               | Piège à robots : laisse-le caché et vide. Rempli, l’envoi semble réussir mais rien n’est gardé.  |
| `cf-turnstile-response` | Jeton de Cloudflare Turnstile, posé par son widget. Obligatoire quand le formulaire a Turnstile. |

### Erreurs

Toujours `{ "ok": false, "error": { "code", "message" } }`. Le message s’adresse au visiteur, en vouvoiement : tu peux l’afficher tel quel.

| Statut | `code`               | Quand                                                                         |
| ------ | -------------------- | ----------------------------------------------------------------------------- |
| 404    | `not_found`          | Identifiant inconnu.                                                          |
| 403    | `origin_not_allowed` | La page n’est pas sur un des sites autorisés du formulaire.                   |
| 403    | `form_closed`        | Le formulaire est en pause.                                                   |
| 413    | `too_large`          | Plus de 64 Ko.                                                                |
| 429    | `rate_limited`       | Trop d’envois en peu de temps depuis cette adresse IP, ou pour ce formulaire. |
| 422    | `captcha_failed`     | Le jeton Turnstile manque ou n’est pas valide.                                |
| 422    | `validation_error`   | Un champ est à corriger : `errors` donne un message par champ.                |

```json
{
  "ok": false,
  "error": { "code": "validation_error", "message": "Certains champs sont à corriger." },
  "errors": { "email": "Cette adresse e-mail n'est pas valide." }
}
```

### Origine et CORS

Le formulaire n’accepte que les pages de ses sites autorisés, lus dans l’en-tête `Origin` (sinon `Referer`). Sans site autorisé, toute page peut l’envoyer. La requête `OPTIONS` d’un navigateur est acceptée, et la réponse porte `Access-Control-Allow-Origin` pour une origine permise.

## GET `/forms`

Clé à accès complet. Renvoie les 100 formulaires les plus récents, avec leurs champs et leur adresse d’envoi.

```bash
curl https://api.mail.hgdc.ch/forms \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY"
```

```json
{
  "object": "list",
  "data": [
    {
      "object": "form",
      "id": "jd7f3k9m2p5q8r1t4v6w0x3y",
      "public_id": "k7m2p9x4q8ab",
      "name": "Contact",
      "status": "active",
      "endpoint": "https://api.mail.hgdc.ch/f/k7m2p9x4q8ab",
      "fields": [],
      "allowed_origins": ["ton-site.ch"],
      "turnstile_site_key": null,
      "response_count": 12,
      "created_at": "2026-10-15T09:00:00.000Z"
    }
  ]
}
```

Types de champ : `text`, `email`, `phone`, `textarea`, `choice`, `checkbox`, `consent`.

## GET `/forms/:id`

Lit un formulaire. `:id` est l’identifiant ou l’identifiant public. Même forme qu’un élément de la liste. Un formulaire d’un autre espace renvoie `404 not_found`.

## GET `/forms/:id/responses`

Lit les réponses, les plus récentes d’abord. `limit` de 1 à 100 (50 par défaut), puis `after` avec le `next_cursor` reçu.

```json
{
  "object": "list",
  "has_more": false,
  "next_cursor": null,
  "data": [
    {
      "object": "form_response",
      "id": "m2k8p4x7q1ab9c3d5e6f0g2h",
      "values": { "prenom": "Léa", "email": "lea@exemple.ch", "message": "Bonjour", "consentement": "oui" },
      "email": "lea@exemple.ch",
      "contact_id": "kn4d7b2x9q1m6v3r8c0w5h2t",
      "consent": { "status": "confirmed", "text": "J'accepte de recevoir vos nouvelles", "at": "2026-10-15T09:12:00.000Z" },
      "created_at": "2026-10-15T09:12:00.000Z"
    }
  ]
}
```

## Si ça ne marche pas

- **`403 origin_not_allowed`** : le site qui envoie le formulaire n’est pas dans la liste des sites autorisés. Ajoute son domaine dans les réglages du formulaire.
- **`422 captcha_failed`** : le widget Turnstile n’a pas transmis son jeton. Vérifie que `cf-turnstile-response` est bien dans le formulaire.
- **`429 rate_limited`** : trop de soumissions depuis la même adresse. Réessaie dans une minute.
