
# Möten

Appointments API låter dig boka möten för dina kontakter på dina händelsetyper, samt hämta, lista, uppdatera, avboka eller ta bort dem. Det besvarar även frågan som kommer först i de flesta bokningsflöden — vilka tider som faktiskt är lediga — och täcker kalendersidan: att lista de Google-kalendrar du har anslutit och importera händelser som redan finns i dem. När en Google-kalenderanslutning är aktiv skapas motsvarande kalenderhändelse och synkroniseras automatiskt i bakgrunden. Restauranger som använder Zenchef eller Formitable för sina egna bokningssystem kan också verifieras och anslutas här, så att AI-agenten bokar riktiga bord istället för interna möten.

Alla sökvägar på denna sida är relativa till bas-URL:en `https://api.youraiconnector.com/v1`. Varje anrop kräver din API-nyckel — se [Autentisering](authentication.md) för en fullständig lista över hur den kan skickas. Exemplen nedan använder headern `X-API-Key`, där ett cURL-exempel även visar frågeformuläret `?apiKey=`.

> **Händelser vs. möten:** En *händelsetyp* är en definition av en bokningsbar tid (typ av möte, längd, rum). Ett *möte* är en bokad instans av en händelsetyp för en specifik kontakt. Du bokar ett möte genom att referera till kontakten och händelsetypen.

---

## Mötesobjektet

Varje slutpunkt som returnerar ett möte använder samma struktur:

| Fält | Beskrivning |
|---|---|
| `id` | Unikt ID för mötet. |
| `contact_id` | ID för kontakten som mötet är bokat med. |
| `event_id` | ID för händelsetypen som mötet bokades på. |
| `status` | `Confirmed` eller `Canceled`. |
| `start_time` | Mötets starttid, ISO 8601 i UTC. |
| `end_time` | Mötets sluttid, ISO 8601 i UTC. |
| `created_at` | När mötet skapades. |
| `last_modified_at` | När mötet senast ändrades. |
| `room_name` | Rum eller resurs som mötet är bokat i, när händelsetypen använder rum. |
| `description` | Fritextbeskrivning av mötet. |
| `summary` | Kort sammanfattning eller titel. |
| `cancelation_reason` | Orsak som angavs när mötet avbokades, om någon. |
| `google_calendar_event_id` | ID för den länkade Google Kalender-händelsen. Sätts när kalendersynkroniseringen är klar; `null` när ingen kalender är ansluten eller medan synkroniseringen pågår. |
| `calendar_synced` | `true` när mötet är länkat till en kalenderhändelse. |
| `imported` | `true` när mötet har importerats från en extern kalender istället för att bokas direkt. |
| `is_recurring` | `true` när mötet är en del av en återkommande serie. |
| `recurrence_frequency` | Hur ofta mötet upprepas, vid återkommande möten. |
| `recurring_event_id` | ID för den återkommande serie som detta möte tillhör. |
| `recurring_interval` | Intervall mellan upprepningar, vid återkommande möten. |
| `recurring_sequence` | Position för detta möte inom dess återkommande serie. |
| `end_after_x_occurrences` | Antal förekomster efter vilka den återkommande serien avslutas. |
| `booking_provider` | Källsystem som bokningen kom ifrån, när den bokats via en ansluten bokningsleverantör. |

> **Om kalendersynkronisering:** Direkt efter att du bokat eller ändrat ett möte kan `google_calendar_event_id` fortfarande vara `null` och `calendar_synced` kan vara `false` eftersom synkroniseringen körs i bakgrunden en stund senare. Hämta mötet igen en kort stund senare för att se de ifyllda kalenderfälten.

---

## Hitta tillgängliga tider

`GET /appointments/available-slots`

Returnerar de tider som faktiskt är lediga för en händelsetyp mellan två tidpunkter. Detta är normalt det **första** anropet i ett bokningsflöde: visa dessa tider, låt personen välja en, och skicka sedan den valda tiden till [Boka ett möte](#book-an-appointment).

Svaret tar redan hänsyn till händelsetypens egna öppettider och tidslängd, dess rum, möten du redan har bokat på den, samt allt som är blockerat i de anslutna Google-kalendrarna — så en tid som returneras här är en tid du kan boka.

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `event_id` | Ja | Händelsetypen att kontrollera. Måste tillhöra ditt konto. |
| `start_time` | Ja | Starten på tidsfönstret du vill ha tider för, ISO 8601 datum-tid. |
| `end_time` | Ja | Slutet på tidsfönstret, ISO 8601 datum-tid. Hela slutdagen inkluderas. |

Resultaten returneras grupperade per dag — och när händelsetypen använder rum, en grupp per rum per dag:

| Fält | Beskrivning |
|---|---|
| `date` | Dagen som gruppen täcker, skrivet `DD/MM/YYYY`. |
| `day` | Veckodagsnamn med gemener, till exempel `monday`. |
| `room_name` | Rummet eller resursen som denna grupp tillhör, när händelsetypen använder rum. |
| `available_slots` | De bokningsbara blocken den dagen, tidigast först. |

Varje post i `available_slots` har:

| Fält | Beskrivning |
|---|---|
| `start_time` | Blockstart som `HH:mm`. |
| `end_time` | Blockslut som `HH:mm`. |
| `available` | `true` — endast ledig tid returneras. |
| `spots_left` | Hur många bokningar som fortfarande får plats i detta block. Visas endast för händelsetyper som tillåter mer än en bokning per tid. |

> **Tiderna är lokala för händelsetypen, inte UTC.** `date`, `start_time` och `end_time` är klockslag i händelsetypens egen tidszon (dess åsidosättning, eller ditt kontos tidszon om ingen sådan finns). [Boka ett möte](#book-an-appointment) förväntar sig ett ISO 8601 UTC-ögonblick, så konvertera tiden du valde innan du skickar den.

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

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

En dag utan lediga tider visas helt enkelt inte. Om `event_id`, `start_time` eller `end_time` saknas returneras `400`; en händelsetyp som inte finns på ditt konto returnerar `404`.

---

## Boka ett möte

`POST /appointments`

Bokar ett nytt möte för en kontakt på en av dina händelsetyper. Sluttiden beräknas automatiskt baserat på händelsetypens tidslängd.

Bokningen kontrolleras mot konflikter: om den begärda tiden överlappar ett befintligt bekräftat möte på samma händelsetyp misslyckas anropet med ett `409` och ingenting skapas.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `contact_id` | Ja | ID för kontakten som ska bokas. Måste tillhöra ditt konto. |
| `event_id` | Ja | ID för händelsetypen som ska bokas. Måste tillhöra ditt konto. |
| `start_time` | Ja | Önskad starttid som ett ISO 8601-datum/tid. |
| `room_name` | Nej | Namn på rum eller resurs, när händelsetypen använder rum. |

**cURL** (använder frågeformuläret `?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"])
```

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

---

## Hämta ett möte

`GET /appointments/{appointmentId}`

Returnerar ett enskilt möte via dess ID, inklusive dess synkroniseringsstatus för kalendern.

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

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

---

## Lista möten

`GET /appointments`

Listar möten för ditt konto, med det nyaste först, med markörbaserad paginering.

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `contact_id` | Nej | Returnera endast möten för denna kontakt. Kontaktfiltrerade listor inkluderar **endast bekräftade möten**. |
| `date` | Nej | Returnera endast möten för denna kalenderdag (`YYYY-MM-DD`). **Kräver `contact_id`.** |
| `status` | Nej | Filtrera efter `Confirmed` eller `Canceled`. Endast tillgängligt **utan** `contact_id`. |
| `limit` | Nej | Sidstorlek, ett heltal mellan 1 och 100. Standardvärde `50`. |
| `cursor` | Nej | Värdet `next_cursor` från ett tidigare svar. |

Några regler att komma ihåg:

- **Utan filter** får du varje möte på kontot, sida för sida.
- **Per kontakt** — ställ in `contact_id` för att se en kontakts bekräftade möten. Du kan begränsa detta till en enskild dag genom att även skicka med `date`.
- **Per status** — ställ in `status` (utan `contact_id`) för att endast lista `Confirmed` eller endast `Canceled` möten för hela kontot.
- Filtret `date` utan `contact_id`, eller `status=Canceled` tillsammans med `contact_id`, returnerar ett `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"])
```

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

För att bläddra igenom resultaten, skicka med `next_cursor` från ett svar som `cursor` i nästa begäran. Fortsätt tills `next_cursor` är `null`. Se [Fel & Paginering](errors-and-pagination.md) för det gemensamma pagineringsmönstret.

---

## Uppdatera ett möte

`PUT /appointments/{appointmentId}`

Boka om ett möte eller ändra dess detaljer. Skicka endast de fält du vill ändra — minst ett krävs. Den kombinerade start- och sluttiden måste vara i kronologisk ordning (`end_time` måste vara efter `start_time`). Ändringar synkroniseras automatiskt till den länkade kalenderhändelsen.

| Fält | Beskrivning |
|---|---|
| `start_time` | Ny starttid, ISO 8601 datum-tid. |
| `end_time` | Ny sluttid, ISO 8601 datum-tid. Måste vara efter starttiden. |
| `room_name` | Nytt namn på rum eller resurs. |
| `description` | Ny beskrivning, eller `null` för att rensa den. |
| `summary` | Ny sammanfattning, eller `null` för att rensa den. |

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

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

---

## Avboka en tid

`POST /appointments/{appointmentId}/cancel`

Avbokar en bekräftad tid, med möjlighet att ange en orsak. Tiden finns kvar på ditt konto med status `Canceled`, och den länkade kalenderhändelsen tas bort automatiskt i bakgrunden. Att avboka en redan avbokad tid returnerar en `400`.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `cancellation_reason` | Nej | Orsak till avbokningen, sparas på bokningen. |

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

**Svar** (`200 OK`):

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

---

## Ta bort en bokning

`DELETE /appointments/{appointmentId}`

Tar permanent bort en bokning och dess referenser. Om du bara vill avboka tiden men behålla posten, använd [avboka](#cancel-an-appointment) istället.

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

**Svar** (`200 OK`):

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

---

## Lista dina anslutna Google-kalendrar

`GET /appointments/google-calendars`

Returnerar de Google-kalendrar som är tillgängliga på detta konto, direkt från Google — användbart för att visa kontoinnehavaren en väljare för vilken kalender som ska importeras från nedan, eller bara för att bekräfta att anslutningen är aktiv.

Detta fungerar endast när kontot har anslutit Google Kalender (Inställningar → Integrationer) med minst läsbehörighet. Om det inte har gjorts, eller om den beviljade åtkomsten inte längre inkluderar läsbehörighet för kalendern, får du ett `400` som ber dig att (åter)ansluta den.

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

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

Varje post har Googles egen [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList)-form, så fältnamnen följer Googles `camelCase`, inte detta API:s vanliga `snake_case` — det är Googles data som skickas vidare som den är, inte vår. En saknad eller återkallad anslutning returnerar `400` med ett felmeddelande som förklarar att Google Kalender behöver (åter)anslutas.

---

## Importera händelser från en Google-kalender

`POST /appointments/import-calendar-events`

Hämtar händelser som redan finns i en kampanjs eller AI-agents anslutna Google-kalender(ar) och gör om dem till möten — användbart första gången du ansluter en kalender som redan har bokningar. Detta kan ta en stund (varje händelse går igenom extrahering för att ta reda på vem den är till för), så det körs aldrig direkt: begäran köar ett bakgrundsjobb och ger dig tillbaka ett `job_id` att polla.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `campaign_id` | En av dessa två | Kampanjen vars anslutna kalender(ar) ska importeras från. |
| `agent_id` | En av dessa två | AI-agenten vars anslutna kalender(ar) ska importeras från. |
| `identifier` | Ja | `"EMAIL"` eller `"PHONE_NUMBER"` — vilken kontaktinformation som ska extraheras från varje kalenderhändelse för att matcha eller skapa kontakten den tillhör. |

Skicka exakt en av `campaign_id` / `agent_id`, aldrig båda och aldrig ingen — båda kombinationerna returnerar ett `400`. Den du skickar måste tillhöra ditt konto, annars får du ett `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"])
```

**Svar** (`202 Accepted`):

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

`campaign_id` och `agent_id` ekar tillbaka den du skickade; den andra är alltid `null`.

### Polla importjobbet

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

**Svar** (`200 OK`):

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

| `status` | Betydelse |
|---|---|
| `queued` | Inte hämtad än. Fortsätt polla. |
| `processing` | Importen körs. Fortsätt polla. |
| `completed` | Klar — `message` innehåller en kort sammanfattning som är lätt att läsa. |
| `failed` | Något gick fel — `error` innehåller orsaken. |

`GET` på ett `jobId` som inte finns (eller tillhör ett annat konto) returnerar `404`.

---

## Integrationer för restaurangbokning (Zenchef / Formitable)

Zenchef och Formitable är restaurangbokningssystem som din AI-agent kan boka riktiga bord via. Var och en har en **publik, oautentiserad bokningswidget** (`https://api.youraiconnector.com/v1/zenchef-widget/...` och `https://api.youraiconnector.com/v1/formitable-widget/...`) som renderas i chatten för gästen — dessa widget-vägar är vanliga HTML-sidor avsedda att öppnas i en webbläsare, inte JSON API-slutpunkter, så de dokumenteras inte här. Det som följer är slutpunkterna för kontohantering: verifiering av att ett restaurang-ID tillhör kontoinnehavaren, samt att lägga till, uppdatera eller ta bort det.

### Zenchef

Att ansluta en Zenchef-restaurang är en tvåstegsverifiering, så kontoinnehavaren bevisar att de faktiskt driver restaurangen innan den kopplas till boten: kontrollera först att ID:t finns (utan att avslöja namnet), låt dem sedan skriva in restaurangens namn själva och verifiera att det matchar.

**Steg 1 — Kontrollera att ett restaurang-ID finns**

`POST /appointments/zenchef-restaurants/check`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_id` | Ja | Zenchef-restaurangens ID som ska kontrolleras. |

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

**Svar** (`200 OK`):

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

`exists: false` betyder att ingen Zenchef-restaurang har det ID:t — inget mer att göra. Hastighetsbegränsat till 10 kontroller per 5 minuter per konto; att överskrida detta returnerar `429`.

**Steg 2 — Verifiera restaurangens namn**

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

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_id` | Ja | Zenchef-restaurangens ID från steg 1. |
| `user_input_name` | Ja | Namnet som kontoinnehavaren skrev in — jämförs med restaurangens riktiga namn på Zenchef (skiftläges- och blankstegsokänsligt). |

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

**Svar** (`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` betyder att namnet inte matchade — `restaurantDetails` utelämnas, be kontoinnehavaren att försöka igen. Hastighetsbegränsat till 3 försök per 5 minuter (strängare än existenskontrollen, eftersom detta är det faktiska bevissteget). Ett `restaurant_id` som inte längre kan matchas på Zenchef returnerar `404`.

**Steg 3 — Spara restaurangen**

`POST /appointments/zenchef-restaurants`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_id` | Ja | 1–64 tecken, bokstäver/siffror/understreck/bindestreck. |
| `restaurant_name` | Ja | Det verifierade restaurangnamnet från steg 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" }'
```

**Svar** (`201 Created`):

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

**Uppdatera en sparad Zenchef-restaurang**

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

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_name` | Nej | Nytt visningsnamn. |
| `is_active` | Nej | Ange `false` för att hindra boten från att boka mot denna restaurang utan att ta bort den. |

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

**Svar** (`200 OK`): samma format som spar-svaret ovan.

**Ta bort en Zenchef-restaurang**

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

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

En `restaurantId` som för närvarande inte finns på kontot returnerar `404` vid uppdatering eller borttagning.

### Formitable

Formitable behöver inte den tvåstegsnamnverifiering som Zenchef kräver — dess restaurang-ID:n är redan begränsade per företag, så ett verifieringsanrop räcker. Den har också en detaljsökning som används för att cachelagra restaurangens webbplats-URL under konfigurationen.

**Verifiera ett restaurang-ID**

`POST /appointments/formitable-restaurants/verify`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_id` | Ja | Formitable-restaurangens ID. |
| `language` | Nej | Språktagg för sondförfrågan. Standardvärde är `"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" }'
```

**Svar** (`200 OK`):

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

Ett `restaurant_id` som Formitable inte känner igen returnerar `404`. Hastighetsbegränsat till 10 försök per 5 minuter per konto.

**Hämta restaurangdetaljer**

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

Hämtar restaurangens offentliga profil från Formitable, inklusive dess webbplats — används för att cachelagra webbplatsens URL när restaurangen konfigureras. `language` är en valfri frågeparameter som som standard är `"en"`.

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

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

**Spara restaurangen**

`POST /appointments/formitable-restaurants`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_id` | Ja | 1–64 tecken, bokstäver/siffror/understreck/bindestreck. |
| `restaurant_name` | Ja | Visningsnamn. |
| `language` | Ja | ISO-språktagg, t.ex. `"en"` eller `"en-GB"`. |
| `website_url` | Nej | Restaurangens webbplats, från detaljsökningen ovan. Måste vara `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"
  }'
```

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

**Uppdatera en sparad Formitable-restaurang**

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

| Fält | Krävs | Beskrivning |
|---|---|---|
| `restaurant_name` | Nej | Nytt visningsnamn. |
| `language` | Nej | Ny ISO-språktagg. |
| `is_active` | Nej | Sätt `false` för att hindra boten från att boka mot denna restaurang utan att ta bort den. |
| `website_url` | Nej | Ny webbadress. |

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

**Svar** (`200 OK`): samma format som spar-svaret ovan.

**Ta bort en Formitable-restaurang**

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

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

En `restaurantId` som för närvarande inte finns på kontot returnerar `404` vid uppdatering eller borttagning.

> **Felformat på alla Zenchef/Formitable-slutpunkter:** till skillnad från resten av denna sida innehåller fel här sin status två gånger — en gång som HTTP-status och en gång som `error_code` i brödtexten — till exempel `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Hantera det på samma sätt som alla andra fel: kontrollera `success`, läs `error` för meddelandet.

---

## Fel i Appointments API

Slutpunkter för möten returnerar standardfelmeddelandet:

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

| Status | När det inträffar på en slutpunkt för möten |
|---|---|
| `400` | Ett obligatoriskt fält saknas eller är ogiltigt — till exempel ett felaktigt `start_time`, ett `end_time` som inte är efter `start_time`, en ogiltig filterkombination, inga fält att uppdatera eller ett redan avbokat möte. |
| `404` | Mötet, kontakten eller händelsetypen hittades inte. |
| `409` | Den begärda tidsluckan är redan upptagen (bokningskonflikt). |

De delade koderna som alla slutpunkter kan returnera — `401`, `403` (din plan inkluderar inte API-åtkomst), `429` (hastighetsbegränsning) och `500` — listas med vägledning för återförsök i [Fel & Paginering](errors-and-pagination.md).

---

## Nästa steg

- [Kontakter](contacts.md) — skapa och sök efter de kontakter du bokar för.
- [Meddelanden och konversationer](messages.md) — skicka en bekräftelse eller påminnelse till en kontakt.
- [Webhooks](webhooks.md) — få aviseringar när bokningar ändras.
