> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ventry.es/llms.txt
> Use this file to discover all available pages before exploring further.

# Volcar entradas

> Cómo llevar las entradas que vendes en tu plataforma a VENTRY.

Es la integración más habitual: tú vendes, VENTRY acredita. Una entrada que no
esté dada de alta en VENTRY no sirve para entrar al recinto.

**Permisos necesarios:** `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.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.ventry.es/v1/ticket-types \
    -H "Authorization: Bearer $VENTRY_KEY"
  ```

  ```js Node.js theme={null}
  const types = await (
    await fetch("https://api.ventry.es/v1/ticket-types", {
      headers: { Authorization: `Bearer ${process.env.VENTRY_KEY}` },
    })
  ).json();
  ```

  ```python Python theme={null}
  types = requests.get(
      "https://api.ventry.es/v1/ticket-types",
      headers={"Authorization": f"Bearer {os.environ['VENTRY_KEY']}"},
      timeout=10,
  ).json()
  ```

  ```php PHP theme={null}
  $types = callVentry("GET", "/ticket-types");
  ```
</CodeGroup>

```json theme={null}
{
  "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 }
      ]
    }
  ]
}
```

```bash theme={null}
curl https://api.ventry.es/v1/days \
  -H "Authorization: Bearer $VENTRY_KEY"
```

```json theme={null}
{
  "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" }
  ]
}
```

Guarda esa correspondencia en tu configuración: producto tuyo → `ticket_type` +
lista de `valid_days`.

## 2. Crea las entradas

<CodeGroup>
  ```bash cURL theme={null}
  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": "ada@example.com",
            "document_type": "DNI",
            "document_number": "12345678Z"
          }
        },
        {
          "code": "TR-84213-002",
          "ticket_type": "6a3d04709bcd4477a1bfe4b3",
          "valid_days": ["6a3d0512..."],
          "attendee": { "name": "Alan Turing", "email": "alan@example.com" }
        }
      ]
    }'
  ```

  ```js Node.js theme={null}
  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: "ada@example.com",
            document_type: "DNI",
            document_number: "12345678Z",
          },
        },
        {
          code: "TR-84213-002",
          ticket_type: "6a3d04709bcd4477a1bfe4b3",
          valid_days: ["6a3d0512..."],
          attendee: { name: "Alan Turing", email: "alan@example.com" },
        },
      ],
    }),
  });
  ```

  ```python Python theme={null}
  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": "ada@example.com",
                      "document_type": "DNI",
                      "document_number": "12345678Z",
                  },
              },
              {
                  "code": "TR-84213-002",
                  "ticket_type": "6a3d04709bcd4477a1bfe4b3",
                  "valid_days": ["6a3d0512..."],
                  "attendee": {"name": "Alan Turing", "email": "alan@example.com"},
              },
          ]
      },
      timeout=60,
  )
  ```

  ```php PHP theme={null}
  <?php
  $payload = [
      "tickets" => [
          [
              "code" => "TR-84213-001",
              "ticket_type" => "6a3d04709bcd4477a1bfe4c1",
              "valid_days" => ["6a3d0512...", "6a3d0518..."],
              "attendee" => [
                  "name" => "Ada Lovelace",
                  "email" => "ada@example.com",
                  "document_type" => "DNI",
                  "document_number" => "12345678Z",
              ],
          ],
          [
              "code" => "TR-84213-002",
              "ticket_type" => "6a3d04709bcd4477a1bfe4b3",
              "valid_days" => ["6a3d0512..."],
              "attendee" => ["name" => "Alan Turing", "email" => "alan@example.com"],
          ],
      ],
  ];

  $response = callVentry("POST", "/tickets", [
      "headers" => [
          "Idempotency-Key: pedido-84213-entradas",
          "Content-Type: application/json",
      ],
      "body" => json_encode($payload),
  ]);
  ```
</CodeGroup>

```json theme={null}
{
  "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": "ada@example.com",
        "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
}
```

<Tip>
  **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.
</Tip>

### Puntos importantes

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## 3. Anula lo que se devuelva

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.ventry.es/v1/tickets/TR-84213-001/cancel \
    -H "Authorization: Bearer $VENTRY_KEY"
  ```

  ```js Node.js theme={null}
  await fetch("https://api.ventry.es/v1/tickets/TR-84213-001/cancel", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.VENTRY_KEY}` },
  });
  ```

  ```python Python theme={null}
  requests.post(
      "https://api.ventry.es/v1/tickets/TR-84213-001/cancel",
      headers={"Authorization": f"Bearer {os.environ['VENTRY_KEY']}"},
      timeout=30,
  )
  ```

  ```php PHP theme={null}
  callVentry("POST", "/tickets/TR-84213-001/cancel");
  ```
</CodeGroup>

La entrada pasa a `inactive` y deja de servir para acreditarse.

<Warning>
  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.
</Warning>

## 4. Comprueba el estado

Durante el evento, para saber quién ha entrado:

```bash theme={null}
curl "https://api.ventry.es/v1/tickets?status=used&updated_since=2026-06-12T18:00:00Z&limit=200" \
  -H "Authorization: Bearer $VENTRY_KEY"
```

O una entrada concreta:

```bash theme={null}
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

<CodeGroup>
  ```js Node.js theme={null}
  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();
  }
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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]),
      ]);
  }
  ```
</CodeGroup>
