event.read y tickets.write (este último requiere
activación manual por parte del organizador).
1. Mapea tu catálogo contra el de VENTRY
Lo primero, y sólo una vez por evento: averigua qué identificadores usar.curl https://api.ventry.es/v1/ticket-types \
-H "Authorization: Bearer $VENTRY_KEY"
const types = await (
await fetch("https://api.ventry.es/v1/ticket-types", {
headers: { Authorization: `Bearer ${process.env.VENTRY_KEY}` },
})
).json();
types = requests.get(
"https://api.ventry.es/v1/ticket-types",
headers={"Authorization": f"Bearer {os.environ['VENTRY_KEY']}"},
timeout=10,
).json()
$types = callVentry("GET", "/ticket-types");
{
"object": "list",
"data": [
{
"object": "ticket_type",
"id": "6a3d04709bcd4477a1bfe4b3",
"name": "Abono General",
"allowed_zones": [{ "id": "6a3d2a67a1e963300ee2c99c", "name": "Recinto" }],
"included_items": []
},
{
"object": "ticket_type",
"id": "6a3d04709bcd4477a1bfe4c1",
"name": "Abono VIP",
"allowed_zones": [
{ "id": "6a3d2a67a1e963300ee2c99c", "name": "Recinto" },
{ "id": "6a3d2a6ea1e963300ee2c99d", "name": "Zona VIP" }
],
"included_items": [
{ "product_id": "6a3d2b11a1e963300ee2ca02", "name": "Consumición", "quantity": 2 }
]
}
]
}
curl https://api.ventry.es/v1/days \
-H "Authorization: Bearer $VENTRY_KEY"
{
"object": "list",
"data": [
{ "object": "day", "id": "6a3d0512...", "name": "Viernes", "date": "2026-06-12T00:00:00.000Z",
"starts_at": "2026-06-12T16:00:00.000Z", "ends_at": "2026-06-13T05:00:00.000Z" },
{ "object": "day", "id": "6a3d0518...", "name": "Sábado", "date": "2026-06-13T00:00:00.000Z",
"starts_at": "2026-06-13T16:00:00.000Z", "ends_at": "2026-06-14T05:00:00.000Z" }
]
}
ticket_type +
lista de valid_days.
2. Crea las entradas
curl -X POST https://api.ventry.es/v1/tickets \
-H "Authorization: Bearer $VENTRY_KEY" \
-H "Idempotency-Key: pedido-84213-entradas" \
-H "Content-Type: application/json" \
-d '{
"tickets": [
{
"code": "TR-84213-001",
"ticket_type": "6a3d04709bcd4477a1bfe4c1",
"valid_days": ["6a3d0512...", "6a3d0518..."],
"attendee": {
"name": "Ada Lovelace",
"email": "[email protected]",
"document_type": "DNI",
"document_number": "12345678Z"
}
},
{
"code": "TR-84213-002",
"ticket_type": "6a3d04709bcd4477a1bfe4b3",
"valid_days": ["6a3d0512..."],
"attendee": { "name": "Alan Turing", "email": "[email protected]" }
}
]
}'
const response = await fetch("https://api.ventry.es/v1/tickets", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VENTRY_KEY}`,
"Idempotency-Key": "pedido-84213-entradas",
"Content-Type": "application/json",
},
body: JSON.stringify({
tickets: [
{
code: "TR-84213-001",
ticket_type: "6a3d04709bcd4477a1bfe4c1",
valid_days: ["6a3d0512...", "6a3d0518..."],
attendee: {
name: "Ada Lovelace",
email: "[email protected]",
document_type: "DNI",
document_number: "12345678Z",
},
},
{
code: "TR-84213-002",
ticket_type: "6a3d04709bcd4477a1bfe4b3",
valid_days: ["6a3d0512..."],
attendee: { name: "Alan Turing", email: "[email protected]" },
},
],
}),
});
response = requests.post(
"https://api.ventry.es/v1/tickets",
headers={
"Authorization": f"Bearer {os.environ['VENTRY_KEY']}",
"Idempotency-Key": "pedido-84213-entradas",
},
json={
"tickets": [
{
"code": "TR-84213-001",
"ticket_type": "6a3d04709bcd4477a1bfe4c1",
"valid_days": ["6a3d0512...", "6a3d0518..."],
"attendee": {
"name": "Ada Lovelace",
"email": "[email protected]",
"document_type": "DNI",
"document_number": "12345678Z",
},
},
{
"code": "TR-84213-002",
"ticket_type": "6a3d04709bcd4477a1bfe4b3",
"valid_days": ["6a3d0512..."],
"attendee": {"name": "Alan Turing", "email": "[email protected]"},
},
]
},
timeout=60,
)
<?php
$payload = [
"tickets" => [
[
"code" => "TR-84213-001",
"ticket_type" => "6a3d04709bcd4477a1bfe4c1",
"valid_days" => ["6a3d0512...", "6a3d0518..."],
"attendee" => [
"name" => "Ada Lovelace",
"email" => "[email protected]",
"document_type" => "DNI",
"document_number" => "12345678Z",
],
],
[
"code" => "TR-84213-002",
"ticket_type" => "6a3d04709bcd4477a1bfe4b3",
"valid_days" => ["6a3d0512..."],
"attendee" => ["name" => "Alan Turing", "email" => "[email protected]"],
],
],
];
$response = callVentry("POST", "/tickets", [
"headers" => [
"Idempotency-Key: pedido-84213-entradas",
"Content-Type: application/json",
],
"body" => json_encode($payload),
]);
{
"object": "list",
"data": [
{
"object": "ticket",
"id": "6a3d0611a1e963300ee2cb10",
"code": "TR-84213-001",
"status": "active",
"ticket_type": { "id": "6a3d04709bcd4477a1bfe4c1", "name": "Abono VIP" },
"attendee": {
"name": "Ada Lovelace",
"email": "[email protected]",
"document": { "type": "DNI", "last4": "5678" }
},
"valid_days": [
{ "id": "6a3d0512...", "name": "Viernes" },
{ "id": "6a3d0518...", "name": "Sábado" }
],
"extra_zones": [],
"wristband": null,
"checked_in_at": null,
"created_at": "2026-05-30T09:14:02.113Z",
"updated_at": "2026-05-30T09:14:02.113Z"
}
],
"has_more": false,
"next_cursor": null
}
Envía tu propio
code. Es opcional —si lo omites, VENTRY genera uno— pero
mandar el número de entrada de tu plataforma te permite reconciliar los dos
sistemas después sin guardar una tabla de equivalencias.Puntos importantes
El lote es todo o nada
El lote es todo o nada
Si un código ya existe o una referencia no es válida, no se crea ninguna
entrada del lote y recibes
409 o 422. Corrige y reenvía el lote entero:
no tienes que averiguar cuáles cuajaron.Máximo 500 por llamada
Máximo 500 por llamada
Y con el cupo de escritura en 20 llamadas por minuto, eso son 10.000 entradas
por minuto. Agrupa: hacer una llamada por entrada es la forma más rápida de
chocar con el límite.
Documento y fecha de nacimiento son opcionales
Documento y fecha de nacimiento son opcionales
Si no los recoges, no los mandes. VENTRY rellena valores neutros y la
acreditación funciona igual. El número de documento nunca se devuelve
completo: sólo los últimos cuatro dígitos.
extra_zones para los complementos
extra_zones para los complementos
Si vendes un complemento que da acceso a una zona adicional —un pase de zona
VIP sobre una entrada general— pásalo en
extra_zones con el id de la zona.
Al acreditar, la pulsera recibirá las zonas del tipo de entrada más éstas.3. Anula lo que se devuelva
curl -X POST https://api.ventry.es/v1/tickets/TR-84213-001/cancel \
-H "Authorization: Bearer $VENTRY_KEY"
await fetch("https://api.ventry.es/v1/tickets/TR-84213-001/cancel", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.VENTRY_KEY}` },
});
requests.post(
"https://api.ventry.es/v1/tickets/TR-84213-001/cancel",
headers={"Authorization": f"Bearer {os.environ['VENTRY_KEY']}"},
timeout=30,
)
callVentry("POST", "/tickets/TR-84213-001/cancel");
inactive y deja de servir para acreditarse.
Una entrada ya canjeada no se puede anular: responde
409 ticket_already_claimed. El asistente está dentro del recinto con una pulsera
activa, y anular la entrada dejaría la pulsera funcionando con la entrada muerta.
Para ese caso hay que bloquear la pulsera desde el panel de VENTRY.4. Comprueba el estado
Durante el evento, para saber quién ha entrado:curl "https://api.ventry.es/v1/tickets?status=used&updated_since=2026-06-12T18:00:00Z&limit=200" \
-H "Authorization: Bearer $VENTRY_KEY"
curl https://api.ventry.es/v1/tickets/TR-84213-001 \
-H "Authorization: Bearer $VENTRY_KEY"
checked_in_at te dice cuándo se acreditó y wristband qué pulsera se le
asignó.
Flujo completo
const VENTRY = "https://api.ventry.es/v1";
const headers = {
Authorization: `Bearer ${process.env.VENTRY_KEY}`,
"Content-Type": "application/json",
};
// Mapa configurado una vez por evento.
const TYPE_BY_PRODUCT = {
"abono-vip": "6a3d04709bcd4477a1bfe4c1",
"abono-general": "6a3d04709bcd4477a1bfe4b3",
};
const ALL_DAYS = ["6a3d0512...", "6a3d0518..."];
async function pushOrder(order) {
const body = {
tickets: order.tickets.map((t) => ({
code: t.number,
ticket_type: TYPE_BY_PRODUCT[t.product],
valid_days: t.product === "abono-viernes" ? [ALL_DAYS[0]] : ALL_DAYS,
attendee: {
name: `${t.firstName} ${t.lastName}`.trim(),
email: t.email,
...(t.documentNumber && {
document_type: t.documentType ?? "DNI",
document_number: t.documentNumber,
}),
},
})),
};
const response = await fetch(`${VENTRY}/tickets`, {
method: "POST",
headers: {
...headers,
// Derivada del pedido: sobrevive a que se caiga este proceso.
"Idempotency-Key": `pedido-${order.id}-entradas`,
},
body: JSON.stringify(body),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}
return response.json();
}
import os, requests
VENTRY = "https://api.ventry.es/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['VENTRY_KEY']}"}
# Mapa configurado una vez por evento.
TYPE_BY_PRODUCT = {
"abono-vip": "6a3d04709bcd4477a1bfe4c1",
"abono-general": "6a3d04709bcd4477a1bfe4b3",
}
ALL_DAYS = ["6a3d0512...", "6a3d0518..."]
def push_order(order):
tickets = []
for t in order["tickets"]:
attendee = {
"name": f"{t['first_name']} {t['last_name']}".strip(),
"email": t["email"],
}
if t.get("document_number"):
attendee["document_type"] = t.get("document_type", "DNI")
attendee["document_number"] = t["document_number"]
tickets.append({
"code": t["number"],
"ticket_type": TYPE_BY_PRODUCT[t["product"]],
"valid_days": [ALL_DAYS[0]] if t["product"] == "abono-viernes" else ALL_DAYS,
"attendee": attendee,
})
response = requests.post(
f"{VENTRY}/tickets",
headers={
**HEADERS,
# Derivada del pedido: sobrevive a que se caiga este proceso.
"Idempotency-Key": f"pedido-{order['id']}-entradas",
},
json={"tickets": tickets},
timeout=60,
)
if not response.ok:
error = response.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")
return response.json()
<?php
const VENTRY = "https://api.ventry.es/v1";
// Mapa configurado una vez por evento.
const TYPE_BY_PRODUCT = [
"abono-vip" => "6a3d04709bcd4477a1bfe4c1",
"abono-general" => "6a3d04709bcd4477a1bfe4b3",
];
const ALL_DAYS = ["6a3d0512...", "6a3d0518..."];
function pushOrder(array $order): array
{
$tickets = [];
foreach ($order["tickets"] as $t) {
$attendee = [
"name" => trim("{$t['first_name']} {$t['last_name']}"),
"email" => $t["email"],
];
if (!empty($t["document_number"])) {
$attendee["document_type"] = $t["document_type"] ?? "DNI";
$attendee["document_number"] = $t["document_number"];
}
$tickets[] = [
"code" => $t["number"],
"ticket_type" => TYPE_BY_PRODUCT[$t["product"]],
"valid_days" => $t["product"] === "abono-viernes"
? [ALL_DAYS[0]]
: ALL_DAYS,
"attendee" => $attendee,
];
}
// callVentry(): ver la implementación en la página de Errores.
return callVentry("POST", "/tickets", [
"headers" => [
// Derivada del pedido: sobrevive a que se caiga este proceso.
"Idempotency-Key: pedido-{$order['id']}-entradas",
"Content-Type: application/json",
],
"body" => json_encode(["tickets" => $tickets]),
]);
}