Ir al contenido

POST /v1/partner/tickets/bulk

Upsert de hasta 1000 boletos por request, idempotente por externalRef. Reenviar la misma fila actualiza en lugar de duplicar; puedes reenviar tu inventario completo con seguridad (por ejemplo, un sync cada hora). Es también el único endpoint para transferencias y para cambiar maxUses.

Método y rutaPOST /v1/partner/tickets/bulk
AuthAuthorization: Bearer agk_…
Content-Typeapplication/json
Rate limit60 req/min por llave
CampoTipoObligatorioLímiteDescripción
experienceIdstring (UUID)Experiencia (evento) enlazada a tu proveedor.
rowsTicketImportRow[]1 – 1000Filas a upsertar.
CampoTipoObligatorioLímiteDescripción
qrContentstring1 – 500 charsContenido del QR tal cual está en tu boleto. Se normaliza en el servidor.
externalRefstringNo (recomendado)≤ 200 charsTu ID estable del boleto: clave de idempotencia y de void/consulta. Sin él, el boleto se identifica solo por qrContent.
status"valid" | "void"Nodefault validredeemed no se acepta: solo lo produce la puerta.
maxUsesintegerNo1 – 1000, default 1Entradas que admite el boleto. Ver Multi-usos.
holderRefstringNo (recomendado)≤ 200 charsID estable del titular actual en tu sistema (userId, teléfono…). Base de transferencias y del sello multi-uso.
holderNamestringNo≤ 200 charsNombre del titular para mostrar al operador en puerta.
metadataobject (JSON libre)NoSe muestra al operador (tipo de entrada, asiento, zona…). No lo uses para datos sensibles.

Las llaves son camelCase exactamente como aparecen; las desconocidas se ignoran. holderRef, holderName y externalRef se recortan (trim); un valor vacío se trata como ausente.

Para cada fila, el servidor busca el boleto en este orden:

  1. Si la fila trae externalRef: por (tu proveedor, externalRef).
    • Existe en esta experiencia con el mismo QR → updated.
    • Existe en esta experiencia con otro QR → reissued (reemisión; ver Transferencias).
    • Existe en otra experiencia → rechazo EXPERIENCE_MISMATCH.
  2. Si no se encontró por externalRef (o la fila no lo trae): por (experiencia, qrContent).
    • Existe → updated (y se le asigna el externalRef si venía).
    • No existe → inserted.

En un update los campos ausentes conservan su valor anterior (externalRef, metadata, maxUses, holderRef, holderName). Hay una excepción importante:

Un boleto ya redimido nunca se pisa desde el bulk (REDEEMED_CONFLICT).

Antes de guardar (y antes de comparar en puerta) aplicamos la misma función a cada código:

  1. trim de espacios.
  2. Si el resultado es una URL http(s)://… con parámetro de query chl no vacío (QRs estilo Google Charts), el código real es el valor de chl (también recortado). Si la URL no trae chl, se conserva la URL completa.
qrContent enviadoGuardado
" ABC.DEF "ABC.DEF
"https://chart.googleapis.com/chart?cht=qr&chl=GHI.JKL"GHI.JKL
"https://tickets.example.com/t/9f3a"la URL completa

Envía el contenido tal cual está en tu QR; no lo pre-proceses.

{
"inserted": 2,
"updated": 0,
"reissued": 0,
"rejected": [
{ "index": 2, "reason": "REDEEMED_CONFLICT" }
]
}
CampoTipoDescripción
insertedintegerBoletos nuevos en el espejo.
updatedintegerBoletos existentes actualizados (mismo externalRef, mismo QR). No incluye reemisiones.
reissuedintegerBoletos cuyo QR cambió (mismo externalRef, qrContent nuevo). Opcional: si falta, es 0.
rejectedarrayFilas rechazadas: index es el índice 0-based dentro de rows, reason un código estable.

Las filas rechazadas no afectan al resto del request (cada fila se procesa en su propia subtransacción): revisa rejected[].index, corrige solo esas y reenvía.

reasonQué pasóQué hacer
EMPTY_CODEqrContent quedó vacío tras la normalización.Revisa el contenido del QR que envías.
DUPLICATE_IN_BATCHOtra fila anterior del mismo request tiene el mismo qrContent (normalizado). Gana la primera aparición.Deduplica antes de enviar.
INVALID_STATUSstatus distinto de valid | void.En la práctica el API lo detecta antes con 400 VALIDATION_FAILED.
INVALID_MAX_USESmaxUses no es un entero entre 1 y 1000.Ídem: normalmente 400 VALIDATION_FAILED.
USES_CONFLICTEl maxUses nuevo es menor que los usos ya consumidos por el boleto.No reduzcas el cupo por debajo de lo consumido. Ver Multi-usos.
PROVIDER_MISMATCHEse qrContent ya existe en la experiencia en el espejo de otro proveedor.Contacta a alpadevs: el evento tiene inventario de otra ticketera con ese código.
EXPERIENCE_MISMATCHEse externalRef ya existe en otra experiencia tuya. Un boleto no cambia de evento.Usa un externalRef distinto por evento.
REDEEMED_CONFLICTEl boleto ya fue redimido en puerta: no se modifica, anula, transfiere ni reemite.Nada que corregir: la fila se ignora.
EXTERNAL_REF_TAKENChoque de unicidad: el externalRef ya está en uso por otro boleto del proveedor (por ejemplo, un import concurrente en otra experiencia).Reintenta la fila; si persiste, revisa tu mapeo de referencias.
QR_TAKENAl reemitir, el qrContent nuevo ya pertenece a otro boleto (otro externalRef) de la experiencia.Emite otro QR o revisa tu mapeo.

Trata cualquier reason desconocido como un rechazo genérico: podemos añadir motivos nuevos sin romper el contrato.

HTTPcodeCuándo
400VALIDATION_FAILEDBody fuera de schema: más de 1000 filas, qrContent vacío o > 500, maxUses fuera de rango, status inválido, UUID malformado…
401KEY_INVALIDLlave ausente, malformada, revocada o proveedor desactivado.
403EXPERIENCE_NOT_LINKEDLa experiencia no está enlazada a tu proveedor (o no existe).
429RATE_LIMITEDMás de 60 requests por minuto en esta ruta.
500INTERNALError nuestro; reintenta con backoff (el request es idempotente).

Formato y detalle en Errores comunes.

Ventana de terminal
curl -s -X POST "https://gate-partners.alpadevs.com/v1/partner/tickets/bulk" \
-H "Authorization: Bearer $GATE_KEY" \
-H "Content-Type: application/json" \
-d '{
"experienceId": "22222222-2222-4222-8222-222222222222",
"rows": [
{
"qrContent": "ABC.DEF",
"externalRef": "TG-1001",
"holderRef": "user-42",
"holderName": "Ana Pérez",
"metadata": { "priceName": "VIP", "experienceName": "Main Event" }
},
{
"qrContent": "https://chart.googleapis.com/chart?cht=qr&chl=GHI.JKL",
"externalRef": "TG-1002",
"maxUses": 3,
"holderRef": "+5215512345678",
"holderName": "Carlos Ruiz"
},
{ "qrContent": "MNO.PQR", "externalRef": "TG-1003", "status": "void" }
]
}'

Primera carga:

{ "inserted": 3, "updated": 0, "reissued": 0, "rejected": [] }

Mismo request repetido después de que TG-1001 entró por la puerta:

{
"inserted": 0,
"updated": 2,
"reissued": 0,
"rejected": [{ "index": 0, "reason": "REDEEMED_CONFLICT" }]
}