Saltar al contenido principal

API de e-commerce

Gestiona tu catálogo de productos, rastrea pedidos, supervisa la actividad de los carritos y analiza segmentos RFM de clientes.

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.

Permisos​

Leer datos de catálogo, carrito y pedidos requiere analytics:read. Crear o actualizar productos, carritos y pedidos requiere ecommerce:write. Ambos se pueden conceder a una clave de API en Configuración → Claves de API.

Las rutas de escritura requerían antes integrations:write, que no se ofrece en las claves de API porque también rige el OAuth de los conectores y la configuración de sincronización del almacén. Siguen aceptándolo, así que las integraciones existentes no se ven afectadas.

Convención de nombres de ID​

Joryio usa una convención clara para los identificadores:

Tus ID (úsalos al crear o rastrear datos)​

CampoDescripciónEjemplo
productIdTu identificador de producto (SKU o ID de producto de tu plataforma de e-commerce)."SKU-12345"
orderIdTu identificador de pedido (número de pedido de tu plataforma)."ORD-2024-001"
userIdTu identificador de usuario."user-123"

ID de Joryio (devueltos en las respuestas API)​

CampoDescripciónCuándo usarlo
idID interno de Joryio para productos/pedidos.Se devuelve en respuestas API; úsalo para actualizar/eliminar.
joryioUserIdID de usuario interno de Joryio (el id de 24 caracteres hexadecimales devuelto por la API de usuarios).Alternativa a userId cuando quieras referenciar mediante el ID de Joryio.

Identificación flexible de usuario​

Al crear pedidos o rastrear eventos, puedes identificar usuarios mediante uno de estos campos:

  • userId: tu identificador de usuario (el más habitual).
  • joryioUserId: ID de usuario interno de Joryio.
// Usando tu ID de usuario (recomendado)
{ "orderId": "ORD-001", "userId": "user-123", ... }

// Usando el ID de usuario interno de Joryio
{ "orderId": "ORD-001", "joryioUserId": "66a1f2c3d4e5f6a7b8c9d0e1", ... }

Autenticación​

Todas las solicitudes requieren autenticación JWT:

Authorization: Bearer your_jwt_token
Content-Type: application/json
X-Workspace-Id: your_workspace_id

Catálogo de productos​

Crear un producto​

Crea un producto nuevo en tu catálogo.

POST /catalog/products

Cuerpo de la solicitud:

CampoTipoObligatorioDescripción
productIdcadenaSíTu identificador de producto único (SKU o ID de producto).
namecadenaSíNombre del producto.
pricenúmeroSíPrecio del producto.
descriptioncadenaNoDescripción del producto.
compareAtPricenúmeroNoPrecio original (para descuentos).
currencycadenaNoCódigo de moneda (predeterminado: USD).
categoriescadena[]NoCategorías de producto.
tagscadena[]NoEtiquetas de producto.
brandcadenaNoNombre de la marca.
imageUrlcadenaNoURL de imagen principal de producto.
urlcadenaNoURL de página de producto.
inStockbooleanoNoDisponibilidad de stock (predeterminado: true).
skucadenaNoUnidad de mantenimiento de stock.
variantsobjeto[]NoVariantes de producto.
customFieldsobjetoNoAtributos personalizados.

Solicitud de ejemplo:

curl -X POST https://api-eu1.joryio.com/api/catalog/products \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"productId": "SKU-12345",
"name": "Classic Blue T-Shirt",
"price": 29.99,
"compareAtPrice": 39.99,
"currency": "USD",
"categories": ["Clothing", "T-Shirts"],
"tags": ["sale", "bestseller"],
"brand": "Acme Apparel",
"imageUrl": "https://example.com/images/blue-tshirt.jpg",
"url": "https://example.com/products/blue-tshirt",
"inStock": true,
"sku": "BTS-001-BL",
"variants": [
{
"id": "var-s",
"name": "Small",
"sku": "BTS-001-BL-S",
"price": 29.99,
"inStock": true,
"options": { "size": "S", "color": "Blue" }
},
{
"id": "var-m",
"name": "Medium",
"sku": "BTS-001-BL-M",
"price": 29.99,
"inStock": true,
"options": { "size": "M", "color": "Blue" }
}
]
}'

Respuesta:

{
"id": "prod_abc123",
"productId": "SKU-12345",
"name": "Classic Blue T-Shirt",
"price": 29.99,
"createdAt": "2024-01-20T10:00:00.000Z"
}

Upsert masivo de productos (cuerpo de matriz)​

No hay un endpoint masivo independiente: POST /catalog/products acepta un objeto de producto único o una matriz JSON de objetos de producto, sin objeto envoltorio. El formato de matriz crea o actualiza hasta 500 productos en una solicitud, mediante upsert por productId.

POST /catalog/products

Cuerpo de la solicitud:

Una matriz JSON de hasta 500 elementos; una matriz vacía o con más de 500 devuelve 400. Cada elemento sigue el mismo formato que el objeto único, incluido el campo opcional source por producto.

Cada elemento recibe una validación completa. Un elemento no válido se informa en failed mediante su índice de matriz, nunca se acepta silenciosamente, y los elementos válidos restantes reciben upsert de todos modos.

[
{ "productId": "SKU-001", "name": "Product 1", "price": 19.99 },
{ "productId": "SKU-002", "name": "Product 2", "price": 29.99, "source": "shopify" }
]

Respuesta:

A diferencia del formato de objeto, que devuelve el producto creado, el formato de matriz devuelve un resumen agregado:

{
"processed": 2,
"upserted": 2,
"failed": []
}
CampoDescripción
processedNúmero de elementos recibidos en la matriz de solicitud.
upsertedProductos creados o actualizados realmente.
failedErrores por elemento: index (posición en la matriz de solicitud), productId / sku (cuando existen en el elemento) y reason.

Listar productos​

Consulta productos con filtrado y paginación.

GET /catalog/products

Parámetros de consulta:

ParámetroTipoDescripción
searchcadenaBusca por nombre, descripción o SKU.
categoriescadena[]Filtra por categorías.
tagscadena[]Filtra por etiquetas.
brandcadenaFiltra por marca.
inStockbooleanoFiltra por estado de stock.
minPricenúmeroPrecio mínimo.
maxPricenúmeroPrecio máximo.
limitnúmeroResultados por página (predeterminado: 50).
offsetnúmeroDesplazamiento de paginación.
sortBycadenaCampo de orden: name, price, createdAt, updatedAt.
sortOrdercadenaasc o desc.

Ejemplo:

curl "https://api-eu1.joryio.com/api/catalog/products?categories=T-Shirts&inStock=true&limit=20" \
-H "Authorization: Bearer $TOKEN"

Campos que rellenan las integraciones de tienda. Las respuestas de producto también incluyen campos que rellena una tienda conectada (WooCommerce) y que las rutas de creación y actualización no aceptan: gtin (GTIN, UPC, EAN o ISBN, o null), attributes (nombre del atributo a sus valores, por ejemplo {"Material": ["Linen"]}, o null), variants[].gtin y sourceModifiedAt (cuándo cambió la tienda el producto por última vez). brand y description (texto sin formato) se rellenan igual en los productos sincronizados.

Obtener categorías​

Lista todas las categorías de producto.

GET /catalog/categories

Obtener marcas​

Lista todas las marcas de producto.

GET /catalog/brands

Pedidos​

Crear un pedido​

Rastrea un pedido nuevo.

POST /orders

Cuerpo de la solicitud:

CampoTipoObligatorioDescripción
orderIdcadenaSíTu ID de pedido (número de pedido de tu plataforma).
userIdcadenaUno de los dosID de usuario de tu cliente.
joryioUserIdcadenaUno de los dosID de usuario interno de Joryio (alternativa a userId).
totalnúmeroSíTotal del pedido.
itemsobjeto[]SíLíneas de pedido.
statuscadenaNoEstado del pedido (predeterminado: pending).
paymentStatuscadenaNopaid o unpaid: si el pedido se ha pagado. Un pedido contra reembolso o por transferencia bancaria está processing y unpaid hasta que llega el dinero, y en la página Pedidos se muestra como Pendiente de pago en lugar de Pagado. Sin él, un pedido en proceso / enviado / entregado cuenta como pagado (un pedido existente conserva su valor). Los ingresos no dependen de él. Consulta Pagado o pendiente de pago.
currencycadenaNoCódigo de moneda.
subtotalnúmeroNoSubtotal antes de descuentos.
discountnúmeroNoImporte del descuento.
shippingnúmeroNoCoste de envío.
taxnúmeroNoImporte de impuestos.
totalRefundednúmeroNoImporte reembolsado hasta ahora, en la moneda del pedido (predeterminado 0). Para un reembolso parcial, envía el importe parcial con status: partiallyRefunded; para un reembolso total, define total con status: refunded. Los reembolsos se deducen de los ingresos atribuidos.
refundsobjeto[]NoTodos los reembolsos hasta ahora (hasta 100): { "id": "RF-1", "amount": 29.99, "createdAt": "2026-06-03T09:00:00Z", "reason": "Wrong size", "items": [{ "productId": "SKU-1", "quantity": 1 }] }; solo amount es obligatorio. Cada reembolso nuevo emite un evento Order Refunded (por id; sin él, cuando sube el importe reembolsado) y un reembolso ya informado no vuelve a emitir nada. Si se envían sin totalRefunded, su suma es el importe reembolsado. Pasar a status: cancelled (o POST /orders/:id/cancel) emite un Order Cancelled. Consulta Eventos de reembolso y cancelación.
couponCodecadenaNoCupón aplicado.
couponsobject[]NoCada código de cupón que usó el pedido (hasta 50): { "code": "SUMMER10", "amount": 10.0, "type": "fixed_amount" }. amount (el descuento que dio el código) y type son opcionales. En el upsert, coupons reemplaza los códigos guardados ([] los borra); omítelo para conservarlos. Si se envían ambos, coupons prevalece sobre couponCode.
shippingAddressobjetoNoDirección de envío.
campaignIdcadenaNoCampaña de atribución.
canvasIdcadenaNoCanvas de atribución.
sourcecadenaNoFuente del pedido (email, sms, direct).
utmSourcecadenaNoFuente UTM.
utmMediumcadenaNoMedio UTM.
utmCampaigncadenaNoCampaña UTM.
salesChannelcadenaNoDónde se hizo el pedido: online, pos, app, phone, marketplace u other (mayúsculas o minúsculas). Cualquier otro valor devuelve 400. Si se omite, un pedido nuevo es online y uno existente conserva su canal. Consulta Tienda y canal de venta.
storeIdcadenaNoTu propio ID para la tienda a la que pertenece el pedido (una tienda física, una sucursal, un segundo sitio web), hasta 100 caracteres. Lo comparan el filtro de Tienda de la página de Pedidos y la condición de segmento "Compró en la tienda".
storeNamecadenaNoEl nombre visible de la tienda, hasta 255 caracteres, que se muestra junto a storeId.
Identificación de usuario

Debes proporcionar userId o joryioUserId. Usa userId con tus propios identificadores de usuario (lo más habitual). Usa joryioUserId si dispones del ID de usuario interno de Joryio (el id de 24 caracteres hexadecimales devuelto por la API de usuarios).

Estructura de línea de pedido:

{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"sku": "BTS-001",
"quantity": 2,
"price": 29.99,
"total": 59.98,
"imageUrl": "https://example.com/image.jpg"
}

Solicitud de ejemplo:

curl -X POST https://api-eu1.joryio.com/api/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "ORD-2024-001",
"userId": "user-123",
"total": 89.97,
"subtotal": 99.97,
"discount": 10.00,
"shipping": 5.99,
"tax": 4.99,
"currency": "USD",
"coupons": [
{ "code": "SAVE10", "amount": 10.00, "type": "fixed_amount" }
],
"items": [
{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"quantity": 2,
"price": 29.99,
"total": 59.98
},
{
"productId": "prod_def456",
"name": "Black Jeans",
"quantity": 1,
"price": 49.99,
"total": 49.99
}
],
"source": "email",
"salesChannel": "pos",
"storeId": "tlv-dizengoff",
"storeName": "Tel Aviv - Dizengoff",
"campaignId": "camp_xyz789"
}'

Tienda y canal de venta​

Cada pedido indica dónde se vendió (salesChannel) y, cuando la nombra, a qué tienda pertenece (storeId). Así, un comercio con una tienda física y un sitio web, o con dos tiendas, puede filtrar y dividir los pedidos por cada una y crear segmentos como "compró en la tienda física en los últimos 90 días".

salesChannelSignificado
onlineTu propio sitio web o checkout web (el valor por defecto)
posUn punto de venta en una tienda física
appTu propia app móvil
phoneIntroducido por el personal: pedidos por teléfono, email o manuales, borradores
marketplaceUn marketplace de terceros
otherCualquier otra cosa

Los valores son fijos e iguales en todas partes: en esta API, en las propiedades de eventos (sales_channel), en las condiciones de segmento y en la exportación CSV de Pedidos.

Las tiendas conectadas rellenan ambos por ti. La tienda es la propia conexión de tienda; el canal sale de lo que registra la plataforma:

TiendaCanal de venta
ShopifyDel source_name del pedido: web, online_store, checkout y los pedidos de suscripción son online; pos es pos; shopify_draft_order, iphone y android (pedidos que crea el personal en el admin o en la app de Shopify) son phone; cualquier otro es other
WooCommerceDe created_via: checkout y store-api son online; admin es phone; un plugin de punto de venta es pos; rest-api y cualquier otro es other
MagentoUn pedido creado en el admin (sin IP del comprador) es phone; cualquier otro es online
PrestaShopSiempre online - PrestaShop no registra dónde se hizo el pedido

Un evento Order Completed del SDK puede enviar sales_channel (y store_id, store_name). Sin él, un pedido desde una app iOS o Android es app y uno desde un sitio web es online. Los eventos Order Completed, Order Refunded y Order Cancelled llevan sales_channel y, cuando el pedido tiene tienda, store_id, para que un recorrido pueda dispararse con ellos.

Completar un pedido​

Marca un pedido como enviado.

POST /orders/:id/fulfill

Cuerpo de la solicitud:

{
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS"
}

Cancelar un pedido​

Cancela un pedido.

POST /orders/:id/cancel

Cuerpo de la solicitud:

{
"reason": "Customer requested cancellation"
}

Reembolsar un pedido​

Procesa un reembolso.

POST /orders/:id/refund

Cuerpo de la solicitud:

{
"refundAmount": 29.99,
"reason": "Product defective",
"partial": true
}

Estadísticas de pedidos​

Obtiene estadísticas de pedidos para un intervalo de fechas.

GET /orders/stats?startDate=2024-01-01&endDate=2024-01-31

Respuesta:

{
"totalOrders": 156,
"totalRevenue": 12450.50,
"averageOrderValue": 79.81,
"ordersByStatus": {
"pending": 5,
"processing": 12,
"shipped": 45,
"delivered": 90,
"cancelled": 4
}
}

Rastreo de carritos​

Actualizar un carrito​

Rastrea o actualiza el carrito de un usuario.

PUT /carts

Cuerpo de la solicitud:

{
"userId": "user_123",
"items": [
{
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"price": 29.99,
"quantity": 2,
"total": 59.98,
"imageUrl": "https://example.com/image.jpg"
}
],
"value": 59.98,
"subtotal": 59.98,
"currency": "USD",
"checkoutUrl": "https://store.example.com/checkout?cart=abc123"
}

value es el total del carrito tal como lo cuentas (puede incluir envío e impuestos). subtotal es opcional: el subtotal de los artículos, antes de envío e impuestos - si no se envía, se calcula a partir de los artículos (el total de cada línea, o si no price x quantity). El subtotal es el valor del carrito en E-Commerce → Carritos, en el filtro de segmento Valor del carrito, en minValue y en totalAbandonedValue.

Añadir al carrito​

Añade un artículo al carrito de un usuario.

POST /carts/add

Cuerpo de la solicitud:

{
"userId": "user_123",
"item": {
"productId": "prod_abc123",
"name": "Blue T-Shirt",
"price": 29.99,
"quantity": 1,
"total": 29.99
}
}

Obtener carritos abandonados​

Lista carritos abandonados para campañas de recuperación.

GET /carts/abandoned

Parámetros de consulta:

ParámetroTipoDescripción
minValuenúmeroValor mínimo del carrito.
abandonedMinutesAgonúmeroMáximo de minutos desde el abandono.
limitnúmeroResultados por página.
offsetnúmeroDesplazamiento de paginación.

Respuesta:

{
"data": [
{
"id": "cart_123",
"userId": "user_456",
"items": [...],
"value": 149.99,
"abandonedAt": "2024-01-20T15:30:00.000Z",
"checkoutUrl": "https://store.example.com/checkout?cart=abc"
}
],
"total": 42,
"hasMore": true
}

Estadísticas de carritos​

Obtiene estadísticas de carritos y abandonos.

GET /carts/stats

Respuesta:

{
"totalCarts": 1250,
"abandonedCarts": 312,
"recoveredCarts": 87,
"totalAbandonedValue": 45670.50,
"recoveryRate": 27.88
}

Análisis RFM​

El análisis RFM (recencia, frecuencia y valor monetario) segmenta a los clientes según su comportamiento de compra.

Obtener datos RFM de un usuario​

Obtiene las puntuaciones y el segmento RFM de un usuario concreto.

GET /rfm/user/:userId

:userId es el id interno del usuario o el id externo con el que tu app identifica a la persona; un id desconocido devuelve 404.

Respuesta:

{
"totalOrders": 12,
"totalSpent": 849.50,
"averageOrderValue": 70.79,
"firstOrderDate": "2023-06-15T10:00:00.000Z",
"lastOrderDate": "2024-01-18T14:30:00.000Z",
"daysSinceLastOrder": 2,
"hasActiveCart": false,
"rfmRecency": 5,
"rfmFrequency": 4,
"rfmMonetary": 4,
"rfmScore": "544",
"rfmSegment": "champions"
}

Los campos RFM son null para un contacto sin pedidos contados (los pedidos cancelados y reembolsados no cuentan).

Obtener distribución RFM​

Obtiene la distribución de clientes entre los segmentos RFM.

GET /rfm/distribution

Respuesta:

{
"champions": { "count": 150, "totalValue": 125000 },
"loyal": { "count": 320, "totalValue": 89000 },
"potential_loyalists": { "count": 180, "totalValue": 32000 },
"new_customers": { "count": 450, "totalValue": 28000 },
"at_risk": { "count": 95, "totalValue": 42000 },
"lost": { "count": 280, "totalValue": 15000 },
"scoring": {
"computedAt": "2026-09-02T02:00:00.000Z",
"method": "quantile",
"buyers": 1475
}
}

Los niveles son quintiles dentro de la tienda (method: "quantile"): la recencia, la frecuencia y el gasto neto se ordenan entre los compradores de la tienda y se dividen en cinco grupos iguales, de modo que el nivel superior es el quinto superior de tus clientes, sea cual sea la moneda. Una tienda con menos de 25 compradores usa umbrales fijos (method: "fixed"). Las puntuaciones se recalculan cada noche y con cada pedido; scoring.computedAt indica cuándo.

Segmentos RFM​

SegmentoDescripciónPuntuaciones RFM típicas
CampeonesMejores clientes; compran a menudo y gastan más.555, 554, 545
LealesClientes constantes.444, 443, 434
Leales potencialesRecientes, con frecuencia media.433, 343, 333
Clientes nuevosAcaban de hacer su primera compra.511, 512, 411
PrometedoresRecientes, pero con frecuencia baja.422, 322, 312
Necesitan atenciónInteracción media en descenso.332, 322, 233
A punto de dormirsePor debajo de la media, en riesgo.211, 212, 221
En riesgoFueron leales, pero no han comprado recientemente.144, 143, 244
No se pueden perderFueron los mejores clientes y ahora están inactivos.155, 154, 255
HibernandoInteracción baja, inactivos desde hace tiempo.122, 121, 112
PerdidosPuntuaciones más bajas, probablemente abandonaron.111

Seguimiento de la tienda Shopify​

Activa (o actualiza) el píxel de Joryio en la tienda Shopify conectada - eventos de la tienda y del checkout - y escribe los ajustes que lee el app embed de Joryio. Cuando el píxel está activo, el antiguo script de seguimiento de la tienda (tiendas conectadas antes de octubre de 2026) deja de enviar eventos - Joryio no lo elimina; Shopify lo quita al desinstalar la app y deja de cargarlo el 1 de marzo de 2027. Se puede volver a llamar sin riesgo. Requiere integrations:write.

POST /api/integrations/shopify/storefront-tracking
Authorization: Bearer YOUR_API_KEY
{
"pixel": { "status": "active", "id": "gid://shopify/WebPixel/123", "error": null, "checkedAt": "2026-10-03T10:00:00.000Z" },
"missingScopes": [],
"legacyScriptTag": false,
"appEmbed": {
"configured": true,
"themeEditorUrl": "https://my-store.myshopify.com/admin/themes/current/editor?context=apps&activateAppId=<api-key>/joryio-embed"
}
}
pixel.statusSignificado
activeEl píxel está en la tienda y envía eventos
needs_scopesLa tienda no ha aprobado write_pixels y read_customer_events. Vuelve a iniciar la conexión con Shopify (pide al propietario que los apruebe); missingScopes los enumera
errorShopify rechazó el píxel; pixel.error indica el motivo
not_registeredAún no está configurado

GET /api/integrations/shopify devuelve el mismo objeto como storefrontTracking. Consulta Integración con Shopify.

También devuelve themeEmbed, la fila Ventanas emergentes en tu tienda: activateUrl abre el editor de temas con el embed de Joryio activado (el propietario pulsa Guardar; null cuando el servidor no tiene una app de Shopify configurada), y status es on cuando el SDK de la tienda ha informado en los últimos 7 días, si no not_reported. Consulta Activar las ventanas emergentes.

"themeEmbed": {
"activateUrl": "https://my-store.myshopify.com/admin/themes/current/editor?context=apps&activateAppId=<api-key>/joryio-embed",
"status": "not_reported"
}

installUrl (también cuando no hay ninguna tienda conectada) es la dirección a la que Instalar Joryio desde Shopify envía a un comerciante que aún no ha instalado Joryio: la ficha de la app Joryio en la Shopify App Store o un enlace de instalación, o null cuando el servidor no tiene ninguna configurada (el panel enlaza entonces con los pasos de instalación). Joryio nunca pide la dirección de una tienda - la tienda se instala desde Shopify y se vincula. Consulta Conectar tu tienda.

Apertura de la aplicación desde Shopify​

Cuando un comerciante instala o abre la aplicación de Joryio desde Shopify, Shopify añade a la URL de la aplicación parámetros firmados (shop, hmac, timestamp, host). Envíalos sin cambios; Joryio comprueba la firma de Shopify y que el enlace no tenga más de una hora, y devuelve el estado de la tienda en el espacio de trabajo actual. Nunca conecta nada. Requiere integrations:read.

POST /api/integrations/shopify/app-launch
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "query": { "shop": "my-store.myshopify.com", "host": "YWRtaW4uc2hvcGlmeS5jb20vc3RvcmUvbXktc3RvcmU", "timestamp": "1760000000", "hmac": "<from Shopify>" } }
{ "status": "not_connected", "shopDomain": "my-store.myshopify.com" }
statusSignificado
connected_hereLa tienda está conectada a este espacio de trabajo
connected_elsewhereConectada a otro espacio de trabajo de la organización; workspaceId lo indica
not_connectedNo está conectada en la organización - inicia la conexión con POST /api/integrations/shopify/oauth/initiate

Los parámetros sin firma, alterados o con más de una hora reciben 400. Consulta Integración de Shopify.

Ajuste de nueva suscripción en el pago de la tienda​

Activa o desactiva el ajuste Permitir que las suscripciones en el pago vuelvan a suscribir a quien se dio de baja de la conexión de la tienda. :platform es shopify, woocommerce, magento o prestashop. Desactivado por defecto. Si está activado, una suscripción en el pago o en el registro del cliente de esa tienda - incluidos los pagos como invitado - con fecha posterior a la última baja del contacto lo vuelve a suscribir. Nunca pasa por encima de una queja de spam, un rebote permanente, un STOP por SMS (solo una confirmación de doble opt-in), un bloqueo de toda la organización ni la baja de otro contacto. Cada cambio queda en el registro de auditoría. Requiere settings:write.

PUT /api/integrations/shopify/consent-settings
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "checkoutResubscribe": true }
{ "checkoutResubscribe": true }

GET /api/integrations/<platform> devuelve el valor actual en checkoutResubscribe. Consulta Consentimiento de marketing de tiendas.

Salvaguardas de las casillas de consentimiento en el pago (WooCommerce, Magento, PrestaShop)​

La misma ruta acepta las salvaguardas de Record consent immediately de una conexión WooCommerce o Magento, y las mismas salvaguardas para la casilla de newsletter del checkout de PrestaShop, que siempre llega a Joryio antes del pedido (módulo 1.2.8 o posterior - consulta Cuándo cuenta una marca). :platform es woocommerce, magento o prestashop; Shopify recibe 400. Una marca en el checkout que llega antes de un pedido cuenta al momento solo para un comprador o suscriptor conocido; cualquier otro queda pendiente hasta su pedido o el enlace de confirmación - consulta Cuándo cuenta una marca. checkoutConsentConfirmEmail (por defecto false) envía a ese invitado un enlace de confirmación por correo. checkoutConsentTickLimit (1-100, por defecto 3) es cuántas marcas se aceptan por dirección IP del comprador, por checkout y por dirección de correo cada hora; una marca por encima se ignora. Envía uno o ambos, solos o con checkoutResubscribe. Cada cambio queda en el registro de auditoría. Requiere settings:write.

PUT /api/integrations/magento/consent-settings
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "checkoutConsentConfirmEmail": true, "checkoutConsentTickLimit": 5 }
{ "checkoutConsentConfirmEmail": true, "checkoutConsentTickLimit": 5 }

GET /api/integrations/woocommerce, GET /api/integrations/magento y GET /api/integrations/prestashop devuelven ambos valores.

newShopperOptIns - Consentimientos de compradores nuevos (que aún no han comprado) - es hold (predeterminado) o immediate. Rige todo consentimiento que llega sin pedido de alguien que Joryio aún no conoce: una casilla marcada en el checkout enviada antes del pedido, y un consentimiento del registro, de la página de la cuenta, del boletín o de la sincronización de clientes. hold lo deja pendiente hasta el pedido o el enlace de confirmación; immediate lo cuenta al momento (menos estricto - cualquier visitante puede introducir cualquier correo). Los compradores conocidos y los suscriptores existentes cuentan al momento en ambos casos, una baja siempre se aplica al momento y checkoutConsentTickLimit sigue vigente. La misma ruta y plataformas, solo o con los demás campos; cada cambio queda en el registro de auditoría; requiere settings:write. GET /api/integrations/<platform> lo devuelve como newShopperOptIns.

PUT /api/integrations/woocommerce/consent-settings
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "newShopperOptIns": "immediate" }
{ "newShopperOptIns": "immediate" }

GET /api/integrations/woocommerce y GET /api/integrations/magento también devuelven storeCheckoutOptIn: los ajustes de consentimiento en el pago del propio plugin tal como los informó por última vez (plugin de WooCommerce 1.6.16 o posterior, extensión de Magento 1.4.17 o posterior). Son de solo lectura - se configuran en la tienda, nunca con esta API - y valen null hasta que el plugin los informa. recordMode es order (solo cuando se realiza el pedido) o immediate (registrar el consentimiento de inmediato).

{ "storeCheckoutOptIn": { "pluginVersion": "1.6.16", "checkoutBox": true, "preTicked": false, "recordMode": "order", "reportedAt": "2026-10-27T10:00:00.000Z" } }

Identidad del visitante: reconocer a los visitantes por el email que escriben​

Recognise visitors by the email they type - consulta WooCommerce y Shopify. Activado por defecto: un email escrito en el checkout o en un formulario identifica el navegador del visitante como el contacto con ese email. Desactivado (identidad estricta): un email escrito nunca identifica el navegador; los carritos, pedidos y envíos de formularios siguen yendo al contacto del email.

GET /api/integrations/<platform> devuelve visitorIdentity: identifyByTypedEmail, source (default - aún no se ha informado ni configurado nada, es decir, activado; plugin - informado por el plugin de WooCommerce 1.6.22 o posterior, la extensión de Magento 1.4.21 o posterior o el módulo de PrestaShop 1.2.10 o posterior; joryio - configurado en Joryio), pluginVersion y reportedAt. En WooCommerce, Magento y PrestaShop el ajuste vive en el plugin de la tienda y aquí es solo de lectura. En Shopify, configúralo con la ruta siguiente; las demás plataformas reciben 400. Cada cambio queda en el registro de auditoría. Requiere settings:write.

PUT /api/integrations/shopify/visitor-identity
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "identifyByTypedEmail": false }
{ "visitorIdentity": { "identifyByTypedEmail": false, "source": "joryio", "pluginVersion": null, "reportedAt": "2026-11-01T10:00:00.000Z" } }

Conexión con PrestaShop (acceso anticipado)​

Lee y gestiona la conexión con PrestaShop del espacio de trabajo (una tienda PrestaShop por conexión; consulta Integración con PrestaShop). GET devuelve { "connected": false, "platform": "prestashop" } cuando no hay nada conectado. La ruta api-key devuelve la clave jry_ps_ que pegas en el módulo (requiere integrations:write, igual que settings, sync y DELETE). GET también devuelve workspaceId, prestashopVersion y moduleVersion. sync acepta type: full (por defecto), delta (productos, clientes y pedidos que cambiaron desde la última sincronización correcta), products, customers, orders o carts (carritos abiertos de las últimas 24 horas), y responde al momento mientras la sincronización se ejecuta. DELETE desconecta la tienda y revoca la clave.

GET /api/integrations/prestashop
Authorization: Bearer YOUR_API_KEY
{
"connected": true,
"status": "connected",
"platform": "prestashop",
"storeName": "My Shop",
"storeUrl": "https://shop.example.com",
"shopId": 1,
"workspaceId": "6f1c2d3e-...",
"prestashopVersion": "8.2.1",
"moduleVersion": "1.0.0",
"syncSettings": { "syncProducts": true, "syncOrders": true, "syncCustomers": true, "syncCarts": true, "historicalDays": 90 },
"checkoutResubscribe": false,
"syncStats": { "productCount": 120, "orderCount": 860, "customerCount": 540 },
"lastSyncAt": "2026-10-21T10:00:00.000Z",
"connectedAt": "2026-10-21T09:58:00.000Z"
}

connectedAt es la última vez que la tienda se conectó (cada reconexión lo actualiza); GET /api/integrations/shopify, /woocommerce y /magento también lo devuelven. Para una conexión anterior a que se registrara, se devuelve la fecha de creación del registro.

GET /api/integrations/prestashop/api-key
Authorization: Bearer YOUR_API_KEY
{ "apiKey": "jry_ps_...", "isNew": false }
PUT /api/integrations/prestashop/settings
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "syncCarts": true, "historicalDays": 90 }
POST /api/integrations/prestashop/sync
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "type": "full" }
DELETE /api/integrations/prestashop
Authorization: Bearer YOUR_API_KEY

Webhooks​

Los eventos de e-commerce emiten webhooks que pueden activar flujos de Canvas:

EventoDescripción
ecommerce.order.createdSe realizó un pedido nuevo.
ecommerce.order.fulfilledPedido enviado.
ecommerce.order.cancelledPedido cancelado.
ecommerce.order.refundedPedido reembolsado.
ecommerce.order.status_changedCambió el estado del pedido.
ecommerce.cart.abandonedCarrito marcado como abandonado.
ecommerce.cart.recoveredCarrito abandonado recuperado.
ecommerce.cart.updatedCambió el contenido del carrito.

Consultar por tus ID​

Obtener un producto por tu ID de producto​

Recupera un producto mediante tu identificador de producto.

GET /catalog/products/by-product-id/:productId

Un identificador sin registro asociado responde 404, no 200 con un cuerpo vacío.

Ejemplo:

curl "https://api-eu1.joryio.com/api/catalog/products/by-product-id/SKU-12345" \
-H "Authorization: Bearer $TOKEN"

Obtener un pedido por tu ID de pedido​

Recupera un pedido mediante tu identificador de pedido.

GET /orders/by-order-id/:orderId

Un identificador sin registro asociado responde 404, no 200 con un cuerpo vacío.

Ejemplo:

curl "https://api-eu1.joryio.com/api/orders/by-order-id/ORD-2024-001" \
-H "Authorization: Bearer $TOKEN"

Pedidos, carritos y RFM por API​

Alta/actualización masiva de pedidos​

POST /orders/upsert

La forma principal en que una tienda que no está en Shopify, WooCommerce o Magento envía sus pedidos. Idempotente por orderId: reenviar el mismo pedido lo actualiza en lugar de duplicarlo, así que reintentar tras un fallo de red es seguro.

Envía cada pedido en la moneda en que se pagó, sin convertir nada: Joryio convierte total a la moneda de los informes del espacio de trabajo al tipo del Banco Central Europeo de la fecha placedAt del pedido, y los informes suman ese importe convertido.

Cuerpo de la petición:

{
"orders": [
{
"orderId": "1001",
"userId": "user_123",
"email": "[email protected]",
"total": 89.90,
"currency": "EUR",
"placedAt": "2026-08-27T10:15:00Z",
"status": "completed",
"items": [
{ "productId": "prod_abc123", "quantity": 2, "price": 29.95 }
]
}
]
}

Respuesta:

{ "upserted": 1, "created": 1, "updated": 0 }

Tendencia de ingresos​

GET /orders/revenue-trend?days=30&granularity=day

Ingresos netos en el tiempo (sin reembolsos ni cancelaciones). granularity es day, week o month.

Respuesta:

{
"currency": "EUR",
"points": [
{ "date": "2026-08-25", "revenue": 1240.50, "orders": 14 },
{ "date": "2026-08-26", "revenue": 980.00, "orders": 11 }
]
}

Ciclo de vida del carrito​

POST /carts/remove
POST /carts/clear
POST /carts/mark-abandoned
POST /carts/recovered

Cuatro acciones que impulsan los recorridos de recuperación de carrito. clear y recovered terminan ambos el carrito y NO son intercambiables: recovered dice que la recuperación funcionó y le atribuye el pedido; clear dice que el carrito simplemente ya no existe. Usar clear tras una compra borra en silencio la prueba de que tu recorrido de recuperación aportó algo.

{ "userId": "user_123", "orderId": "1001" }

Listar segmentos RFM​

GET /rfm/segments

Los segmentos RFM del espacio de trabajo con sus tamaños actuales, para que una integración pueda segmentar sin recalcular la puntuación.

Respuesta:

{
"segments": [
{ "segment": "champions", "users": 412, "avgRecencyDays": 8, "avgOrders": 6.2 },
{ "segment": "at_risk", "users": 1180, "avgRecencyDays": 96, "avgOrders": 2.1 }
],
"computedAt": "2026-08-27T02:10:00Z"
}

Estadísticas del catálogo​

GET /catalog/stats

Totales y salud del catálogo, contados sobre todos los productos, nunca sobre una página.

Respuesta:

{
"productCount": 8214,
"categoryCount": 37,
"brandCount": 12,
"outOfStockCount": 341,
"lowStockCount": 96,
"unclassifiedCount": 18,
"lowStockThreshold": 10
}

outOfStockCount cuenta los productos con quantity 0, o inStock: false cuando no se controla cantidad; lowStockCount, los que están entre 1 y lowStockThreshold; unclassifiedCount, los que no tienen categoría o marca.

Respuestas de error​

{
"statusCode": 404,
"message": "Product SKU-12345 not found",
"error": "Not Found"
}
Código de estadoDescripción
400Cuerpo de solicitud no válido.
401Se requiere autenticación.
403Permisos insuficientes.
404Recurso no encontrado.
409Conflicto (ID externo duplicado).
500Error interno del servidor.