> ## 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.

# Pushing tickets

> How to get the tickets you sell on your platform into VENTRY.

This is the most common integration: you sell, VENTRY accredits. A ticket that is
not registered in VENTRY cannot be used to enter the venue.

**Scopes needed:** `event.read` and `tickets.write` (the latter requires manual
activation by the organiser).

## 1. Map your catalogue against VENTRY's

First, and only once per event: find out which identifiers to use.

<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": "General pass",
      "allowed_zones": [{ "id": "6a3d2a67a1e963300ee2c99c", "name": "Venue" }],
      "included_items": []
    },
    {
      "object": "ticket_type",
      "id": "6a3d04709bcd4477a1bfe4c1",
      "name": "VIP pass",
      "allowed_zones": [
        { "id": "6a3d2a67a1e963300ee2c99c", "name": "Venue" },
        { "id": "6a3d2a6ea1e963300ee2c99d", "name": "VIP area" }
      ],
      "included_items": [
        { "product_id": "6a3d2b11a1e963300ee2ca02", "name": "Drink", "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": "Friday", "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": "Saturday", "date": "2026-06-13T00:00:00.000Z",
      "starts_at": "2026-06-13T16:00:00.000Z", "ends_at": "2026-06-14T05:00:00.000Z" }
  ]
}
```

Store that mapping in your configuration: your product → `ticket_type` plus a
list of `valid_days`.

## 2. Create the tickets

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.ventry.es/v1/tickets \
    -H "Authorization: Bearer $VENTRY_KEY" \
    -H "Idempotency-Key: order-84213-tickets" \
    -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": "order-84213-tickets",
      "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": "order-84213-tickets",
      },
      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: order-84213-tickets",
          "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": "VIP pass" },
      "attendee": {
        "name": "Ada Lovelace",
        "email": "ada@example.com",
        "document": { "type": "DNI", "last4": "5678" }
      },
      "valid_days": [
        { "id": "6a3d0512...", "name": "Friday" },
        { "id": "6a3d0518...", "name": "Saturday" }
      ],
      "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>
  **Send your own `code`.** It is optional — omit it and VENTRY generates one — but
  sending your platform's ticket number lets you reconcile the two systems later
  without keeping a lookup table.
</Tip>

### Things that matter

<AccordionGroup>
  <Accordion title="The batch is all or nothing">
    If a code already exists or a reference is invalid, **no** ticket in the batch
    is created and you get a `409` or `422`. Fix it and resend the whole batch:
    you do not have to work out which ones went through.
  </Accordion>

  <Accordion title="500 per call, maximum">
    And with a write quota of 20 calls per minute, that is 10,000 tickets per
    minute. Batch them: one call per ticket is the fastest way to hit the limit.
  </Accordion>

  <Accordion title="Document and date of birth are optional">
    If you do not collect them, do not send them. VENTRY fills in neutral values
    and accreditation works the same. The document number is never returned in
    full: only the last four digits.
  </Accordion>

  <Accordion title="extra_zones for add-ons">
    If you sell an add-on that grants access to an extra zone — a VIP area pass on
    top of a general ticket — pass it in `extra_zones` with the zone id. On
    accreditation, the wristband receives the ticket type's zones **plus** these.
  </Accordion>
</AccordionGroup>

## 3. Cancel refunded tickets

<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>

The ticket becomes `inactive` and can no longer be used for accreditation.

<Warning>
  A ticket that has **already been redeemed** cannot be cancelled: it returns `409
    ticket_already_claimed`. The attendee is inside the venue with an active
  wristband, and cancelling the ticket would leave that wristband working against a
  dead ticket. For that case, the wristband has to be blocked from the VENTRY
  dashboard.
</Warning>

## 4. Check the state

During the event, to find out who has come in:

```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"
```

Or a single ticket:

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

`checked_in_at` tells you when it was accredited and `wristband` which wristband
was assigned to it.

## End-to-end flow

<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",
  };

  // Map configured once per event.
  const TYPE_BY_PRODUCT = {
    "vip-pass": "6a3d04709bcd4477a1bfe4c1",
    "general-pass": "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 === "friday-pass" ? [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,
        // Derived from the order: survives this process crashing.
        "Idempotency-Key": `order-${order.id}-tickets`,
      },
      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']}"}

  # Map configured once per event.
  TYPE_BY_PRODUCT = {
      "vip-pass": "6a3d04709bcd4477a1bfe4c1",
      "general-pass": "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"] == "friday-pass" else ALL_DAYS,
              "attendee": attendee,
          })

      response = requests.post(
          f"{VENTRY}/tickets",
          headers={
              **HEADERS,
              # Derived from the order: survives this process crashing.
              "Idempotency-Key": f"order-{order['id']}-tickets",
          },
          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";

  // Map configured once per event.
  const TYPE_BY_PRODUCT = [
      "vip-pass" => "6a3d04709bcd4477a1bfe4c1",
      "general-pass" => "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"] === "friday-pass"
                  ? [ALL_DAYS[0]]
                  : ALL_DAYS,
              "attendee" => $attendee,
          ];
      }

      // callVentry(): see the implementation on the Errors page.
      return callVentry("POST", "/tickets", [
          "headers" => [
              // Derived from the order: survives this process crashing.
              "Idempotency-Key: order-{$order['id']}-tickets",
              "Content-Type: application/json",
          ],
          "body" => json_encode(["tickets" => $tickets]),
      ]);
  }
  ```
</CodeGroup>
