
# Janji Temu

API Janji Temu memungkinkan Anda membuat janji temu untuk kontak Anda pada jenis acara Anda, kemudian mengambil, mencantumkan, memperbarui, membatalkan, atau menghapusnya. API ini juga menjawab pertanyaan yang muncul pertama kali dalam sebagian besar alur pemesanan — waktu mana yang benar-benar kosong — dan mencakup sisi kalender: mencantumkan Kalender Google yang telah Anda hubungkan dan mengimpor acara yang sudah ada di dalamnya. Saat koneksi Kalender Google aktif, acara kalender yang cocok akan dibuat dan disinkronkan secara otomatis di latar belakang. Restoran yang menggunakan Zenchef atau Formitable untuk sistem reservasi mereka sendiri juga dapat diverifikasi dan dihubungkan di sini, sehingga Agen AI dapat memesan meja sungguhan alih-alih janji temu internal.

Semua jalur di halaman ini relatif terhadap URL dasar `https://api.youraiconnector.com/v1`. Setiap permintaan memerlukan kunci API Anda — lihat [Autentikasi](authentication.md) untuk daftar lengkap cara mengirimkannya. Contoh di bawah menggunakan header `X-API-Key`, dengan satu contoh cURL yang juga menunjukkan formulir kueri `?apiKey=`.

> **Acara vs. janji temu:** *Jenis acara* adalah definisi slot yang dapat dipesan (jenis pertemuan, durasinya, ruangannya). *Janji temu* adalah satu instans yang dipesan dari jenis acara untuk kontak tertentu. Anda memesan janji temu dengan mereferensikan kontak dan jenis acara tersebut.

---

## Objek janji temu

Setiap endpoint yang mengembalikan janji temu menggunakan bentuk yang sama:

| Bidang | Deskripsi |
|---|---|
| `id` | ID unik janji temu. |
| `contact_id` | ID kontak yang dipesan untuk janji temu. |
| `event_id` | ID jenis acara tempat janji temu dipesan. |
| `status` | `Confirmed` atau `Canceled`. |
| `start_time` | Waktu mulai janji temu, ISO 8601 dalam UTC. |
| `end_time` | Waktu berakhir janji temu, ISO 8601 dalam UTC. |
| `created_at` | Kapan janji temu dibuat. |
| `last_modified_at` | Kapan janji temu terakhir diubah. |
| `room_name` | Ruangan atau sumber daya tempat janji temu dipesan, saat jenis acara menggunakan ruangan. |
| `description` | Deskripsi janji temu dalam bentuk bebas. |
| `summary` | Ringkasan atau judul singkat. |
| `cancelation_reason` | Alasan yang diberikan saat janji temu dibatalkan, jika ada. |
| `google_calendar_event_id` | ID acara Google Kalender yang ditautkan. Ditetapkan setelah sinkronisasi kalender selesai; `null` saat tidak ada kalender yang terhubung atau saat sinkronisasi masih berlangsung. |
| `calendar_synced` | `true` setelah janji temu ditautkan ke acara kalender. |
| `imported` | `true` saat janji temu diimpor dari kalender eksternal, bukan dipesan secara langsung. |
| `is_recurring` | `true` saat janji temu merupakan bagian dari seri berulang. |
| `recurrence_frequency` | Seberapa sering janji temu berulang, saat berulang. |
| `recurring_event_id` | ID seri berulang tempat janji temu ini berada. |
| `recurring_interval` | Interval antar pengulangan, saat berulang. |
| `recurring_sequence` | Posisi janji temu ini dalam seri berulangnya. |
| `end_after_x_occurrences` | Jumlah kejadian setelah seri berulang berakhir. |
| `booking_provider` | Sistem sumber asal pemesanan, saat dipesan melalui penyedia reservasi yang terhubung. |

> **Tentang sinkronisasi kalender:** Tepat setelah Anda memesan atau mengubah janji temu, `google_calendar_event_id` mungkin masih `null` dan `calendar_synced` mungkin `false` karena sinkronisasi berjalan di latar belakang beberapa saat kemudian. Ambil kembali janji temu tersebut beberapa saat kemudian untuk melihat bidang kalender yang telah terisi.

---

## Temukan slot yang tersedia

`GET /appointments/available-slots`

Mengembalikan waktu yang benar-benar kosong pada satu jenis acara di antara dua momen. Ini biasanya merupakan panggilan **pertama** dalam alur pemesanan: tampilkan slot ini, biarkan orang tersebut memilih satu, lalu kirim waktu yang dipilih ke [Buat janji temu](#book-an-appointment).

Jawaban tersebut sudah memperhitungkan jam buka dan durasi slot jenis acara itu sendiri, ruangannya, janji temu yang sudah Anda buat, dan semua yang diblokir di Kalender Google yang terhubung — jadi slot yang dikembalikan di sini adalah slot yang dapat Anda pesan.

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `event_id` | Ya | Jenis acara yang akan diperiksa. Harus milik akun Anda. |
| `start_time` | Ya | Awal jendela waktu yang Anda inginkan slotnya, tanggal-waktu ISO 8601. |
| `end_time` | Ya | Akhir jendela waktu, tanggal-waktu ISO 8601. Seluruh hari terakhir disertakan. |

Hasil dikembalikan dikelompokkan berdasarkan hari — dan, jika jenis acara menggunakan ruangan, satu grup per ruangan per hari:

| Bidang | Deskripsi |
|---|---|
| `date` | Hari yang dicakup oleh grup, ditulis `DD/MM/YYYY`. |
| `day` | Nama hari kerja dalam huruf kecil, contohnya `monday`. |
| `room_name` | Ruangan atau sumber daya milik grup ini, jika jenis acara menggunakan ruangan. |
| `available_slots` | Blok yang dapat dipesan pada hari itu, diurutkan dari yang paling awal. |

Setiap entri dalam `available_slots` memiliki:

| Bidang | Deskripsi |
|---|---|
| `start_time` | Awal blok sebagai `HH:mm`. |
| `end_time` | Akhir blok sebagai `HH:mm`. |
| `available` | `true` — hanya waktu kosong yang dikembalikan. |
| `spots_left` | Berapa banyak pemesanan yang masih muat di blok ini. Hanya ada pada jenis acara yang menerima lebih dari satu pemesanan per slot. |

> **Waktu bersifat lokal untuk jenis acara, bukan UTC.** `date`, `start_time`, dan `end_time` adalah nilai jam dinding di zona waktu jenis acara itu sendiri (penggantiannya, atau zona waktu akun Anda jika tidak ada). [Buat janji temu](#book-an-appointment) mengharapkan instan UTC ISO 8601, jadi konversikan slot yang Anda pilih sebelum mengirimnya.

**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"])
```

**Respons** (`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 }
      ]
    }
  ]
}
```

Hari yang tidak memiliki waktu kosong tidak akan muncul. `event_id`, `start_time`, atau `end_time` yang hilang akan mengembalikan `400`; jenis acara yang tidak ada di akun Anda akan mengembalikan `404`.

---

## Pesan janji temu

`POST /appointments`

Memesan janji temu baru untuk kontak pada salah satu jenis acara Anda. Waktu berakhir dihitung secara otomatis dari durasi slot jenis acara tersebut.

Pemesanan diperiksa konfliknya: jika slot yang diminta tumpang tindih dengan janji temu yang sudah dikonfirmasi pada jenis acara yang sama, permintaan akan gagal dengan `409` dan tidak ada yang dibuat.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `contact_id` | Ya | ID kontak yang akan dipesan. Harus milik akun Anda. |
| `event_id` | Ya | ID jenis acara untuk dipesan. Harus milik akun Anda. |
| `start_time` | Ya | Waktu mulai yang diinginkan sebagai tanggal-waktu ISO 8601. |
| `room_name` | Tidak | Nama ruangan atau sumber daya, saat jenis acara menggunakan ruangan. |

**cURL** (menggunakan formulir kueri `?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"])
```

**Respons** (`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
  }
}
```

---

## Mendapatkan janji temu

`GET /appointments/{appointmentId}`

Mengembalikan satu janji temu berdasarkan ID-nya, termasuk status sinkronisasi kalendernya.

**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"])
```

**Respons** (`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
  }
}
```

---

## Mencantumkan janji temu

`GET /appointments`

Mencantumkan janji temu untuk akun Anda, yang terbaru terlebih dahulu, dengan penomoran halaman berbasis kursor.

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `contact_id` | Tidak | Hanya mengembalikan janji temu untuk kontak ini. Daftar yang difilter berdasarkan kontak hanya menyertakan **janji temu yang dikonfirmasi** saja. |
| `date` | Tidak | Hanya mengembalikan janji temu pada hari kalender ini (`YYYY-MM-DD`). **Memerlukan `contact_id`.** |
| `status` | Tidak | Filter berdasarkan `Confirmed` atau `Canceled`. Hanya tersedia **tanpa** `contact_id`. |
| `limit` | Tidak | Ukuran halaman, bilangan bulat antara 1 dan 100. Default `50`. |
| `cursor` | Tidak | Nilai `next_cursor` dari respons sebelumnya. |

Beberapa aturan yang perlu diingat:

- **Tanpa filter**, Anda mendapatkan setiap janji temu di akun, halaman demi halaman.
- **Berdasarkan kontak** — atur `contact_id` untuk melihat janji temu yang dikonfirmasi dari satu kontak. Anda dapat mempersempitnya ke satu hari dengan juga menyertakan `date`.
- **Berdasarkan status** — atur `status` (tanpa `contact_id`) untuk mencantumkan hanya janji temu `Confirmed` atau hanya `Canceled` di seluruh akun.
- Filter `date` tanpa `contact_id`, atau `status=Canceled` bersama dengan `contact_id`, mengembalikan `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"])
```

**Respons** (`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
}
```

Untuk menelusuri hasil, teruskan `next_cursor` dari satu respons sebagai `cursor` untuk permintaan berikutnya. Lanjutkan sampai `next_cursor` bernilai `null`. Lihat [Error & Penomoran Halaman](errors-and-pagination.md) untuk pola penomoran halaman bersama.

---

## Memperbarui janji temu

`PUT /appointments/{appointmentId}`

Jadwalkan ulang janji temu atau ubah detailnya. Kirim hanya kolom yang ingin Anda ubah — setidaknya satu kolom wajib diisi. Gabungan waktu mulai dan berakhir harus tetap dalam urutan kronologis (`end_time` harus setelah `start_time`). Perubahan akan disinkronkan ke acara kalender yang tertaut secara otomatis.

| Bidang | Deskripsi |
|---|---|
| `start_time` | Waktu mulai baru, tanggal-waktu ISO 8601. |
| `end_time` | Waktu akhir baru, tanggal-waktu ISO 8601. Harus setelah waktu mulai. |
| `room_name` | Nama ruangan atau sumber daya baru. |
| `description` | Deskripsi baru, atau `null` untuk menghapusnya. |
| `summary` | Ringkasan baru, atau `null` untuk menghapusnya. |

**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"])
```

**Respons** (`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
  }
}
```

---

## Membatalkan janji temu

`POST /appointments/{appointmentId}/cancel`

Membatalkan janji temu yang telah dikonfirmasi, dengan opsi untuk mencatat alasannya. Janji temu akan tetap ada di akun Anda dengan status `Canceled`, dan acara kalender yang tertaut akan dihapus secara otomatis di latar belakang. Membatalkan janji temu yang sudah dibatalkan akan mengembalikan `400`.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `cancellation_reason` | Tidak | Alasan pembatalan, disimpan pada janji temu. |

**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"])
```

**Respons** (`200 OK`):

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

---

## Menghapus janji temu

`DELETE /appointments/{appointmentId}`

Menghapus janji temu dan referensinya secara permanen. Jika Anda hanya ingin membatalkan pemesanan namun tetap menyimpan catatannya, gunakan [batal](#cancel-an-appointment) sebagai gantinya.

**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"])
```

**Respons** (`200 OK`):

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

---

## Cantumkan Kalender Google Anda yang terhubung

`GET /appointments/google-calendars`

Mengembalikan Kalender Google yang tersedia di akun ini, langsung dari Google — berguna untuk menampilkan pemilih kalender kepada pemilik akun untuk menentukan kalender mana yang akan diimpor dari bawah, atau sekadar untuk mengonfirmasi bahwa koneksi sudah aktif.

Ini hanya berfungsi setelah akun menghubungkan Google Kalender (Pengaturan → Integrasi) dengan setidaknya akses baca. Jika belum, atau akses yang diberikan tidak lagi menyertakan cakupan baca-kalender, Anda akan mendapatkan `400` yang meminta Anda untuk (menghubungkan) kembali.

**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"])
```

**Respons** (`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"
    }
  ]
}
```

Setiap entri adalah bentuk [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) milik Google sendiri, jadi nama bidang mengikuti `camelCase` Google, bukan `snake_case` API ini yang biasanya digunakan — itu adalah data Google yang diteruskan apa adanya, bukan data kami. Koneksi yang hilang atau dicabut akan mengembalikan `400` dengan kesalahan yang menjelaskan bahwa Google Kalender perlu (dihubungkan) kembali.

---

## Impor acara dari Google Kalender

`POST /appointments/import-calendar-events`

Menarik acara yang sudah ada di Google Kalender kampanye atau Agen AI yang terhubung dan mengubahnya menjadi janji temu — berguna saat pertama kali Anda menghubungkan kalender yang sudah memiliki pemesanan. Proses ini mungkin memakan waktu (setiap acara melalui ekstraksi untuk mengetahui untuk siapa acara tersebut), jadi proses ini tidak pernah berjalan secara inline: permintaan akan mengantrekan pekerjaan latar belakang dan memberikan Anda `job_id` untuk melakukan polling.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `campaign_id` | Salah satu dari keduanya | Kampanye yang kalender terhubungnya akan diimpor. |
| `agent_id` | Salah satu dari keduanya | Agen AI yang kalender terhubungnya akan diimpor. |
| `identifier` | Ya | `"EMAIL"` atau `"PHONE_NUMBER"` — bagian informasi kontak mana yang akan diekstrak dari setiap acara kalender untuk mencocokkan atau membuat kontak yang memilikinya. |

Kirim tepat satu dari `campaign_id` / `agent_id`, jangan pernah keduanya dan jangan pernah tidak keduanya — kombinasi apa pun akan mengembalikan `400`. Mana pun yang Anda kirim harus milik akun Anda, atau Anda akan mendapatkan `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"])
```

**Respons** (`202 Accepted`):

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

`campaign_id` dan `agent_id` akan menggemakan kembali mana pun yang Anda kirim; yang lainnya akan selalu `null`.

### Polling pekerjaan impor

`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"
```

**Respons** (`200 OK`):

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

| `status` | Arti |
|---|---|
| `queued` | Belum diambil. Terus lakukan polling. |
| `processing` | Impor sedang berjalan. Terus lakukan polling. |
| `completed` | Selesai — `message` memiliki ringkasan singkat yang dapat dibaca manusia. |
| `failed` | Terjadi kesalahan — `error` berisi alasannya. |

`GET` pada `jobId` yang tidak ada (atau milik akun lain) akan mengembalikan `404`.

---

## Integrasi pemesanan restoran (Zenchef / Formitable)

Zenchef dan Formitable adalah sistem reservasi restoran tempat Agen AI Anda dapat memesan meja secara nyata. Masing-masing memiliki **widget pemesanan publik tanpa autentikasi** (`https://api.youraiconnector.com/v1/zenchef-widget/...` dan `https://api.youraiconnector.com/v1/formitable-widget/...`) yang ditampilkan di dalam obrolan untuk pelanggan — rute widget tersebut adalah halaman HTML biasa yang dimaksudkan untuk dibuka di browser, bukan titik akhir API JSON, jadi tidak didokumentasikan di sini. Berikut ini adalah titik akhir manajemen akun: memverifikasi ID restoran milik pemegang akun, lalu menambah, memperbarui, atau menghapusnya.

### Zenchef

Menghubungkan restoran Zenchef memerlukan verifikasi dua langkah, sehingga pemilik akun membuktikan bahwa mereka benar-benar mengelola restoran tersebut sebelum dihubungkan ke bot: pertama, periksa apakah ID ada (tanpa mengungkapkan namanya), kemudian minta mereka mengetikkan nama restoran sendiri dan verifikasi apakah cocok.

**Langkah 1 — Periksa apakah ID restoran ada**

`POST /appointments/zenchef-restaurants/check`

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_id` | Ya | ID restoran Zenchef yang akan diperiksa. |

```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" }'
```

**Respons** (`200 OK`):

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

`exists: false` berarti tidak ada restoran Zenchef yang memiliki ID tersebut — tidak ada lagi yang perlu dilakukan. Dibatasi hingga 10 pemeriksaan per 5 menit per akun; melebihi batas akan mengembalikan `429`.

**Langkah 2 — Verifikasi nama restoran**

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

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_id` | Ya | ID restoran Zenchef dari langkah 1. |
| `user_input_name` | Ya | Nama yang diketikkan oleh pemilik akun — dibandingkan dengan nama asli restoran di Zenchef (tidak peka terhadap huruf besar/kecil/spasi). |

```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" }'
```

**Respons** (`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` berarti nama tidak cocok — `restaurantDetails` dihilangkan, minta pemilik akun untuk mencoba lagi. Dibatasi hingga 3 percobaan per 5 menit (lebih ketat daripada pemeriksaan keberadaan, karena ini adalah langkah pembuktian yang sebenarnya). `restaurant_id` yang tidak lagi dapat diselesaikan di Zenchef akan mengembalikan `404`.

**Langkah 3 — Simpan restoran**

`POST /appointments/zenchef-restaurants`

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_id` | Ya | 1–64 karakter, huruf/angka/garis bawah/tanda hubung. |
| `restaurant_name` | Ya | Nama restoran terverifikasi dari langkah 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" }'
```

**Respons** (`201 Created`):

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

**Perbarui restoran Zenchef yang disimpan**

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

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_name` | Tidak | Nama tampilan baru. |
| `is_active` | Tidak | Atur `false` untuk menghentikan bot agar tidak melakukan pemesanan pada restoran ini tanpa menghapusnya. |

```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 }'
```

**Respons** (`200 OK`): bentuk yang sama seperti respons simpan di atas.

**Hapus restoran 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"
```

**Respons** (`200 OK`): `{ "success": true, "data": { "restaurantId": "12345" } }`

`restaurantId` yang saat ini tidak ada di akun akan mengembalikan `404` saat diperbarui atau dihapus.

### Formitable

Formitable tidak memerlukan bukti nama dua langkah seperti Zenchef — ID restorannya sudah dicakup per bisnis, jadi satu panggilan verifikasi sudah cukup. Formitable juga memiliki pencarian detail yang digunakan untuk menyimpan URL situs web restoran selama penyiapan.

**Verifikasi ID restoran**

`POST /appointments/formitable-restaurants/verify`

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_id` | Ya | ID restoran Formitable. |
| `language` | Tidak | Tag bahasa untuk permintaan probe. Defaultnya adalah `"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" }'
```

**Respons** (`200 OK`):

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

`restaurant_id` yang tidak dikenali oleh Formitable akan mengembalikan `404`. Dibatasi hingga 10 percobaan per 5 menit per akun.

**Dapatkan detail restoran**

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

Mengambil profil publik restoran dari Formitable, termasuk situs webnya — digunakan untuk menyimpan URL situs web saat menyiapkan restoran. `language` adalah parameter kueri opsional, dengan default `"en"`.

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

**Respons** (`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"
  }
}
```

**Simpan restoran**

`POST /appointments/formitable-restaurants`

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_id` | Ya | 1–64 karakter, huruf/angka/garis bawah/tanda hubung. |
| `restaurant_name` | Ya | Nama tampilan. |
| `language` | Ya | Tag bahasa ISO, contoh `"en"` atau `"en-GB"`. |
| `website_url` | Tidak | Situs web restoran, dari pencarian detail di atas. Harus berupa `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"
  }'
```

**Respons** (`201 Created`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

**Perbarui restoran Formitable yang disimpan**

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

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `restaurant_name` | Tidak | Nama tampilan baru. |
| `language` | Tidak | Tag bahasa ISO baru. |
| `is_active` | Tidak | Atur `false` untuk menghentikan bot agar tidak melakukan pemesanan pada restoran ini tanpa menghapusnya. |
| `website_url` | Tidak | URL situs web baru. |

```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 }'
```

**Respons** (`200 OK`): bentuk yang sama seperti respons simpan di atas.

**Hapus restoran 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"
```

**Respons** (`200 OK`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

`restaurantId` yang saat ini tidak ada di akun akan mengembalikan `404` saat diperbarui atau dihapus.

> **Bentuk kesalahan pada semua endpoint Zenchef/Formitable:** tidak seperti bagian lain di halaman ini, kesalahan di sini membawa statusnya dua kali — sekali sebagai status HTTP dan sekali sebagai `error_code` di dalam isi — contohnya `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Tangani dengan cara yang sama seperti kesalahan lainnya: periksa `success`, baca `error` untuk pesannya.

---

## Kesalahan API Janji Temu

Endpoint janji temu mengembalikan amplop kesalahan standar:

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

| Status | Kapan ini terjadi pada endpoint janji temu |
|---|---|
| `400` | Bidang yang diperlukan tidak ada atau tidak valid — contohnya `start_time` yang buruk, `end_time` yang tidak setelah `start_time`, kombinasi filter yang tidak valid, tidak ada bidang untuk diperbarui, atau janji temu yang sudah dibatalkan. |
| `404` | Janji temu, kontak, atau jenis acara tidak ditemukan. |
| `409` | Slot waktu yang diminta sudah terisi (konflik pemesanan). |

Kode bersama yang dapat dikembalikan oleh setiap endpoint — `401`, `403` (paket Anda tidak menyertakan akses API), `429` (batas kecepatan) dan `500` — tercantum beserta panduan percobaan ulang di [Kesalahan & Penomoran Halaman](errors-and-pagination.md).

---

## Langkah berikutnya

- [Kontak](contacts.md) — buat dan cari kontak yang Anda buatkan pemesanannya.
- [Pesan & Percakapan](messages.md) — kirim konfirmasi atau pengingat kepada kontak.
- [Webhook](webhooks.md) — dapatkan pemberitahuan saat janji temu berubah.
