Overview
kyoPass es una infraestructura de reventa de tickets white-label para LATAM. Los partners (plataformas de tickets) se integran vía API REST para habilitar un mercado secundario dentro de sus productos sin construir la infraestructura ellos mismos.
Base URL
Versión
v1Formato
JSON (UTF-8)Proveedores de pago
dLocal (LATAM)Flujo general
- 1.Tu plataforma recibe API Key al ser aprobada como partner.
- 2.Creas eventos con sus funciones (schedules) y rangos de precio (priceTiers) via API.
- 3.Antes de abrir el widget, tu backend llama /v1/auth/federate para obtener un FCToken del usuario.
- 4.El widget embebido usa ese FCToken para que el usuario vea, compre y venda tickets.
- 5.FC notifica a tu webhook cuando una orden se paga (order.paid).
Autenticación
La API usa dos mecanismos de autenticación según el contexto de llamada.
API Key — llamadas servidor a servidor
Envía tu API Key en el header Authorization: Bearer <apiKey>. Nunca la expongas en código cliente.
POST /v1/auth/federate HTTP/1.1
Host:
Authorization: Bearer kyo_live_xxxxxxxxxxxxxxxx
Content-Type: application/jsonFCToken — llamadas desde el widget
Es un JWT de corta duración obtenido via /v1/auth/federate. Se pasa como Authorization: Bearer <fcToken>.
El FCToken expira en 2 horas. Genera uno nuevo antes de abrir el widget.
Partner API
Endpoints que llamas desde tu backend con Authorization: Bearer <apiKey>.
Federar usuario
/v1/auth/federateRegistra o actualiza un usuario de tu plataforma y devuelve un FCToken. Llámalo desde tu backend antes de abrir el widget.
REQUEST BODY
partnerUserIdREQdisplayNameREQemailphonebankAccountBANK ACCOUNT OBJECT
countryREQdocumentREQdocumentTypeaccountTypeaccountNumberREQbankCodeREQbankName{
"partnerUserId": "usr_123",
"displayName": "María García",
"email": "maria@ejemplo.com"
}{
"partnerUserId": "usr_123",
"displayName": "María García",
"email": "maria@ejemplo.com",
"bankAccount": {
"country": "GT",
"document": "12345678",
"documentType": "DPI",
"accountType": "SAVINGS",
"accountNumber": "0000123456",
"bankCode": "BARED",
"bankName": "Banrural"
}
}/v1/auth/federate es un upsert: puedes llamarlo primero sin bankAccount cuando el usuario solo va a comprar, y volver a llamarlo con los datos bancarios cuando decida vender. El mismo partnerUserId actualiza al usuario existente.
{
"fcToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 7200,
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Eventos
/v1/eventsCrea un evento. Acepta funciones (schedules) y rangos de precio (priceTiers) en el mismo request. La moneda se hereda del partner.
/v1/eventsLista todos los eventos de tu partner, incluyendo sus schedules y priceTiers.
/v1/events/{id}Obtiene un evento por ID con todos sus schedules y priceTiers.
/v1/events/{id}Actualiza los campos de un evento existente. Solo envía los campos que quieres cambiar.
POST /v1/events — REQUEST BODY
nameREQvenueREQvenueAddresscityREQtimezonestartsAtREQendsAtexternalIddescriptionimageUrlwidgetSellEnabledschedulespriceTiersPATCH /v1/events/{id} — REQUEST BODY
Todos los campos son opcionales. Solo se actualizan los que envíes.
namevenuevenueAddresscitytimezonedescriptionimageUrlstartsAtwidgetSellEnabledwidgetSellEnabled: cuando es false, el endpoint POST /v1/listings rechaza requests con FCToken (widget users) con error 403. Los listings solo pueden crearse via POST /v1/partner/listings usando API Key. Útil cuando la ticketera quiere controlar el inventario disponible para reventa.
Objeto Schedule (función)
Un evento puede tener múltiples schedules para tours, residencias o eventos con varias fechas. Si el evento tiene schedules, los vendedores deben indicar el scheduleId al crear un listing.
titleREQstartsAtREQexternalShowIdticketingUrlresaleEnabledObjeto PriceTier (categoría de precio)
Define las categorías de precio permitidas. Si el evento tiene priceTiers, los vendedores deben seleccionar uno al publicar un ticket. El sistema valida que el precio esté dentro del rango permitido.
nameREQamountREQdescriptionsortOrderexternalPriceTypeIdminResalePricemaxResalePriceLos precios con rangos permiten flexibilidad: un vendedor puede publicar a cualquier precio entre minResalePrice y maxResalePrice. Si no se definen rangos, el precio del tier es exactamente amount.
{
"name": "Bad Bunny — World Hottest Tour",
"venue": "Estadio Doroteo Guamuch Flores",
"venueAddress": "Av. Las Américas 16-01, Zona 13, Ciudad de Guatemala",
"city": "Guatemala City",
"timezone": "America/Guatemala",
"startsAt": "2026-03-15T20:00:00Z",
"externalId": "EVT-BB-GT-2026",
"imageUrl": "https://cdn.miplataforma.com/events/bad-bunny-gt.jpg",
"widgetSellEnabled": true,
"schedules": [
{
"title": "Noche 1 — 15 marzo",
"startsAt": "2026-03-15T20:00:00Z",
"externalShowId": "SHOW-001",
"ticketingUrl": "https://miplataforma.com/eventos/bad-bunny-noche-1",
"resaleEnabled": true
},
{
"title": "Noche 2 — 16 marzo",
"startsAt": "2026-03-16T20:00:00Z",
"externalShowId": "SHOW-002",
"ticketingUrl": "https://miplataforma.com/eventos/bad-bunny-noche-2",
"resaleEnabled": true
},
{
"title": "Noche Extra VIP — 17 marzo",
"startsAt": "2026-03-17T20:00:00Z",
"externalShowId": "SHOW-003",
"resaleEnabled": false
}
],
"priceTiers": [
{
"name": "General",
"amount": 35000,
"description": "Acceso general de pie",
"sortOrder": 1,
"externalPriceTypeId": "PT-GEN",
"minResalePrice": 35000,
"maxResalePrice": 70000
},
{
"name": "VIP",
"amount": 75000,
"description": "Zona VIP con vista privilegiada",
"sortOrder": 2,
"externalPriceTypeId": "PT-VIP",
"minResalePrice": 75000,
"maxResalePrice": 150000
},
{
"name": "Palco",
"amount": 120000,
"description": "Palco privado para grupos",
"sortOrder": 3,
"externalPriceTypeId": "PT-PAL"
}
]
}{
"eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2026-01-10T15:00:00Z",
"schedules": [
{
"id": "sch_aaa...",
"title": "Noche 1 — 15 marzo",
"startsAt": "2026-03-15T20:00:00Z",
"externalShowId": "SHOW-001",
"status": "active",
"resaleEnabled": true
},
{
"id": "sch_bbb...",
"title": "Noche Extra VIP — 17 marzo",
"startsAt": "2026-03-17T20:00:00Z",
"status": "active",
"resaleEnabled": false
}
],
"priceTiers": [
{
"id": "pt_111...",
"name": "General",
"amount": 35000,
"currency": "GTQ",
"externalPriceTypeId": "PT-GEN",
"minResalePrice": 35000,
"maxResalePrice": 70000,
"sortOrder": 1
}
]
}Perfil y transacciones
/v1/partner/profileDevuelve el perfil del partner autenticado.
/v1/partner/transactionsLista las órdenes pagadas del partner. Soporta ?limit= y ?offset=.
{
"id": "...",
"name": "Mi Plataforma",
"slug": "mi-plataforma",
"country": "GT",
"currency": "GTQ",
"commissionPct": 0.10,
"status": "active",
"webhookUrl": "https://mi-sitio.com/webhooks/fc",
"createdAt": "2025-01-01T00:00:00Z"
}Widget / User API
Endpoints que llama el widget embebido con el FCToken del usuario viaAuthorization: Bearer <fcToken>. No los llames desde tu backend.
Listings (usuario final · JWT)
Rutas que llama el vendedor con su fcToken (obtenido vía /auth/federate). Úsalas cuando el botón "Vender ticket" está en el frontend del partner y llama directamente a FC.
/v1/listingsLista tickets disponibles. Filtra por eventId, scheduleId, minPrice, maxPrice, limit, offset.
/v1/listings/{id}Obtiene un listing específico.
/v1/listingsEl vendedor publica un ticket para vender.
/v1/listings/{id}El vendedor actualiza precio, notas o datos del ticket.
/v1/listings/{id}El vendedor cancela/retira su ticket del mercado.
POST /v1/listings — REQUEST BODY
eventIdREQscheduleIdpriceTierIdseatSectionREQseatRowseatNumberbasePriceAmountquantitysplitTypeexternalTicketIdnotesticketLocatorticketAccessTypefaceValueAmountholderNameholderDocumentholderDocumentTypeisNominal{
"eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"seatSection": "General",
"seatRow": "A",
"seatNumber": "12",
"basePriceAmount": 120000,
"ticketLocator": "QR-A1B2C3D4E5",
"ticketAccessType": "QR"
}{
"eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"seatSection": "Platea",
"seatRow": "B",
"seatNumber": "12, 13, 14",
"basePriceAmount": 150000,
"quantity": 3,
"splitType": "TOGETHER"
}{
"eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"scheduleId": "sch_aaa...",
"priceTierId": "pt_111...",
"seatSection": "VIP",
"seatRow": "B",
"seatNumber": "7",
"basePriceAmount": 350000,
"faceValueAmount": 350000,
"externalTicketId": "TKT-99812",
"ticketLocator": "QR-VIP-XYZXYZXYZ",
"ticketAccessType": "QR",
"holderName": "María González",
"holderDocument": "12345678",
"holderDocumentType": "CI",
"isNominal": true,
"notes": "Ticket digital con acceso VIP"
}PATCH /v1/listings/{id} — REQUEST BODY (todos opcionales)
priceAmountnotesticketLocatorticketAccessTypeholderNameholderDocumentholderDocumentTypeSi el evento tiene schedules configurados, scheduleId es obligatorio y debe corresponder a una función con resaleEnabled: true. Si la función tiene resaleEnabled: false, el listing será rechazado.
Si el evento tiene priceTiers, priceTierId es obligatorio. El precio basePriceAmount debe estar dentro del rango [minResalePrice, maxResalePrice] del tier. Si se omite, se usa el precio mínimo del tier.
Multi-ticket: cuando publiques un listing con quantity > 1, seatNumber debe listar todos los asientos separados por coma (ej: "12, 13, 14" para 3 entradas). El backend valida que la cantidad de asientos coincida con quantity y normaliza el formato. Si no hay asiento asignado (general admission), deja seatNumber vacío independientemente de la cantidad. splitType controla cómo se pueden comprar: TOGETHER fuerza que se lleven todos, ANY permite comprar cualquier subconjunto.
Listings (backend del partner · API Key)
Rutas para que el backend del partner gestione listings en nombre de sus usuarios. Úsalas cuando tu sistema tiene los datos de los tickets y quieres sincronizar el estado con FC automáticamente (botón server-side, cambios de estado desde tu CMS, etc.).
/v1/partner/listingsLista todos los listings de los eventos de este partner. Filtra por eventId, scheduleId, status, minPrice, maxPrice.
/v1/partner/listingsCrea un listing en nombre de un usuario (partnerUserId).
/v1/partner/listings/{id}Actualiza un listing (precio, locator, datos del titular). El partner puede actualizar cualquier listing suyo.
/v1/partner/listings/{id}Cancela un listing. Úsalo cuando el ticket ya no está disponible en tu sistema.
POST /v1/partner/listings — REQUEST BODY
partnerUserIdREQeventIdREQscheduleIdpriceTierIdseatSectionREQseatRowseatNumberbasePriceAmountquantitysplitTypeexternalTicketIdticketLocatorticketAccessTypefaceValueAmountholderNameholderDocumentholderDocumentTypeisNominalnotes{
"partnerUserId": "user-123",
"eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"scheduleId": "sch_aaa...",
"priceTierId": "pt_111...",
"seatSection": "General",
"seatRow": "A",
"seatNumber": "15",
"basePriceAmount": 120000,
"faceValueAmount": 120000,
"externalTicketId": "TKT-88821",
"ticketLocator": "QR-GEN-ABC123",
"ticketAccessType": "QR",
"holderName": "Carlos Pérez",
"holderDocument": "87654321",
"holderDocumentType": "DNI",
"isNominal": false,
"notes": "Ticket digital, entrega vía email"
}El partnerUserId se usa para generar un sellerId interno reproducible (UUID v5). No necesitas federar al usuario previamente para crear listings desde el backend. Sin embargo, si el usuario quiere recibir payouts, sí deberá estar federado con datos bancarios.
Flujo de compra
/v1/listings/{id}/intentionPaso 1: Crea una intención de compra. Reserva el listing por 10 minutos y devuelve el precio final con comisión aplicada.
/v1/ordersPaso 2: Confirma la orden con datos del pagador. Devuelve un paymentUrl de dLocal.
/v1/orders/{id}Obtiene el estado de una orden.
/v1/orders/{id}/syncConsulta dLocal y sincroniza el estado del pago (útil en desarrollo sin webhook).
POST /v1/orders — REQUEST BODY
intentionIdREQpaymentMethodREQpayerNameREQpayerEmailREQpayerPhonepayerDocumentcountryREQ{
"intentionId": "550e8400-e29b-41d4-a716-446655440000",
"paymentMethod": "CARD",
"payerName": "Juan López",
"payerEmail": "juan@ejemplo.com",
"payerPhone": "+50212345678",
"payerDocument": "12345678",
"country": "GT"
}{
"orderId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"paymentUrl": "https://sandbox.dlocal.com/collect/pay?...",
"total": 16500,
"currency": "GTQ",
"expiresAt": "2025-04-10T16:00:00Z"
}Redirige al usuario al paymentUrl. dLocal procesará el pago y notificará al webhook configurado.
Historial del usuario
/v1/users/me/listingsTickets que el usuario tiene publicados para venta.
/v1/users/me/ordersÓrdenes de compra del usuario.
/v1/users/me/payoutsPagos recibidos por ventas completadas.
Webhooks salientes
FC envía un POST a tu webhookUrl cuando ocurren eventos importantes. Configura la URL en tu perfil de partner.
Evento: order.paid
Se dispara cuando una orden es confirmada como pagada por dLocal.
{
"event": "order.paid",
"orderId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"listingId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"buyerId": "...",
"sellerId": "...",
"amount": 16500,
"currency": "GTQ",
"paymentId": "PAY-dlocal-abc123"
}Verificación de firma
Cada request incluye el header X-FC-Signature. Verifica la firma para validar que proviene de FC.
const crypto = require('crypto')
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return `sha256=${expected}` === signature
}
// En tu handler:
app.post('/webhooks/fc', (req, res) => {
const sig = req.headers['x-fc-signature']
const raw = req.rawBody // Buffer sin parsear
if (!verifyWebhook(raw, sig, process.env.FC_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const { event, orderId } = req.body
if (event === 'order.paid') {
// Tu lógica aquí
}
res.sendStatus(200)
})FC reintenta la entrega hasta 3 veces con backoff exponencial de 2 segundos si tu endpoint responde con status != 2xx.
Onboarding
Endpoint público para solicitar acceso como partner. No requiere autenticación.
/v1/onboardingEnvía una solicitud de integración. Un admin la revisará y aprobará en 24–48 horas.
REQUEST BODY
nameREQslugREQcountryREQcurrencyREQcontactEmailREQwebhookUrl{
"name": "MiPlataforma Guatemala",
"slug": "miplataforma-gt",
"country": "GT",
"currency": "GTQ",
"contactEmail": "dev@miplataforma.com",
"webhookUrl": "https://miplataforma.com/webhooks/fc"
}{
"id": "...",
"name": "MiPlataforma Guatemala",
"status": "pending",
"message": "Solicitud recibida. Te contactaremos a dev@miplataforma.com."
}Pagos & payouts
kyoPass procesa cobros y payouts a través de un único proveedor regional. Esta sección resume las reglas operativas que condicionan qué usuarios pueden cobrar y en qué países. Si tu integración sólo recibe compradores (no paga a vendedores), sólo te afecta la sección de países/monedas.
Proveedor: dLocal
Todo el flujo de pago corre sobre dLocal. Los compradores pagan con los métodos locales del país (tarjetas, Pix, OXXO, Yappy, transferencia bancaria, etc.) y los vendedores reciben el payout por transferencia bancaria directa a su cuenta.
Futuros proveedores: la integración está detrás de un puerto (port.PaymentService). Podemos sumar otros procesadores (ej. Stripe Connect para US/EU) sin romper el contrato de la API.
Países y monedas soportadas
Estos son los países donde dLocal puede emitir payouts a cuentas bancarias. El campo country de bankAccount debe ser uno de esta lista (ISO 3166-1 alpha-2). La moneda local se usa por defecto; si el evento está en otra moneda, el payout se convierte vía USD (flujo P2P/REMITTANCE).
GTGuatemalaGTQMXMéxicoMXNBRBrasilBRLARArgentinaARSCOColombiaCOPPEPerúPENCLChileCLPUYUruguayUYUSVEl SalvadorUSDHNHondurasHNLCRCosta RicaCRCPAPanamáPABNINicaraguaNIODORep. DominicanaDOPECEcuadorUSDBOBoliviaBOBPYParaguayPYGUSEstados UnidosUSDCross-border (P2P): si la moneda del evento no coincide con la moneda local del beneficiario (ej. evento en GTQ pero payout a MX), el payout se convierte primero a USD y luego a la moneda destino. Requiere que la tasa de cambio origen → USD esté configurada en el backend (usdRates). Sin esa tasa, el payout falla con el error "tasa FX no configurada".
Reglas para la cuenta del beneficiario
Para que un vendedor reciba payouts, su registro FederatedUser debe incluir el objeto bankAccount con datos válidos. Si el bankAccount falta o tiene datos inválidos, las ventas se completan igual pero el payout queda en estado failed y no se libera el dinero al vendedor.
- ·
Titular = beneficiario
El nombre del titular de la cuenta bancaria debe coincidir con el displayName del usuario federado. dLocal rechaza el payout si el nombre no matchea.
- ·
Documento válido del país
El document + documentType deben ser de la misma persona titular y del mismo país que la cuenta. Ver sección de documentos por país.
- ·
Tipo de cuenta soportado
accountType: "CHECKING" (cuenta corriente/monetaria) o "SAVINGS" (cuenta de ahorro). Default: CHECKING.
- ·
Código de banco de dLocal
bankCode debe ser un identificador que dLocal reconozca. Varía por país (ej. GT: BARED para Banrural, BDAG para Agromercantil). Consulta la tabla de códigos de dLocal o usa el endpoint de lookup.
- ·
Cuenta propia
La cuenta debe ser del mismo titular federado, no de un tercero. En cross-border (P2P) esto aplica estrictamente — dLocal valida el CURP/RFC/DPI contra la titularidad.
- ·
Mayoría de edad
El titular debe ser mayor de 18 años en su país. dLocal valida esto contra el documento.
Payout scheduling: los payouts no se emiten al instante. Se agregan a la cola cuando la orden pasa a completed y se procesan por un job diario (/internal/jobs/process-payouts). El vendedor recibe el dinero en su cuenta en 24–72h hábiles una vez liberado el payout, según su banco local.
Tipo de documento por país
Si no envías documentType en el bankAccount, el backend asume el tipo por defecto según el país. Puedes enviar cualquiera de los tipos reconocidos; si el tipo es desconocido, se normaliza a OTHER.
GTDPI (Documento Personal de Identificación)DPIMXRFC (Registro Federal de Contribuyentes)RFCBRCPF (Cadastro de Pessoas Físicas)CPFARCUIL / CUITCUILCOCédula de CiudadaníaCCPEDNIDNICLRUTRUTUYCédula de IdentidadCISVDUIDUIHNDNIDNICRCédula de IdentidadCIPACédula de IdentidadCIDOCédula de IdentidadCIECCédula de IdentidadCIBOCédula de IdentidadCIPYCédula de IdentidadCITipos adicionales aceptados: PASSPORT, CE (Cédula de Extranjería CO), NIT, RUC, CUIT, CUI, OTHER.
Códigos de banco
El campo bankCode del bankAccount es un string de 3–5 caracteres definido por dLocal. Cada país tiene su propia lista — no hay formato universal. Abajo los códigos más comunes por país. Para la lista completa y actualizada consulta la documentación oficial de dLocal.
GUATEMALA (GT)
BAREDBanruralBDAGBanco Agromercantil (BAM)BIDUSBanco IndustrialBGTCG&T ContinentalCHNGTBanco de los TrabajadoresBANPRBanco PromericaMÉXICO (MX)
BBVAMBBVA MéxicoBANMXBanamexSANMXSantander MéxicoBNRMXBanorteHSBMXHSBC MéxicoSCOMXScotiabank MéxicoCOLOMBIA (CO)
BANCOBancolombiaBBOGBanco de BogotáDAVDaviviendaBBVACBBVA ColombiaBPOPBanco PopularNEQUINequiBRASIL (BR)
ITAUItaúBBBanco do BrasilBRADBradescoSANBRSantander BrasilCEFCaixa EconômicaNUNubankCómo obtener el código: en el widget de onboarding del vendedor, mostramos un selector con los bancos disponibles del país seleccionado y mapeamos al bankCode de dLocal automáticamente. Si integras el sell-flow desde tu propio frontend, usa un selector similar — no dejes que el usuario escriba el código a mano.
Errores
Todos los errores devuelven JSON con el campo error.
{
"error": "listing not available"
}400Bad RequestParámetros inválidos. Ej: precio fuera del rango del tier, scheduleId inválido, resaleEnabled: false.401UnauthorizedAPI Key o FCToken inválido/expirado.403ForbiddenSin permisos para este recurso.404Not FoundRecurso no encontrado.409ConflictEl listing ya fue vendido o reservado.500Server ErrorError interno. Reintentar con backoff.FC SECONDARY MARKET — API v1 — 2026