# Guide: Event Call Booking

This guide shows the standard flow to book a call:

1. Get an API key
2. Get the event `linkPrefix` (`username/event-name`)
3. Create (or upsert) a contact
4. **Insert invitee answers** (primary invitee questions + secondary / custom fields)
5. Use the response to **disqualify** the contact when needed, or to **narrow hosts** with `conditionalUsers`
6. Fetch availabilities (optionally constrained to `conditionalUsers`)
7. Create the event call using an exact available `dateTime` (pass the same `conditionalUsers` when routing applies)


Reference: OpenAPI [`openapi/v1/openapi.json`](/openapi/v1/openapi) — operations `insertInviteeAnswers`, `updateContact`, `getPublicEventDates`, `createEventCall`. You can also inspect these endpoints with the iClosed MCP (`get-endpoint-info` for each path/method).

## 1) Get your API key

Create an API key from your iClosed account settings, then send it in every request:

```http
Authorization: Bearer iclosed_<your-api-key>
```

If you have not done this yet, follow the [Authentication](/docs/authentication) guide.

## 2) Get the event link prefix

For booking APIs, use an event `linkPrefix` in this format:

```text
username/event-name
```

Example:

```text
company/discovery-call
```

Save this value. You will use it for invitee answers, availability lookup, and booking.

## 3) Create (or upsert) the contact

Before booking, create a contact with a minimal payload:

- `firstName`
- `lastName`
- `email` or `phoneNumber`


This endpoint is idempotent for existing records by `email`/`phoneNumber` and upserts the same contact when it already exists.

### Request

```http
POST /v1/contacts
Content-Type: application/json
Authorization: Bearer iclosed_<your-api-key>
```

```json
{
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane.doe@example.com"
}
```

### Response (example)

```json
{
  "data": {
    "contact": {
      "id": 139,
      "accountId": 4,
      "userId": null,
      "email": "jane.doe.updated@example.com",
      "firstName": "Jane",
      "lastName": "Doe",
      "status": "QUALIFIED",
      "phoneNumber": "+15551234567",
      "previewId": "contact_BpK9_yC5DFG7",
      "tagId": 7,
      "blockedByRecaptcha": false,
      "joinedTime": "2026-03-04T20:38:06.409Z",
      "country": "US",
      "timeZone": "America/New_York",
      "ipAddress": "192.168.1.1",
      "blockedId": null,
      "referrerUrl": "https://example.com/referral",
      "createdAt": "2026-03-04T20:38:06.410Z",
      "updatedAt": "2026-03-05T09:41:22.614Z",
      "deletedAt": null
    }
  }
}
```

Save `data.contact.id` as your `contactId`.

## 4) Insert invitee answers

After the contact exists, call **`POST /v1/fields/inviteeAnswers`** to submit:

- **Primary invitee questions** — `inviteeQuestionAnswers`: each item has `type` and `answer`. Allowed `type` values in the API are `EMAIL`, `PHONE_NO`, `NAME`, `FIRST_NAME`, and `LAST_NAME`. Each `type` must match an invitee question of that **kind** on the event (see your event setup in iClosed, or `GET /v1/events/detail` for context).
- **Secondary (custom) questions** — `secondaryQuestionsAnswer`: each item has `answer` (always an array of strings) and either `customFieldId` or `identifier` (slug). Use `[]` if there are none.


`linkPrefix` and `contactId` tie answers to the event and contact. You may use `previewId` instead of `contactId` when that fits your integration.

### Request

```http
POST /v1/fields/inviteeAnswers
Content-Type: application/json
Authorization: Bearer iclosed_<your-api-key>
```

```json
{
  "linkPrefix": "company/discovery-call",
  "contactId": 139,
  "inviteeQuestionAnswers": [
    { "type": "EMAIL", "answer": "jane.doe@example.com" },
    { "type": "PHONE_NO", "answer": "+15551234567" },
    { "type": "FIRST_NAME", "answer": "Jane" },
    { "type": "LAST_NAME", "answer": "Doe" }
  ],
  "secondaryQuestionsAnswer": [
    {
      "identifier": "company-size",
      "answer": ["11-50"]
    }
  ]
}
```

If you have no secondary answers, send an empty array (the field is required):

```json
"secondaryQuestionsAnswer": []
```

### Response (shape)

On success (**201**), `data` includes:

| Field | Meaning |
|  --- | --- |
| `message` | Result message (e.g. confirmation that answers were stored). |
| `disqualifyingCondition` | Array of objects describing **disqualification rules that fired**, if any. Each entry typically identifies the rule (e.g. `id`, `eventId`, `disqualificationGroupId`), human-readable `statement`, how it was evaluated (`operator`, `condition`, `value`), and ties back to your event’s disqualification group. Use for logging, UI copy, or redirect URLs alongside `isDisqualified`. |
| `conditionalUsers` | **Array of user IDs** (numbers) selected by the event’s **conditional routing** after evaluating answers. Empty when no conditional hosts apply. |
| `isDisqualified` | `true` when at least one disqualification condition triggered. |


Example:

```json
{
  "data": {
    "message": "Questions Answers Inserted Successfully",
    "disqualifyingCondition": [],
    "conditionalUsers": [1, 2],
    "isDisqualified": false
  }
}
```

When a disqualification rule matches, `isDisqualified` is `true` and `disqualifyingCondition` lists the rules that matched. Field names and extra keys can vary by event configuration; a typical entry looks like this:

```json
{
  "data": {
    "message": "Questions Answers Inserted Successfully",
    "disqualifyingCondition": [
      {
        "id": 4,
        "eventId": 1,
        "statement": "Disqualify",
        "operator": "AND",
        "value": "Yes",
        "condition": "IS",
        "disqualificationGroupId": 4
      }
    ],
    "conditionalUsers": [],
    "isDisqualified": true
  }
}
```

In this example, the invitee’s answers satisfied a rule labeled “Disqualify” (e.g. a secondary question answered **Yes** with condition **IS** and logical **AND** within its disqualification group). Your app should treat **`isDisqualified`** as the authoritative flag and use **`disqualifyingCondition`** for detail when updating CRM, showing messaging, or choosing a redirect.

### Troubleshooting

- **`INVALID_INVITEE_QUESTION_TYPE`**: The event has no invitee question matching the `type` you sent. Align `inviteeQuestionAnswers[].type` with the invitee questions configured on that event for the public API (the event detail payload may label some fields differently in the UI; the insert endpoint expects the normalized types above).


## 5) Disqualify the contact (after invitee answers)

When `isDisqualified` is `true` (and optionally when `disqualifyingCondition` is non-empty), update the contact’s pipeline status to **`DISQUALIFIED`** with **`PUT /v1/contacts`**. The request body must include `id` (the contact id); other fields are optional.

`disqualifyingCondition` does not perform the status change by itself — your app should decide when to sync CRM state (for example, whenever `isDisqualified` is true).

### Request

```http
PUT /v1/contacts
Content-Type: application/json
Authorization: Bearer iclosed_<your-api-key>
```

```json
{
  "id": 139,
  "status": "DISQUALIFIED"
}
```

Allowed `status` values on this endpoint: `POTENTIAL`, `QUALIFIED`, `DISQUALIFIED`.

### Response (example)

```json
{
  "data": {
    "contact": {
      "id": 139,
      "accountId": 4,
      "userId": null,
      "email": "jane.doe@example.com",
      "firstName": "Jane",
      "lastName": "Doe",
      "status": "DISQUALIFIED",
      "phoneNumber": "+15551234567",
      "previewId": "contact_BpK9_yC5DFG7",
      "tagId": 7,
      "blockedByRecaptcha": false,
      "joinedTime": "2026-03-04T20:38:06.409Z",
      "country": "US",
      "timeZone": "America/New_York",
      "blockedId": null,
      "referrerUrl": "https://example.com/referral",
      "createdAt": "2026-03-04T20:38:06.410Z",
      "updatedAt": "2026-03-05T10:00:00.000Z",
      "deletedAt": null
    }
  }
}
```

If the lead is disqualified, **stop the booking flow** (no need to fetch slots or create a call unless your product allows exceptions).

## 6) Fetch available dates/times (with optional `conditionalUsers`)

Call **`POST /v1/events/eventDates`** with:

- `linkPrefix` (required)
- `timeZone` and `currentDate` when you want a specific anchor (recommended for predictable calendars)
- **`conditionalUsers`** (optional): a **comma-separated string of user IDs**, e.g. `"3684"` or `"1,2"`. Pass the same IDs returned in `data.conditionalUsers` from **Insert invitee answers** (join the numeric array into a string). This **restricts availability to those hosts** so slots reflect conditional routing.


### Request (no conditional routing)

```http
POST /v1/events/eventDates
Content-Type: application/json
Authorization: Bearer iclosed_<your-api-key>
```

```json
{
  "linkPrefix": "company/discovery-call",
  "timeZone": "America/New_York",
  "currentDate": "2026-02-26"
}
```

### Request (after invitee answers returned `conditionalUsers: [3684, 3685]`)

```json
{
  "linkPrefix": "company/discovery-call",
  "timeZone": "America/New_York",
  "currentDate": "2026-02-26",
  "conditionalUsers": "3684,3685"
}
```

In application code, derive the string from the array, for example: `conditionalUsers.join(',')` (skip the field or pass `undefined` when the array is empty).

### Response (example)

```json
{
  "data": {
    "isPreview": false,
    "availabilities": {
      "2026-02-26": ["09:00", "09:15", "10:00"],
      "2026-02-27": ["09:00", "14:00"]
    }
  }
}
```

Pick an available slot and construct an exact booking `dateTime` in ISO 8601 UTC format (for example, `2026-03-15T14:00:00.000Z`).

## 7) Create the event call

Use the exact available `dateTime` from the previous step.

Minimum booking fields:

- `dateTime`
- `timeZone`
- `linkPrefix` (or `eventId`)
- `secondaryQuestionsAnswer` (may be `[]`)


And one of:

- `contactId`, or
- contact details (`firstName`, `lastName`, and `email`/`phoneNumber`) to upsert at booking time


**`conditionalUsers`** (optional): same semantics as for **Get Event Date and Time** — a **comma-separated string** of user IDs. If you computed availabilities with conditional hosts, pass the **same** `conditionalUsers` value here so the booking is validated against **those users’ calendars** and assignment stays consistent.

### Request (using `contactId` + `conditionalUsers`)

```http
POST /v1/eventCalls
Content-Type: application/json
Authorization: Bearer iclosed_<your-api-key>
```

```json
{
  "linkPrefix": "company/discovery-call",
  "contactId": 139,
  "dateTime": "2026-03-15T14:00:00.000Z",
  "timeZone": "America/New_York",
  "conditionalUsers": "3684,3685",
  "secondaryQuestionsAnswer": []
}
```

### Request (upsert contact during booking)

```json
{
  "linkPrefix": "company/discovery-call",
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane.doe@example.com",
  "dateTime": "2026-03-15T14:00:00.000Z",
  "timeZone": "America/New_York",
  "conditionalUsers": "3684",
  "secondaryQuestionsAnswer": []
}
```

### Response (example)

```json
{
  "data": {
    "eventCall": {
      "message": "Event call created successfully",
      "status": 200,
      "data": {
        "startTime": "2026-03-15T14:00:00.000Z",
        "endTime": "2026-03-15T14:15:00.000Z",
        "id": 1585584,
        "closerId": 3685,
        "timeZone": "America/New_York",
        "guestEmail": "nayyab142@iclosed.io",
        "token": "ULC5lA1vKpS3OLD+m07eOz1U9/mAaXm6Q2j7wLCp1agN53Qt2gYOmbqAx3UQfMmMRQU4FIdSuh0kbIbUJBeBmw==",
        "closerName": "zarrar khan",
        "previewId": "call_fBdaH9gAZm8h",
        "closerEmail": "zarrar1961@gmail.com",
        "confirmationLink": "www.google.com"
      }
    }
  }
}
```

Save `data.eventCall.data.id` and `data.eventCall.data.previewId` for later operations (search, reschedule, cancel, and tracking).

## End-to-end flow (summary)

End-to-end flow (summary): create contact, submit qualifying answers, branch on isDisqualified, then either disqualify the contact or schedule event slots and an event call.
## Verification notes

The following were exercised against the production API with a valid account key:

- **`POST /v1/events/eventDates`** with `conditionalUsers` set to a single host id returned **201** with a normal `availabilities` payload.
- **`PUT /v1/contacts`** with `{ "id", "status": "DISQUALIFIED" }` returned **200** and an updated contact.


**`POST /v1/fields/inviteeAnswers`** depends on the event having invitee questions whose **types match** the `inviteeQuestionAnswers[].type` values you send; otherwise the API returns **`INVALID_INVITEE_QUESTION_TYPE`**. Confirm invitee question configuration on the event before relying on this step in production.