API de suscripciones
La API de suscripciones es la superficie de consentimiento de Joryio. Gestiona, por contacto, el estado de aceptación o exclusión de cada canal de mensajería (email, SMS, WhatsApp, push y Viber), la pertenencia a listas de suscripción (categorías o temas de consentimiento), el estado de rebotes de email y el historial de auditoría completo de los cambios de consentimiento.
Esta página cubre tres recursos:
- Consentimiento de contacto:
/subscriptions/contacts/..., para leer y modificar el consentimiento por canal y lista de un contacto. - Listas de suscripción:
/lists, para crear y gestionar las listas y las operaciones masivas de pertenencia. - Página de preferencias alojada:
/subscriptions/hosted-page, para crear la página alojada del centro de preferencias del espacio de trabajo.
Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com/api. Consulta el resumen de la API.
Autenticación
Todas las solicitudes requieren autenticación mediante clave de API (también funciona un JWT de sesión del dashboard):
Authorization: Bearer jry_live_your_api_key_here
Content-Type: application/json
Scopes por endpoint
| Endpoint | Scope |
|---|---|
GET /subscriptions/contacts/:userId | subscriptions:read or compliance:read |
PUT /subscriptions/contacts/:userId/channels/:channel | subscriptions:write or compliance:write |
POST /subscriptions/contacts/:userId/clear-bounce | subscriptions:write or compliance:write |
GET /subscriptions/contacts/:userId/lists | subscriptions:read or compliance:read |
POST /subscriptions/contacts/:userId/lists/:listId | subscriptions:write or compliance:write |
DELETE /subscriptions/contacts/:userId/lists/:listId | subscriptions:write or compliance:write |
GET /subscriptions/contacts/:userId/history | compliance:read |
GET /subscriptions/unsubscribe-reasons | compliance:read |
GET /subscriptions/contacts/:userId/channels/:channel/status | subscriptions:read or compliance:read |
GET /subscriptions/contacts/:userId/email-valid | subscriptions:read or compliance:read |
POST /lists | lists:write or settings:write |
GET /lists, GET /lists/:listId, GET /lists/:listId/stats | lists:read or settings:read |
GET /lists/:listId/members | compliance:read |
PUT /lists/:listId, DELETE /lists/:listId, POST /lists/:listId/restore | lists:write or settings:write |
POST /lists/:listId/members/bulk, DELETE /lists/:listId/members/bulk | lists:write or settings:write |
GET /subscriptions/hosted-page/preference-center, POST /subscriptions/hosted-page/preview | settings:read |
PUT /subscriptions/hosted-page/preference-center | settings:write |
Dos rutas quedan deliberadamente cerradas a una clave de API con los ámbitos de suscripción: GET .../history es el registro de consentimiento - prueba para un regulador, no estado operativo - y GET /lists/:listId/members revela contactos. Ambas conservan solo compliance:read.
Conceptos básicos
El ID de contacto
Cada ruta /subscriptions/contacts/:userId recibe el ID de contacto interno de Joryio, el id devuelto por la API de usuarios, no el userId externo que envías en eventos. Debe ser un ObjectId hexadecimal de 24 caracteres (o UUID); cualquier otro valor se rechaza con 400 Bad Request.
Suscribirse al crear el contacto
Los contactos se crean con POST /users/track, y la misma llamada puede definir sus suscripciones a listas: cada elemento de attributes acepta subscriptions (detalles), que se aplican por la misma vía de consentimiento que los endpoints de esta página. Los endpoints independientes de esta página siguen siendo la forma de gestionar el consentimiento después de crear el contacto: define el consentimiento de canal con PUT /subscriptions/contacts/:userId/channels/:channel y la pertenencia a listas con POST /subscriptions/contacts/:userId/lists/:listId. Las suscripciones en el momento de creación nunca restauran una exclusión existente; para una nueva aceptación explícita debes usar POST /subscriptions/contacts/:userId/lists/:listId.
Canales
Existen cinco canales de suscripción: email, sms, whatsapp, push y viber. Cualquier otro valor de canal devuelve 400 Bad Request.
Valores de estado
| Estado | Descripción |
|---|---|
optedIn | Aceptación explícita (por ejemplo, doble opt-in confirmado). |
subscribed | Suscrito (aceptación simple o estado predeterminado). |
unsubscribed | Exclusión. |
pending | Pendiente de confirmación: pidió suscribirse y aún no ha confirmado el doble opt-in. No recibe mensajes hasta confirmar; al confirmar pasa a subscribed. Lo fija un flujo de confirmación (por ejemplo, el doble opt-in de Delivyr), nunca los SDK. |
Los valores de estado usan camelCase: optedIn, no opted_in. Las barreras de envío tratan como suscrito a un contacto que no tiene estado registrado para un canal («sin registro» no equivale a exclusión).
Los cambios de consentimiento se auditan y se reflejan
Cada escritura mediante esta API se registra en el historial de auditoría de suscripciones del contacto, con fuente, dirección IP, agente de usuario e identidad de la clave de API o administrador que actúa, y se puede recuperar mediante el endpoint de historial. Las exclusiones también se reflejan en los registros de supresión vinculados al identificador: una baja de SMS/WhatsApp sigue al número de teléfono y una baja de email sigue a la dirección en todo el espacio de trabajo, por lo que también cubre contactos duplicados que compartan el identificador. Consulta la API de supresiones.
Consentimiento de contacto
Obtener suscripciones de contacto
Devuelve el estado de consentimiento por canal de un contacto y sus pertenencias a listas.
Endpoint
GET /subscriptions/contacts/:userId
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
userId | cadena | ID de contacto de Joryio. |
Solicitud de ejemplo
curl -X GET https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0 \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{
"channels": {
"email": {
"status": "subscribed",
"optInDate": "2026-05-14T09:21:07.000Z",
"optInSource": "api",
"consentText": "Send me product updates",
"isValid": true,
"bounceType": null,
"bounceCount": 0
},
"sms": {
"status": "unsubscribed",
"optOutDate": "2026-06-02T18:40:00.000Z",
"optInSource": "preference_center"
}
},
"lists": [
{
"_id": "665f2e11aa22bb33cc44dd55",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"listId": "3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b",
"channel": "email",
"status": "subscribed",
"subscribedAt": "2026-05-14T09:21:07.000Z",
"optInSource": "api",
"createdAt": "2026-05-14T09:21:07.000Z",
"updatedAt": "2026-05-14T09:21:07.000Z"
}
]
}
Los canales para los que el contacto no tiene estado registrado simplemente no aparecen en channels. El canal email incluye los campos adicionales de rebote (isValid, bounceType, bounceCount, lastBounceAt). lists devuelve las primeras 500 filas de pertenencia del contacto.
Devuelve 404 Not Found si el contacto no existe en el espacio de trabajo.
Actualizar suscripción de canal
Define el estado de consentimiento de un contacto para un canal.
Endpoint
PUT /subscriptions/contacts/:userId/channels/:channel
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
userId | cadena | ID de contacto de Joryio. |
channel | cadena | email, sms, whatsapp, push o viber. |
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
channel | cadena | Sí | Debe coincidir con el canal de la URL (si difieren, prevalece el valor de la URL). |
status | cadena | Sí | optedIn, subscribed o unsubscribed. También se acepta pending (pendiente de confirmación). |
source | cadena | No | Origen del cambio, por ejemplo api, preference_center (predeterminado api). |
consentText | cadena | No | Texto de consentimiento mostrado al aceptar (se guarda para cumplimiento). |
reason | cadena | No | Motivo de texto libre guardado en la entrada de auditoría. |
Solicitud de ejemplo
curl -X PUT https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/channels/email \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"status": "unsubscribed",
"source": "preference_center",
"reason": "User requested via support ticket"
}'
Respuesta
El objeto channels completo y actualizado del contacto:
{
"email": {
"status": "unsubscribed",
"optOutDate": "2026-07-12T10:15:00.000Z",
"optInSource": "preference_center",
"isValid": true,
"bounceType": null,
"bounceCount": 0
},
"sms": {
"status": "subscribed",
"optInDate": "2026-05-14T09:21:07.000Z"
}
}
Notas de comportamiento
- Un opt-in en el canal de email borra el estado temporal de rebote suave, pero nunca revive un rebote duro ni una queja de spam; solo el soporte de Joryio puede levantarlos.
- Un
unsubscribedensms/whatsappse refleja en el registro de supresión vinculado al teléfono (la baja sigue al número en toda la organización). Un cambio de estado en email se refleja en el registro vinculado a la dirección para el espacio de trabajo. - El cambio se registra en auditoría con IP, agente de usuario e identidad del administrador/clave que actúa.
Doble opt-in por SMS
Cuando el espacio de trabajo activó el Doble opt-in por SMS (Configuración > Suscripciones), un PUT .../channels/sms con subscribed u optedIn para un contacto sin estado de SMS no lo suscribe: el estado se guarda como pending y el contacto recibe un mensaje "Reply YES". La respuesta lo muestra:
{ "sms": { "status": "pending", "optInSource": "api", "confirmation": { "requestedAt": "2026-10-17T10:00:00.000Z", "expiresAt": "2026-10-24T10:00:00.000Z", "sentAt": "2026-10-17T10:00:01.000Z" } } }
- El contacto pasa a
subscribed(conoptInSource: "double_opt_in") solo respondiendo YES (o START, u otra palabra de alta) a tu número. Ninguna llamada a la API puede confirmar a un contacto pendiente (409). - Volver a pedirlo mientras el contacto está
pendingdevuelve200con la misma solicitud abierta, sin un segundo mensaje. - Un contacto dado de baja, o cuyo número tiene una baja, se trata como un alta nueva (decisión del propietario 2026-10-17):
pendingy UN mensaje de confirmación, suscrito solo tras YES; sin respuesta caduca de vuelta aunsubscribedy no se envía nada más. Un contacto que había enviado STOP recibe además la entrada habitual dewarningssms_stop_provider_block; si el proveedor rechaza el mensaje con su código de STOP, el contacto vuelve a estar dado de baja. Un rebote permanente, una queja de spam o un bloqueo de toda la organización se siguen rechazando (409). - Las solicitudes sin respuesta caducan tras los días configurados (7 por defecto): el estado de SMS se elimina (no
unsubscribed). - No se aplica cuando el número de SMS del espacio de trabajo está en una cuenta de Delivyr: Delivyr tiene su propio doble opt-in.
Consulta Doble opt-in por SMS.
Contactos duplicados y nuevas suscripciones
- Una baja se aplica a todos los contactos con el mismo email o teléfono del espacio de trabajo: el estado de cada uno (o su pertenencia, en una lista) pasa a
unsubscribedy recibe una entrada de historial con el origenshared_identifier. - Volver a suscribir mediante la API está permitido y se aplica solo a ese contacto. Los demás contactos con el mismo email o teléfono siguen de baja. El cambio se registra con el origen
apiy el id de la clave de API enmetadata.principal. - Un
sourceen el cuerpo que termina en_keyword, o que esshared_identifier,complaint_webhookostaff, se registra comoapi; solo Joryio establece esos valores. - Una baja por
STOPse puede anular (SMS, WhatsApp, Viber, cualquier proveedor) con esta actualización normal - sin flag adicional. Se aplica solo a ese contacto, y el historial registra que anuló unSTOP(metadata.overrodeStop) y la clave de API. Una respuestaSTARTsigue funcionando como antes. - Solo SMS: volver a suscribir a un contacto de SMS que envió
STOPresponde200 OKcon un campowarningsque no es un error, porque el proveedor de SMS puede mantener su propio bloqueo hasta que el contacto envíeSTART. WhatsApp y Viber no devuelven aviso. El endpoint de listas (POST /subscriptions/contacts/:userId/lists/:listId, para una lista de SMS) y el endpoint de canal del SDK devuelven el mismo campo.
{
"sms": { "status": "subscribed", "optInSource": "api" },
"warnings": [
{
"code": "sms_stop_provider_block",
"channel": "sms",
"message": "This contact opted out by texting STOP. Your SMS provider may keep blocking messages until they text START."
}
]
}
- Un envío que el proveedor de SMS rechaza después por su propio bloqueo
STOP(por ejemplo Twilio 21610 o un rechazo opted-out de Delivyr) se registra como bloqueado por el proveedor (dado de baja) - no es un fallo y no cuenta para las alertas de tasa de rebote o de fallos. La baja se vuelve a aplicar, con la nota de historial provider still blocks: re-opted out. - Algunos bloqueos no se levantan con una nueva suscripción. La solicitud se rechaza con
409 Conflicty no cambia nada:
code | message |
|---|---|
deliverability | This address hard-bounced or marked a message as spam. Only Joryio support can lift that block. |
organization_block | This identifier is blocked for the whole organization. Only Joryio support can lift that block. |
verified_identity_required | 403 - Solo endpoints del SDK (/v1/subscriptions/channel, /v1/subscriptions/group): volver a suscribir a una persona que se dio de baja explícitamente (su baja o la de una lista, un STOP, una baja en su email o teléfono - también de otro contacto) desde un sujeto no verificado - un userId sin SDK Authentication, o un anonymousId que pertenece a una persona identificada. Una primera alta se acepta; una lista con doble opt-in responde pending: true y envía el email de confirmación. Darse de baja siempre está permitido. |
{ "statusCode": 409, "error": "Conflict", "code": "deliverability", "message": "This address hard-bounced or marked a message as spam. Only Joryio support can lift that block." }
El endpoint de listas (POST /subscriptions/contacts/:userId/lists/:listId) nunca se rechaza por ellos; una suscripción a una lista tampoco los levanta. Contactos que comparten un email o un teléfono.
Borrar estado de rebote
Restablece los contadores de rebote suave de un contacto. Un rebote duro o una queja de spam solo los levanta el soporte de Joryio: cuando la dirección tiene uno, la solicitud se rechaza con 403 y code: "deliverability".
Endpoint
POST /subscriptions/contacts/:userId/clear-bounce
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
reason | cadena | No | Motivo de texto libre guardado en la entrada de auditoría. |
Solicitud de ejemplo
curl -X POST https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/clear-bounce \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "reason": "Mailbox restored, confirmed with customer" }'
Respuesta
{ "success": true }
Borra bounceType, bounceCount y lastBounceAt de un rebote suave. No elimina filas hard_bounce / complaint del registro de supresiones.
Obtener suscripciones a listas
Devuelve todas las filas de pertenencia a listas de un contacto, tanto suscritas como dadas de baja.
Endpoint
GET /subscriptions/contacts/:userId/lists
Solicitud de ejemplo
curl -X GET https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/lists \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
[
{
"_id": "665f2e11aa22bb33cc44dd55",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"listId": "3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b",
"channel": "email",
"status": "subscribed",
"subscribedAt": "2026-05-14T09:21:07.000Z",
"optInSource": "api",
"createdAt": "2026-05-14T09:21:07.000Z",
"updatedAt": "2026-05-14T09:21:07.000Z"
}
]
La pertenencia es única por contacto + lista + canal: el mismo contacto puede estar suscrito a una lista en email y dado de baja de ella en sms, como dos filas independientes.
Suscribir un contacto a una lista
Suscribe un contacto a una lista en un canal. Es idempotente: repetir la llamada reafirma la suscripción. También es la ruta de aceptación explícita y auditada que puede volver a suscribir un contacto que se dio de baja de la lista; la adición masiva nunca lo hace.
Endpoint
POST /subscriptions/contacts/:userId/lists/:listId
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
userId | cadena | ID de contacto de Joryio. |
listId | cadena | ID de lista de suscripción (UUID). |
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
channel | cadena | Sí | email, sms, whatsapp, push o viber. |
source | cadena | No | Por ejemplo, api, form, import, preference_center (predeterminado api). |
consentText | cadena | No | Texto de consentimiento mostrado al aceptar. |
Solicitud de ejemplo
curl -X POST https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "channel": "email", "source": "api", "consentText": "Weekly newsletter signup" }'
Respuesta
La fila de pertenencia creada o actualizada:
{
"_id": "665f2e11aa22bb33cc44dd55",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"listId": "3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b",
"channel": "email",
"status": "subscribed",
"subscribedAt": "2026-07-12T10:20:00.000Z",
"optInSource": "api",
"consentText": "Weekly newsletter signup",
"createdAt": "2026-07-12T10:20:00.000Z",
"updatedAt": "2026-07-12T10:20:00.000Z"
}
Dar de baja un contacto de una lista
Da de baja un contacto de una lista en un canal. El canal se envía en el cuerpo de la solicitud, no en la URL.
Endpoint
DELETE /subscriptions/contacts/:userId/lists/:listId
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
channel | cadena | Sí | email, sms, whatsapp, push o viber. |
source | cadena | No | Por ejemplo, api, preference_center (predeterminado api). |
Solicitud de ejemplo
curl -X DELETE https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "channel": "email", "source": "preference_center" }'
Respuesta
{ "success": true }
Si el contacto no tenía fila de pertenencia para esta lista + canal, se crea de todos modos una fila explícita unsubscribed: una retirada de consentimiento nunca se pierde silenciosamente y la barrera de envío bloqueará desde ese momento los envíos de lista a este contacto.
Obtener historial de suscripciones
Devuelve el historial de auditoría de consentimiento del contacto, del más reciente al más antiguo.
Endpoint
GET /subscriptions/contacts/:userId/history
Parámetros de consulta
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
limit | número | 50 | Entradas por página (los valores no numéricos vuelven al predeterminado). |
offset | número | 0 | Desplazamiento de paginación. |
Solicitud de ejemplo
curl -X GET "https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/history?limit=50&offset=0" \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{
"items": [
{
"id": "8a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"channel": "email",
"listId": null,
"action": "unsubscribe",
"previousStatus": "subscribed",
"newStatus": "unsubscribed",
"source": "preference_center",
"ipAddress": "203.0.113.7",
"userAgent": "Mozilla/5.0 ...",
"consentText": null,
"metadata": { "reason": "User requested via support ticket", "unsubscribeSource": "preferences_page", "sinceSendMs": 3600000 },
"createdAt": "2026-07-12T10:15:00.000Z",
"changeKind": "unsubscribed",
"changeLabel": "Unsubscribed",
"sourceKind": "preference_center",
"sourceLabel": "Preference center",
"sourcePlatform": null,
"page": null
}
],
"total": 12
}
action es uno de subscribe, unsubscribe, resubscribe, bounce, hard_bounce, soft_bounce, complaint, import, api_update. listId se define para cambios a nivel de lista y es null para cambios a nivel de canal.
Cada entrada unsubscribe escrita desde el 2026-10-15 lleva metadata.unsubscribeSource - cómo llegó: header_one_click, page_button, preferences_page, page_everything, api, admin, sdk, import, keyword, complaint, esp_unsubscribe, store_sync, other. header_one_click es el propio botón de baja de la app de correo (RFC 8058); page_button es una persona que pulsó Darse de baja en la página alojada. Cuando se conoce el mensaje, metadata.sinceSendMs es el tiempo desde su envío hasta la baja. userAgent se guarda sin caracteres de control y recortado a 256 caracteres. Las entradas con metadata.pageMode auto las hizo la página alojada sin clic, entre el 2026-10-11 y el 2026-10-15.
Prueba de consentimiento (desde 2026-11-02). Cada entrada lleva también changeKind / changeLabel - qué hizo el cambio: subscribed, resubscribed, unsubscribed, pending_order (una casilla del pago de una tienda retenida hasta un pedido o el enlace de confirmación), pending_confirmation, withdrawn (desmarcada en el mismo pago), confirmed_by_order, confirmed, confirmation_expired, bounce, complaint, import, updated - y sourceKind / sourceLabel / sourcePlatform - de dónde vino: store_checkout, store_registration, store_customer_record, store_newsletter, store_sync (con sourcePlatform woocommerce, shopify, magento o prestashop), landing_page, in_app_form, website_form, api, admin, sdk, import, unsubscribe_link, preference_center, bounce, complaint, keyword_stop, keyword_start, double_opt_in, push_permission, journey, email_provider, shared_identifier, contact_merge (un estado heredado al fusionar dos contactos), support, other. Los campos *Label son el texto en inglés. page es la página donde se dio el consentimiento, si se guardó (URL de página o tienda, o slug de una landing page); si no, null. consentText es el texto exacto que se mostró a la persona. ipAddress es un dato personal: quien llama sin pii:view (una clave API solo con ese scope) recibe null e ipHidden: true cuando había una guardada. location es dónde en la tienda se recogió el consentimiento - checkout, block_checkout, registration, customer_record o newsletter - y collectedAt cuándo se recogió (ISO-8601, reloj de la tienda; puede ser anterior a createdAt cuando la casilla se retuvo hasta el pedido); ambos null si no se registraron.
Listar motivos de baja recientes
Las bajas recientes del espacio de trabajo que tienen un motivo o se marcaron como posiblemente automáticas, de la más reciente a la más antigua. Una fila por acción de baja: cuando una acción dio de baja varios canales (por ejemplo, un enlace antiguo que dio de baja todo), los canales se agrupan en una sola fila.
Endpoint
GET /subscriptions/unsubscribe-reasons
Parámetros de consulta
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
limit | number | 50 | Filas por página (máximo 100) |
offset | number | 0 | Desplazamiento de paginación |
days | number | 90 | Cuántos días hacia atrás (máximo 365) |
flagged | boolean | false | true devuelve solo las bajas marcadas como posiblemente automáticas |
Ejemplo de solicitud y respuesta
curl -X GET "https://api-eu1.joryio.com/api/subscriptions/unsubscribe-reasons?limit=50&offset=0&days=90" \
-H "Authorization: Bearer jry_live_your_api_key"
{
"items": [
{
"at": "2026-10-11T09:12:44.000Z",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"name": "Dana Levi",
"channels": ["email"],
"listId": null,
"reason": "too_frequent",
"campaignId": "3f2a1b0c-9d8e-4f7a-b6c5-d4e3f2a1b0c9",
"source": "one_click",
"suspectedAutomatic": false,
"suspectedAutomaticReasons": []
}
],
"total": 37
}
reason es el motivo que la persona eligió en la página de preferencias (null si no indicó ninguno). suspectedAutomatic es true cuando la baja ocurrió en la página alojada en los 10 segundos posteriores al envío del mensaje, o cuando la solicitud parecía tráfico de un escáner; suspectedAutomaticReasons indica cuál. La misma marca se guarda en el metadata de la entrada de auditoría (ver Obtener historial de suscripciones). name sigue las reglas de pii:view: quien no tiene ese permiso lo recibe enmascarado. Requiere compliance:read.
Comprobar estado de suscripción de canal
Comprobación booleana ligera: ¿se puede enviar al contacto en este canal desde el punto de vista del consentimiento?
Endpoint
GET /subscriptions/contacts/:userId/channels/:channel/status
Solicitud de ejemplo
curl -X GET https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/channels/email/status \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{ "subscribed": true }
subscribed es true salvo que el contacto esté explícitamente unsubscribed en el canal. Un contacto sin estado registrado cuenta como suscrito. Un ID de contacto desconocido devuelve { "subscribed": false }, no 404.
Comprobar la validez del email
¿La dirección de email del contacto es entregable (no está marcada como no válida por un rebote permanente)?
Endpoint
GET /subscriptions/contacts/:userId/email-valid
Solicitud de ejemplo
curl -X GET https://api-eu1.joryio.com/api/subscriptions/contacts/665f1c2ab3d4e5f6a7b8c9d0/email-valid \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{ "valid": true }
valid es false solo cuando un rebote permanente marcó la dirección como no válida. Un ID de contacto desconocido devuelve { "valid": false }, no 404. Esto refleja entregabilidad, no consentimiento: un contacto dado de baja con una dirección válida seguirá devolviendo true.
Listas de suscripción
Las listas son las categorías o temas de consentimiento a los que pueden suscribirse los contactos (newsletter, actualizaciones de producto, etc.). Las definiciones de lista están en /lists; la pertenencia por contacto se gestiona con los endpoints de consentimiento de contacto anteriores o con los endpoints masivos siguientes.
Crear una lista
Endpoint
POST /lists
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
name | cadena | Sí | Nombre de lista (máx. 255 caracteres, único por espacio de trabajo). |
description | cadena | No | Descripción (máx. 1000 caracteres). |
channels | cadena[] | No | Canales a los que se aplica la lista (predeterminado ["email"]). |
isPublic | booleano | No | Mostrar en el centro de preferencias (predeterminado true). |
type | cadena | No | marketing o transactional (predeterminado marketing). |
requireDoubleOptIn | booleano | No | Requiere aceptación confirmada (predeterminado false). |
Solicitud de ejemplo
curl -X POST https://api-eu1.joryio.com/api/lists \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Newsletter",
"description": "Our weekly digest of product updates",
"channels": ["email"],
"isPublic": true,
"type": "marketing",
"requireDoubleOptIn": false
}'
Respuesta
{
"id": "3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"name": "Weekly Newsletter",
"description": "Our weekly digest of product updates",
"channels": ["email"],
"isPublic": true,
"type": "marketing",
"requireDoubleOptIn": false,
"isDefault": false,
"displayOrder": 0,
"senderId": null,
"archivedAt": null,
"createdAt": "2026-07-12T10:30:00.000Z",
"updatedAt": "2026-07-12T10:30:00.000Z"
}
isDefault marca la categoría Marketing predeterminada creada automáticamente; senderId se define cuando la lista es un grupo de suscripción SMS/WhatsApp por número vinculado a un remitente específico.
Listar todas las listas
Endpoint
GET /lists
Parámetros de consulta
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
includeArchived | cadena | false | Envía true para incluir listas archivadas. |
Solicitud de ejemplo
curl -X GET "https://api-eu1.joryio.com/api/lists?includeArchived=false" \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
Una matriz de objetos de lista (con la misma estructura que crear una lista), del más reciente al más antiguo, limitada a 200.
Obtener una lista
GET /lists/:listId
Devuelve el objeto de lista. 404 Not Found si la lista no existe o está archivada.
Actualizar una lista
PUT /lists/:listId
Cuerpo: cualquier subconjunto de los campos de crear una lista (name, description, channels, isPublic, type, requireDoubleOptIn). Devuelve el objeto de lista actualizado.
Archivar una lista
DELETE /lists/:listId
Eliminación reversible: define archivedAt y conserva las filas de pertenencia. Devuelve 204 No Content. Restaura con:
POST /lists/:listId/restore
que devuelve el objeto de lista restaurado.
Obtener miembros de una lista
Endpoint
GET /lists/:listId/members
Parámetros de consulta
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
channel | cadena | Filtro opcional: email, sms, whatsapp, push, viber. | |
status | cadena | Filtro opcional: subscribed o unsubscribed. | |
limit | número | 50 | Filas por página. |
offset | número | 0 | Desplazamiento de paginación. |
Solicitud de ejemplo
curl -X GET "https://api-eu1.joryio.com/api/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b/members?channel=email&status=subscribed&limit=50" \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{
"members": [
{
"_id": "665f2e11aa22bb33cc44dd55",
"contactId": "665f1c2ab3d4e5f6a7b8c9d0",
"listId": "3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b",
"channel": "email",
"status": "subscribed",
"subscribedAt": "2026-05-14T09:21:07.000Z",
"optInSource": "api",
"organizationId": "org-uuid",
"workspaceId": "ws-uuid",
"createdAt": "2026-05-14T09:21:07.000Z",
"updatedAt": "2026-05-14T09:21:07.000Z"
}
],
"total": 1234
}
Obtener estadísticas de lista
Endpoint
GET /lists/:listId/stats
Solicitud de ejemplo
curl -X GET https://api-eu1.joryio.com/api/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b/stats \
-H "Authorization: Bearer jry_live_your_api_key"
Respuesta
{
"total": 1500,
"byChannel": {
"email": 1180,
"sms": 120,
"whatsapp": 0,
"push": 0,
"viber": 0
},
"subscribed": 1300,
"unsubscribed": 200
}
byChannel cuenta las pertenencias que no están dadas de baja por canal.
Añadir miembros masivamente
Añade hasta 10 000 contactos a una lista en un canal con una sola llamada.
Endpoint
POST /lists/:listId/members/bulk
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
contactIds | cadena[] | Sí | ID de contacto de Joryio (máx. 10 000). |
channel | cadena | Sí | email, sms, whatsapp, push o viber. |
source | cadena | No | Se registra como optInSource de la pertenencia. |
Solicitud de ejemplo
curl -X POST https://api-eu1.joryio.com/api/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b/members/bulk \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactIds": ["665f1c2ab3d4e5f6a7b8c9d0", "665f1c2ab3d4e5f6a7b8c9d1"],
"channel": "email",
"source": "import"
}'
Respuesta
{ "added": 2, "updated": 0, "skippedUnsubscribed": 0 }
La adición masiva solo crea pertenencias nuevas. Los contactos que ya están en la lista no se modifican y los que se dieron de baja explícitamente nunca vuelven a suscribirse mediante una adición masiva: se cuentan en skippedUnsubscribed. Para volver a suscribir un contacto dado de baja se requiere la llamada explícita y auditada suscribir un contacto a una lista. updated siempre es 0 (se conserva por compatibilidad con versiones anteriores).
Eliminar miembros masivamente
Da de baja hasta 10 000 contactos de una lista en un canal.
Endpoint
DELETE /lists/:listId/members/bulk
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
contactIds | cadena[] | Sí | ID de contacto de Joryio (máx. 10 000). |
channel | cadena | Sí | email, sms, whatsapp, push o viber. |
Solicitud de ejemplo
curl -X DELETE https://api-eu1.joryio.com/api/lists/3f6c1a2e-9d4b-4f0a-8c7e-1b2d3e4f5a6b/members/bulk \
-H "Authorization: Bearer jry_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactIds": ["665f1c2ab3d4e5f6a7b8c9d0"],
"channel": "email"
}'
Respuesta
{ "removed": 1 }
La eliminación cambia las filas de pertenencia existentes a unsubscribed (se conservan para la auditoría), por lo que los contactos eliminados quedan bloqueados para futuros envíos de lista.
Página de preferencias alojada
API de creación para la página alojada del centro de preferencias del espacio de trabajo, a la que llegan los destinatarios desde un enlace de baja/preferencias. El renderizado público de la página se sirve mediante endpoints públicos basados en token (sin clave de API), que no forman parte de esta referencia.
Obtener la plantilla del centro de preferencias
GET /subscriptions/hosted-page/preference-center
Respuesta
{
"type": "preference_center",
"mode": "default",
"html": "",
"redirectUrl": "",
"designJson": null,
"updatedAt": null
}
mode es uno de default (página incorporada de Joryio), custom (tu plantilla HTML/Liquid), dnd (creada en el editor visual; mismo html renderizado) o redirect (registra la baja y después redirige a redirectUrl).
Actualizar la plantilla del centro de preferencias
PUT /subscriptions/hosted-page/preference-center
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
mode | cadena | No | default, custom, dnd o redirect. |
html | cadena | No | Cuerpo HTML/Liquid para el modo custom / dnd (máx. 100 KB); debe incluir el espacio {{ preferences_form }}. |
redirectUrl | cadena | No | Destino del modo redirect (máx. 2048 caracteres). |
designJson | objeto | No | Estado de ida y vuelta del editor visual (solo modo dnd; se borra en otros modos). |
Devuelve la plantilla guardada con la misma estructura que la respuesta GET.
Previsualizar una plantilla
POST /subscriptions/hosted-page/preview
Cuerpo: { "html": "..." }, la plantilla que se renderizará con datos de ejemplo. Respuesta: { "html": "<rendered, sanitized html>" }.
Superficies relacionadas (no incluidas en esta página)
- Endpoints dirigidos al destinatario: la baja en un clic (
POST /u/:token, el botón de la app de correo), el botón Darse de baja de la página alojada (POST /u/:token/confirm, solo con el clic de una persona) y las páginas de preferencias alojadas son endpoints públicos autenticados por token para destinatarios, no endpoints con clave de API. - SDK web:
POST /v1/subscriptions/channelyPOST /v1/subscriptions/groupse autentican con una clave de SDK (X-SDK-Key) para interfaces de preferencias del lado cliente. Consulta la guía de gestión de suscripciones. - Webhooks de proveedores: los webhooks de rebotes y palabras clave de SMS entrantes (
/webhooks/email/...,/webhooks/sms/...) son integraciones de proveedor verificadas mediante firma. - Supresiones: el registro de no enviar de todo el espacio de trabajo tiene su propia API de supresiones.
Siguientes pasos
- API de usuarios: crea el contacto antes de definir el consentimiento.
- API de supresiones: registro de no enviar vinculado al identificador.
- Guía de gestión de suscripciones: conceptos, uso de SDK, palabras clave y cumplimiento.