POST /api/v1/signup-pages
Crea una página de suscripción. Responde 201 con la página creada: su id es lo que reciben todas las demás llamadas de páginas de suscripción.
Sin status, la página es un borrador: responde con una página de no encontrado en su dirección y no recoge nada hasta que la publiques. Aprovisionar nunca pone una dirección en línea por accidente.
Autorización
| Rol en la organización | Este endpoint |
|---|---|
OWNER | Permitido |
ADMIN | Permitido |
MEMBER | Rechazado — 403 |
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | string | sí | Nombre interno, de 1 a 100 caracteres. Solo lo ve tu equipo |
slug | string | sí | Última parte de la dirección pública. De 2 a 100 caracteres, solo letras minúsculas, dígitos y guiones. Único en la organización |
listId | string | sí | La lista que recibe a los suscriptores. Debe pertenecer a tu organización |
content | string | sí | El HTML de la página. Debe llevar {{form}} |
heroTitle | string | sí | El título que leen los visitantes, hasta 200 caracteres |
submitLabel | string | sí | Texto del botón de suscripción, hasta 60 caracteres |
successMessage | string | sí | Se muestra tras una suscripción cuando no hay redirección, hasta 300 caracteres |
sourceTemplateName | string | no | El tema del que se copió el HTML, hasta 100 caracteres |
heroSubtitle | string | no | Subtítulo bajo el título, hasta 300 caracteres |
redirectUrl | string | no | Dirección http:// o https:// a la que enviar al visitante en lugar de mostrar successMessage |
collectFirstName | boolean | no | true por defecto |
collectLastName | boolean | no | false por defecto |
status | string | no | draft (por defecto) o 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"
}Ejemplo
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"Respuesta
201 Created: la página creada,contentincluido.400 Bad Request: falta un campo obligatorio o está mal formado, o el HTML no lleva{{form}}. Cuando el fallo está en la forma,detailslleva los problemas campo por campo.401 Unauthorized: clave ausente o no válida, o su propietario dejó la organización.403 Forbidden: el propietario de la clave esmemberde la organización, noadmin.404 Not Found:listIdno nombra ninguna lista de tu organización. Desconocida y ajena responden lo mismo.409 Conflict: otra página ya ocupa eseslug; el mensaje la nombra. No se creó nada.429 Too Many Requests: la clave superó su límite de peticiones.
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | Indica si la operación tuvo éxito |
data.id | string | Identificador único de la página |
data.name | string | Nombre interno |
data.slug | string | Última parte de la dirección pública |
data.status | string | draft o published |
data.listId | string | La lista que recibe a los suscriptores |
data.content | string | El HTML de la página |
data.sourceTemplateName | string | null | El tema del que se copió, null para HTML pegado |
data.heroTitle | string | El título que leen los visitantes |
data.heroSubtitle | string | null | El subtítulo, null si no está definido |
data.submitLabel | string | Texto del botón de suscripción |
data.successMessage | string | Se muestra tras una suscripción cuando no hay redirección |
data.redirectUrl | string | null | Adónde va el visitante tras suscribirse, null si no hay ninguna |
data.collectFirstName | boolean | Si el formulario pide el nombre |
data.collectLastName | boolean | Si el formulario pide el apellido |
data.createdAt | string | Fecha de creación ISO |
data.updatedAt | string | Fecha ISO del último cambio |
Ejemplo de respuesta
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"
}
}Receta para agentes
Copia un tema y publica una vez confirmada la dirección: tres llamadas, en este orden: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
// Aquí la página sigue siendo un borrador: nada responde todavía en su dirección
await patch(`/api/v1/signup-pages/${page.id}`, {status: 'published'})409 en la segunda llamada significa que el slug está ocupado. Elige otro: reintentar no servirá.
Notas
- El HTML es una copia, nunca un enlace. Si el tema cambia después en el catálogo, tu página queda exactamente como está.
redirectUrlysuccessMessageno son alternativas que se definen a la vez: una página con redirección nunca muestra el mensaje.