Landing Pages API
Una página de destino es una página pública que tu equipo construye en el editor de Joryio (arrastrar y soltar o HTML), publica en una URL y, opcionalmente, dota de un formulario. Un formulario enviado identifica a la persona, escribe atributos y suscripciones a listas en su perfil y registra los eventos Landing Page Viewed y Landing Page Form Submitted, que segmentos, embudos, journeys y campañas disparadas pueden usar como cualquier otro evento.
Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com/api - ver Referencia de la API.
Permisos: landing_pages:read, landing_pages:write, landing_pages:publish (ver Claves de API).
Dónde vive una página
Una página publicada se sirve en <pages host>/<workspace handle>/<slug>. El handle del espacio de trabajo es su propio id - opaco, permanente, nunca se elige ni se cambia (https://<workspace id>.eu1.jrypages.com/<slug> en el dominio de páginas); el slug es único por espacio de trabajo y se deriva del nombre de la página salvo que lo definas. Un espacio de trabajo puede servir sus páginas en un subdominio propio en su lugar (https://pages.tumarca.com/<slug>) - consulta Usa tu propio dominio. Cada página tiene además un enlace de vista previa (?preview=<signature>, de previewUrl): el borrador actual, visible sin iniciar sesión en Joryio para quien tenga el enlace, nunca cacheado ni indexado; deja de funcionar al retirar la página.
El esquema del formulario
form es lo que valida la ruta pública de envío. El marcado de la página solo lleva los nombres de los campos.
{
"fields": [
{ "key": "email", "type": "email", "label": "Email", "required": true },
{ "key": "first_name", "type": "text", "label": "First name", "attribute": "firstName" },
{ "key": "size", "type": "radio", "options": [{ "label": "S", "value": "s" }, { "label": "M", "value": "m" }] },
{ "key": "marketing", "type": "checkbox", "label": "Email me offers", "lists": ["<listId>"] },
{ "key": "lists", "type": "subscriptions", "lists": ["<listId>", "<listId>"] },
{ "key": "src", "type": "hidden", "value": "utm_source" }
],
"submit": { "action": "message", "message": "Thanks - check your inbox." },
"identity": "email"
}
| Tipo | Escribe | Notas |
|---|---|---|
email, phone | el email / teléfono del contacto | como máximo uno de cada; phone con lists suscribe a esas listas |
text, number, date, dropdown, radio | el atributo nombrado en attribute (por defecto: la clave) | dropdown y radio necesitan options; radio siempre es obligatorio |
checkbox | un atributo booleano; con lists, suscribe al marcarlo | las casillas de consentimiento nunca vienen marcadas |
checkbox_group | un atributo de tipo lista | options obligatorio |
subscriptions | suscribe a las listas marcadas | lists obligatorio |
hidden | un atributo con un value fijo, o utm_source, utm_medium, utm_campaign, utm_content, utm_term, referrer, page |
submit.eventName (opcional) dispara ese evento personalizado en la persona además del evento de plataforma, con los valores enviados como propiedades; submit.setAttributes (opcional, { "lead_source": "spring_sale" }) escribe valores fijos de atributos en cada envío. Ambos pueden fijarse también en el botón de envío del editor. submit.action es message (muestra message en el sitio) o redirect (a url, solo https). Un formulario necesita un campo de email o teléfono para identificar a la persona; un enlace desde un mensaje de Joryio podrá llevar un token de persona en su lugar (previsto).
Endpoints
| Método | Ruta | Permiso | Qué hace |
|---|---|---|---|
GET | /landing-pages | landing_pages:read | Lista paginada ({ data, pagination }) con estado, estadísticas, quién creó y quién editó por última vez, y URL pública. ?templates=true lista plantillas (tuyas y luego las integradas); filtros ?status= (separados por comas), ?q=, ?tags=, ?createdBy=, ?editedBy=; orden sobre toda la lista con ?sortBy= (name, status, views, submissions, createdAt, editedAt, publishedAt) y ?sortOrder=; ?limit= (máx. 100), ?offset= |
GET | /landing-pages/:id/history | landing_pages:read | Historial de auditoría de una página, lo más reciente primero: creación, actualización, publicación, retirada y eliminación con el actor. ?limit= 1-200 |
POST | /landing-pages | landing_pages:write | Crea un borrador: name, slug, mode (dnd o html), html, css, grapejsData, form, seo (title, description, faviconUrl, ogImageUrl, noIndex), language, dir, tags, sdkEnabled, personalized |
GET | /landing-pages/:id | landing_pages:read | Una página completa, con publicUrl y previewUrl |
PUT | /landing-pages/:id | landing_pages:write | Actualiza; la página en vivo solo cambia en la siguiente publicación |
DELETE | /landing-pages/:id | landing_pages:write | Elimina un borrador (retira primero la publicación) |
POST | /landing-pages/:id/duplicate | landing_pages:write | Copia una página o crea una desde una plantilla; { "asTemplate": true } guarda la copia como plantilla |
POST | /landing-pages/:id/publish | landing_pages:publish | Congela el documento saneado y lo sirve |
POST | /landing-pages/:id/unpublish | landing_pages:publish | La retira; el token de firma rota |
GET | /landing-pages/:id/preview | landing_pages:read | El borrador renderizado como la página en vivo (HTML); sus envíos se descartan |
GET | /landing-pages/:id/submissions | landing_pages:read | El registro de envíos, del más reciente al más antiguo; datos personales enmascarados sin pii:view. ?limit= (máx. 200), ?before= |
GET | /landing-pages/:id/submissions.csv | landing_pages:read | Todos los envíos como CSV en streaming, del más antiguo al más reciente: submitted_at, una columna por campo del formulario (cada respuesta, asignada o no), person_id, identified_by, lists_subscribed, referrer y los valores utm_*; datos personales enmascarados sin pii:view |
GET | /landing-pages/:id/report | landing_pages:read | Vistas, primeras vistas y envíos por día (UTC), totales y tasa de conversión del rango, contadores acumulados. ?days= 1-365 (30 por defecto) |
GET | /landing-pages/:id/review | landing_pages:read | Revisión de lanzamiento: hallazgos (code, severity blocker o recommendation, scope, params). Los bloqueadores impiden publicar; POST .../publish responde 400 con los mismos hallazgos |
GET | /landing-pages/handle | landing_pages:read | La dirección de páginas del espacio de trabajo: { handle, host, example, customHost }. El handle es el id del propio espacio de trabajo, la primera etiqueta de cada URL de página - opaco, permanente, nunca se elige ni se cambia; customHost es el dominio propio activo del espacio de trabajo, cuando lo tiene |
GET | /landing-pages/domain | landing_pages:publish | El dominio propio del espacio de trabajo: { domain, limit, used, pagesHost } - domain (o null) con id, hostname, status (pending, active, failed), verifiedAt, target, records (los registros DNS a añadir), providerConfigured, lastError; limit y used cuentan los dominios de landing pages de la organización |
POST | /landing-pages/domain | landing_pages:publish | Añade tu propio dominio: { "hostname": "pages.tumarca.com" }. Responde el dominio como pending con sus registros DNS. 409 cuando el espacio de trabajo ya tiene uno o la organización está en su límite (el mensaje nombra el límite) |
POST | /landing-pages/domain/:id/verify | landing_pages:publish | Vuelve a comprobar el DNS y el certificado en el edge; responde el dominio con su estado actual |
DELETE | /landing-pages/domain/:id | landing_pages:publish | Quita el dominio; las URL de las páginas vuelven de inmediato al dominio de páginas |
GET | /landing-pages/capabilities | landing_pages:read | { botCheck }: si el servidor puede verificar Cloudflare Turnstile, para que un formulario pueda poner botCheck: true |
Rutas públicas (sin autenticación)
| Método | Ruta | Qué hace |
|---|---|---|
GET | /p/:handle/:slug | La página publicada |
POST | /p/:id/view | Baliza de vista que envía la propia página |
POST | /p/:id/submit | Envío del formulario; el cuerpo lleva la firma incrustada en el documento publicado, así que solo la página tal como se publicó puede enviar |
GET | /p/go/:workspaceId/:slug | Adonde apunta {{ 'slug' | landing_page_url }} en un mensaje: un 302 a la URL canónica de la página, con la cadena de consulta (token de la persona, valores UTM) |
GET | /p/confirm/:token | El enlace de un correo de doble opt-in: suscribe las listas que nombra el token y muestra una página de confirmación |
Los límites de tasa se fijan por quién hace la petición, no por la dirección IP: un operador móvil o una oficina pueden poner miles de personas detrás de una dirección. La página, su baliza de vista y /p/go solo tienen un tope contra inundaciones de 30.000 peticiones por minuto por IP. Un enlace de confirmación válido nunca se limita; los no válidos se rechazan tras 300 por minuto desde una misma IP. Envíos de formularios: 15 por minuto y 60 por hora de la misma persona (email, si no teléfono) en el mismo formulario, 3.000 por minuto por página y 2.000 por minuto por IP; un envío rechazado muestra un mensaje en el idioma de la página. El cuerpo de una página está limitado a 500 KB y 50 campos. Cada formulario lleva un campo honeypot y una permanencia mínima de dos segundos.
Usa tu propio dominio
En lugar de https://<workspace id>.eu1.jrypages.com/<slug>, un espacio de trabajo puede servir sus páginas en un subdominio de su propiedad - https://pages.tumarca.com/<slug>. El dominio se configura desde Ajustes -> Landing pages (la tarjeta "Tu dominio") o con las cuatro rutas de arriba; requiere landing_pages:publish.
- Añade el subdominio (
POST /landing-pages/domain). Un subdominio de tu propiedad, comopages.tumarca.com- no un dominio raíz, y nunca un dominio de Joryio. Joryio lo registra en el edge y responde con el dominio en estadopendingy los registros DNS que hay que configurar. - Añade los registros DNS en tu proveedor de DNS: un
CNAMEdel subdominio al destino que te da Joryio, más los registros de validación del certificado (TXT). Ajustes -> Landing pages los muestra con un botón de copiar;recordslleva la misma lista. - Verifica (
POST /landing-pages/domain/:id/verify). En cuanto el CNAME resuelve, el certificado se emite en unos minutos y el estado pasa aactive; los dominios pendientes también se vuelven a comprobar solos cada cinco minutos. Una validación que caduca pasa afailed, con el motivo enlastError- corrige los registros y verifica de nuevo, o quita el dominio y añádelo otra vez.
Mientras el dominio está active, cada URL de página que escribe Joryio - la publicUrl de una página, el enlace de vista previa, {{ 'slug' | landing_page_url }} en los mensajes y la redirección /p/go que hay detrás - lo usa; la dirección de páginas sigue sirviendo las mismas páginas, así que nada se rompe antes ni después. Quitar el dominio (DELETE /landing-pages/domain/:id) devuelve las URL a la dirección de páginas de inmediato; las páginas siguen publicadas.
Cuántos. Un espacio de trabajo tiene como máximo un dominio. Una organización puede tener por defecto un dominio de landing pages entre todos sus espacios de trabajo; GET /landing-pages/domain informa del limit y de cuántos hay used, y añadir uno por encima del límite responde 409 con un mensaje que lo nombra. Pídenos subir el límite para más espacios de trabajo o marcas.
Consentimiento: doble opt-in y verificación anti-bots
Una lista que requiere doble opt-in (ajustes de la lista) nunca se suscribe directamente desde un formulario. La persona se escribe y el evento se registra, la respuesta indica revisar la bandeja de entrada, y un correo (en el idioma de la página, a través de la configuración de email del espacio de trabajo) lleva un enlace firmado válido siete días; GET /p/confirm/:token suscribe las listas con optInSource: double_opt_in y muestra una página de confirmación. La fila del registro las lista en listsPending; la revisión de lanzamiento dice qué listas pedirán confirmación.
form.botCheck: true muestra un widget de Cloudflare Turnstile antes del botón de envío y rechaza un envío cuyo token no se verifique. Solo está disponible cuando el servidor está configurado para Turnstile (GET /landing-pages/capabilities). Los formularios dentro de mensajes in-app nunca lo necesitan: el SDK los autentica.
Visitantes, el Web SDK y personalización
Una página publicada cuenta por sí sola vistas, primeras vistas y envíos; a un visitante que llega por un enlace de mensaje con su token (?jp=, ver el filtro Liquid más abajo) se le escribe Landing Page Viewed en el perfil. Dos interruptores por página van más allá:
sdkEnabledcarga el Web SDK del espacio de trabajo en la página en vivo (necesita una app Web activa en Ajustes > Apps; si no, el guardado se rechaza). Cada visitante recibe una sesión y eventos como en cualquier página con el SDK; el runtime registra Landing Page Viewed a través del SDK, un visitante con token se identifica al llegar, y un formulario enviado identifica al visitante como la persona en la que escribió, así que su historial anónimo se fusiona en el perfil. Los mensajes in-app dirigidos a la app Web pueden mostrarse en la página. La vista previa del editor nunca carga el SDK.personalizedrenderiza el Liquid de la página para un visitante que llega con token, como lo haría un mensaje ({{ user.firstName | default: "there" }}, atributos,landing_page_url); sin token la misma página se renderiza con una persona vacía, así que se ven los valores por defecto. Un renderizado personal se sirve conCache-Control: private, no-store; el filtro catalog no está disponible en páginas.
Plantillas, IA y enlaces desde mensajes
Plantillas integradas. ?templates=true lista las plantillas del espacio de trabajo y cinco integradas (alta a boletín, inscripción a evento, lista de espera de producto, sorteo, próximamente). Una plantilla integrada tiene un id fijo; POST /landing-pages/:id/duplicate sobre ella crea una página normal en el espacio de trabajo. Cualquier página se convierte en plantilla con { "asTemplate": true }.
Diseño y revisión con IA (solo sesión del panel, landing_pages:write): POST /ai/pages/design con brief y, opcionalmente, language, brandVoiceId, archetype (bold, editorial, minimal, product, event), colorStrategy (restrained, committed, drenched), goal (signup, waitlist, event, download, contest, other) devuelve { name, seo, design, html, css, form, language, dir } - el modelo planifica las secciones y escribe el texto, código determinista construye el HTML sobre el Brand Kit, y nada se guarda hasta que guardes. POST /ai/pages/revise con source (el HTML de la página) e instruction devuelve { source, reply }, el documento revisado completo; una revisión que elimine el formulario o añada un script se rechaza.
Enlazar a una página desde un mensaje. En cualquier cuerpo de email, SMS o WhatsApp, {{ 'spring-sale' | landing_page_url }} genera un enlace a la página publicada con ese slug. El enlace lleva el token del destinatario (?jp=), así que la vista y el envío del formulario se escriben en esa persona antes incluso de que escriba un email; un segundo argumento añade una cadena de consulta: {{ 'spring-sale' | landing_page_url: 'utm_campaign=spring' }}. Un slug no publicado genera una cadena vacía.
Eventos. Landing Page Viewed y Landing Page Form Submitted aparecen en todos los selectores de eventos (disparadores de journeys, campañas disparadas, segmentos) bajo los eventos de plataforma, con sus propiedades.
Qué hace un envío
- Valida los valores contra el esquema; los campos desconocidos se ignoran y los errores vuelven como
{ "ok": false, "errors": [...] }. - Identifica a la persona: un perfil existente con el mismo email se actualiza; si no, se crea un perfil con un id externo generado y
sourcelanding_page:<slug>. Un email o un teléfono solo se rellena donde el perfil no tiene ninguno; una persona identificada por el token del enlace (jp) puede actualizar su propio teléfono, nunca su email. - Escribe los atributos mapeados, suscribe a las listas marcadas y registra Landing Page Form Submitted con
pageId,pageSlug,pageName, las claves de los campos,listsSubscribed,referrer, los valores UTM, y elemaily elphonedel formulario conphoneUpdated(truecuando el perfil tiene ahora el teléfono del formulario,falsecuando conservó uno distinto; ausente cuando el formulario no tenía teléfono o tenía el mismo). - Guarda una fila del registro (pestaña Envíos) e incrementa el contador
submissionsde la página. - Responde
{ "ok": true, "message": "..." }o{ "ok": true, "redirect": "https://..." }.
Ver también: Páginas de destino y formularios en la guía de usuario - la misma función desde el lado del marketer.