POST /api/v1/signup-pages
Crée une page d'inscription. Répond 201 avec la page créée — son id est ce que prend tout autre appel sur les pages d'inscription.
Sans status, la page est un brouillon : elle répond par une page introuvable à son adresse et ne collecte rien tant que vous ne la publiez pas. Le provisionnement ne met jamais une adresse en ligne par accident.
Autorisation
| Rôle dans l'organisation | Cet endpoint |
|---|---|
OWNER | Autorisé |
ADMIN | Autorisé |
MEMBER | Refusé — 403 |
Corps de la requête
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
name | string | oui | Nom interne, de 1 à 100 caractères. Seule votre équipe le voit |
slug | string | oui | Dernière partie de l'adresse publique. De 2 à 100 caractères, lettres minuscules, chiffres et tirets uniquement. Unique dans l'organisation |
listId | string | oui | La liste qui reçoit les inscrits. Doit appartenir à votre organisation |
content | string | oui | Le HTML de la page. Doit contenir {{form}} |
heroTitle | string | oui | Le titre que lisent les visiteurs, jusqu'à 200 caractères |
submitLabel | string | oui | Libellé du bouton d'inscription, jusqu'à 60 caractères |
successMessage | string | oui | Affiché après une inscription quand il n'y a pas de redirection, jusqu'à 300 caractères |
sourceTemplateName | string | non | Le thème dont le HTML a été copié, jusqu'à 100 caractères |
heroSubtitle | string | non | Sous-titre sous le titre, jusqu'à 300 caractères |
redirectUrl | string | non | Adresse http:// ou https:// vers laquelle envoyer le visiteur au lieu d'afficher successMessage |
collectFirstName | boolean | non | true par défaut |
collectLastName | boolean | non | false par défaut |
status | string | non | draft (par défaut) ou published |
json
{
"name": "Newsletter footer",
"slug": "newsletter",
"listId": "9f8e7d6c-1111-4bbb-8ccc-ddddeeeeffff",
"content": "<html><body><h1>{{heroTitle}}</h1>{{form}}{{poweredBy}}</body></html>",
"heroTitle": "Join the list",
"submitLabel": "Sign me up",
"successMessage": "Check your inbox.",
"status": "published"
}Exemple
bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
-d '{"name":"Newsletter footer","slug":"newsletter","listId":"'$LIST_ID'","content":"<html><body>{{form}}</body></html>","heroTitle":"Join the list","submitLabel":"Sign me up","successMessage":"Check your inbox."}' \
"https://www.agentsmail.io/api/v1/signup-pages"Réponse
201 Created— la page créée,contentcompris.400 Bad Request— un champ obligatoire est absent ou mal formé, ou le HTML ne contient pas{{form}}. Quand c'est la forme qui est en cause,detailsporte les erreurs champ par champ.401 Unauthorized— clé absente ou invalide, ou son propriétaire a quitté l'organisation.403 Forbidden— le propriétaire de la clé estmemberde l'organisation, pasadmin.404 Not Found—listIdne désigne aucune liste de votre organisation. Inconnue ou pas à vous : la réponse est la même.409 Conflict— une autre page occupe déjà ceslug; le message la nomme. Rien n'a été créé.429 Too Many Requests— la clé a dépassé sa limite de débit.
Champs de la réponse
| Champ | Type | Description |
|---|---|---|
success | boolean | Indique si l'opération a réussi |
data.id | string | Identifiant unique de la page |
data.name | string | Nom interne |
data.slug | string | Dernière partie de l'adresse publique |
data.status | string | draft ou published |
data.listId | string | La liste qui reçoit les inscrits |
data.content | string | Le HTML de la page |
data.sourceTemplateName | string | null | Le thème dont elle a été copiée, null pour un HTML collé |
data.heroTitle | string | Le titre que lisent les visiteurs |
data.heroSubtitle | string | null | Le sous-titre, null s'il n'est pas défini |
data.submitLabel | string | Libellé du bouton d'inscription |
data.successMessage | string | Affiché après une inscription quand il n'y a pas de redirection |
data.redirectUrl | string | null | Où va le visiteur après s'être inscrit, null pour aucune redirection |
data.collectFirstName | boolean | Si le formulaire demande un prénom |
data.collectLastName | boolean | Si le formulaire demande un nom de famille |
data.createdAt | string | Date de création ISO |
data.updatedAt | string | Date ISO de la dernière modification |
Exemple de réponse
json
{
"success": true,
"data": {
"id": "3d3d3d3d-4444-4eee-9fff-000011112222",
"name": "Newsletter footer",
"slug": "newsletter",
"status": "published",
"listId": "9f8e7d6c-1111-4bbb-8ccc-ddddeeeeffff",
"content": "<html><body>{{form}}</body></html>",
"sourceTemplateName": null,
"heroTitle": "Join the list",
"heroSubtitle": null,
"submitLabel": "Sign me up",
"successMessage": "Check your inbox.",
"redirectUrl": null,
"collectFirstName": true,
"collectLastName": false,
"createdAt": "2026-08-23T10:22:05.000Z",
"updatedAt": "2026-08-23T10:22:05.000Z"
}
}Recette pour agent
Copiez un thème, puis publiez une fois l'adresse confirmée — trois appels, dans cet ordre :js
const themes = (await get('/api/v1/signup-page-templates')).data
const theme = themes[0]
const page = (
await post('/api/v1/signup-pages', {
name: 'Newsletter footer',
slug: 'newsletter',
listId,
content: theme.content,
sourceTemplateName: theme.name,
heroTitle: 'Join the list',
submitLabel: 'Sign me up',
successMessage: 'Check your inbox.',
})
).data
// The page is still a draft here: nothing answers at its address yet
await patch(`/api/v1/signup-pages/${page.id}`, {status: 'published'})409 au deuxième appel signifie que le slug est pris. Choisissez-en un autre — réessayer n'y changera rien.
Remarques
- Le HTML est une copie, jamais un lien. Modifier plus tard le thème dans le catalogue laisse votre page exactement telle quelle.
redirectUrletsuccessMessagene sont pas deux options qui s'additionnent : une page avec redirection n'affiche jamais le message.