Saltar al contenido principal

API de cupones

Un programa de cupones es la receta a partir de la cual un mensaje emite códigos personales ({{ coupons.<slug>.code }}). Joryio crea cada código en la tienda WooCommerce, Shopify, Magento o PrestaShop conectada (o lo genera y sigue por sí sola cuando el programa no tiene tienda) y reconoce el pedido que lo usó. El comportamiento de los programas se describe en Códigos de cupón.

Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com/api - consulta Resumen de la API.

Permisos​

PermisoPermite
campaigns:readListar y leer programas, el informe y las tiendas conectadas
campaigns:writeCrear programas, cambiar programas que aún no han emitido códigos, borrarlos, emitir un código
settings:writeNecesario además para cambiar un programa activo con códigos - uno que ya emitió códigos reales
users:readListar códigos emitidos (los códigos pertenecen a personas)

El objeto programa​

{
"id": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"name": "Welcome 10%",
"slug": "welcome10",
"description": "10% off the first order",
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"platform": "woocommerce",
"storeName": "My Store",
"source": "personal",
"status": "active",
"rules": {
"discountType": "percent",
"amount": 10,
"currency": null,
"minimumSubtotal": 50,
"minimumQuantity": null,
"scope": { "kind": "all" },
"excludeSaleItems": false,
"usageLimit": 1,
"oncePerCustomer": true,
"combinable": false,
"expiry": { "mode": "relative", "amount": 14, "unit": "days", "endOfDay": true }
},
"rulesVersion": 1,
"codeFormat": { "length": 10, "charset": "unambiguous", "prefix": "WELCOME-", "suffix": "", "separatorEvery": null },
"fallback": { "policy": "skip", "sharedCode": null },
"reuseOpenCode": true,
"lockToPerson": false,
"live": true,
"sampleCode": "WELCOME-PREVIEW",
"storeError": null,
"stats": { "issued": 1240, "redeemed": 311, "revenue": 18420.5, "currency": "EUR" },
"createdAt": "2026-10-04T08:00:00.000Z",
"updatedAt": "2026-10-04T08:00:00.000Z"
}
CampoValores
integrationIdUna tienda conectada, o null: Joryio genera y sigue los códigos sin tienda
statusdraft, active, paused, archived - solo active emite códigos
rules.discountTypepercent, fixed_cart, fixed_product (no en PrestaShop: 400), free_shipping
rules.scope{ "kind": "all" }, o products (WooCommerce, Shopify, PrestaShop) / categories (WooCommerce, Magento, PrestaShop) / collections (Shopify) con ids - los ids de la propia tienda; products en Magento responde 400
rules.minimumQuantitySolo Shopify
rules.excludeSaleItemsSolo WooCommerce
rules.usageLimitUsos por código, por defecto 1; null = ilimitado
rules.expiry{ "mode": "none" }, { "mode": "fixed", "date": "2026-12-31T23:59:59Z" }, o { "mode": "relative", "amount": 14, "unit": "days" | "hours", "endOfDay": true }
codeFormat.charsetunambiguous (letras y dígitos sin caracteres parecidos, predeterminado), letters, digits; length 6-20
fallback.policyskip (predeterminado), shared_code (necesita sharedCode), send_without
lockToPersonWooCommerce, Magento y PrestaShop (la cuenta de cliente de la persona)
livetrue en cuanto el programa ha emitido un código real (no de prueba)
rulesVersionSube cuando cambian las reglas de un programa activo con códigos; los códigos ya emitidos conservan las reglas con las que se crearon

Programas​

Listar programas​

GET /coupons/programs?status=active&includeArchived=false

Devuelve un array de objetos programa. Los programas archivados se omiten salvo con includeArchived=true.

Crear un programa​

POST /coupons/programs

El cuerpo es el objeto programa sin los campos de solo lectura (id, platform, storeName, rulesVersion, stock, live, sampleCode, storeError, stats, fechas). slug es [a-z0-9_], único por espacio de trabajo. status puede ser draft (predeterminado) o active.

{
"name": "Welcome 10%",
"slug": "welcome10",
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"rules": { "discountType": "percent", "amount": 10, "scope": { "kind": "all" }, "usageLimit": 1, "oncePerCustomer": true, "combinable": false, "expiry": { "mode": "relative", "amount": 14, "unit": "days" } },
"codeFormat": { "length": 10, "charset": "unambiguous", "prefix": "WELCOME-" },
"fallback": { "policy": "skip" },
"reuseOpenCode": true,
"lockToPerson": false
}

Leer un programa​

GET /coupons/programs/{id}

Actualizar un programa​

PATCH /coupons/programs/{id}

Cualquier campo del cuerpo de creación, o status (draft, active, paused, archived). Cambiar un programa activo con códigos necesita settings:write además de campaigns:write.

Borrar un programa​

DELETE /coupons/programs/{id}

204 para un programa que nunca emitió un código real. Un programa activo con códigos responde 409 - archívalo en su lugar (PATCH con "status": "archived").

Informe del programa​

GET /coupons/programs/{id}/report
{
"programId": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"totals": { "issued": 1240, "redeemed": 311, "redemptionRate": 0.2508, "revenue": 18420.5, "discount": 1842.05, "currency": "EUR", "expired": 402, "failed": 3, "redeemedBySomeoneElse": 12 },
"bySource": [
{ "kind": "journey", "id": "a1b2c3d4-0000-4000-8000-000000000001", "name": "Welcome journey", "issued": 980, "redeemed": 270, "revenue": 15900 }
],
"daily": [{ "date": "2026-10-04", "issued": 52, "redeemed": 14 }]
}

Los ingresos y el descuento están ambos en la moneda de informes del espacio de trabajo (currency): un pedido en otra moneda se convierte con el tipo de cambio de ese mismo pedido, el mismo con el que se convirtieron sus ingresos. Los códigos de prueba no cuentan.

Emitir un código​

POST /coupons/programs/{id}/issue

Entrega un código de un programa activo a un contacto - para socios que muestran el código en su propio canal. Identifica al contacto con uno de:

CampoDescripción
contactIdEl id interno del contacto en Joryio
externalIdTu id de usuario
emailEl email del contacto
issueKeyClave de idempotencia opcional: el mismo issueKey devuelve siempre el mismo código
{ "externalId": "customer-123", "issueKey": "order-confirmation-9876" }

Respuesta:

{
"issued": true,
"reused": false,
"applyUrl": "https://shop.example.com/?jry_coupon=WELCOME-7KQ4M2XH9P",
"issue": {
"id": "1f6a...", "programId": "6c0f...", "programSlug": "welcome10", "programName": "Welcome 10%",
"contactId": "66a1f2c3d4e5f6a7b8c9d0e1", "code": "WELCOME-7KQ4M2XH9P", "status": "issued", "isTest": false,
"sourceKind": "api", "campaignId": null, "canvasId": null, "nodeId": null, "channel": null,
"issuedAt": "2026-10-04T09:00:00.000Z", "expiresAt": "2026-10-18T23:59:59.000Z",
"redeemedAt": null, "redeemedOrderId": null, "redeemedRevenue": null, "failureReason": null
}
}

Con reuseOpenCode activado en el programa, un contacto que todavía tiene un código abierto del programa recibe ese código ("reused": true). Cuando no se puede emitir un código la respuesta es { "issued": false, "reason": "..." } - por ejemplo program_paused, store_error, store_permission_missing (la tienda Shopify aún no ha aprobado write_discounts), store_reconnect_required (WooCommerce se añadió al sitio WordPress después de conectarlo - desconecta y vuelve a conectar el plugin de Joryio).

Códigos emitidos​

GET /coupons/issues?contactId=66a1f2c3d4e5f6a7b8c9d0e1&programId=&status=&limit=50&offset=0

Devuelve { "items": [issue, ...], "total": 3 }, del más reciente al más antiguo. status es pending, issued, failed, redeemed, expired o revoked. limit hasta 200.

Tiendas conectadas​

GET /coupons/stores
[
{
"integrationId": "0b8d6f2a-5f43-4e0a-b9a3-7a8b2d1c4e55",
"platform": "shopify",
"storeName": "My Shop",
"storeUrl": "https://my-shop.myshopify.com",
"status": "connected",
"capabilities": { "lockToPerson": false, "perCodeExpiry": false, "percentCap": false, "scopes": ["all", "products", "collections"] },
"missingPermission": "write_discounts"
}
]

missingPermission indica lo que el comerciante debe conceder antes de poder crear códigos (Shopify: abre Integrations → Shopify → Approve new permissions).

Orígenes de los códigos​

El campo source de un programa indica de dónde salen sus códigos. Se fija al crearlo (por defecto personal) y no puede cambiar una vez que el programa tiene códigos. Consulta Orígenes de los códigos.

sourceCódigossourceConfig
personalUn código por persona, creado cuando un mensaje lo necesita{}
bankCreados en la tienda por adelantado y repartidos uno por persona. Necesita integrationId.{ "bankTargetSize": 5000 } - cuántos códigos sin usar mantener listos (0 = solo a petición y antes de las campañas)
uploadedSubidos por ti (Subir códigos) y repartidos uno por persona; nunca se crean en una tienda. integrationId opcional; lockToPerson no permitido.{}
sharedUn código para todos, registrado por emisión{ "sharedCode": "SUMMER10", "createInStore": true } - createInStore necesita integrationId; false solo registra un código que ya existe

Los programas también incluyen:

CampoValores
lowStock{ "threshold": 500, "percent": null } - para bank y uploaded: alerta cuando quedan menos de threshold códigos sin usar, o menos del percent (1-99) % de todos los códigos cargados. null desactiva una regla.
stockSolo lectura. { "available": 1830, "total": 5000, "level": "ok" } para bank y uploaded (level: ok, low, empty); null para los demás orígenes.

En un programa uploaded, y en uno shared con createInStore: false, las rules solo describen el valor que muestra {{ coupons.<slug>.value }} - la tienda nunca las ve.

{
"name": "Partner coffee voucher",
"slug": "coffee",
"source": "uploaded",
"integrationId": null,
"lowStock": { "threshold": 200 },
"rules": { "discountType": "fixed_cart", "amount": 3, "currency": "EUR", "scope": { "kind": "all" }, "usageLimit": 1, "oncePerCustomer": true, "combinable": false, "expiry": { "mode": "none" } },
"fallback": { "policy": "send_without" }
}

Existencias​

GET /coupons/programs/{id}/stock
{
"programId": "6c0f3c1e-8a52-4a8e-9a39-2a1d7f0b9a11",
"source": "bank",
"tracked": true,
"available": 1830,
"total": 5000,
"used": 3170,
"level": "ok",
"lowStock": { "threshold": 500, "percent": null },
"bank": { "targetSize": 5000, "filling": false, "lastRunAt": "2026-10-04T09:15:00.000Z", "lastAdded": 250, "lastError": null }
}

tracked es false (y los recuentos 0) en los programas personal y shared. bank solo aparece en los bancos de códigos; filling es true mientras se ejecuta una reposición.

Subir códigos​

POST /coupons/programs/{id}/codes
POST /coupons/programs/{id}/codes?dryRun=true

Añade códigos a un programa uploaded. Envía o bien un archivo CSV como multipart/form-data (campo file) o bien JSON { "text": "...", "fileName": "optional.csv" } con los códigos pegados. Cada subida se suma; los códigos anteriores se quedan. dryRun=true valida y devuelve el mismo informe sin guardar nada - la vista previa.

{ "text": "code,expires_at,value\nCOFFEE-001,2026-12-31,Free coffee\nCOFFEE-002,,Free coffee" }
ColumnaObligatoriaFormato
codeSí3-64 caracteres: letras, dígitos y - _ . # / +; se comparan sin distinguir mayúsculas y minúsculas
expires_atNoYYYY-MM-DD (el final de ese día, UTC) o una fecha y hora ISO; debe estar en el futuro
valueNoHasta 100 caracteres; se muestra como {{ coupons.<slug>.value }} para este código

La fila de encabezado es opcional (sin ella las columnas se leen en este orden); los separadores coma, punto y coma y tabulador se detectan en la primera línea. Límites: 100.000 filas y 10 MB por subida, 2.000.000 de códigos por programa.

Respuesta (CouponUploadReport):

{
"uploadId": "9d2e4b10-1c3f-4b7a-8a55-0e6f2c9d1a77",
"dryRun": false,
"totalRows": 3,
"accepted": 2,
"rejected": 1,
"duplicates": 1,
"truncated": false,
"maxRows": 100000,
"hasHeader": true,
"preview": [{ "line": 2, "code": "COFFEE-001", "expiresAt": "2026-12-31T23:59:59.000Z", "value": "Free coffee" }],
"rejectedRows": [{ "line": 4, "code": "COFFEE-001", "reason": "duplicate_in_file", "message": "The same code appears earlier in this file." }]
}

uploadId es null en una prueba (dry run). preview contiene las 10 primeras filas aceptadas y rejectedRows las 500 primeras rechazadas. truncated es true cuando el archivo tenía más filas que maxRows (el resto no se leyó).

reasonSignificado
missing_codeLa fila no tiene código
invalid_codeNo son 3-64 letras, dígitos y - _ . # / +
invalid_expiryLa caducidad no es una fecha
expiredLa caducidad ya pasó
value_too_longEl valor tiene más de 100 caracteres
duplicate_in_fileEl mismo código aparece antes en el archivo
already_in_programEl programa ya tiene el código
already_existsOtro programa del espacio de trabajo tiene el código
over_limitEl programa está lleno

Un programa con otro origen responde 400.

Subidas anteriores​

GET /coupons/programs/{id}/uploads

Las últimas 100 subidas, de la más reciente a la más antigua: { "id", "kind", "fileName", "totalRows", "accepted", "rejected", "duplicates", "rejectedRows", "createdBy", "createdAt" }.

Generar códigos del banco​

POST /coupons/programs/{id}/bank/generate
{ "count": 5000 }

Crea count (1-100.000) códigos nuevos en la tienda para un programa bank. El trabajo se ejecuta en segundo plano: la respuesta es 202 Accepted con { "started": true, "requested": 5000 } al instante; síguelo con Existencias (bank.filling, bank.lastAdded, bank.lastError). Una segunda petición mientras se ejecuta una reposición responde 400. Shopify añade los códigos al descuento del programa, WooCommerce crea un cupón por código, Magento los genera bajo la regla de precio de carrito del programa con su formato de código y PrestaShop crea una regla de carrito por código.

Antes de que se envíe una campaña, cada banco que usa su contenido se repone automáticamente hasta cubrir la audiencia. Launch Review bloquea una campaña cuya audiencia es mayor que los códigos de un banco o una lista (coupon_stock_short) y avisa cuando el envío deja menos códigos que la alerta de pocas existencias (coupon_stock_low).

Errores​

EstadoCuándo
400Validación - por ejemplo lockToPerson en un programa de Shopify (Shopify decide quién puede usar un descuento por descuento, no por código), un slug ya usado o que no es [a-z0-9_], shared_code sin sharedCode, una fecha de caducidad pasada
403Falta un permiso - incluido settings:write al cambiar un programa activo con códigos
404El programa no está en este espacio de trabajo
409Borrar un programa activo con códigos