Tickets are issued for event products. These endpoints power no-login buyer access (airline-PNR style) using the short access code from the confirmation email plus the purchase email, and a per-seat attendee view via the token embedded in each attendee link/QR code.

For security, ticket lookup responds with a uniform `404` for an unknown code, an email mismatch, or an order with no tickets – the endpoint never reveals whether a code exists.

## Get Tickets

```
GET /api/v1/tickets/:code
```

Looks up an order's tickets by the access code (from the ticket confirmation email) and the purchase email. Returns the order's ticket lines with event details and per-seat attendee entries. Each seat carries its own `token` for the attendee link.

### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `email` | `string` | Yes | Buyer email – must match the order's customer email |

```bash
curl \
  -H "Authorization: Bearer your_api_key" \
  "https://your-store.yns.store/api/v1/tickets/A1B2C3?email=jane@example.com"
```

### Response

```json
{
  "code": "A1B2C3",
  "order": {
    "id": "0191abc0-1234-7def-8000-000000000001",
    "lookup": 1042,
    "createdAt": "2024-06-15T10:30:00.000Z",
    "status": "paid",
    "currency": "usd",
    "totalGross": 9000
  },
  "buyerEmail": "jane@example.com",
  "lines": [
    {
      "lineItemId": "0191abc0-2222-7def-8000-000000000010",
      "product": {
        "id": "0191abc0-0000-7000-8000-000000000100",
        "name": "Summer Festival Pass",
        "slug": "summer-festival-pass",
        "image": "https://cdn.example.com/festival.jpg"
      },
      "event": {
        "startsAt": "2024-08-01T18:00:00.000Z",
        "location": "Central Park, New York",
        "guestLabel": "Attendee",
        "guest": null
      },
      "seats": [
        {
          "token": "tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718",
          "name": "Jane Doe",
          "email": "jane@example.com"
        },
        {
          "token": "tok_1a2b3c4d5e6f70819a8b7c6d5e4f3021",
          "name": null,
          "email": null
        }
      ]
    }
  ]
}
```

A `404` is returned for an unknown code, email mismatch, or an order with no tickets.

---

## Update Tickets

```
PATCH /api/v1/tickets/:code
```

Buyer bulk-edit of attendee names and emails, verified by access code plus purchase email (same uniform `404` on mismatch). Seat tokens not belonging to the order are silently ignored. Returns the updated ticket bundle (same shape as `GET`).

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | `string` | Yes | Buyer email – must match the order's customer email |
| `seats` | `array` | Yes | Seats to update (1-200). Tokens not on the order are ignored |
| `seats[].token` | `string` | Yes | Per-seat ticket token (min 16 chars) |
| `seats[].name` | `string` | Yes | Attendee name (max 200); empty string clears the seat |
| `seats[].email` | `string \| null` | Yes | Attendee email; empty string or `null` clears it |

```bash
curl -X PATCH \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "seats": [
      {
        "token": "tok_1a2b3c4d5e6f70819a8b7c6d5e4f3021",
        "name": "John Smith",
        "email": "john@example.com"
      }
    ]
  }' \
  https://your-store.yns.store/api/v1/tickets/A1B2C3
```

### Response (200)

Returns the updated ticket bundle (same shape as `GET /api/v1/tickets/:code`).

| Status | Meaning |
|--------|---------|
| `404` | Tickets not found (unknown code, email mismatch, or no tickets) |
| `409` | Order is no longer active (cancelled or refunded) |

---

## Get Attendee Ticket

```
GET /api/v1/tickets/attendee/:token
```

Attendee ticket view by per-seat token – the capability embedded in the attendee link/QR code. Returns only that seat's details plus event/product display data, never the buyer's access code or other seats. The holder of the token can edit their own seat with `PATCH /api/v1/tickets/attendee/:token`; the buyer can edit every seat on the order with `PATCH /api/v1/tickets/:code`.

```bash
curl \
  -H "Authorization: Bearer your_api_key" \
  https://your-store.yns.store/api/v1/tickets/attendee/tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718
```

### Response

```json
{
  "token": "tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718",
  "attendee": {
    "name": "Jane Doe",
    "email": "jane@example.com"
  },
  "product": {
    "name": "Summer Festival Pass",
    "slug": "summer-festival-pass",
    "image": "https://cdn.example.com/festival.jpg"
  },
  "event": {
    "startsAt": "2024-08-01T18:00:00.000Z",
    "location": "Central Park, New York",
    "guestLabel": "Attendee",
    "guest": null
  },
  "orderStatus": "paid"
}
```

A `404` is returned when the token does not match a ticket.

---

## Update Attendee Profile

```
PATCH /api/v1/tickets/attendee/:token
```

Attendee self-service edit of their own seat, gated only by the per-seat token. It updates the networking profile plus this seat's name and email – it cannot reach sibling seats or the order.

The body is treated as a full replacement of the profile, so send every field you want to keep.

### Body Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | `string` | Attendee display name (max 200 characters) |
| `email` | `string \| null` | Private contact email – never shown in the directory. Send `null` or `""` to clear. |
| `bio` | `string \| null` | Short headline or bio (max 240 characters) |
| `linkedinUrl` | `string \| null` | LinkedIn profile URL, validated server-side |
| `listed` | `boolean` | Opt in to the event's [networking directory](/docs/api-reference/events). Only honoured when a LinkedIn URL is present. |
| `photoUrl` | `string \| null` | Profile photo — a URL returned by [Upload Attendee Photo](#upload-attendee-photo). Only URLs on this store's CDN are accepted. |

`photoUrl` is the one field with tri-state semantics, so older forms that never send it keep the current photo: **omit** it to keep the photo, send `null` or `""` to remove it, send a URL to replace it.

```bash
curl -X PATCH \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "bio": "Ceramics studio owner",
    "linkedinUrl": "https://www.linkedin.com/in/janedoe",
    "listed": true
  }' \
  https://your-store.yns.store/api/v1/tickets/attendee/tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718
```

### Errors

| Status | Meaning |
|--------|---------|
| `400` | A non-empty `linkedinUrl` that isn't a real linkedin.com URL, or a `photoUrl` that was not returned by the photo upload endpoint |
| `404` | Token does not match a ticket |
| `409` | Order is no longer active (cancelled or refunded) |

---

## Upload Attendee Photo

```
POST /api/v1/tickets/attendee/:token/photo
```

Uploads an attendee profile photo, gated by the same per-seat token. The image is stored on the store's CDN and its URL returned; save it on the seat by sending it back as `photoUrl` on `PATCH /api/v1/tickets/attendee/:token`. Uploading alone does not change the seat.

Send the image as the `file` field of a `multipart/form-data` body.

| Constraint | Value |
|------------|-------|
| Form field | `file` |
| Formats | JPEG, PNG, WebP |
| Maximum size | 2 MB |

```bash
curl -X POST \
  -H "Authorization: Bearer your_api_key" \
  -F "file=@headshot.jpg" \
  https://your-store.yns.store/api/v1/tickets/attendee/tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718/photo
```

### Response (201)

```json
{
  "photoUrl": "https://cdn.example.com/images/store-id/live/attendee-photos/photo-a1b2c3.jpg"
}
```

Then persist it on the seat:

```bash
curl -X PATCH \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"photoUrl": "https://cdn.example.com/images/store-id/live/attendee-photos/photo-a1b2c3.jpg"}' \
  https://your-store.yns.store/api/v1/tickets/attendee/tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718
```

### Errors

| Status | Meaning |
|--------|---------|
| `400` | No `file` field, an empty file, an unsupported image type, or an image larger than 2 MB |
| `404` | Token does not match a ticket |
| `409` | Order is no longer active (cancelled or refunded) |

---

## Check In Attendee

```
POST /api/v1/tickets/attendee/:token/checkin
```

Marks a seat as present at the door, keyed by the per-seat token from the QR credential.

Idempotent: a re-scan returns the original check-in with `alreadyCheckedIn: true` and never overwrites the recorded time.

### Body Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `by` | `string \| null` | Free-text name of whoever is checking the attendee in (max 200 characters) |

```bash
curl -X POST \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"by":"Front desk"}' \
  https://your-store.yns.store/api/v1/tickets/attendee/tok_8f3a1c9b2e7d4f60a1b2c3d4e5f60718/checkin
```

### Response

```json
{
  "checkedInAt": "2024-08-01T18:04:11.000Z",
  "checkedInBy": "Front desk",
  "alreadyCheckedIn": false
}
```

### Errors

| Status | Meaning |
|--------|---------|
| `404` | Token does not match a ticket |
| `409` | Order is no longer active (cancelled or refunded) |

To see everyone on the door list, use the event [check-in roster](/docs/api-reference/events).