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

# Candidate Self-Scheduling

> API endpoints for booking schedules, candidate scheduling links, bookings, and attendance

The Candidate Self-Scheduling API lets you integrate with TapTalent's self-scheduling flow — the one candidates use to pick their own interview slot, or an appointment at a vendor site such as a background-check or assessment center. Recruiters create and configure **booking schedules** (sites, durations, availability) from the TapTalent dashboard; this API covers everything a partner system needs at runtime:

1. Look up a schedule and its open slots.
2. Generate a unique scheduling link for a candidate and hand it to them.
3. Read the resulting booking once the candidate picks a slot (or subscribe to [webhooks](/webhooks/overview) instead of polling).
4. Reschedule or cancel a booking on the candidate's behalf.
5. Push back attendance (check-in/no-show) captured on your side.

**Base path:** `/v1/partner/booking-schedules` and `/v1/partner/bookings`
**Authentication:** `Authorization: Bearer YOUR_API_KEY`

Booking schedule creation and availability configuration are dashboard-only, the same way webhook URLs and API keys are — see [API Reference Overview](/api-reference/overview) for the general conventions (response envelopes, pagination, errors) this API follows.

<Info>
  `POST /booking-schedules/{bookingScheduleId}/links` only supports `INTERVIEW`-profile schedules. For an `EVENT`-profile schedule (candidate picks a location and answers custom questions), use `GET /booking-schedules/{bookingScheduleId}/locations` and `GET /booking-schedules/{bookingScheduleId}/questions` to get the choices, then `POST /booking-schedules/{bookingScheduleId}/bookings` to record the booking directly. Attendance recording and rescheduling/cancellation work the same way for both profiles.
</Info>

<Note>
  A booking's candidate isn't always a job applicant. `Booking`/`BookingLink` identify them with `entityType` + `entityId` (both always present):

  * `entityType: "JOB_CANDIDATE"` — a job applicant; `entityId` matches a `jobCandidateId` from the [Jobs](/api-reference/jobs)/[Candidates](/api-reference/candidates) APIs.
  * `entityType: "OUTREACH_CAMPAIGN_CANDIDATE"` — a lead from an outreach campaign; `entityId` is internal to TapTalent.
  * `entityType: "NOVA_CANDIDATE"` — a candidate from TapTalent's Agentic Onboarding flow; `entityId` is internal to TapTalent.

  If you just need "the booking for the link I created" rather than which candidate it is, use **`bookingLinkId`** instead — returned from [Create a candidate scheduling link](#create-a-candidate-scheduling-link), and it works the same regardless of `entityType`. Since a schedule is shared with TapTalent's own outreach and onboarding flows, `GET /bookings` can include the other two types alongside yours — filter by `entityType=JOB_CANDIDATE` if you only want your own.
</Note>

***

## List booking schedules

### `GET /booking-schedules`

Retrieves the non-deleted booking schedules for your company. Use this to discover which schedules (sites, interview types) are available before creating a scheduling link for a candidate.

### Query parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `scheduleProfile` | string | No | Filter to `INTERVIEW` or `EVENT` |
| `bookingMode` | string | No | Filter to `ONLINE`, `ONSITE`, or `BOTH` |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/booking-schedules?scheduleProfile=INTERVIEW" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json"
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": 501,
      "name": "Bridgetown Site — Background Check Appointment",
      "description": "Bring a valid government ID.",
      "durationMinutes": 30,
      "timezone": "Asia/Manila",
      "bookingMode": "ONSITE",
      "scheduleProfile": "INTERVIEW",
      "companySiteId": 12,
      "visibleDaysAhead": 7,
      "expiresAt": null,
      "allowCandidateReschedule": true,
      "rescheduleCutoffHours": 4,
      "isAllDayEvent": false
    }
  ]
}
```

***

## Get a booking schedule

### `GET /booking-schedules/:bookingScheduleId`

Retrieves a single booking schedule. You can only access schedules that belong to your company; cross-company access returns **404**.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule id |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/booking-schedules/501" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv"
```

### Booking Schedule object

| Field | Type | Description |
| - | - | - |
| `id` | integer | Unique identifier |
| `name` | string | Display name |
| `description` | string or null | Shown to the candidate on the booking page |
| `durationMinutes` | integer | Length of each slot |
| `timezone` | string | IANA timezone slots are computed in |
| `bookingMode` | string | `ONLINE`, `ONSITE`, or `BOTH` |
| `scheduleProfile` | string | `INTERVIEW` or `EVENT` |
| `companySiteId` | integer or null | Bound site, for `ONSITE`/`BOTH` schedules |
| `visibleDaysAhead` | integer | How far ahead availability is shown |
| `expiresAt` | string or null | Schedule stops accepting bookings after this time |
| `allowCandidateReschedule` | boolean | Whether bookings on it can be rescheduled |
| `rescheduleCutoffHours` | integer or null | Minimum notice required to reschedule |
| `isAllDayEvent` | boolean | Whether slots span the full day |

***

## List available slots

### `GET /booking-schedules/:bookingScheduleId/slots`

Returns the still-bookable slots for a schedule within a date range, after excluding slots already filled by active bookings. Use this to preview availability before creating a booking link, or to validate a `scheduledStartAt` before calling [Reschedule a booking](#reschedule-a-booking).

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule id |

### Query parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `from` | string (date) | Yes | Start of the range, inclusive |
| `to` | string (date) | Yes | End of the range, exclusive. Capped by the schedule's `visibleDaysAhead` |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/booking-schedules/501/slots?from=2026-09-14&to=2026-09-21" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv"
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": [
    { "startAt": "2026-09-15T01:00:00.000Z", "endAt": "2026-09-15T01:30:00.000Z", "remainingCapacity": 1 },
    { "startAt": "2026-09-15T01:30:00.000Z", "endAt": "2026-09-15T02:00:00.000Z", "remainingCapacity": 3 }
  ]
}
```

`remainingCapacity` reports how many more candidates can still book that slot; schedules configured with no booking limit report a large sentinel value rather than `null` — treat any positive number as bookable.

***

## List locations

### `GET /booking-schedules/:bookingScheduleId/locations`

Returns the locations a candidate can pick from when booking this schedule. Only meaningful for `EVENT`-profile schedules; an `INTERVIEW`-profile schedule always returns an empty list.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule id |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/booking-schedules/501/locations" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv"
```

### Location object

| Field | Type | Description |
| - | - | - |
| `id` | integer | Unique identifier — pass as `selectedLocationId` when creating a booking |
| `type` | string | `video`, `phone`, `in_person`, or `custom` |
| `label` | string | Short display name, shown to the candidate in the location picker |
| `details` | string or null | Address (`in_person`), meeting link (`video`), phone number (`phone`), or free text (`custom`) |
| `instructions` | string or null | Extra guidance alongside `details` (e.g. "Ring the bell") |
| `lat` / `lng` | number or null | Map coordinates, for `in_person` locations with a pin set |
| `photoUrls` | array of strings | Up to 5 photo URLs in display order (first is the cover photo); empty array if none |

### Example response

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": 701,
      "type": "in_person",
      "label": "Downtown Office — 3rd Floor",
      "details": "123 Main St, Manila",
      "instructions": "Ring the bell at the lobby",
      "lat": 14.5547,
      "lng": 121.0244,
      "photoUrls": ["https://cdn.taptalent.io/locations/701/lobby.jpg"]
    },
    {
      "id": 702,
      "type": "video",
      "label": "Video call",
      "details": null,
      "instructions": null,
      "lat": null,
      "lng": null,
      "photoUrls": []
    }
  ]
}
```

***

## List questions

### `GET /booking-schedules/:bookingScheduleId/questions`

Returns the custom questions a candidate must answer when booking this schedule. Only meaningful for `EVENT`-profile schedules; an `INTERVIEW`-profile schedule always returns an empty list.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule id |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/booking-schedules/501/questions" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv"
```

### Question object

| Field | Type | Description |
| - | - | - |
| `id` | string | Use as the key when submitting `bookingAnswers` |
| `type` | string | `text`, `textarea`, `phone` (single string answer), `single_choice` (one value from `options`), or `multi_choice` (array of values from `options`) |
| `label` | string | The question text shown to the candidate |
| `required` | boolean | Whether an answer is mandatory to complete the booking |
| `options` | array of strings | Present only for `single_choice`/`multi_choice` — the selectable choices |
| `maxLength` | integer | Present only for `text`/`textarea`, and only if the recruiter set a limit |

### Example response

```json theme={null}
{
  "status": "success",
  "data": [
    {
      "id": "q_visa_status",
      "type": "single_choice",
      "label": "Do you currently hold a valid work visa?",
      "required": true,
      "options": ["Yes", "No"]
    },
    {
      "id": "q_languages",
      "type": "multi_choice",
      "label": "Which languages are you comfortable interviewing in?",
      "required": false,
      "options": ["English", "Filipino", "Spanish"]
    },
    {
      "id": "q_notes",
      "type": "textarea",
      "label": "Anything else we should know before your appointment?",
      "required": false,
      "maxLength": 500
    }
  ]
}
```

`bookingAnswers` echoes these shapes back: a plain string for `text`/`textarea`/`phone`, a single string for `single_choice`, and an array of strings for `multi_choice`.

***

## Create a candidate scheduling link

### `POST /booking-schedules/:bookingScheduleId/links`

Generates a unique self-scheduling link for the given entity on this schedule — or returns its existing active link for it, rather than creating a duplicate. Identify the entity with `entityType` + `entityId` (see the candidate-identification note near the top of this page); TapTalent resolves the record and snapshots its contact details (`firstName`/`lastName`/`email`/`phone`) onto the link — you don't supply those yourself. The entity must already exist; this endpoint doesn't create new candidate records. Only `JOB_CANDIDATE` ids are obtainable through this API — `OUTREACH_CAMPAIGN_CANDIDATE` and `NOVA_CANDIDATE` ids have no corresponding lookup here.

Share `data.url` with the candidate yourself (email, SMS, your own portal); TapTalent does not send it on your behalf.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule to book against. Must be an `INTERVIEW`-profile schedule |

### Body (JSON)

| Field | Type | Required | Description |
| - | - | - | - |
| `entityType` | string | Yes | See the possible values above. Only `JOB_CANDIDATE` ids are obtainable through this API |
| `entityId` | string | Yes | The id within `entityType`'s source — for `JOB_CANDIDATE`, a `jobCandidateId` from the [Jobs](/api-reference/jobs)/[Candidates](/api-reference/candidates) APIs |
| `expiresInHours` | integer | No | Link validity window. Defaults to the schedule's own expiry behavior |

### Example request

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/booking-schedules/501/links" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "expiresInHours": 168
  }'
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": {
    "id": 8001,
    "token": "bkl_9f2c1a7e4d",
    "url": "https://apply.taptalent.io/book/bkl_9f2c1a7e4d",
    "status": "ACTIVE",
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "+15551234567",
    "expiresAt": "2026-09-17T00:00:00.000Z",
    "createdAt": "2026-09-10T02:00:00.000Z"
  }
}
```

`status` is `ACTIVE` while awaiting a booking, `LOCKED` once the candidate has booked, `REVOKED` if invalidated, or `REPLACED` if superseded by a newer link for the same candidate and schedule. `firstName`/`lastName`/`email`/`phone` are snapshotted from the resolved entity at creation time, not supplied by you — any of them may be `null` if the source record doesn't have that field on file.

***

## Create a booking

### `POST /booking-schedules/:bookingScheduleId/bookings`

Creates a booking directly for a given entity, slot, and — for an `EVENT`-profile schedule — a location and question answers. TapTalent validates the slot is still available, that `selectedFormat` is given when required (a `BOTH`-mode schedule) and omitted otherwise, and that `selectedLocationId` plus every required answer are present for an `EVENT`-profile schedule.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `bookingScheduleId` | integer | Yes | The schedule to book against |

### Body (JSON)

| Field | Type | Required | Description |
| - | - | - | - |
| `entityType` | string | Yes | See the candidate-identification note near the top of this page |
| `entityId` | string | Yes | The id within `entityType`'s source |
| `scheduledStartAt` | string (date-time) | Yes | Must match an available slot — see [List available slots](#list-available-slots) |
| `selectedFormat` | string | Conditional | `ONLINE` or `ONSITE` — required for a `BOTH`-mode schedule, omitted otherwise |
| `selectedLocationId` | integer | Conditional | An `id` from [List locations](#list-locations) — required for an `EVENT`-profile schedule, omitted otherwise |
| `bookingAnswers` | object | Conditional | Answers keyed by question `id` from [List questions](#list-questions) — required for an `EVENT`-profile schedule, omitted otherwise. Each value's shape follows that question's `type`: a string for `text`/`textarea`/`phone`/`single_choice`, an array of strings for `multi_choice` |

### Example request — `INTERVIEW`-profile schedule

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/booking-schedules/501/bookings" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "scheduledStartAt": "2026-09-12T02:00:00.000Z"
  }'
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": {
    "id": 9001,
    "bookingScheduleId": 501,
    "bookingLinkId": 8001,
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "+15551234567",
    "scheduledStartAt": "2026-09-12T02:00:00.000Z",
    "scheduledEndAt": "2026-09-12T02:30:00.000Z",
    "timezone": "Asia/Manila",
    "status": "ACTIVE",
    "selectedFormat": null,
    "selectedLocation": null,
    "bookingAnswers": null,
    "meetingLink": null,
    "attendanceCode": "7F3K9Q",
    "attendanceCodeExpiresAt": "2026-09-12T04:30:00.000Z",
    "attendanceMarkedVia": null,
    "attendanceMarkedAt": null,
    "createdAt": "2026-09-10T02:00:00.000Z",
    "updatedAt": "2026-09-10T02:00:00.000Z"
  }
}
```

### Example request — `EVENT`-profile schedule

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/booking-schedules/601/bookings" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "scheduledStartAt": "2026-09-15T09:00:00.000Z",
    "selectedLocationId": 701,
    "bookingAnswers": {
      "q_visa_status": "Yes",
      "q_languages": ["English", "Filipino"]
    }
  }'
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": {
    "id": 9101,
    "bookingScheduleId": 601,
    "bookingLinkId": 8101,
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "+15551234567",
    "scheduledStartAt": "2026-09-15T09:00:00.000Z",
    "scheduledEndAt": "2026-09-15T10:00:00.000Z",
    "timezone": "Asia/Manila",
    "status": "ACTIVE",
    "selectedFormat": null,
    "selectedLocation": {
      "id": 701,
      "type": "in_person",
      "label": "Downtown Office — 3rd Floor",
      "details": "123 Main St, Manila",
      "instructions": "Ring the bell at the lobby",
      "lat": 14.5547,
      "lng": 121.0244,
      "photoUrls": ["https://cdn.taptalent.io/locations/701/lobby.jpg"]
    },
    "bookingAnswers": {
      "q_visa_status": "Yes",
      "q_languages": ["English", "Filipino"]
    },
    "meetingLink": null,
    "attendanceCode": "9K2M7P",
    "attendanceCodeExpiresAt": "2026-09-16T10:00:00.000Z",
    "attendanceMarkedVia": null,
    "attendanceMarkedAt": null,
    "createdAt": "2026-09-10T02:00:00.000Z",
    "updatedAt": "2026-09-10T02:00:00.000Z"
  }
}
```

Note that a check-in `attendanceCode` is issued for `EVENT`-profile bookings too, the same as `INTERVIEW` — see [Record attendance / check-in](#record-attendance--check-in).

***

## List bookings

### `GET /bookings`

Paginated list of bookings for your company.

### Query parameters

| Parameter | Type | Description |
| - | - | - |
| `bookingScheduleId` | integer | Filter to one schedule |
| `selectedLocationId` | integer | For `EVENT`-profile schedules — filter to bookings made against one location, an `id` from [List locations](#list-locations). Ignored for `INTERVIEW`-profile schedules |
| `bookingLinkId` | integer | Filter to one candidate's scheduling link — the id returned by [Create a candidate scheduling link](#create-a-candidate-scheduling-link). Works regardless of `entityType`; use this when you don't need to filter by a specific candidate |
| `entityType` | string | Must be combined with `entityId`. See the `Booking` object below |
| `entityId` | string | Must be combined with `entityType` — ids are not necessarily unique across candidate types |
| `status` | string | `ACTIVE`, `ATTENDED`, `NOT_ATTENDED`, or `CANCELLED` |
| `from` / `to` | string (date-time) | Filter by `scheduledStartAt` range |
| `page` | integer | Default `1` |
| `perPage` | integer | Default `20`, any value 1–100 |

### Example response

```json theme={null}
{
  "status": "success",
  "data": [],
  "pagination": { "page": 1, "perPage": 20, "total": 0, "totalPages": 0 }
}
```

***

## Get a booking

### `GET /bookings/:bookingId`

Retrieves a single booking.

### Booking object

| Field | Type | Description |
| - | - | - |
| `id` | integer | Unique identifier |
| `bookingScheduleId` | integer | The schedule this booking is on |
| `bookingLinkId` | integer | The link used to make the booking. Present on every booking regardless of how the candidate was identified — the reliable field to correlate by |
| `entityType` | string | How this candidate was identified. Not a fixed enum — known values are `JOB_CANDIDATE`, `OUTREACH_CAMPAIGN_CANDIDATE`, and `NOVA_CANDIDATE`. Always present |
| `entityId` | string | The id of the underlying record within `entityType`'s source — not necessarily a "candidate" record (e.g. a lead, for `OUTREACH_CAMPAIGN_CANDIDATE`). Always a string, always present |
| `firstName` / `lastName` / `email` / `phone` | string or null | Snapshotted from the resolved entity's own record when the link was created, not supplied by you. Any may be `null` if the source record doesn't have that field on file |
| `scheduledStartAt` / `scheduledEndAt` | string | Appointment window, in UTC |
| `timezone` | string | The schedule's timezone, for display |
| `status` | string | `ACTIVE`, `ATTENDED`, `NOT_ATTENDED`, or `CANCELLED` |
| `selectedFormat` | string or null | `ONLINE`/`ONSITE`, set only for `BOTH`-mode schedules |
| `selectedLocation` | object or null | `EVENT`-profile only — the [Location object](#list-locations) the candidate picked |
| `bookingAnswers` | object or null | `EVENT`-profile only — keyed by question `id`; see [Question object](#list-questions) for each answer's expected shape |
| `meetingLink` | string or null | Video meeting link, for online appointments |
| `attendanceCode` | string or null | Desk check-in code; `null` once consumed, invalidated, or for `EVENT`-profile bookings |
| `attendanceCodeExpiresAt` | string or null | |
| `attendanceMarkedVia` | string or null | `QR_DESK`, `CODE_DESK`, `ADMIN_UI`, `SELF_LINK`, or `PARTNER_API` |
| `attendanceMarkedAt` | string or null | |
| `createdAt` / `updatedAt` | string | |

### Example request

```bash theme={null}
curl -X GET "https://partner-api.taptalent.io/v1/partner/bookings/9001" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv"
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": {
    "id": 9001,
    "bookingScheduleId": 501,
    "bookingLinkId": 8001,
    "entityType": "JOB_CANDIDATE",
    "entityId": "55021",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "+15551234567",
    "scheduledStartAt": "2026-09-12T02:00:00.000Z",
    "scheduledEndAt": "2026-09-12T02:30:00.000Z",
    "timezone": "Asia/Manila",
    "status": "ACTIVE",
    "selectedFormat": null,
    "selectedLocation": null,
    "bookingAnswers": null,
    "meetingLink": null,
    "attendanceCode": "7F3K9Q",
    "attendanceCodeExpiresAt": "2026-09-12T04:30:00.000Z",
    "attendanceMarkedVia": null,
    "attendanceMarkedAt": null,
    "createdAt": "2026-09-10T02:00:00.000Z",
    "updatedAt": "2026-09-10T02:00:00.000Z"
  }
}
```

***

## Reschedule a booking

### `POST /bookings/:bookingId/reschedule`

Moves a booking to a new slot on the same schedule. This **cancels the current booking and creates a new one** — the response returns the new booking with `previousBookingId` set, matching how candidate-initiated reschedules behave. The original booking's own `status` becomes `CANCELLED`.

Blocked (returns **409**) when:

* The schedule has `allowCandidateReschedule: false`
* Fewer than `rescheduleCutoffHours` remain before the current `scheduledStartAt`
* The booking is not `ACTIVE`
* The requested slot has no remaining capacity

### Body (JSON)

| Field | Type | Required | Description |
| - | - | - | - |
| `scheduledStartAt` | string (date-time) | Yes | Must match one of the schedule's currently available slots — see [List available slots](#list-available-slots) |

### Example request

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/bookings/9001/reschedule" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{ "scheduledStartAt": "2026-09-13T02:00:00.000Z" }'
```

### Example response

```json theme={null}
{
  "status": "success",
  "data": {
    "id": 9042,
    "previousBookingId": 9001,
    "bookingScheduleId": 501,
    "status": "ACTIVE",
    "scheduledStartAt": "2026-09-13T02:00:00.000Z",
    "scheduledEndAt": "2026-09-13T02:30:00.000Z"
  }
}
```

### Error response (409)

```json theme={null}
{
  "error": {
    "code": "RESCHEDULE_CUTOFF_PASSED",
    "type": "validation_error",
    "message": "This booking can no longer be rescheduled; the reschedule window has closed."
  }
}
```

***

## Cancel a booking

### `POST /bookings/:bookingId/cancel`

Cancels an `ACTIVE` booking. Already-cancelled, already-attended, or no-show bookings cannot be cancelled again (**409**).

### Body (JSON)

| Field | Type | Required | Description |
| - | - | - | - |
| `reason` | string | No | Free-text reason, stored on the booking's activity log |

### Example request

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/bookings/9001/cancel" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Candidate no longer interviewing" }'
```

***

## Record attendance / check-in

Two ways to mark attendance, depending on what your check-in point knows at that moment:

* **You already know `bookingId`** (e.g. you're processing bookings fetched from `GET /bookings`) — use [`POST /bookings/:bookingId/attendance`](#record-attendance-by-bookingid) below.
* **You only have the candidate's `attendanceCode`** — the normal case for a desk or QR scan, since a scanner doesn't know `bookingId` up front — use [`POST /attendance-codes/:attendanceCode/check-in`](#record-attendance-by-code) instead, which resolves the booking from the code itself.

Both work for a booking on any schedule profile (`INTERVIEW` or `EVENT`), and both emit [`booking.attendance_updated`](/webhooks/events#booking-attendance_updated) to your webhook endpoint.

### Record attendance by bookingId

### `POST /bookings/:bookingId/attendance`

Marks a candidate as attended or not-attended. The booking does not need to already be past its `scheduledStartAt` — you can check a candidate in early. Not valid on a `CANCELLED` booking (**409**).

### Body (JSON)

| Field | Type | Required | Description |
| - | - | - | - |
| `status` | string | Yes | `ATTENDED` or `NOT_ATTENDED` |
| `occurredAt` | string (date-time) | No | When it actually happened; defaults to receipt time |
| `method` | string | No | How you captured it on your side (e.g. `QR_SCAN`), free text |
| `note` | string | No | |

### Example request

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/bookings/9001/attendance" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "ATTENDED",
    "occurredAt": "2026-09-12T02:05:00.000Z",
    "method": "QR_SCAN"
  }'
```

TapTalent records the fixed value `PARTNER_API` in `Booking.attendanceMarkedVia` for any attendance update made through either endpoint, alongside whatever you pass in `method`.

***

### Record attendance by code

### `POST /attendance-codes/:attendanceCode/check-in`

Resolves the booking directly from its `attendanceCode` and records attendance — call this from a desk, kiosk, or QR scanner at the point of check-in. Consumes the code: on success, `Booking.attendanceCode` becomes `null`, same as the `bookingId`-based endpoint.

### Path parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `attendanceCode` | string | Yes | The code shown to or scanned from the candidate (typed in, or decoded from a QR) |

### Body (JSON)

Same body as [Record attendance by bookingId](#record-attendance-by-bookingid) above.

### Example request

```bash theme={null}
curl -X POST "https://partner-api.taptalent.io/v1/partner/attendance-codes/7F3K9Q/check-in" \
  -H "Authorization: Bearer sk_live_AbCdEfGhIjKlMnOpQrStUv" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "ATTENDED",
    "occurredAt": "2026-09-12T02:05:00.000Z",
    "method": "QR_SCAN"
  }'
```

Returns **404** if the code doesn't match any booking in your company — wrong, already consumed, or expired.

***

## Webhooks

Rather than polling, subscribe to booking lifecycle events at your configured webhook endpoint: [`booking.scheduled`](/webhooks/events#booking-scheduled), [`booking.rescheduled`](/webhooks/events#booking-rescheduled), [`booking.cancelled`](/webhooks/events#booking-cancelled), and [`booking.attendance_updated`](/webhooks/events#booking-attendance_updated). See [Webhooks Overview](/webhooks/overview) for setup.

***

## Errors

Validation errors return **400** with `code: "INVALID_REQUEST"` (or a specific code such as `RESCHEDULE_CUTOFF_PASSED`). Missing or cross-company schedules/bookings return **404** with `code: "NOT_FOUND"`. State conflicts (reschedule/cancel/attendance not allowed from the booking's current status) return **409**. Other failures may return **401**, **429**, or **500**; see [API Reference Overview](/api-reference/overview) for the shared error envelope and authentication error shapes.

***

## Related

* [Webhooks Overview](/webhooks/overview) and [Webhook Event Types](/webhooks/events) — real-time alternative to polling `GET /bookings`
* [API Reference Overview](/api-reference/overview) — base URL, authentication, and shared conventions for all partner endpoints
