
# 予約

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

このページのすべてのパスは、ベースURL `https://api.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.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.dmchamp.com/v1/appointments/google-calendars" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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`が返されます。

---

<a id="restaurant-booking-integrations-zenchef-formitable"></a>

## 外部予約連携 (Zenchef / Formitable / OpenTable / TheFork / Trafft)

ZenchefとFormitableは、AIエージェントが実際のテーブルを予約できるレストラン予約システムです。[Trafft](#trafft)は予約ビジネス向けのスケジューリングプラットフォームであり、レストランごとではなくアカウントごとに1回接続します。2つのレストランプラットフォームにはそれぞれ、ダイナー（食事客）のためにチャット内でレンダリングされる**公開された認証不要の予約ウィジェット**（`https://api.dmchamp.com/v1/zenchef-widget/...`および`https://api.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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.dmchamp.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` が返されます。

### OpenTable

OpenTableのレストランには、プラットフォームのOpenTableパートナー資格情報を通じてアクセスします。OpenTableでは、OpenTableの「Integrations Marketplace」内でプラットフォームのリスティングを接続したレストランのみが、これらの資格情報から参照可能になります。そのため、Formitableと同様に、1回の検証呼び出しで十分です。到達可能なレストランID（数値の「RID」）は、レストランが存在することと、統合が接続されていることの両方を証明します。OpenTableのパートナーリスティングがプラットフォーム上で有効になるまで、検証呼び出しは `503` を返します。

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

`POST /appointments/opentable-restaurants/verify`

| フィールド | 必須 | 説明 |
| --- | --- | --- |
| `restaurant_id` | はい | OpenTableレストランID（RID）。`1038007`のような数値です。 |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "1038007" }'
```

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

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "1038007",
      "diningAreaCount": 2,
      "diningAreaNames": ["Main Room", "Garden"],
      "tableTypes": ["default", "outdoor", "bar"]
    }
  }
}
```

`verified: false` は、そのIDを持つOpenTableレストランが存在しないことを意味します。`403` は、レストランは存在するものの、まだOpenTable内でプラットフォームの統合を接続していないことを意味します。アカウントごとに5分あたり10回までの試行にレート制限されています。

**OpenTableレストランの追加**

`POST /appointments/opentable-restaurants`

| フィールド | 必須 | 説明 |
| --- | --- | --- |
| `restaurant_id` | はい | 検証済みのレストランID。 |
| `restaurant_name` | はい | 表示名（ラベル。AIがそのレストランを呼ぶ際の名前でもあります）。 |
| `website_url` | いいえ | AIがゲストをレストランに引き継ぐ際に表示されるhttp(s) URL。 |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "1038007", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
```

**レスポンス** (`201 Created`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

**保存済みOpenTableレストランの更新**

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

`restaurant_name`、`is_active`（`false`で一時停止）、または`website_url`（空文字でクリア）のいずれかを送信します。省略されたフィールドは変更されません。

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

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

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

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

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
  -H "X-API-Key: YOUR_API_KEY"
```

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

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

### TheFork

TheForkのレストランには、プラットフォームのTheForkパートナー資格情報を通じてアクセスします。TheForkでは、その資格情報を持つユーザーは、TheForkアカウントでプラットフォームのパートナー機能が有効になっているレストランのみを参照できます。そのため、FormitableやOpenTableと同様に、1回の検証呼び出しで十分です。到達可能なレストランIDは、レストランが存在することと、そのレストランでパートナー機能が有効になっていることの両方を証明します。IDは、TheForkがTheFork Managerでレストランに割り当てるUUIDであり、文字列として送信されます。TheForkがプラットフォームをパートナーとして承認し、資格情報を発行するまで、検証呼び出しは `503` を返します。現在の状況については [TheFork](../integrations/thefork.md) を参照してください。

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

`POST /appointments/thefork-restaurants/verify`

| フィールド | 必須 | 説明 |
| --- | --- | --- |
| `restaurant_id` | はい | TheForkレストランID（`9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60`のようなUUID）。 |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" }'
```

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

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60",
      "partySizes": [1, 2, 3, 4, 5, 6, 7, 8]
    }
  }
}
```

`partySizes` は、そのレストランが今後30日間にオンラインで受け付ける人数です。`404` または `403` は、TheForkがそのIDを持つレストランを提供しなかったことを意味します（IDが間違っているか、そのレストランでプラットフォームのパートナー機能がまだ有効になっていないかのいずれかです）。`400` は、IDがUUIDではないことを意味します。レート制限は、アカウントごとに5分間あたり10回までです。

**TheForkレストランの追加**

`POST /appointments/thefork-restaurants`

| フィールド | 必須 | 説明 |
| --- | --- | --- |
| `restaurant_id` | はい | 検証済みのレストランID（UUID）。 |
| `restaurant_name` | はい | 表示名（ラベル。AIがレストランを呼ぶ際の名前でもあります）。 |
| `website_url` | いいえ | AIがゲストをレストランに引き継ぐ際に表示されるhttp(s) URL。 |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
```

**レスポンス** (`201 Created`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

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

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

`restaurant_name`、`is_active`（`false`で一時停止）、または`website_url`（空文字でクリア）のいずれかを送信します。省略されたフィールドは変更されません。

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**レスポンス** (`200 OK`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

**TheForkのレストランを削除する**

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

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
  -H "X-API-Key: YOUR_API_KEY"
```

**レスポンス** (`200 OK`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

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

### Trafft

Trafftは場所ごとではなく、アカウント全体に対して1回接続します。会社のアドレスと、Trafft管理パネル（**Features & Integrations → API & Connectors**、Trafftのビジネスプランの一部）から取得したAPI認証情報が必要です。接続呼び出しは、何かを保存する前にTrafftに対してそれらの認証情報をチェックするため、アドレスや認証情報が間違っている場合、あるいはAPIアクセス権のないプランである場合は、顧客との会話中ではなくこの時点で失敗します。クライアントシークレットは暗号化されて保存され、どのエンドポイントからも返されることはありません。

3つの書き込み呼び出し（`POST`、`PUT`、`DELETE`）はすべて、Integrationsの**編集**権限を必要とします。ステータス`GET`にはIntegrationsの**表示**権限が必要です。

**Trafftを接続する**

`POST /appointments/trafft/connect`

| フィールド | 必須 | 説明 |
|---|---|---|
| `subdomain` | はい | 会社のアドレス。サインイン時のURLの`.admin.trafft.com`より前の部分（例: `acme`）。完全なアドレスも受け付けられ、同じ値に短縮されます。 |
| `client_id` | はい | TrafftのAPI & Connectorsページから取得したクライアントID。 |
| `client_secret` | はい | 同じページから取得したクライアントシークレット。暗号化して保存され、返されることはありません。 |
| `company_name` | いいえ | 自身のリスト用のラベル。 |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/trafft/connect?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subdomain": "acme",
    "client_id": "YOUR_TRAFFT_CLIENT_ID",
    "client_secret": "YOUR_TRAFFT_CLIENT_SECRET",
    "company_name": "Acme Salon"
  }'
```

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

```json
{
  "success": true,
  "data": {
    "subdomain": "acme",
    "service_count": 12,
    "employee_count": 4,
    "location_count": 2
  }
}
```

カウント数はチェック中にTrafftから返されます。これは、認証情報が意図したアカウントを指していることを確認する最も迅速な方法です。

**接続ステータスを取得する**

`GET /appointments/trafft`

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

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

```json
{
  "success": true,
  "data": {
    "connected": true,
    "subdomain": "acme",
    "hostname": "acme.admin.trafft.com",
    "client_id": "YOUR_TRAFFT_CLIENT_ID",
    "company_name": "Acme Salon",
    "is_active": true,
    "service_count": 12,
    "employee_count": 4,
    "location_count": 2
  }
}
```

`connected: false`は、まだ何も設定されていないことを意味します。クライアントシークレットはこのレスポンスに含まれることはありません。

**接続を更新する**

`PUT /appointments/trafft`

| フィールド | 必須 | 説明 |
|---|---|---|
| `is_active` | いいえ | `false` を設定して一時停止します。AIはTrafftへの予約を停止しますが、接続は維持されます。`true` で再開します。 |
| `company_name` | いいえ | 新しいラベル。 |

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

**レスポンス** (`200 OK`): 上記の `GET` と同じ接続オブジェクトです。

**Trafftの接続解除**

`DELETE /appointments/trafft`

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

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

接続を解除すると、保存された認証情報のみが削除されます。Trafftにすでにある予約には影響しません。

> **Zenchef/Formitable/OpenTable/TheForkの全エンドポイントにおけるエラー形式:** このページの他の箇所とは異なり、ここでのエラーはステータスを2回（HTTPステータスとして1回、ボディ内の `error_code` として1回）伝えます。例: `{ "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)に記載されています。

---

::: master-only
## 独自のGoogle OAuthクライアント（カレンダー同意画面）を使用する

アカウントがGoogleカレンダーに接続される際、GoogleのサインインウィンドウにはOAuthクライアントのプロジェクト名が表示されます（デフォルトではプラットフォーム名）。代理店は、代理店アカウント上で独自のGoogle OAuth 2.0クライアントを登録できます。それ以降、そのアカウントおよびその配下のすべてのサブアカウントのカレンダー接続は、そのクライアントを通じて実行されるため、同意画面には代理店の名前とロゴが表示されます。それ以外の変更はありません。接続フロー、双方向同期、および上記のアポイントメントエンドポイントは、これまでと全く同じように動作します。

> **Googleカレンダーのみ。** メールチャネル用のGmailメールボックスOAuthには影響しません。

### クライアントが事前に必要なもの

1. Google Cloudプロジェクト内のWebアプリケーションタイプの **OAuth 2.0クライアント**。そのプロジェクトで **Google Calendar API** が有効になっている必要があります。
2. **すべての `redirect_uris` エントリ**（以下のエンドポイントによって返される）が、クライアントの「承認済みのリダイレクトURI」に追加されていること。最初のエントリは、検証済みの `api.` ドメイン（お持ちの場合）です。Googleは、所有するドメイン上にリダイレクト先があるブランドのみを検証します。その後に、それまで使用されるフォールバックとしてプラットフォームの中立的なホストが続きます。
3. **同意画面**。ブランド名、承認済みドメイン配下のドメイン、および宣言された2つのカレンダー範囲（レスポンス内の `scopes`）が含まれている必要があります。アプリが公開されGoogleによって検証されるまで、ユーザーには未検証アプリの警告が表示され、クライアントは100ユーザーまでに制限されます。

### クライアントを保存する

`PUT /account-config/google-oauth-client`

| フィールド | 必須 | 説明 |
|---|---|---|
| `client_id` | はい | `.apps.googleusercontent.com` で終わるOAuth 2.0クライアントID。 |
| `client_secret` | はい | クライアントシークレット。保存前にGoogleに対して検証され、その後暗号化されます。どのエンドポイントからも返されることはありません。 |

```bash
curl -X PUT "https://api.dmchamp.com/v1/account-config/google-oauth-client?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
    "client_secret": "GOCSPX-your-client-secret"
  }'
```

**レスポンス**

```json
{
  "success": true,
  "configured": true,
  "client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
  "redirect_uris": [
    "https://api.youragency.com/v1/auth-google-callback",
    "https://api.dmchamp.com/v1/auth-google-callback"
  ],
  "scopes": [
    "https://www.googleapis.com/auth/calendar.events",
    "https://www.googleapis.com/auth/calendar.readonly"
  ],
  "setup": ["…"]
}
```

誤ったシークレットや不明なクライアントIDは `400` で拒否され、`error` にGoogle独自の理由が示されます。また、何も保存されません。

### 読み取りまたは削除

`GET /account-config/google-oauth-client` はいつでも同じ概要を返します。`configured: false` に加え、保存される前の `redirect_uris` と `scopes` が含まれるため、先に Google 側の設定を行うことができます。`DELETE /account-config/google-oauth-client` はクライアントを削除します。新しい接続はプラットフォームクライアントに戻り、削除されたクライアント経由で接続されていたカレンダーは再接続が必要になります。これは、接続を発行したクライアントのみがその接続を更新できるためです。

チームメンバーには、`GET` には **Integrations: view**、`PUT` / `DELETE` には **Integrations: edit** の権限が必要です。
:::

---

## 次のステップ

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