Saltar al contenido principal

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​

EndpointScope
GET /subscriptions/contacts/:userIdsubscriptions:read or compliance:read
PUT /subscriptions/contacts/:userId/channels/:channelsubscriptions:write or compliance:write
POST /subscriptions/contacts/:userId/clear-bouncesubscriptions:write or compliance:write
GET /subscriptions/contacts/:userId/listssubscriptions:read or compliance:read
POST /subscriptions/contacts/:userId/lists/:listIdsubscriptions:write or compliance:write
DELETE /subscriptions/contacts/:userId/lists/:listIdsubscriptions:write or compliance:write
GET /subscriptions/contacts/:userId/historycompliance:read
GET /subscriptions/unsubscribe-reasonscompliance:read
GET /subscriptions/contacts/:userId/channels/:channel/statussubscriptions:read or compliance:read
GET /subscriptions/contacts/:userId/email-validsubscriptions:read or compliance:read
POST /listslists:write or settings:write
GET /lists, GET /lists/:listId, GET /lists/:listId/statslists:read or settings:read
GET /lists/:listId/memberscompliance:read
PUT /lists/:listId, DELETE /lists/:listId, POST /lists/:listId/restorelists:write or settings:write
POST /lists/:listId/members/bulk, DELETE /lists/:listId/members/bulklists:write or settings:write
GET /subscriptions/hosted-page/preference-center, POST /subscriptions/hosted-page/previewsettings:read
PUT /subscriptions/hosted-page/preference-centersettings: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​

EstadoDescripción
optedInAceptación explícita (por ejemplo, doble opt-in confirmado).
subscribedSuscrito (aceptación simple o estado predeterminado).
unsubscribedExclusión.
pendingPendiente 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ámetroTipoDescripción
userIdcadenaID 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ámetroTipoDescripción
userIdcadenaID de contacto de Joryio.
channelcadenaemail, sms, whatsapp, push o viber.

Cuerpo de la solicitud​

CampoTipoObligatorioDescripción
channelcadenaSíDebe coincidir con el canal de la URL (si difieren, prevalece el valor de la URL).
statuscadenaSíoptedIn, subscribed o unsubscribed. También se acepta pending (pendiente de confirmación).
sourcecadenaNoOrigen del cambio, por ejemplo api, preference_center (predeterminado api).
consentTextcadenaNoTexto de consentimiento mostrado al aceptar (se guarda para cumplimiento).
reasoncadenaNoMotivo 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 unsubscribed en sms/whatsapp se 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 (con optInSource: "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á pending devuelve 200 con 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): pending y UN mensaje de confirmación, suscrito solo tras YES; sin respuesta caduca de vuelta a unsubscribed y no se envía nada más. Un contacto que había enviado STOP recibe además la entrada habitual de warnings sms_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 unsubscribed y recibe una entrada de historial con el origen shared_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 api y el id de la clave de API en metadata.principal.
  • Un source en el cuerpo que termina en _keyword, o que es shared_identifier, complaint_webhook o staff, se registra como api; solo Joryio establece esos valores.
  • Una baja por STOP se 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ó un STOP (metadata.overrodeStop) y la clave de API. Una respuesta START sigue funcionando como antes.
  • Solo SMS: volver a suscribir a un contacto de SMS que envió STOP responde 200 OK con un campo warnings que no es un error, porque el proveedor de SMS puede mantener su propio bloqueo hasta que el contacto envíe START. 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 Conflict y no cambia nada:
codemessage
deliverabilityThis address hard-bounced or marked a message as spam. Only Joryio support can lift that block.
organization_blockThis identifier is blocked for the whole organization. Only Joryio support can lift that block.
verified_identity_required403 - 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​

CampoTipoObligatorioDescripción
reasoncadenaNoMotivo 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ámetroTipoDescripción
userIdcadenaID de contacto de Joryio.
listIdcadenaID de lista de suscripción (UUID).

Cuerpo de la solicitud​

CampoTipoObligatorioDescripción
channelcadenaSíemail, sms, whatsapp, push o viber.
sourcecadenaNoPor ejemplo, api, form, import, preference_center (predeterminado api).
consentTextcadenaNoTexto 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​

CampoTipoObligatorioDescripción
channelcadenaSíemail, sms, whatsapp, push o viber.
sourcecadenaNoPor 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ámetroTipoPredeterminadoDescripción
limitnúmero50Entradas por página (los valores no numéricos vuelven al predeterminado).
offsetnúmero0Desplazamiento 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ámetroTipoPredeterminadoDescripción
limitnumber50Filas por página (máximo 100)
offsetnumber0Desplazamiento de paginación
daysnumber90Cuántos días hacia atrás (máximo 365)
flaggedbooleanfalsetrue 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",
"email": "[email protected]",
"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​

CampoTipoObligatorioDescripción
namecadenaSíNombre de lista (máx. 255 caracteres, único por espacio de trabajo).
descriptioncadenaNoDescripción (máx. 1000 caracteres).
channelscadena[]NoCanales a los que se aplica la lista (predeterminado ["email"]).
isPublicbooleanoNoMostrar en el centro de preferencias (predeterminado true).
typecadenaNomarketing o transactional (predeterminado marketing).
requireDoubleOptInbooleanoNoRequiere 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ámetroTipoPredeterminadoDescripción
includeArchivedcadenafalseEnví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ámetroTipoPredeterminadoDescripción
channelcadenaFiltro opcional: email, sms, whatsapp, push, viber.
statuscadenaFiltro opcional: subscribed o unsubscribed.
limitnúmero50Filas por página.
offsetnúmero0Desplazamiento 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​

CampoTipoObligatorioDescripción
contactIdscadena[]SíID de contacto de Joryio (máx. 10 000).
channelcadenaSíemail, sms, whatsapp, push o viber.
sourcecadenaNoSe 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​

CampoTipoObligatorioDescripción
contactIdscadena[]SíID de contacto de Joryio (máx. 10 000).
channelcadenaSí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​

CampoTipoObligatorioDescripción
modecadenaNodefault, custom, dnd o redirect.
htmlcadenaNoCuerpo HTML/Liquid para el modo custom / dnd (máx. 100 KB); debe incluir el espacio {{ preferences_form }}.
redirectUrlcadenaNoDestino del modo redirect (máx. 2048 caracteres).
designJsonobjetoNoEstado 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/channel y POST /v1/subscriptions/group se 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​