
# Tapaamiset

Appointments API:n avulla voit varata aikoja yhteyshenkilöillesi tapahtumatyypeillesi sekä hakea, listata, päivittää, peruuttaa tai poistaa niitä. Se vastaa myös useimmissa varausprosesseissa ensimmäisenä esiin nousevaan kysymykseen – mitkä ajat ovat todella vapaita – ja kattaa kalenteripuolen: yhdistettyjen Google-kalenterien listaamisen ja niissä jo olevien tapahtumien tuomisen. Kun Google-kalenteriyhteys on aktiivinen, vastaava kalenteritapahtuma luodaan ja pidetään automaattisesti synkronoituna taustalla. Ravintolat, jotka käyttävät Zenchefiä, Formitablea, OpenTablea tai TheForkia omana varausjärjestelmänään, voidaan myös vahvistaa ja yhdistää täällä, jolloin tekoälyagentti varaa oikeita pöytiä sisäisten tapaamisten sijaan – ja tapaamispohjaiset yritykset, jotka hallinnoivat aikataulujaan Trafftissa, voivat yhdistää ne samalla tavalla.

Kaikki tämän sivun polut ovat suhteessa perus-URL-osoitteeseen `https://api.dmchamp.com/v1`. Jokainen pyyntö vaatii API-avaimesi – katso [todennus](authentication.md) nähdäksesi täydellisen listan tavoista lähettää se. Alla olevissa esimerkeissä käytetään `X-API-Key`-otsikkoa, ja yhdessä cURL-esimerkissä näytetään myös `?apiKey=`-kyselylomake.

> **Tapahtumat vs. tapaamiset:** *Tapahtumatyyppi* on varattavissa olevan ajan määrittely (tapaamisen tyyppi, kesto, huoneet). *Tapaaminen* on yksi varattu ilmentymä tapahtumatyypistä tietylle yhteyshenkilölle. Varaat tapaamisen viittaamalla yhteyshenkilöön ja tapahtumatyyppiin.

---

## Tapaamisobjekti

Jokainen päätepiste, joka palauttaa tapaamisen, käyttää samaa rakennetta:

| Kenttä | Kuvaus |
|---|---|
| `id` | Varauksen yksilöllinen tunniste. |
| `contact_id` | Sen yhteyshenkilön tunniste, jolle varaus on tehty. |
| `event_id` | Sen tapahtumatyypin tunniste, jolle varaus on tehty. |
| `status` | `Confirmed` tai `Canceled`. |
| `start_time` | Varauksen alkamisaika, ISO 8601 UTC-muodossa. |
| `end_time` | Varauksen päättymisaika, ISO 8601 UTC-muodossa. |
| `created_at` | Aika, jolloin varaus luotiin. |
| `last_modified_at` | Aika, jolloin varaus viimeksi muuttui. |
| `room_name` | Huone tai resurssi, johon varaus on tehty, kun tapahtumatyyppi käyttää huoneita. |
| `description` | Varauksen vapaamuotoinen kuvaus. |
| `summary` | Lyhyt yhteenveto tai otsikko. |
| `cancelation_reason` | Varauksen peruuttamisen syy, jos sellainen on annettu. |
| `google_calendar_event_id` | Linkitetyn Google-kalenteritapahtuman tunniste. Asetetaan, kun kalenterisynkronointi on valmis; `null`, kun kalenteria ei ole yhdistetty tai synkronointi on vielä kesken. |
| `calendar_synced` | `true`, kun varaus on linkitetty kalenteritapahtumaan. |
| `imported` | `true`, kun varaus on tuotu ulkoisesta kalenterista sen sijaan, että se olisi varattu suoraan. |
| `is_recurring` | `true`, kun varaus on osa toistuvaa sarjaa. |
| `recurrence_frequency` | Kuinka usein varaus toistuu, kun se on toistuva. |
| `recurring_event_id` | Sen toistuvan sarjan tunniste, johon tämä varaus kuuluu. |
| `recurring_interval` | Toistojen välinen aikaväli, kun varaus on toistuva. |
| `recurring_sequence` | Tämän varauksen sijainti toistuvassa sarjassa. |
| `end_after_x_occurrences` | Niiden esiintymien määrä, joiden jälkeen toistuva sarja päättyy. |
| `booking_provider` | Lähdejärjestelmä, josta varaus on peräisin, kun se on tehty yhdistetyn varauspalveluntarjoajan kautta. |

> **Tietoja kalenterisynkronoinnista:** Heti tapaamisen varaamisen tai muuttamisen jälkeen `google_calendar_event_id` voi yhä olla `null` ja `calendar_synced` voi olla `false`, koska synkronointi suoritetaan taustalla hetkeä myöhemmin. Hae tapaaminen uudelleen hetken kuluttua nähdäksesi täytetyt kalenterikentät.

---

## Etsi vapaita aikoja

`GET /appointments/available-slots`

Palauttaa ajat, jotka ovat todellisuudessa vapaita tietylle tapahtumatyypille kahden ajankohdan välillä. Tämä on yleensä **ensimmäinen** kutsu varausprosessissa: näytä nämä ajat, anna henkilön valita yksi ja lähetä sitten valittu aika osoitteeseen [Varaa aika](#book-an-appointment).

Vastaus ottaa jo huomioon tapahtumatyypin omat aukioloajat ja varauksen keston, sen huoneet, jo varaamasi ajat sekä kaiken yhdistetyissä Google-kalentereissa estetyn – joten täältä palautuva aika on sellainen, jonka voit varata.

| Kyselyparametri | Pakollinen | Kuvaus |
|---|---|---|
| `event_id` | Kyllä | Tarkistettava tapahtumatyyppi. Täytyy kuulua tilillesi. |
| `start_time` | Kyllä | Sen ajanjakson alku, jolle haluat aikoja, ISO 8601 -päivämäärä ja -aika. |
| `end_time` | Kyllä | Ajanjakson loppu, ISO 8601 -päivämäärä ja -aika. Koko loppupäivä sisältyy. |

Tulokset palautetaan päivittäin ryhmiteltyinä – ja kun tapahtumatyyppi käyttää huoneita, yksi ryhmä per huone per päivä:

| Kenttä | Kuvaus |
|---|---|
| `date` | Päivä, jota ryhmä koskee, muodossa `DD/MM/YYYY`. |
| `day` | Viikonpäivän nimi pienillä kirjaimilla, esimerkiksi `monday`. |
| `room_name` | Huone tai resurssi, johon tämä ryhmä kuuluu, kun tapahtumatyyppi käyttää huoneita. |
| `available_slots` | Varattavissa olevat lohkot kyseisenä päivänä, aikaisin ensin. |

Jokaisella `available_slots`-kohdan merkinnällä on:

| Kenttä | Kuvaus |
|---|---|
| `start_time` | Lohkon alku muodossa `HH:mm`. |
| `end_time` | Lohkon loppu muodossa `HH:mm`. |
| `available` | `true` — vain vapaa aika palautetaan. |
| `spots_left` | Kuinka monta varausta tähän lohkoon vielä mahtuu. Näkyy vain tapahtumatyypeissä, jotka sallivat useamman kuin yhden varauksen per lohko. |

> **Ajat ovat tapahtumatyypin paikallista aikaa, eivät UTC-aikaa.** `date`, `start_time` ja `end_time` ovat kellonaikoja tapahtumatyypin omassa aikavyöhykkeessä (sen ohitusasetus tai tilisi aikavyöhyke, jos ohitusta ei ole). [Varaa aika](#book-an-appointment) odottaa ISO 8601 UTC -hetkeä, joten muunna valitsemasi aika ennen sen lähettämistä.

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

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

Päivä, jolloin ei ole mitään vapaata, ei yksinkertaisesti näy. Puuttuva `event_id`, `start_time` tai `end_time` palauttaa `400`; tapahtumatyyppi, joka ei kuulu tilillesi, palauttaa `404`.

---

## Varaa tapaaminen

`POST /appointments`

Varaa uuden tapaamisen yhteyshenkilölle jollekin tapahtumatyypillesi. Päättymisaika lasketaan automaattisesti tapahtumatyypin keston perusteella.

Varaukselle tehdään ristiriitatarkistus: jos pyydetty aika menee päällekkäin olemassa olevan vahvistetun tapaamisen kanssa samalla tapahtumatyypillä, pyyntö epäonnistuu virheellä `409` eikä mitään luoda.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `contact_id` | Kyllä | Sen yhteyshenkilön tunniste, jolle varaus tehdään. Täytyy kuulua tilillesi. |
| `event_id` | Kyllä | Sen tapahtumatyypin tunniste, jolle varaus tehdään. Täytyy kuulua tilillesi. |
| `start_time` | Kyllä | Haluttu alkamisaika ISO 8601 -päivämäärämuodossa. |
| `room_name` | Ei | Huoneen tai resurssin nimi, kun tapahtumatyyppi käyttää huoneita. |

**cURL** (käyttäen `?apiKey=`-kyselylomaketta)

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

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

---

## Hae tapaaminen

`GET /appointments/{appointmentId}`

Palauttaa yksittäisen tapaamisen sen tunnisteen (ID) perusteella, mukaan lukien sen kalenterin synkronointitila.

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

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

---

## Listaa tapaamiset

`GET /appointments`

Listaa tilisi tapaamiset uusimmasta alkaen käyttäen kursoripohjaista sivutusta.

| Kyselyparametri | Pakollinen | Kuvaus |
|---|---|---|
| `contact_id` | Ei | Palauta vain tämän yhteyshenkilön tapaamiset. Yhteyshenkilösuodatetut listaukset sisältävät **vain vahvistetut tapaamiset** |
| `date` | Ei | Palauta vain tämän kalenteripäivän tapaamiset (`YYYY-MM-DD`). **Vaatii `contact_id`.** |
| `status` | Ei | Suodata `Confirmed` tai `Canceled` perusteella. Käytettävissä vain **ilman** `contact_id` parametria. |
| `limit` | Ei | Sivun koko, kokonaisluku välillä 1–100. Oletus `50`. |
| `cursor` | Ei | `next_cursor`-arvo edellisestä vastauksesta. |

Muista muutamat säännöt:

- **Ilman suodattimia** saat kaikki tilin tapaamiset sivu kerrallaan.
- **Yhteyshenkilön mukaan** — aseta `contact_id` nähdäksesi yhden yhteyshenkilön vahvistetut tapaamiset. Voit rajata tämän yksittäiseen päivään välittämällä myös `date`.
- **Tilan mukaan** — aseta `status` (ilman `contact_id`) listataksesi vain `Confirmed` tai vain `Canceled` tapaamiset koko tililtä.
- `date`-suodatin ilman `contact_id`, tai `status=Canceled` yhdessä `contact_id` kanssa, palauttaa `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"])
```

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

Voit selata tuloksia välittämällä edellisen vastauksen `next_cursor` seuraavan pyynnön `cursor`-parametrina. Jatka, kunnes `next_cursor` on `null`. Katso [Virheet ja sivutus](errors-and-pagination.md) yleisestä sivutusmallista.

---

## Päivitä tapaaminen

`PUT /appointments/{appointmentId}`

Muuta tapaamisen ajankohtaa tai sen tietoja. Lähetä vain ne kentät, joita haluat muuttaa — vähintään yksi on pakollinen. Yhdistettyjen alku- ja loppuajan on pysyttävä kronologisessa järjestyksessä (`end_time` on oltava `start_time` jälkeen). Muutokset synkronoidaan automaattisesti linkitettyyn kalenteritapahtumaan.

| Kenttä | Kuvaus |
|---|---|
| `start_time` | Uusi alkamisaika, ISO 8601 -päivämäärä ja -aika. |
| `end_time` | Uusi päättymisaika, ISO 8601 -päivämäärä ja -aika. On oltava alkamisajan jälkeen. |
| `room_name` | Uusi huoneen tai resurssin nimi. |
| `description` | Uusi kuvaus tai `null` sen tyhjentämiseksi. |
| `summary` | Uusi yhteenveto tai `null` sen tyhjentämiseksi. |

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

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

---

## Peruuta ajanvaraus

`POST /appointments/{appointmentId}/cancel`

Peruuttaa vahvistetun ajanvarauksen ja tallentaa valinnaisesti syyn. Ajanvaraus säilyy tililläsi tilassa `Canceled`, ja siihen linkitetty kalenteritapahtuma poistetaan automaattisesti taustalla. Jo peruutetun ajanvarauksen peruuttaminen palauttaa virheen `400`.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `cancellation_reason` | Ei | Peruutuksen syy, joka tallennetaan ajanvaraukseen. |

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

**Vastaus** (`200 OK`):

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

---

## Poista ajanvaraus

`DELETE /appointments/{appointmentId}`

Poistaa ajanvarauksen ja sen viitteet pysyvästi. Jos haluat vain perua varauksen säilyttäen tietueen, käytä sen sijaan [peruutusta](#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"])
```

**Vastaus** (`200 OK`):

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

---

## Listaa yhdistetyt Google-kalenterisi

`GET /appointments/google-calendars`

Palauttaa tälle tilille saatavilla olevat Google-kalenterit suoraan Googlesta – hyödyllinen tilin haltijalle näytettävässä valitsimessa, josta valitaan, mistä kalenterista tuodaan tietoja, tai vain yhteyden toimivuuden vahvistamiseen.

Tämä toimii vasta, kun tili on yhdistänyt Google-kalenterin (Asetukset → Integraatiot) vähintään lukuoikeudella. Jos näin ei ole, tai myönnetty käyttöoikeus ei enää sisällä kalenterin lukuoikeutta, saat `400`-vastauksen, joka kehottaa yhdistämään sen (uudelleen).

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

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

Jokainen merkintä on Googlen oma [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList)-muoto, joten kenttien nimet noudattavat Googlen `camelCase`-määrityksiä, eivät tämän rajapinnan tavallisia `snake_case`-nimiä — kyseessä on Googlen data sellaisenaan, ei meidän. Puuttuva tai peruutettu yhteys palauttaa `400`-vastauksen, jossa selitetään, että Google-kalenteri on yhdistettävä (uudelleen).

---

## Tapahtumien tuominen Google-kalenterista

`POST /appointments/import-calendar-events`

Hakee kampanjan tai tekoälyagentin yhdistetyssä Google-kalenterissa olevat tapahtumat ja muuttaa ne tapaamisiksi — hyödyllinen, kun yhdistät ensimmäistä kertaa kalenterin, jossa on jo varauksia. Tämä voi viedä aikaa (jokainen tapahtuma käy läpi erotteluprosessin sen selvittämiseksi, kenelle se kuuluu), joten se ei koskaan toimi suoraan pyynnön yhteydessä: pyyntö asettaa taustatyön jonoon ja palauttaa `job_id`-tunnisteen, jota voit kysellä.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `campaign_id` | Toinen näistä kahdesta | Kampanja, jonka yhdistetystä kalenterista tuonti tehdään. |
| `agent_id` | Toinen näistä kahdesta | Tekoälyagentti, jonka yhdistetystä kalenterista tuonti tehdään. |
| `identifier` | Kyllä | `"EMAIL"` tai `"PHONE_NUMBER"` — mikä yhteystieto kunkin kalenteritapahtuman tiedoista poimitaan, jotta se voidaan yhdistää olemassa olevaan yhteystietoon tai luoda uusi. |

Lähetä täsmälleen joko `campaign_id` tai `agent_id`, ei koskaan molempia eikä kumpaakaan — mikä tahansa muu yhdistelmä palauttaa `400`-vastauksen. Lähettämäsi kohteen on kuuluttava tiliisi, muuten saat `404`-vastauksen.

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

**Vastaus** (`202 Accepted`):

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

`campaign_id` ja `agent_id` palauttavat sen, minkä lähetit; toinen on aina `null`.

### Tuontityön kysely

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

**Vastaus** (`200 OK`):

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

| `status` | Merkitys |
|---|---|
| `queued` | Ei vielä aloitettu. Jatka kyselyä. |
| `processing` | Tuonti on käynnissä. Jatka kyselyä. |
| `completed` | Valmis — `message` sisältää lyhyen ihmisluettavan yhteenvedon. |
| `failed` | Jotain meni pieleen — `error` sisältää syyn. |

`GET`-pyyntö `jobId`-tunnisteelle, jota ei ole olemassa (tai joka kuuluu eri tilille), palauttaa `404`-vastauksen.

---

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

## Ulkoiset varausintegraatiot (Zenchef / Formitable / OpenTable / TheFork / Trafft)

Zenchef ja Formitable ovat ravintoloiden varausjärjestelmiä, joiden kautta tekoälyagenttisi voi varata oikeita pöytiä; [Trafft](#trafft) on tapaamispohjaisten yritysten aikataulutusalusta, joka yhdistetään kerran tiliä kohden, ei ravintolaa kohden. Molemmilla ravintola-alustoilla on **julkinen, todennusta vaatimaton varauswidget** (`https://api.dmchamp.com/v1/zenchef-widget/...` ja `https://api.dmchamp.com/v1/formitable-widget/...`), joka näkyy ruokailijalle chatissa – kyseiset widget-reitit ovat tavallisia HTML-sivuja, jotka on tarkoitettu avattavaksi selaimessa, eivät JSON API -päätepisteitä, joten niitä ei ole dokumentoitu tässä. Seuraavassa esitellään tilinhallinnan päätepisteet: ravintolatunnuksen vahvistaminen tilinhaltijalle kuuluvaksi sekä sen lisääminen, päivittäminen tai poistaminen.

### Zenchef

Zenchef-ravintolan yhdistäminen on kaksivaiheinen vahvistusprosessi, jolla tilinhaltija todistaa hallinnoivansa ravintolaa ennen kuin se yhdistetään bottiin: tarkista ensin, että tunnus on olemassa (paljastamatta nimeä), ja pyydä sitten käyttäjää kirjoittamaan ravintolan nimi itse ja varmista, että se täsmää.

**Vaihe 1 — Tarkista, että ravintolan tunnus on olemassa**

`POST /appointments/zenchef-restaurants/check`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_id` | Kyllä | Tarkistettava Zenchef-ravintolan tunnus. |

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

**Vastaus** (`200 OK`):

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

`exists: false` tarkoittaa, ettei kyseisellä tunnuksella ole Zenchef-ravintolaa — muuta ei tarvitse tehdä. Nopeusrajoitus on 10 tarkistusta 5 minuutissa tiliä kohden; rajan ylittäminen palauttaa `429`.

**Vaihe 2 — Vahvista ravintolan nimi**

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

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_id` | Kyllä | Vaiheesta 1 saatu Zenchef-ravintolan tunnus. |
| `user_input_name` | Kyllä | Tilinhaltijan kirjoittama nimi — verrataan Zenchefin ravintolan todelliseen nimeen (ei huomioi kirjainkokoa tai välilyöntejä). |

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

**Vastaus** (`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` tarkoittaa, että nimi ei täsmännyt — `restaurantDetails` jätetään pois, pyydä tilinhaltijaa yrittämään uudelleen. Nopeusrajoitus on 3 yritystä 5 minuutissa (tiukempi kuin olemassaolon tarkistus, koska tämä on varsinainen todistusvaihe). `restaurant_id`, joka ei enää vastaa Zenchefissä, palauttaa `404`.

**Vaihe 3 — Tallenna ravintola**

`POST /appointments/zenchef-restaurants`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_id` | Kyllä | 1–64 merkkiä, kirjaimia/numeroita/alaviiva/yhdysmerkki. |
| `restaurant_name` | Kyllä | Vaiheesta 2 saatu vahvistettu ravintolan nimi. |

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

**Vastaus** (`201 Created`):

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

**Päivitä tallennettu Zenchef-ravintola**

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

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_name` | Ei | Uusi näyttönimi. |
| `is_active` | Ei | Aseta `false` estääksesi bottia tekemästä varauksia tästä ravintolasta poistamatta sitä. |

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

**Vastaus** (`200 OK`): sama muoto kuin yllä olevassa tallennusvastauksessa.

**Zenchef-ravintolan poistaminen**

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

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

`restaurantId`, jota ei ole tällä hetkellä tilillä, palauttaa `404` päivityksen tai poiston yhteydessä.

### Formitable

Formitable ei tarvitse Zenchefin kaksivaiheista nimen vahvistusta — sen ravintolatunnukset on jo rajattu yrityskohtaisesti, joten yksi vahvistuskutsu riittää. Siinä on myös tietojen haku, jota käytetään ravintolan verkkosivuston URL-osoitteen välimuistiin tallentamiseen määrityksen aikana.

**Ravintolatunnuksen vahvistaminen**

`POST /appointments/formitable-restaurants/verify`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_id` | Kyllä | Formitable-ravintolatunnus. |
| `language` | Ei | Kielitunniste koepyyntöä varten. Oletusarvo on `"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" }'
```

**Vastaus** (`200 OK`):

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

Jos Formitable ei tunnista `restaurant_id`, se palauttaa `404`. Nopeusrajoitus on 10 yritystä 5 minuutin aikana tiliä kohden.

**Ravintolan tietojen hakeminen**

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

Hakee ravintolan julkisen profiilin Formitablesta, mukaan lukien sen verkkosivuston — käytetään verkkosivuston URL-osoitteen välimuistiin tallentamiseen ravintolaa määritettäessä. `language` on valinnainen kyselyparametri, jonka oletusarvo on `"en"`.

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

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

**Tallenna ravintola**

`POST /appointments/formitable-restaurants`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_id` | Kyllä | 1–64 merkkiä, kirjaimia/numeroita/alaviivoja/yhdysviivoja. |
| `restaurant_name` | Kyllä | Näytettävä nimi. |
| `language` | Kyllä | ISO-kielikoodi, esim. `"en"` tai `"en-GB"`. |
| `website_url` | Ei | Ravintolan verkkosivusto, yllä olevasta tietojen hausta. Täytyy olla `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"
  }'
```

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

**Päivitä tallennettu Formitable-ravintola**

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

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `restaurant_name` | Ei | Uusi näytettävä nimi. |
| `language` | Ei | Uusi ISO-kielikoodi. |
| `is_active` | Ei | Aseta `false` estääksesi bottia tekemästä varauksia tähän ravintolaan poistamatta sitä. |
| `website_url` | Ei | Uusi verkkosivuston URL-osoite. |

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

**Vastaus** (`200 OK`): sama muoto kuin yllä olevassa tallennusvastauksessa.

**Poista Formitable-ravintola**

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

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

`restaurantId`, jota ei ole tällä hetkellä tilillä, palauttaa `404` päivityksen tai poiston yhteydessä.

### OpenTable

OpenTable-ravintoloihin otetaan yhteys alustan OpenTable-kumppanitunnuksilla, ja OpenTable sallii näiden tunnusten nähdä vain ne ravintolat, jotka ovat yhdistäneet alustan listauksen OpenTablen integraatiomarkkinapaikassa. Kuten Formitablen kohdalla, yksi vahvistuskutsu riittää: tavoitettavissa oleva ravintolatunnus (numeerinen "RID") todistaa sekä ravintolan olemassaolon että sen, että se on yhdistänyt integraation. Ennen kuin OpenTable-kumppanilistaus on otettu käyttöön alustalla, vahvistuskutsu vastaa `503`.

**Vahvista OpenTable-ravintola**

`POST /appointments/opentable-restaurants/verify`

| Kenttä | Pakollinen | Kuvaus |
| --- | --- | --- |
| `restaurant_id` | Kyllä | OpenTable-ravintolatunnus (RID), numero kuten `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" }'
```

**Vastaus** (`200 OK`):

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

`verified: false` tarkoittaa, ettei millään OpenTable-ravintolalla ole kyseistä tunnusta. `403` tarkoittaa, että ravintola on olemassa, mutta se ei ole vielä yhdistänyt alustan integraatiota OpenTablessa. Nopeusrajoitus on 10 yritystä 5 minuutissa tiliä kohden.

**Lisää OpenTable-ravintola**

`POST /appointments/opentable-restaurants`

| Kenttä | Pakollinen | Kuvaus |
| --- | --- | --- |
| `restaurant_id` | Kyllä | Vahvistettu ravintolatunnus. |
| `restaurant_name` | Kyllä | Näytettävä nimi (otsikko; myös se, miksi tekoäly kutsuu ravintolaa). |
| `website_url` | Ei | http(s)-URL-osoite, joka näytetään vieraille, kun tekoäly ohjaa heidät ravintolaan. |

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

**Vastaus** (`201 Created`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

**Päivitä tallennettu OpenTable-ravintola**

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

Lähetä mikä tahansa kentistä `restaurant_name`, `is_active` (tauko `false`:lla) tai `website_url` (tyhjä merkkijono tyhjentää sen); pois jätettyjä kenttiä ei muuteta.

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

**Vastaus** (`200 OK`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

**Poista OpenTable-ravintola**

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

**Vastaus** (`200 OK`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

Jos `restaurantId` ei ole tällä hetkellä tilillä, päivitys palauttaa `404`.

### TheFork

TheFork-ravintoloihin otetaan yhteys alustan TheFork-kumppanitunnuksilla, ja TheFork sallii näiden tunnusten nähdä vain ne ravintolat, joilla kyseisen alustan kumppanuus on otettu käyttöön niiden TheFork-tilillä. Kuten Formitablen ja OpenTablen kohdalla, yksi vahvistuskutsu riittää: tavoitettavissa oleva ravintolatunnus (Restaurant ID) todistaa sekä ravintolan olemassaolon että sen, että kumppanuus on käytössä. Tunnus on UUID, jonka TheFork antaa ravintolalle TheFork Managerissa, ja se lähetetään merkkijonona. Ennen kuin TheFork on hyväksynyt alustan kumppaniksi ja myöntänyt tunnukset, vahvistuskutsu vastaa `503` – katso [TheFork](../integrations/thefork.md) nähdäksesi, mitä se tarkoittaa nykyään.

**Vahvista TheFork-ravintola**

`POST /appointments/thefork-restaurants/verify`

| Kenttä | Pakollinen | Kuvaus |
| --- | --- | --- |
| `restaurant_id` | Kyllä | TheFork-ravintolatunnus, UUID, kuten `9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60`. |

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

**Vastaus** (`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` ovat seuruekoot, joita ravintola ottaa vastaan verkossa seuraavan 30 päivän aikana. `404` tai `403` tarkoittaa, ettei TheFork antanut meille ravintolaa kyseisellä tunnuksella – joko tunnus on väärä tai alustan kumppanuus ei ole vielä käytössä kyseisessä ravintolassa; `400` tarkoittaa, ettei tunnus ole UUID. Nopeusrajoitus on 10 yritystä 5 minuutissa tiliä kohden.

**Lisää TheFork-ravintola**

`POST /appointments/thefork-restaurants`

| Kenttä | Pakollinen | Kuvaus |
| --- | --- | --- |
| `restaurant_id` | Kyllä | Vahvistettu ravintolatunnus (UUID). |
| `restaurant_name` | Kyllä | Näytettävä nimi (otsikko; myös nimi, jolla tekoäly kutsuu ravintolaa). |
| `website_url` | Ei | http(s)-URL-osoite, joka näytetään vieraille, kun tekoäly ohjaa heidät ravintolaan. |

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

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

**Päivitä tallennettu TheFork-ravintola**

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

Lähetä mikä tahansa kentistä `restaurant_name`, `is_active` (tauko `false`:lla) tai `website_url` (tyhjä merkkijono tyhjentää sen); pois jätettyjä kenttiä ei muuteta.

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

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

**Poista TheFork-ravintola**

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

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

`restaurantId`, jota ei ole tällä hetkellä tilillä, palauttaa `404` päivityksen tai poiston yhteydessä.

### Trafft

Trafft yhdistetään kerran koko tilille, ei toimipistettä kohden: yksi yrityksen osoite sekä API-tunnistetiedot Trafft-hallintapaneelista (**Features & Integrations → API & Connectors**, osa Trafftin Business-pakettia). Yhteyskutsu tarkistaa nämä tunnistetiedot Trafftia vasten ennen tallennusta, joten väärä osoite, väärät tunnistetiedot tai paketti ilman API-käyttöoikeutta aiheuttavat virheen tässä vaiheessa eikä vasta asiakaskeskustelun aikana. Asiakassalaisuus (client secret) tallennetaan salattuna, eikä sitä palauteta koskaan minkään päätepisteen kautta.

Kaikki kolme kirjoituskutsua (`POST`, `PUT`, `DELETE`) vaativat Integraatioiden **muokkausoikeuden**; tila `GET` vaatii Integraatioiden **katseluoikeuden**.

**Yhdistä Trafft**

`POST /appointments/trafft/connect`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `subdomain` | Kyllä | Yrityksen osoite – osa ennen `.admin.trafft.com`-osaa URL-osoitteessa, jolla kirjaudut sisään, esim. `acme`. Täysi osoite hyväksytään ja se lyhennetään samaan arvoon. |
| `client_id` | Kyllä | Asiakastunnus (Client ID) Trafftin API & Connectors -sivulta. |
| `client_secret` | Kyllä | Asiakassalaisuus (Client Secret) samalta sivulta. Tallennetaan salattuna, ei palauteta koskaan. |
| `company_name` | Ei | Otsikko omaa listaasi varten. |

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

**Vastaus** (`200 OK`):

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

Määrät palautuvat Trafftista tarkistuksen aikana – ne ovat nopein tapa varmistaa, että tunnistetiedot osoittavat haluamaasi tiliin.

**Hae yhteyden tila**

`GET /appointments/trafft`

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

**Vastaus** (`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` tarkoittaa, ettei mitään ole vielä määritetty. Asiakassalaisuutta ei koskaan sisällytetä tähän vastaukseen.

**Päivitä yhteys**

`PUT /appointments/trafft`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `is_active` | Ei | Aseta `false` tauolle – tekoäly lopettaa varausten tekemisen Trafft-palveluun, mutta yhteys säilyy. `true` jatkaa sitä. |
| `company_name` | Ei | Uusi tunniste. |

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

**Vastaus** (`200 OK`): sama yhteysolio kuin yllä olevassa `GET`-kohdassa.

**Katkaise Trafft-yhteys**

`DELETE /appointments/trafft`

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

**Vastaus** (`200 OK`): `{ "success": true }`

Yhteyden katkaiseminen poistaa vain tallennetut tunnistetiedot. Trafft-palvelussa jo oleviin varauksiin ei kosketa.

> **Virheen muoto kaikissa Zenchef/Formitable/OpenTable/TheFork-päätepisteissä:** toisin kuin muualla tällä sivulla, virheet sisältävät tässä tilan kahdesti – kerran HTTP-tilana ja kerran `error_code`-kenttänä rungossa – esimerkiksi `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Käsittele se samalla tavalla kuin mikä tahansa muu virhe: tarkista `success` ja lue viesti kohdasta `error`.

---

## Appointments-rajapinnan virheet

Ajanvarauksen päätepisteet palauttavat vakioidun virhekuoren:

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

| Tila | Milloin se tapahtuu ajanvarauksen päätepisteessä |
|---|---|
| `400` | Pakollinen kenttä puuttuu tai on virheellinen – esimerkiksi virheellinen `start_time`, `end_time`, joka ei ole `start_time`:n jälkeen, virheellinen suodatinyhdistelmä, ei päivitettäviä kenttiä tai jo peruttu ajanvaraus. |
| `404` | Ajanvarausta, yhteystietoa tai tapahtumatyyppiä ei löytynyt. |
| `409` | Pyydetty aika on jo varattu (varausristiriita). |

Jaetut koodit, joita jokainen päätepiste voi palauttaa — `401`, `403` (tilauksesi ei sisällä API-käyttöoikeutta), `429` (nopeusrajoitus) ja `500` — on lueteltu uudelleenyritysohjeiden kera kohdassa [Virheet ja sivutus](errors-and-pagination.md).

---

::: master-only
## Käytä omaa Google OAuth -asiakasohjelmaasi (Kalenterin suostumusnäyttö)

Kun tili yhdistää Google-kalenterin, Googlen kirjautumisikkuna näyttää OAuth-asiakasohjelman projektin nimen – oletusarvoisesti alustan nimen. Toimisto voi rekisteröidä oman Google OAuth 2.0 -asiakasohjelman toimistotililleen; tästä eteenpäin kyseisen tilin ja kaikkien sen ala-tilien kalenteriyhteys kulkee kyseisen asiakasohjelman kautta, joten suostumusnäytössä näkyy toimiston nimi ja logo. Mikään muu ei muutu: yhteysprosessi, kaksisuuntainen synkronointi ja yllä mainitut ajanvarauksen päätepisteet toimivat täsmälleen kuten ennenkin.

> **Vain Google-kalenteri.** Sähköpostikanavan Gmail-postilaatikon OAuth ei muutu.

### Mitä asiakkaasi tarvitsee ensin

1. **OAuth 2.0 -asiakasohjelma**, tyypiltään Web-sovellus, Google Cloud -projektissasi, ja **Google Calendar API** otettuna käyttöön kyseisessä projektissa.
2. **Jokainen `redirect_uris`-merkintä** (jonka alla olevat päätepisteet palauttavat) lisättynä asiakasohjelman valtuutettuihin uudelleenohjaus-URI-osoitteisiin (Authorized redirect URIs). Ensimmäinen merkintä on vahvistettu `api.`-verkkotunnuksesi, jos sinulla on sellainen – Google vahvistaa vain brändin, jonka uudelleenohjaus sijaitsee omistamallasi verkkotunnuksella – ja sen jälkeen alustan neutraali isäntä varavaihtoehtona, jota käytetään siihen asti.
3. **Suostumusnäyttö**, jossa on brändisi, verkkotunnuksesi kohdassa Valtuutetut verkkotunnukset (Authorized domains) ja kaksi ilmoitettua kalenterin laajuutta (scopes) (`scopes` vastauksessa). Ennen kuin sovellus on julkaistu ja Googlen vahvistama, käyttäjät näkevät varoituksen vahvistamattomasta sovelluksesta, ja asiakasohjelman käyttäjämäärä on rajoitettu 100 käyttäjään.

### Tallenna asiakasohjelmasi

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

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `client_id` | Kyllä | OAuth 2.0 -asiakastunnus (client ID), joka päättyy tunnisteeseen `.apps.googleusercontent.com`. |
| `client_secret` | Kyllä | Asiakassalaisuus (client secret). Vahvistetaan Googlea vasten ennen tallennusta ja sen jälkeen salataan. Ei palauteta koskaan missään päätepisteessä. |

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

**Vastaus**

```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": ["…"]
}
```

Väärä salaisuus tai tuntematon asiakastunnus hylätään virheellä `400`, ja Googlen oma syy näkyy kohdassa `error`. Mitään ei tallenneta.

### Lue tai poista se

`GET /account-config/google-oauth-client` palauttaa saman yhteenvedon milloin tahansa — `configured: false` sekä `redirect_uris` ja `scopes` ennen kuin mitään tallennetaan, joten voit määrittää Googlen puolen ensin. `DELETE /account-config/google-oauth-client` poistaa asiakkaan: uudet yhteydet palautuvat alustan asiakkaaseen, ja poistetun asiakkaan kautta yhdistetyt kalenterit on yhdistettävä uudelleen, koska vain yhteyden muodostanut asiakas voi päivittää sen.

Tiimin jäsenet tarvitsevat **Integraatiot: näytä** -oikeuden `GET` varten ja **Integraatiot: muokkaa** -oikeuden `PUT` / `DELETE` varten.
:::

---

## Seuraavat vaiheet

- [Yhteystiedot](contacts.md) — luo ja etsi yhteystietoja, joille teet varauksia.
- [Viestit ja keskustelut](messages.md) — lähetä yhteystiedolle vahvistus tai muistutus.
- [Webhooks](webhooks.md) — saat ilmoituksen, kun varaukset muuttuvat.
