
# 予約

Appointments APIを使用すると、イベントタイプに基づいて連絡先の予約を登録したり、予約の取得、一覧表示、更新、キャンセル、削除を行ったりできます。また、ほとんどの予約フローで最初に発生する「実際に空いている時間はいつか」という疑問にも回答し、カレンダー側（接続済みのGoogleカレンダーの一覧表示や、既存のイベントのインポート）もカバーします。Googleカレンダーの接続が有効な場合、対応するカレンダーイベントが作成され、バックグラウンドで自動的に同期が維持されます。ZenchefやFormitableを独自の予約システムとして使用しているレストランもここで認証・接続できるため、AIエージェントは内部的な予約ではなく、実際のテーブルを予約します。

このページのすべてのパスは、ベースURL `https://api.youraiconnector.com/v1` からの相対パスです。すべてのリクエストにはAPIキーが必要です。送信方法の全リストについては[認証](authentication.md)を参照してください。以下の例では `X-API-Key` ヘッダーを使用しており、1つのcURL例では `?apiKey=` クエリ形式も示しています。

> **イベントと予約の違い:** *イベントタイプ*とは、予約可能なスロットの定義（会議の種類、長さ、部屋など）です。*予約*とは、特定の連絡先に対してイベントタイプを予約した1つのインスタンスです。連絡先とイベントタイプを参照することで予約を行います。

---

## 予約オブジェクト

予約を返す各エンドポイントは、同じ形式を使用します。

| フィールド | 説明 |
|---|---|
| `id` | 予約の一意のID。 |
| `contact_id` | 予約した連絡先のID。 |
| `event_id` | 予約されたイベントタイプのID。 |
| `status` | `Confirmed` または `Canceled`。 |
| `start_time` | 予約の開始日時（ISO 8601形式、UTC）。 |
| `end_time` | 予約の終了日時（ISO 8601形式、UTC）。 |
| `created_at` | 予約が作成された日時。 |
| `last_modified_at` | 予約が最後に変更された日時。 |
| `room_name` | イベントタイプで部屋を使用している場合、予約された部屋またはリソース。 |
| `description` | 予約の自由形式の説明。 |
| `summary` | 短い概要またはタイトル。 |
| `cancelation_reason` | 予約がキャンセルされた場合に提供された理由（ある場合）。 |
| `google_calendar_event_id` | リンクされたGoogleカレンダーイベントのID。カレンダー同期が完了すると設定されます。カレンダーが接続されていない場合や同期中の場合は `null` となります。 |
| `calendar_synced` | 予約がカレンダーイベントにリンクされると `true` になります。 |
| `imported` | 直接予約されたのではなく、外部カレンダーからインポートされた予約の場合に `true` となります。 |
| `is_recurring` | 予約が定期的なシリーズの一部である場合に `true` となります。 |
| `recurrence_frequency` | 定期的な予約の場合の繰り返し頻度。 |
| `recurring_event_id` | この予約が属する定期的なシリーズのID。 |
| `recurring_interval` | 定期的な予約の場合の繰り返し間隔。 |
| `recurring_sequence` | 定期的なシリーズ内でのこの予約の位置。 |
| `end_after_x_occurrences` | 定期的なシリーズが終了するまでの発生回数。 |
| `booking_provider` | 接続された予約プロバイダーを通じて予約された場合の、予約元のシステム。 |

> **カレンダー同期について:** 予約の直後や変更直後は、同期がバックグラウンドで少し遅れて実行されるため、`google_calendar_event_id` が `null` のまま、`calendar_synced` が `false` になっている場合があります。少し時間を置いてから予約を再度取得すると、カレンダーのフィールドが反映されていることが確認できます。

---

## 空き枠の検索

`GET /appointments/available-slots`

2つの時点の間で、特定のイベントタイプにおいて実際に空いている時間を返します。これは通常、予約フローにおける**最初**の呼び出しとなります。これらの枠を表示し、ユーザーに1つ選択してもらい、その選択された時間を[予約の登録](#book-an-appointment)にPOSTします。

この回答には、イベントタイプ自体の営業時間と枠の長さ、部屋、すでに予約済みの予定、および接続されたGoogleカレンダーでブロックされているすべての予定がすでに考慮されています。そのため、ここで返される枠は予約可能なものとなります。

| クエリパラメータ | 必須 | 説明 |
|---|---|---|
| `event_id` | はい | 確認するイベントタイプ。アカウントに属している必要があります。 |
| `start_time` | はい | 枠を取得したい期間の開始日時（ISO 8601形式）。 |
| `end_time` | はい | 期間の終了日時（ISO 8601形式）。終了日全体が含まれます。 |

結果は日ごとにグループ化されて返されます。イベントタイプで部屋を使用している場合は、日ごと、かつ部屋ごとにグループ化されます。

| フィールド | 説明 |
|---|---|
| `date` | グループが対象とする日付（`DD/MM/YYYY`形式）。 |
| `day` | 曜日の小文字表記（例: `monday`）。 |
| `room_name` | イベントタイプで部屋を使用している場合、このグループが属する部屋またはリソース。 |
| `available_slots` | その日の予約可能なブロック（早い順）。 |

`available_slots`の各エントリには以下が含まれます。

| フィールド | 説明 |
|---|---|
| `start_time` | ブロックの開始時刻（`HH:mm`形式）。 |
| `end_time` | ブロックの終了時刻（`HH:mm`形式）。 |
| `available` | `true` — 空き時間のみが返されます。 |
| `spots_left` | このブロックにあと何件の予約が可能か。1つの枠で複数の予約を受け付けるイベントタイプの場合のみ表示されます。 |

> **時間はUTCではなく、イベントタイプのローカル時間です。** `date`、`start_time`、および`end_time`は、イベントタイプ自身のタイムゾーン（オーバーライド設定、または設定がない場合はアカウントのタイムゾーン）における時計の時刻です。[予約の登録](#book-an-appointment)ではISO 8601形式のUTC時刻が期待されるため、選択した枠をPOSTする前に変換してください。

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/appointments/available-slots?event_id=event_xyz789&start_time=2026-06-15T00:00:00.000Z&end_time=2026-06-19T00:00:00.000Z" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  event_id: "event_xyz789",
  start_time: "2026-06-15T00:00:00.000Z",
  end_time: "2026-06-19T00:00:00.000Z",
});
const res = await fetch(
  `https://api.youraiconnector.com/v1/appointments/available-slots?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.data);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/appointments/available-slots",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={
        "event_id": "event_xyz789",
        "start_time": "2026-06-15T00:00:00.000Z",
        "end_time": "2026-06-19T00:00:00.000Z",
    },
)
print(res.json()["data"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": [
    {
      "date": "15/06/2026",
      "day": "monday",
      "room_name": "Room A",
      "available_slots": [
        { "start_time": "10:00", "end_time": "10:30", "available": true },
        { "start_time": "10:30", "end_time": "11:00", "available": true }
      ]
    },
    {
      "date": "16/06/2026",
      "day": "tuesday",
      "room_name": "Room A",
      "available_slots": [
        { "start_time": "09:00", "end_time": "09:30", "available": true, "spots_left": 2 }
      ]
    }
  ]
}
```

空きがない日は単純に表示されません。`event_id`、`start_time`、または`end_time`が欠落している場合は`400`が返されます。アカウントに存在しないイベントタイプを指定した場合は`404`が返されます。

---

## 予約の登録

`POST /appointments`

連絡先に対して、イベントタイプに基づいた新しい予約を登録します。終了時刻は、イベントタイプに設定されたスロット時間から自動的に計算されます。

予約には競合チェックが行われます。リクエストされたスロットが同じイベントタイプ上の既存の確定済み予約と重複する場合、リクエストは `409` で失敗し、何も作成されません。

| フィールド | 必須 | 説明 |
|---|---|---|
| `contact_id` | はい | 予約する連絡先のID。アカウントに属している必要があります。 |
| `event_id` | はい | 予約するイベントタイプのID。アカウントに属している必要があります。 |
| `start_time` | はい | 希望する開始日時（ISO 8601形式）。 |
| `room_name` | いいえ | イベントタイプで部屋を使用している場合の、部屋またはリソース名。 |

**cURL** (`?apiKey=` クエリ形式を使用)

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "start_time": "2026-06-15T10:00:00.000Z",
    "room_name": "Room A"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/appointments", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contact_id: "contact_abc123",
    event_id: "event_xyz789",
    start_time: "2026-06-15T10:00:00.000Z",
    room_name: "Room A",
  }),
});
const data = await res.json();
console.log(data.appointment_id);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/appointments",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "contact_id": "contact_abc123",
        "event_id": "event_xyz789",
        "start_time": "2026-06-15T10:00:00.000Z",
        "room_name": "Room A",
    },
)
print(res.json()["appointment_id"])
```

**レスポンス** (`201 Created`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678",
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-15T10:00:00.000Z",
    "end_time": "2026-06-15T10:30:00.000Z",
    "created_at": "2026-06-10T09:00:00.000Z",
    "last_modified_at": "2026-06-10T09:00:00.000Z",
    "room_name": "Room A",
    "google_calendar_event_id": null,
    "calendar_synced": false
  }
}
```

---

## 予約の取得

`GET /appointments/{appointmentId}`

IDを指定して単一の予約を取得します。これにはカレンダーの同期状態も含まれます。

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointment);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["appointment"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-15T10:00:00.000Z",
    "end_time": "2026-06-15T10:30:00.000Z",
    "room_name": "Room A",
    "google_calendar_event_id": "abc123googleevent",
    "calendar_synced": true
  }
}
```

---

## 予約の一覧表示

`GET /appointments`

アカウントの予約を新しい順に一覧表示します。カーソルベースのページネーションを使用します。

| クエリパラメータ | 必須 | 説明 |
|---|---|---|
| `contact_id` | いいえ | 指定した連絡先の予約のみを返します。連絡先でフィルタリングされた一覧には**確定済みの予約のみ**が含まれます。 |
| `date` | いいえ | 指定したカレンダーの日付（`YYYY-MM-DD`）の予約のみを返します。**`contact_id`が必要です。** |
| `status` | いいえ | `Confirmed`または`Canceled`でフィルタリングします。`contact_id`を**指定していない**場合のみ利用可能です。 |
| `limit` | いいえ | 1から100までの整数でページサイズを指定します。デフォルトは`50`です。 |
| `cursor` | いいえ | 前回のレスポンスから取得した`next_cursor`の値です。 |

留意すべきいくつかのルール:

- **フィルタなし**の場合、アカウント上のすべての予約をページごとに取得します。
- **連絡先別** — `contact_id`を設定すると、特定の連絡先の確定済み予約を表示できます。`date`も併せて渡すことで、特定の日付に絞り込むことも可能です。
- **ステータス別** — `status`を設定（`contact_id`は指定しない）すると、アカウント全体で`Confirmed`または`Canceled`の予約のみを一覧表示できます。
- `contact_id`なしでの`date`フィルタ、または`status=Canceled`と`contact_id`を組み合わせた場合は、`400`が返されます。

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/appointments?contact_id=contact_abc123&date=2026-06-15" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  contact_id: "contact_abc123",
  date: "2026-06-15",
});
const res = await fetch(
  `https://api.youraiconnector.com/v1/appointments?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointments, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/appointments",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"contact_id": "contact_abc123", "date": "2026-06-15"},
)
data = res.json()
print(data["appointments"], data["next_cursor"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "appointments": [
    {
      "id": "aBcD1234eFgH5678",
      "contact_id": "contact_abc123",
      "event_id": "event_xyz789",
      "status": "Confirmed",
      "start_time": "2026-06-15T10:00:00.000Z",
      "end_time": "2026-06-15T10:30:00.000Z",
      "calendar_synced": true
    }
  ],
  "next_cursor": null
}
```

結果をページ送りするには、あるレスポンスの`next_cursor`を次のリクエストの`cursor`として渡します。`next_cursor`が`null`になるまで繰り返します。共通のページネーションパターンについては[エラーとページネーション](errors-and-pagination.md)を参照してください。

---

## 予約の更新

`PUT /appointments/{appointmentId}`

予約の日時変更や詳細の変更を行います。変更したいフィールドのみを送信してください（少なくとも1つは必須です）。開始時刻と終了時刻を組み合わせた場合、時系列順である必要があります（`end_time`は`start_time`より後である必要があります）。変更内容はリンクされたカレンダーイベントに自動的に同期されます。

| フィールド | 説明 |
|---|---|
| `start_time` | 新しい開始日時（ISO 8601形式）。 |
| `end_time` | 新しい終了日時（ISO 8601形式）。開始日時より後の時刻である必要があります。 |
| `room_name` | 新しい会議室またはリソース名。 |
| `description` | 新しい説明。クリアする場合は `null` を指定します。 |
| `summary` | 新しい概要。クリアする場合は `null` を指定します。 |

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "start_time": "2026-06-16T10:00:00.000Z",
    "end_time": "2026-06-16T10:30:00.000Z"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      start_time: "2026-06-16T10:00:00.000Z",
      end_time: "2026-06-16T10:30:00.000Z",
    }),
  }
);
const data = await res.json();
console.log(data.appointment);
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "start_time": "2026-06-16T10:00:00.000Z",
        "end_time": "2026-06-16T10:30:00.000Z",
    },
)
print(res.json()["appointment"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678",
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-16T10:00:00.000Z",
    "end_time": "2026-06-16T10:30:00.000Z",
    "calendar_synced": true
  }
}
```

---

## 予約のキャンセル

`POST /appointments/{appointmentId}/cancel`

確定済みの予約をキャンセルします（オプションで理由を記録可能）。予約はステータス `Canceled` としてアカウント内に保持され、リンクされているカレンダーイベントはバックグラウンドで自動的に削除されます。すでにキャンセル済みの予約をキャンセルしようとすると、`400` が返されます。

| フィールド | 必須 | 説明 |
|---|---|---|
| `cancellation_reason` | いいえ | キャンセルの理由。予約情報に保存されます。 |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678/cancel" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cancellation_reason": "Client asked to reschedule next month"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678/cancel",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      cancellation_reason: "Client asked to reschedule next month",
    }),
  }
);
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678/cancel",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"cancellation_reason": "Client asked to reschedule next month"},
)
print(res.json()["success"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678"
}
```

---

## 予約の削除

`DELETE /appointments/{appointmentId}`

予約とその参照情報を完全に削除します。記録を残したまま予約を取り消したい場合は、代わりに [キャンセル](#cancel-an-appointment) を使用してください。

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true
}
```

---

## 接続済みGoogleカレンダーの一覧表示

`GET /appointments/google-calendars`

このアカウントで利用可能なGoogleカレンダーをGoogleから直接返します。これは、アカウント所有者にインポート元のカレンダーを選択させるピッカーを表示する場合や、接続が有効であることを確認する場合に便利です。

これは、アカウントが（設定 → 連携）からGoogleカレンダーに接続し、少なくとも読み取り権限が付与されている場合にのみ機能します。接続されていない場合、または付与された権限にカレンダーの読み取りスコープが含まれなくなった場合は、接続（または再接続）を促す`400`が返されます。

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/appointments/google-calendars" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/appointments/google-calendars", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/appointments/google-calendars",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["data"])
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": [
    {
      "id": "primary",
      "summary": "jane@example.com",
      "timeZone": "America/New_York",
      "accessRole": "owner",
      "primary": true
    },
    {
      "id": "abcdefg1234567890@group.calendar.google.com",
      "summary": "Bookings",
      "timeZone": "America/New_York",
      "accessRole": "writer"
    }
  ]
}
```

各エントリはGoogle独自の[`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList)形式であるため、フィールド名は本APIの一般的な`snake_case`ではなく、Googleの`camelCase`に従います。これはGoogleのデータをそのまま渡しているものであり、当社のデータではありません。接続が欠落しているか取り消されている場合は、Googleカレンダーの接続（または再接続）が必要であることを説明するエラーとともに`400`が返されます。

---

## Googleカレンダーからイベントをインポートする

`POST /appointments/import-calendar-events`

キャンペーンまたはAIエージェントの接続済みGoogleカレンダーに既に存在するイベントを取得し、アポイントメントに変換します。これは、既に予約が入っているカレンダーを初めて接続する場合に便利です。各イベントの抽出処理（誰のための予約かを判断するため）に時間がかかる可能性があるため、インラインでは実行されません。リクエストはバックグラウンドジョブとしてキューに入れられ、ポーリング用の`job_id`が返されます。

| フィールド | 必須 | 説明 |
|---|---|---|
| `campaign_id` | どちらか一方 | インポート元の接続済みカレンダーを持つキャンペーン。 |
| `agent_id` | どちらか一方 | インポート元の接続済みカレンダーを持つAIエージェント。 |
| `identifier` | はい | `"EMAIL"`または`"PHONE_NUMBER"` — 各カレンダーイベントから抽出して、所属する連絡先を照合または作成するために使用する連絡先情報。 |

`campaign_id`または`agent_id`のいずれか一方のみを送信してください。両方送信したり、どちらも送信しなかったりすると、`400`が返されます。送信するIDは、ご自身のアカウントに属している必要があります。そうでない場合は`404`が返されます。

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/import-calendar-events?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_abc123",
    "identifier": "EMAIL"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/appointments/import-calendar-events", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agent_id: "agent_abc123",
    identifier: "EMAIL",
  }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/appointments/import-calendar-events",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"agent_id": "agent_abc123", "identifier": "EMAIL"},
)
print(res.json()["job_id"])
```

**レスポンス** (`202 Accepted`):

```json
{
  "success": true,
  "job_id": "jK9mQ2xR7pL4wN1t",
  "status": "queued",
  "campaign_id": null,
  "agent_id": "agent_abc123"
}
```

`campaign_id`と`agent_id`は送信した値をそのまま返します。もう一方は常に`null`となります。

### インポートジョブをポーリングする

`GET /appointments/import-calendar-events/{jobId}`

```bash
curl "https://api.youraiconnector.com/v1/appointments/import-calendar-events/jK9mQ2xR7pL4wN1t" \
  -H "X-API-Key: YOUR_API_KEY"
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "job_id": "jK9mQ2xR7pL4wN1t",
  "status": "completed",
  "message": "Imported 12 events as appointments.",
  "error": null
}
```

| `status` | 意味 |
|---|---|
| `queued` | まだ処理されていません。ポーリングを続けてください。 |
| `processing` | インポートを実行中です。ポーリングを続けてください。 |
| `completed` | 完了しました。`message`に人間が読める形式の短い要約が含まれています。 |
| `failed` | 何らかのエラーが発生しました。`error`に理由が記載されています。 |

存在しない（または別のアカウントに属する）`jobId`に対して`GET`を実行すると、`404`が返されます。

---

## レストラン予約連携（Zenchef / Formitable）

ZenchefとFormitableは、AIエージェントが実際のテーブルを予約できるレストラン予約システムです。それぞれ、チャット内で利用者に表示される**公開された認証不要の予約ウィジェット**（`https://api.youraiconnector.com/v1/zenchef-widget/...`および`https://api.youraiconnector.com/v1/formitable-widget/...`）を備えています。これらのウィジェットのルートはブラウザで開くことを目的としたプレーンなHTMLページであり、JSON APIエンドポイントではないため、ここでは説明しません。以下はアカウント管理用のエンドポイントです。レストランIDがアカウント所有者に属していることを確認し、追加、更新、削除を行うためのものです。

### Zenchef

Zenchefレストランの接続は2段階の認証プロセスとなっており、ボットに接続する前に、アカウント所有者が実際にそのレストランを運営していることを証明する必要があります。まずIDが存在するかを確認し（名前は表示されません）、次にレストラン名を本人に入力してもらい、それが一致するかを検証します。

**ステップ 1 — レストランIDの存在確認**

`POST /appointments/zenchef-restaurants/check`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_id` | はい | 確認するZenchefレストランID。 |

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/zenchef-restaurants/check?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345" }'
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": { "exists": true, "requiresNameVerification": true }
}
```

`exists: false`は、そのIDを持つZenchefレストランが存在しないことを意味します。これ以上の操作は不要です。レート制限はアカウントごとに5分間で10回までです。これを超えると`429`が返されます。

**ステップ 2 — レストラン名の検証**

`POST /appointments/zenchef-restaurants/verify-name`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_id` | はい | ステップ1で取得したZenchefレストランID。 |
| `user_input_name` | はい | アカウント所有者が入力した名前。Zenchef上の実際のレストラン名と照合されます（大文字小文字や空白は区別されません）。 |

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/zenchef-restaurants/verify-name?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345", "user_input_name": "The Blue Door Bistro" }'
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "id": "12345",
      "name": "The Blue Door Bistro",
      "address": "1 Rue de Rivoli, Paris",
      "status": "active"
    }
  }
}
```

`verified: false`は名前が一致しなかったことを意味します。`restaurantDetails`は省略されるため、アカウント所有者に再試行を依頼してください。レート制限は5分間で3回までです（これが実際の証明ステップであるため、存在確認よりも厳しくなっています）。Zenchef上で解決できなくなった`restaurant_id`は、`404`を返します。

**ステップ 3 — レストランの保存**

`POST /appointments/zenchef-restaurants`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_id` | はい | 1〜64文字。英数字、アンダースコア、ハイフンが使用可能です。 |
| `restaurant_name` | はい | ステップ2で検証済みのレストラン名。 |

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/zenchef-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345", "restaurant_name": "The Blue Door Bistro" }'
```

**レスポンス** (`201 Created`):

```json
{ "success": true, "data": { "restaurantId": "12345" } }
```

**保存されたZenchefレストランの更新**

`PUT /appointments/zenchef-restaurants/{restaurantId}`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_name` | いいえ | 新しい表示名。 |
| `is_active` | いいえ | レストランを削除せずにボットによる予約を停止するには、`false` を設定します。 |

```bash
curl -X PUT "https://api.youraiconnector.com/v1/appointments/zenchef-restaurants/12345" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**レスポンス** (`200 OK`): 上記の保存レスポンスと同じ形式。

**Zenchefレストランの削除**

`DELETE /appointments/zenchef-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/appointments/zenchef-restaurants/12345" \
  -H "X-API-Key: YOUR_API_KEY"
```

**レスポンス** (`200 OK`): `{ "success": true, "data": { "restaurantId": "12345" } }`

アカウントに現在存在しない `restaurantId` を更新または削除しようとすると、`404` が返されます。

### Formitable

FormitableはZenchefのような2段階の名前確認を必要としません。レストランIDはすでにビジネスごとにスコープ設定されているため、1回の検証呼び出しで十分です。また、セットアップ中にレストランのウェブサイトURLをキャッシュするために使用される詳細ルックアップ機能もあります。

**レストランIDの検証**

`POST /appointments/formitable-restaurants/verify`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_id` | はい | FormitableのレストランID。 |
| `language` | いいえ | プローブ要求の言語タグ。デフォルトは `"nl"` です。 |

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/formitable-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "the-blue-door", "language": "en" }'
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "the-blue-door",
      "productCount": 4,
      "sampleProductTitle": "Dinner for two",
      "language": "en"
    }
  }
}
```

Formitableが認識できない `restaurant_id` は、`404` を返します。レート制限は、アカウントごとに5分間で10回までです。

**レストラン詳細の取得**

`GET /appointments/formitable-restaurants/{restaurantId}/details?language=en`

Formitableからレストランの公開プロフィール（ウェブサイトを含む）を取得します。これは、レストランのセットアップ中にウェブサイトURLをキャッシュするために使用されます。`language` はオプションのクエリパラメータで、デフォルトは `"en"` です。

```bash
curl "https://api.youraiconnector.com/v1/appointments/formitable-restaurants/the-blue-door/details?language=en" \
  -H "X-API-Key: YOUR_API_KEY"
```

**レスポンス** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "uid": "the-blue-door",
    "name": "The Blue Door Bistro",
    "website": "https://thebluedoorbistro.com",
    "email": "info@thebluedoorbistro.com",
    "telephone": "+31201234567",
    "streetAddress": "Prinsengracht 1",
    "zipcode": "1015 AB",
    "city": "Amsterdam",
    "country": "Netherlands",
    "countryCode": "NL",
    "currency": "EUR"
  }
}
```

**レストランを保存する**

`POST /appointments/formitable-restaurants`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_id` | はい | 1〜64文字。英数字、アンダースコア、ハイフンが使用可能です。 |
| `restaurant_name` | はい | 表示名。 |
| `language` | はい | ISO言語タグ（例: `"en"` や `"en-GB"`）。 |
| `website_url` | いいえ | 上記の詳細検索で取得したレストランのウェブサイト。 `http(s)://` である必要があります。 |

```bash
curl -X POST "https://api.youraiconnector.com/v1/appointments/formitable-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "restaurant_id": "the-blue-door",
    "restaurant_name": "The Blue Door Bistro",
    "language": "en",
    "website_url": "https://thebluedoorbistro.com"
  }'
```

**レスポンス** (`201 Created`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

**保存済みのFormitableレストランを更新する**

`PUT /appointments/formitable-restaurants/{restaurantId}`

| フィールド | 必須 | 説明 |
|---|---|---|
| `restaurant_name` | いいえ | 新しい表示名。 |
| `language` | いいえ | 新しいISO言語タグ。 |
| `is_active` | いいえ | `false` を設定すると、レストランを削除せずにボットによる予約を停止できます。 |
| `website_url` | いいえ | 新しいウェブサイトURL。 |

```bash
curl -X PUT "https://api.youraiconnector.com/v1/appointments/formitable-restaurants/the-blue-door" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**レスポンス** (`200 OK`): 上記の保存レスポンスと同じ形式。

**Formitableレストランを削除する**

`DELETE /appointments/formitable-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/appointments/formitable-restaurants/the-blue-door" \
  -H "X-API-Key: YOUR_API_KEY"
```

**レスポンス** (`200 OK`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

アカウントに現在存在しない `restaurantId` を更新または削除しようとすると、`404` が返されます。

> **すべてのZenchef/Formitableエンドポイントのエラー形式:** このページの他の部分とは異なり、ここでのエラーはステータスを2回保持します。1回目はHTTPステータスとして、2回目はボディ内の `error_code` として保持されます（例: `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`）。他のエラーと同様に処理してください。`success` を確認し、メッセージについては `error` を読み取ってください。

---

## Appointments API エラー

Appointment エンドポイントは、標準的なエラーエンベロープを返します。

```json
{
  "success": false,
  "error": "Appointment not found"
}
```

| ステータス | Appointment エンドポイントで発生する場合 |
|---|---|
| `400` | 必須フィールドが欠落しているか無効です。例：不正な `start_time`、`start_time` より後ではない `end_time`、無効なフィルターの組み合わせ、更新するフィールドがない、またはすでにキャンセル済みの予約など。 |
| `404` | 予約、連絡先、またはイベントタイプが見つかりませんでした。 |
| `409` | 要求された時間枠はすでに埋まっています（予約の競合）。 |

すべてのエンドポイントが返す共通コード（`401`、`403`（プランにAPIアクセスが含まれていない）、`429`（レート制限）、`500`）については、再試行のガイダンスと共に[エラーとページネーション](errors-and-pagination.md)に記載されています。

---

## 次のステップ

- [連絡先](contacts.md) — 予約対象の連絡先を作成および検索します。
- [メッセージと会話](messages.md) — 連絡先に確認やリマインダーを送信します。
- [Webhook](webhooks.md) — 予約が変更されたときに通知を受け取ります。
