# Staff

Manage your **door staff** — the people who scan tickets, sell on-site, and
redeem perks at your events. Create staff, tune their permissions, assign them
to specific events, and read their scan totals. All staff endpoints require the
`staff:manage` scope.

The platform provisions and manages the internal credentials a staff member
needs to work the door for you; **none of that is ever exposed through the
API** — the only handle you ever get is the opaque `id`. Everything below is
plain business language: names, permissions, event assignments, and scan counts.

A serialized staff object:

```json
{
  "id": "staff_1720900000000_a1b2c3d4",
  "first_name": "Dana",
  "last_name": "Reeves",
  "email": "dana@example.com",
  "phone": "+1-555-0142",
  "status": "active",
  "permissions": {
    "can_scan_tickets": true,
    "can_sell_onsite": true,
    "can_redeem_perks": false,
    "can_manage_staff": false,
    "can_view_stats": true
  },
  "assigned_event_ids": ["evt_1a2b3c"],
  "scanned_tickets": 128,
  "created_at": "2026-07-10T18:30:00.000Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Opaque staff handle — use it in every `/v1/staff/{id}` path. |
| `first_name` | string | Staff member's first name. |
| `last_name` | string | Staff member's last name. |
| `email` | string | Contact email. |
| `phone` | string | Optional contact phone. Omitted when not set. |
| `status` | string | `active`, `inactive`, or `suspended`. |
| `permissions` | object | The five capability flags below. |
| `assigned_event_ids` | string[] | Events this staff member is assigned to work. |
| `scanned_tickets` | integer | Lifetime tickets this staff member has scanned. |
| `created_at` | string | Creation timestamp (ISO 8601). |

The `permissions` flags:

| Flag | Default | Grants |
| --- | --- | --- |
| `can_scan_tickets` | `true` | Validate and check in tickets at the door. |
| `can_sell_onsite` | `true` | Sell tickets on-site at the event. |
| `can_redeem_perks` | `false` | Redeem attendee perks (drinks, lounge, backstage). |
| `can_manage_staff` | `false` | Manage other staff members. |
| `can_view_stats` | `true` | View scan and sales stats. |

> **How on-site selling works.** Staff with `can_sell_onsite` take walk-up
> payments in the TicketConnect staff app — Stripe **Tap to Pay** on the staff
> member's phone. Gate payments and wristbands are configured per event in the
> organizer panel's one-time "Set up event" step. There are no public `/v1`
> POS endpoints; the API surface for the door is scanning and attendance (see
> [Scanning & Check-in](#scanning-and-check-in)).

---

## POST /v1/staff

Create a door-staff member.

**Scope:** `staff:manage`

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `first_name` | string | Yes | Missing returns `400 parameter_missing`. |
| `last_name` | string | Yes | Missing returns `400 parameter_missing`. |
| `email` | string | Yes | Missing returns `400 parameter_missing`. |
| `phone` | string | No | Optional contact phone. |
| `permissions` | object | No | Any of the five flags. Unset flags fall back to their defaults above. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Dana",
    "last_name": "Reeves",
    "email": "dana@example.com",
    "phone": "+1-555-0142",
    "permissions": { "can_redeem_perks": true }
  }'
```

### Example response

`201 Created`

```json
{
  "id": "staff_1720900000000_a1b2c3d4",
  "first_name": "Dana",
  "last_name": "Reeves",
  "email": "dana@example.com",
  "phone": "+1-555-0142",
  "status": "active",
  "permissions": {
    "can_scan_tickets": true,
    "can_sell_onsite": true,
    "can_redeem_perks": true,
    "can_manage_staff": false,
    "can_view_stats": true
  },
  "assigned_event_ids": [],
  "scanned_tickets": 0,
  "created_at": "2026-07-10T18:30:00.000Z"
}
```

---

## GET /v1/staff

List your door-staff roster, newest first.

**Scope:** `staff:manage`

### Query params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | No | Page size, `1`–`100` (default `20`). |
| `starting_after` | string | No | Cursor — the last staff `id` you saw on the previous page. |

### Example request

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

### Example response

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    { "id": "staff_1720900000000_a1b2c3d4", "first_name": "Dana", "last_name": "Reeves", "email": "dana@example.com", "status": "active", "permissions": { "can_scan_tickets": true, "can_sell_onsite": true, "can_redeem_perks": true, "can_manage_staff": false, "can_view_stats": true }, "assigned_event_ids": ["evt_1a2b3c"], "scanned_tickets": 128, "created_at": "2026-07-10T18:30:00.000Z" }
  ]
}
```

---

## GET /v1/staff/{id}

Retrieve one staff member.

**Scope:** `staff:manage`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Staff id. Unknown id returns `404 resource_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4 \
  -H "Authorization: Bearer sk_test_your_key_here"
```

---

## PATCH /v1/staff/{id}

Update a staff member's permissions and/or status. Only the fields you send
change; everything else is left untouched.

**Scope:** `staff:manage`

### Request body

All fields optional; send only what you want to change.

| Field | Type | Description |
| --- | --- | --- |
| `status` | string | `active`, `inactive`, or `suspended`. Anything else returns `400 parameter_invalid`. |
| `permissions` | object | Any of the five capability flags; supplied flags are overwritten. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4 \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -X PATCH \
  -d '{ "status": "suspended", "permissions": { "can_sell_onsite": false } }'
```

The full updated staff object is returned.

---

## POST /v1/staff/{id}/assignments

Assign a staff member to one of your events. Assigning provisions the internal
access that staff member needs for that event behind the scenes.

**Scope:** `staff:manage`

Assignments are **idempotent** — re-assigning the same event is a no-op and
returns the unchanged staff object.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `event_id` | string | Yes | An event you own. Missing returns `400 parameter_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4/assignments \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "event_id": "evt_1a2b3c" }'
```

The full staff object is returned with `evt_1a2b3c` now in `assigned_event_ids`.

> **Note:** The event must belong to your account. An unknown staff id **or** an
> unknown / unowned `event_id` returns `404 resource_missing` — you can only
> assign staff to events you own.

---

## DELETE /v1/staff/{id}/assignments/{eventId}

Unassign a staff member from an event.

**Scope:** `staff:manage`

Idempotent — unassigning an event the staff member isn't on is a no-op and
still returns the current staff object.

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Staff id. |
| `eventId` | string | Yes | The event to remove from the assignment list. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4/assignments/evt_1a2b3c \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -X DELETE
```

The full staff object is returned with the event removed from
`assigned_event_ids`.

---

## GET /v1/staff/{id}/stats

Read a staff member's scan totals, with a per-event breakdown. The counts
reflect tickets actually scanned by **this** staff member.

**Scope:** `staff:manage`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Staff id. Unknown id returns `404 resource_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4/stats \
  -H "Authorization: Bearer sk_test_your_key_here"
```

### Example response

```json
{
  "staff_id": "staff_1720900000000_a1b2c3d4",
  "scanned_tickets": 128,
  "events": [
    { "event_id": "evt_1a2b3c", "scanned": 96 },
    { "event_id": "evt_4d5e6f", "scanned": 32 }
  ]
}
```

---

## DELETE /v1/staff/{id}

**Soft-deactivate** a staff member: their `status` is set to `inactive` and the
record is retained (so their historical scan stats stay intact). Idempotent.

**Scope:** `staff:manage`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Staff id. Unknown id returns `404 resource_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/staff/staff_1720900000000_a1b2c3d4 \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -X DELETE
```

### Example response

The full staff object is returned with `status` now `inactive`.

> **Note:** This is a deactivation, not a hard delete — the staff member stops
> working the door but stays on the roster and keeps their scan history. Flip
> them back on with `PATCH /v1/staff/{id}` and `{ "status": "active" }`.
