# Reserved seating

Events with a **published seat map** sell seated tiers **by the seat**. The
flow is: read the map, atomically **hold** the seats your customer picked,
take the payment, then issue with the `hold_id` — one ticket per held seat
(see [Issue a ticket](#issue-a-ticket) for the worked flow).

Holds last **10 minutes**, cover up to **10 seats**, and can be extended while
a card entry runs long. Once an event's map is published, issuing on a seated
tier *without* a hold is rejected with `400 seat_required` — the map stays
truthful.

---

## GET /v1/events/{id}/seatmap

The published seat map: layout (sections, rows, stage/text objects), **live
availability**, and schedule-effective per-tier pricing — everything you need
to render a seat picker.

**Scope:** `events:read`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Event id. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/events/evt_a1b2c3d4e5f6a7b8c9d0e1f2/seatmap \
  -H "Authorization: Bearer sk_test_your_key_here"
```

### Example response

```json
{
  "id": "smap_1f2e3d4c",
  "name": "Main Hall",
  "canvas": { "width": 1200, "height": 800 },
  "sections": [
    {
      "id": "S1",
      "name": "Stalls",
      "kind": "seated",
      "tier": "Stalls",
      "x": 100, "y": 220, "rotation": 0,
      "rows": 12, "cols": 20,
      "row_label_scheme": "alpha",
      "capacity": 240
    }
  ],
  "objects": [
    { "id": "O1", "type": "stage", "label": "STAGE", "x": 300, "y": 40, "width": 600, "height": 120 }
  ],
  "version": 3,
  "total_seats": 240,
  "availability": {
    "sold": ["S1-A-1", "S1-A-2"],
    "held": ["S1-B-4"],
    "blocked": ["S1-L-20"]
  },
  "tiers": {
    "Stalls": { "price": 79.00, "currency": "USD" }
  }
}
```

`availability` lists seat ids that are **not** currently sellable — everything
else in the layout is free. `tiers` maps each tier to its
**schedule-effective** price (what issuance will actually charge right now).

> Errors: `404 event_not_found`, `404 seatmap_not_found` (the event has no
> published seat map).

---

## POST /v1/events/{id}/seats/hold

Atomically claim specific seats for **10 minutes**. Either every requested
seat is held, or none are — a conflict returns `409 seats_unavailable` naming
the contested seats. Duplicate seat ids in the request collapse to one claim.

**Scope:** `tickets:issue`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Event id. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `seat_ids` | string[] | Yes | 1–10 seat ids from the seat map. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/events/evt_a1b2c3d4e5f6a7b8c9d0e1f2/seats/hold \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "seat_ids": ["S1-C-7", "S1-C-8"] }'
```

### Example response

```json
{
  "hold_id": "hold_9c8b7a6d5e4f",
  "expires_at": "2026-07-11T09:10:00.000Z",
  "ttl_seconds": 600,
  "seats": [
    { "seat_id": "S1-C-7", "tier": "Stalls", "section": "Stalls", "row": "C", "seat": "7" },
    { "seat_id": "S1-C-8", "tier": "Stalls", "section": "Stalls", "row": "C", "seat": "8" }
  ]
}
```

Pass `hold_id` to [`POST /v1/events/{id}/tickets`](#api-tickets) before
`expires_at` — one ticket is issued per held seat. A hold that lapses simply
releases its seats; issuing against it returns `409 hold_expired`.

> Errors: `400 parameter_missing` / `parameter_invalid` (`seat_ids` empty,
> non-strings, or more than 10), `404 event_not_found`,
> `404 seatmap_not_found`, `409 seats_unavailable`.

---

## POST /v1/events/{id}/seats/hold/{holdId}/extend

Reset the hold's expiry to **10 minutes from now** — use it when a card entry
or 3-D Secure challenge runs long.

**Scope:** `tickets:issue`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Event id. |
| `holdId` | string | Yes | The hold to extend. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/events/evt_a1b2c3d4e5f6a7b8c9d0e1f2/seats/hold/hold_9c8b7a6d5e4f/extend \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -X POST
```

### Example response

```json
{
  "hold_id": "hold_9c8b7a6d5e4f",
  "expires_at": "2026-07-11T09:18:00.000Z",
  "seats_extended": 2
}
```

> Errors: `404 event_not_found`, `404 hold_not_found` (the hold is missing,
> already released, expired, or already used to issue).

---

## DELETE /v1/events/{id}/seats/hold/{holdId}

Release a hold, returning its seats to availability — when the customer walks
away. **Idempotent**: releasing an expired or unknown hold succeeds with
`released: 0`.

**Scope:** `tickets:issue`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Event id. |
| `holdId` | string | Yes | The hold to release. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/events/evt_a1b2c3d4e5f6a7b8c9d0e1f2/seats/hold/hold_9c8b7a6d5e4f \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -X DELETE
```

### Example response

```json
{ "hold_id": "hold_9c8b7a6d5e4f", "released": 2 }
```

`released` is the number of seats returned to availability.

> Errors: `404 event_not_found` if the event does not exist or is not yours.
