
# Spotkania

Interfejs Appointments API umożliwia rezerwowanie spotkań dla Twoich kontaktów w ramach typów wydarzeń, a następnie pobieranie, wyświetlanie, aktualizowanie, anulowanie lub usuwanie tych spotkań. Odpowiada również na pytanie, które pojawia się jako pierwsze w większości procesów rezerwacji — jakie terminy są faktycznie wolne — oraz obsługuje stronę kalendarza: wyświetlanie połączonych kalendarzy Google i importowanie wydarzeń, które już się w nich znajdują. Gdy połączenie z Kalendarzem Google jest aktywne, pasujące wydarzenie w kalendarzu jest tworzone i automatycznie synchronizowane w tle. Restauracje korzystające z systemów Zenchef lub Formitable do własnych rezerwacji również mogą zostać zweryfikowane i połączone w tym miejscu, dzięki czemu Agent AI rezerwuje prawdziwe stoliki zamiast wewnętrznych spotkań.

Wszystkie ścieżki na tej stronie są względne względem podstawowego adresu URL `https://api.youraiconnector.com/v1`. Każde żądanie wymaga Twojego klucza API — zobacz [Uwierzytelnianie](authentication.md), aby uzyskać pełną listę sposobów jego przesyłania. Poniższe przykłady wykorzystują nagłówek `X-API-Key`, a jeden z przykładów cURL pokazuje również formularz zapytania `?apiKey=`.

> **Wydarzenia a spotkania:** *Typ wydarzenia* to definicja dostępnego terminu (rodzaj spotkania, jego długość, sale). *Spotkanie* to jedna zarezerwowana instancja typu wydarzenia dla konkretnego kontaktu. Spotkanie rezerwuje się poprzez odwołanie do kontaktu oraz typu wydarzenia.

---

## Obiekt spotkania

Każdy punkt końcowy, który zwraca spotkanie, używa tego samego formatu:

| Pole | Opis |
|---|---|
| `id` | Unikalny identyfikator spotkania. |
| `contact_id` | Identyfikator kontaktu, dla którego zarezerwowano spotkanie. |
| `event_id` | Identyfikator typu wydarzenia, w ramach którego zarezerwowano spotkanie. |
| `status` | `Confirmed` lub `Canceled`. |
| `start_time` | Początek spotkania, format ISO 8601 w UTC. |
| `end_time` | Koniec spotkania, format ISO 8601 w UTC. |
| `created_at` | Czas utworzenia spotkania. |
| `last_modified_at` | Czas ostatniej zmiany spotkania. |
| `room_name` | Sala lub zasób, w którym zarezerwowano spotkanie, jeśli typ wydarzenia korzysta z sal. |
| `description` | Dowolny opis spotkania. |
| `summary` | Krótkie podsumowanie lub tytuł. |
| `cancelation_reason` | Powód podany podczas anulowania spotkania, jeśli istnieje. |
| `google_calendar_event_id` | Identyfikator powiązanego wydarzenia w Kalendarzu Google. Ustawiany po zakończeniu synchronizacji z kalendarzem; `null`, gdy żaden kalendarz nie jest połączony lub gdy synchronizacja jest w toku. |
| `calendar_synced` | `true` po powiązaniu spotkania z wydarzeniem w kalendarzu. |
| `imported` | `true`, gdy spotkanie zostało zaimportowane z zewnętrznego kalendarza zamiast bezpośredniej rezerwacji. |
| `is_recurring` | `true`, gdy spotkanie jest częścią serii cyklicznej. |
| `recurrence_frequency` | Częstotliwość powtarzania spotkania w przypadku serii cyklicznej. |
| `recurring_event_id` | Identyfikator serii cyklicznej, do której należy to spotkanie. |
| `recurring_interval` | Interwał między powtórzeniami w przypadku serii cyklicznej. |
| `recurring_sequence` | Pozycja tego spotkania w serii cyklicznej. |
| `end_after_x_occurrences` | Liczba wystąpień, po których kończy się seria cykliczna. |
| `booking_provider` | System źródłowy, z którego pochodzi rezerwacja, w przypadku rezerwacji przez połączonego dostawcę usług rezerwacyjnych. |

> **O synchronizacji kalendarza:** Bezpośrednio po zarezerwowaniu lub zmianie spotkania pole `google_calendar_event_id` może nadal mieć wartość `null`, a `calendar_synced` może być `false`, ponieważ synchronizacja odbywa się w tle chwilę później. Pobierz spotkanie ponownie po krótkim czasie, aby zobaczyć wypełnione pola kalendarza.

---

## Znajdź dostępne terminy

`GET /appointments/available-slots`

Zwraca terminy, które są faktycznie wolne dla danego typu wydarzenia pomiędzy dwoma punktami w czasie. Jest to zazwyczaj **pierwsze** wywołanie w procesie rezerwacji: wyświetl te terminy, pozwól użytkownikowi wybrać jeden z nich, a następnie wyślij wybrany czas do [Zarezerwuj spotkanie](#book-an-appointment).

Odpowiedź uwzględnia już godziny otwarcia i długość slotu danego typu wydarzenia, jego sale, spotkania, które już zostały zarezerwowane, oraz wszystko, co jest zablokowane w połączonych Kalendarzach Google — więc każdy zwrócony termin jest terminem, który możesz zarezerwować.

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `event_id` | Tak | Typ wydarzenia do sprawdzenia. Musi należeć do Twojego konta. |
| `start_time` | Tak | Początek okna czasowego, dla którego chcesz sprawdzić dostępność, w formacie daty i godziny ISO 8601. |
| `end_time` | Tak | Koniec okna czasowego w formacie daty i godziny ISO 8601. Cały dzień końcowy jest uwzględniony. |

Wyniki są pogrupowane według dni — a w przypadku, gdy typ wydarzenia korzysta z sal, jedna grupa na salę na dzień:

| Pole | Opis |
|---|---|
| `date` | Dzień, którego dotyczy grupa, zapisany jako `DD/MM/YYYY`. |
| `day` | Nazwa dnia tygodnia małymi literami, na przykład `monday`. |
| `room_name` | Sala lub zasób, do którego należy ta grupa, jeśli typ wydarzenia korzysta z sal. |
| `available_slots` | Dostępne bloki rezerwacyjne w tym dniu, od najwcześniejszego. |

Każdy wpis w `available_slots` zawiera:

| Pole | Opis |
|---|---|
| `start_time` | Początek bloku jako `HH:mm`. |
| `end_time` | Koniec bloku jako `HH:mm`. |
| `available` | `true` — zwracany jest tylko wolny czas. |
| `spots_left` | Ile rezerwacji jeszcze mieści się w tym bloku. Obecne tylko w typach wydarzeń, które przyjmują więcej niż jedną rezerwację na slot. |

> **Czasy są lokalne dla typu wydarzenia, a nie w UTC.** `date`, `start_time` oraz `end_time` to wartości zegarowe w strefie czasowej typu wydarzenia (jego nadpisaniu lub strefie czasowej Twojego konta, jeśli nie określono inaczej). [Zarezerwuj spotkanie](#book-an-appointment) oczekuje momentu w formacie ISO 8601 UTC, więc przekonwertuj wybrany termin przed jego wysłaniem.

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

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

Dzień, w którym nie ma wolnych terminów, po prostu się nie pojawia. Brak `event_id`, `start_time` lub `end_time` zwraca `400`; typ wydarzenia, którego nie ma na Twoim koncie, zwraca `404`.

---

## Zarezerwuj spotkanie

`POST /appointments`

Rezerwuje nowe spotkanie dla kontaktu w ramach jednego z Twoich typów wydarzeń. Czas zakończenia jest obliczany automatycznie na podstawie czasu trwania terminu typu wydarzenia.

Rezerwacja jest sprawdzana pod kątem konfliktów: jeśli żądany termin pokrywa się z istniejącym potwierdzonym spotkaniem w ramach tego samego typu wydarzenia, żądanie kończy się niepowodzeniem z błędem `409` i nic nie zostaje utworzone.

| Pole | Wymagane | Opis |
|---|---|---|
| `contact_id` | Tak | Identyfikator kontaktu, dla którego dokonujemy rezerwacji. Musi należeć do Twojego konta. |
| `event_id` | Tak | Identyfikator typu wydarzenia, w ramach którego dokonujemy rezerwacji. Musi należeć do Twojego konta. |
| `start_time` | Tak | Żądany czas rozpoczęcia jako data i godzina w formacie ISO 8601. |
| `room_name` | Nie | Nazwa sali lub zasobu, jeśli typ wydarzenia korzysta z sal. |

**cURL** (używając formularza zapytania `?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"])
```

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

---

## Uzyskaj spotkanie

`GET /appointments/{appointmentId}`

Zwraca pojedyncze spotkanie na podstawie jego identyfikatora, w tym jego stan synchronizacji z kalendarzem.

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

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

---

## Wyświetl listę spotkań

`GET /appointments`

Wyświetla listę spotkań dla Twojego konta, od najnowszego, z wykorzystaniem stronicowania opartego na kursorze.

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `contact_id` | Nie | Zwraca tylko spotkania dla tego kontaktu. Listy filtrowane według kontaktu zawierają **tylko potwierdzone spotkania** |
| `date` | Nie | Zwraca tylko spotkania z tego dnia kalendarzowego (`YYYY-MM-DD`). **Wymaga `contact_id`.** |
| `status` | Nie | Filtruj według `Confirmed` lub `Canceled`. Dostępne tylko **bez** `contact_id`. |
| `limit` | Nie | Rozmiar strony, liczba całkowita od 1 do 100. Domyślnie `50`. |
| `cursor` | Nie | Wartość `next_cursor` z poprzedniej odpowiedzi. |

Kilka zasad, o których warto pamiętać:

- **Bez filtrów** otrzymasz każde spotkanie na koncie, strona po stronie.
- **Według kontaktu** — ustaw `contact_id`, aby zobaczyć potwierdzone spotkania danego kontaktu. Możesz zawęzić to do jednego dnia, przekazując również `date`.
- **Według statusu** — ustaw `status` (bez `contact_id`), aby wyświetlić tylko spotkania `Confirmed` lub tylko `Canceled` na całym koncie.
- Filtr `date` bez `contact_id` lub `status=Canceled` wraz z `contact_id` zwraca `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"])
```

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

Aby przeglądać wyniki, przekaż `next_cursor` z jednej odpowiedzi jako `cursor` w następnym żądaniu. Kontynuuj, aż `next_cursor` będzie `null`. Zobacz [Błędy i stronicowanie](errors-and-pagination.md), aby poznać wspólny wzorzec stronicowania.

---

## Zaktualizuj spotkanie

`PUT /appointments/{appointmentId}`

Zmień termin spotkania lub edytuj jego szczegóły. Wyślij tylko te pola, które chcesz zmienić — wymagane jest co najmniej jedno. Połączony czas rozpoczęcia i zakończenia musi zachowywać porządek chronologiczny (`end_time` musi być po `start_time`). Zmiany są automatycznie synchronizowane z powiązanym wydarzeniem w kalendarzu.

| Pole | Opis |
|---|---|
| `start_time` | Nowy początek, data i godzina w formacie ISO 8601. |
| `end_time` | Nowy koniec, data i godzina w formacie ISO 8601. Musi przypadać po czasie rozpoczęcia. |
| `room_name` | Nowa nazwa pokoju lub zasobu. |
| `description` | Nowy opis lub `null`, aby go wyczyścić. |
| `summary` | Nowe podsumowanie lub `null`, aby je wyczyścić. |

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

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

---

## Anulowanie spotkania

`POST /appointments/{appointmentId}/cancel`

Anuluje potwierdzone spotkanie, opcjonalnie rejestrując powód. Spotkanie pozostaje na Twoim koncie ze statusem `Canceled`, a powiązane wydarzenie w kalendarzu jest automatycznie usuwane w tle. Anulowanie już anulowanego spotkania zwraca `400`.

| Pole | Wymagane | Opis |
|---|---|---|
| `cancellation_reason` | Nie | Powód anulowania, zapisywany w spotkaniu. |

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

**Odpowiedź** (`200 OK`):

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

---

## Usuwanie spotkania

`DELETE /appointments/{appointmentId}`

Trwale usuwa spotkanie i jego odniesienia. Jeśli chcesz tylko odwołać rezerwację, zachowując rekord, użyj zamiast tego [anuluj](#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"])
```

**Odpowiedź** (`200 OK`):

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

---

## Wyświetl swoje połączone Kalendarze Google

`GET /appointments/google-calendars`

Zwraca kalendarze Google dostępne na tym koncie, bezpośrednio z Google — przydatne do pokazania właścicielowi konta selektora kalendarza, z którego ma importować dane, lub po prostu do potwierdzenia, że połączenie jest aktywne.

Działa to tylko wtedy, gdy konto ma połączony Kalendarz Google (Ustawienia → Integracje) z co najmniej dostępem do odczytu. Jeśli tak nie jest lub przyznany dostęp nie obejmuje już zakresu odczytu kalendarza, otrzymasz `400` z informacją o konieczności (ponownego) połączenia go.

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

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

Każdy wpis ma własny format [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) Google, więc nazwy pól są zgodne z `camelCase` Google, a nie ze standardowym `snake_case` tego API — są to dane Google przekazane w niezmienionej formie, a nie nasze. Brakujące lub cofnięte połączenie zwraca `400` z błędem wyjaśniającym, że Kalendarz Google wymaga (ponownego) połączenia.

---

## Importuj wydarzenia z Kalendarza Google

`POST /appointments/import-calendar-events`

Pobiera wydarzenia znajdujące się już w połączonym(-ych) Kalendarzu(-ach) Google kampanii lub Agenta AI i zamienia je w spotkania — przydatne przy pierwszym łączeniu kalendarza, który ma już istniejące rezerwacje. Może to chwilę potrwać (każde wydarzenie przechodzi przez proces ekstrakcji, aby ustalić, dla kogo jest przeznaczone), więc nigdy nie działa w trybie inline: żądanie dodaje zadanie do kolejki w tle i zwraca `job_id` do odpytywania.

| Pole | Wymagane | Opis |
|---|---|---|
| `campaign_id` | Jedno z tych dwóch | Kampania, z której połączonych kalendarzy importować dane. |
| `agent_id` | Jedno z tych dwóch | Agent AI, z którego połączonych kalendarzy importować dane. |
| `identifier` | Tak | `"EMAIL"` lub `"PHONE_NUMBER"` — który element danych kontaktowych wyodrębnić z każdego wydarzenia w kalendarzu, aby dopasować lub utworzyć kontakt, do którego ono należy. |

Wyślij dokładnie jedno z `campaign_id` / `agent_id`, nigdy oba i nigdy żadnego — każda inna kombinacja zwraca `400`. To, które wyślesz, musi należeć do Twojego konta, w przeciwnym razie otrzymasz `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"])
```

**Odpowiedź** (`202 Accepted`):

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

`campaign_id` i `agent_id` zwracają to, które wysłałeś; drugie jest zawsze `null`.

### Odpytywanie zadania importu

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

**Odpowiedź** (`200 OK`):

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

| `status` | Znaczenie |
|---|---|
| `queued` | Jeszcze nieodebrane. Kontynuuj odpytywanie. |
| `processing` | Import jest w toku. Kontynuuj odpytywanie. |
| `completed` | Gotowe — `message` zawiera krótkie, czytelne podsumowanie. |
| `failed` | Coś poszło nie tak — `error` zawiera przyczynę. |

`GET` dla `jobId`, który nie istnieje (lub należy do innego konta), zwraca `404`.

---

## Integracje z systemami rezerwacji restauracji (Zenchef / Formitable)

Zenchef i Formitable to systemy rezerwacji restauracji, przez które Twój Agent AI może rezerwować prawdziwe stoliki. Każdy z nich posiada **publiczny, nieuwierzytelniony widżet rezerwacji** (`https://api.youraiconnector.com/v1/zenchef-widget/...` i `https://api.youraiconnector.com/v1/formitable-widget/...`), który wyświetla się w czacie dla klienta — te ścieżki widżetów to zwykłe strony HTML przeznaczone do otwierania w przeglądarce, a nie punkty końcowe API JSON, więc nie są tutaj dokumentowane. Poniżej znajdują się punkty końcowe zarządzania kontem: weryfikacja, czy identyfikator restauracji należy do właściciela konta, a następnie dodawanie, aktualizowanie lub usuwanie go.

### Zenchef

Podłączenie restauracji Zenchef to dwuetapowa weryfikacja, dzięki której właściciel konta potwierdza, że faktycznie prowadzi restaurację, zanim zostanie ona połączona z botem: najpierw sprawdź, czy identyfikator istnieje (bez ujawniania nazwy), a następnie poproś o samodzielne wpisanie nazwy restauracji i sprawdź, czy jest ona zgodna.

**Krok 1 — Sprawdź, czy identyfikator restauracji istnieje**

`POST /appointments/zenchef-restaurants/check`

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_id` | Tak | Identyfikator restauracji Zenchef do sprawdzenia. |

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

**Odpowiedź** (`200 OK`):

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

`exists: false` oznacza, że żadna restauracja Zenchef nie posiada tego identyfikatora — nie ma nic więcej do zrobienia. Limit wynosi 10 sprawdzeń na 5 minut na konto; przekroczenie tego limitu zwraca `429`.

**Krok 2 — Zweryfikuj nazwę restauracji**

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

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_id` | Tak | Identyfikator restauracji Zenchef z kroku 1. |
| `user_input_name` | Tak | Nazwa wpisana przez właściciela konta — porównywana z rzeczywistą nazwą restauracji w Zenchef (wielkość liter i białe znaki są ignorowane). |

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

**Odpowiedź** (`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` oznacza, że nazwa nie pasuje — `restaurantDetails` jest pomijane, poproś właściciela konta o ponowną próbę. Limit wynosi 3 próby na 5 minut (bardziej rygorystyczny niż sprawdzenie istnienia, ponieważ jest to właściwy krok weryfikacyjny). `restaurant_id`, który nie jest już rozpoznawany w Zenchef, zwraca `404`.

**Krok 3 — Zapisz restaurację**

`POST /appointments/zenchef-restaurants`

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_id` | Tak | 1–64 znaki, litery/cyfry/podkreślnik/myślnik. |
| `restaurant_name` | Tak | Zweryfikowana nazwa restauracji z kroku 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" }'
```

**Odpowiedź** (`201 Created`):

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

**Aktualizacja zapisanej restauracji Zenchef**

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

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_name` | Nie | Nowa nazwa wyświetlana. |
| `is_active` | Nie | Ustaw `false`, aby powstrzymać bota przed dokonywaniem rezerwacji w tej restauracji bez jej usuwania. |

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

**Odpowiedź** (`200 OK`): ten sam format co odpowiedź zapisu powyżej.

**Usuń restaurację 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"
```

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

`restaurantId`, którego obecnie nie ma na koncie, zwraca `404` przy aktualizacji lub usunięciu.

### Formitable

Formitable nie wymaga dwuetapowego potwierdzenia nazwy, jak Zenchef — jego identyfikatory restauracji są już przypisane do konkretnej firmy, więc wystarczy jedno wywołanie weryfikacyjne. Posiada również funkcję wyszukiwania szczegółów, używaną do buforowania adresu URL strony internetowej restauracji podczas konfiguracji.

**Zweryfikuj identyfikator restauracji**

`POST /appointments/formitable-restaurants/verify`

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_id` | Tak | Identyfikator restauracji Formitable. |
| `language` | Nie | Znacznik języka dla żądania sondowania. Domyślnie `"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" }'
```

**Odpowiedź** (`200 OK`):

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

`restaurant_id`, którego Formitable nie rozpoznaje, zwraca `404`. Limit prędkości wynosi 10 prób na 5 minut na konto.

**Pobierz szczegóły restauracji**

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

Pobiera publiczny profil restauracji z Formitable, w tym jej stronę internetową — używane do buforowania adresu URL strony podczas konfigurowania restauracji. `language` jest opcjonalnym parametrem zapytania, domyślnie ustawionym na `"en"`.

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

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

**Zapisz restaurację**

`POST /appointments/formitable-restaurants`

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_id` | Tak | 1–64 znaki, litery/cyfry/podkreślnik/myślnik. |
| `restaurant_name` | Tak | Nazwa wyświetlana. |
| `language` | Tak | Znacznik języka ISO, np. `"en"` lub `"en-GB"`. |
| `website_url` | Nie | Strona internetowa restauracji, z powyższego wyszukiwania szczegółów. Musi być `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"
  }'
```

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

**Zaktualizuj zapisaną restaurację Formitable**

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

| Pole | Wymagane | Opis |
|---|---|---|
| `restaurant_name` | Nie | Nowa nazwa wyświetlana. |
| `language` | Nie | Nowy znacznik języka ISO. |
| `is_active` | Nie | Ustaw `false`, aby powstrzymać bota przed dokonywaniem rezerwacji w tej restauracji bez jej usuwania. |
| `website_url` | Nie | Nowy adres URL strony internetowej. |

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

**Odpowiedź** (`200 OK`): ten sam format co odpowiedź zapisu powyżej.

**Usuń restaurację 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"
```

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

`restaurantId`, którego obecnie nie ma na koncie, zwraca `404` przy aktualizacji lub usunięciu.

> **Format błędu we wszystkich punktach końcowych Zenchef/Formitable:** w przeciwieństwie do reszty tej strony, błędy tutaj zawierają swój status dwukrotnie — raz jako status HTTP, a raz jako `error_code` w treści — na przykład `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Obsługuj go w taki sam sposób jak każdy inny błąd: sprawdź `success`, odczytaj `error`, aby uzyskać komunikat.

---

## Błędy API wizyt

Punkty końcowe wizyt zwracają standardową kopertę błędu:

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

| Status | Kiedy występuje w punkcie końcowym wizyt |
|---|---|
| `400` | Brakuje wymaganego pola lub jest ono nieprawidłowe — na przykład błędny `start_time`, `end_time` niebędący po `start_time`, nieprawidłowa kombinacja filtrów, brak pól do aktualizacji lub wizyta, która została już anulowana. |
| `404` | Nie znaleziono wizyty, kontaktu lub typu wydarzenia. |
| `409` | Żądany przedział czasowy jest już zajęty (konflikt rezerwacji). |

Wspólne kody, które może zwrócić każdy punkt końcowy — `401`, `403` (Twój plan nie obejmuje dostępu do API), `429` (limit szybkości) oraz `500` — zostały wymienione wraz ze wskazówkami dotyczącymi ponawiania prób w sekcji [Błędy i stronicowanie](errors-and-pagination.md).

---

## Następne kroki

- [Kontakty](contacts.md) — twórz i wyszukuj kontakty, dla których dokonujesz rezerwacji.
- [Wiadomości i konwersacje](messages.md) — wysyłaj do kontaktu potwierdzenia lub przypomnienia.
- [Webhooki](webhooks.md) — otrzymuj powiadomienia o zmianach w spotkaniach.
