# Menodi API (beta)

> Koppla Menodi till era egna system. Hämta samtal, bokningar och kontakter, boka tider och få besked direkt när något händer.

Webbsida: https://menodi.se/docs/api

API-version: v1beta

Referensen i den här filen är en ögonblicksbild. Den aktuella beskrivningen finns alltid i https://menodi.se/api/v1beta/openapi.json

Vill ni hellre att vi bygger kopplingen åt er? Läs om [anpassade flöden](https://menodi.se/docs/api/oversikt).

## Kom igång

API:t är avstängt tills ni aktiverar det. Ägaren eller en behörig användare gör det i appen, under **Integrationer → API**, och skapar sedan nycklar där. En nyckel ger åtkomst till samtal, transkript, inspelningar, bokningar och kontakter, som kan innehålla känsliga personuppgifter. Ni ansvarar för vart uppgifterna skickas.

Skicka nyckeln som en Bearer-token. Ett bra första anrop är `/me`:

```
curl https://menodi.se/api/v1beta/me \
  -H "Authorization: Bearer mnd_live_..."
```

```
const res = await fetch('https://menodi.se/api/v1beta/calls?limit=10', {
  headers: { Authorization: `Bearer ${process.env.MENODI_API_KEY}` },
});
const { data, next_cursor } = await res.json();
```

> **Skydda nyckeln.** Den ger åtkomst till kunduppgifter, transkript och inspelningar. Använd den bara från er server, aldrig i en webbsida eller app. Läcker den återkallar ni den i appen och skapar en ny. Den som tar emot uppgifter via API:t ansvarar för att hantera dem enligt GDPR.

## Så fungerar API:t

- **Bas-URL:** `https://menodi.se/api/v1beta`
- **Versioner:** `v1beta` kan ändras med förvarning. När `v1` släpps är den stabil: nya fält kan tillkomma, så ignorera fält ni inte känner igen. Brytande ändringar kommer i en ny version.
- **Tider** är UTC i ISO 8601. Lediga tider anges också med klockslag i svensk tid.
- **Sidindelning:** listor svarar med `data` och `next_cursor`. Skicka värdet som `cursor` för nästa sida. `null` betyder att det inte finns fler.
- **Stegvis synk:** skicka `updated_after` till `/bookings` eller `/contacts` för att bara hämta det som ändrats.
- **Gränser:** 120 anrop per minut per nyckel och 30 nya bokningar per minut. Över gränsen svarar API:t 429 med headern `Retry-After`.
- **Okända värden:** fält med fasta värden, som `outcome`, kan svara `other`. Hantera det utan att stanna.
- **Testsamtal** ingår aldrig.
- **Felsökning:** varje svar har headern `X-Request-Id`. Ange den när ni kontaktar oss.

## Boka via API:t

Hämta lediga tider, och skicka sedan en av dem till `POST /bookings`. Samma regler gäller som på företagets bokningssida. Headern `Idempotency-Key` krävs: skickar ni samma nyckel igen, till exempel efter ett nätverksfel, får ni samma svar utan att bokningen skapas två gånger.

```
curl -X POST https://menodi.se/api/v1beta/bookings \
  -H "Authorization: Bearer mnd_live_..." \
  -H "Idempotency-Key: 3f8a1c52-7e0b-4a7e-9b1d-2c6d5e4f3a10" \
  -H "Content-Type: application/json" \
  -d '{
    "start": "2026-12-01T09:00:00Z",
    "service_id": "...",
    "customer": { "name": "Kim Andersson", "phone": "+46701234567" },
    "notify_customer": true
  }'
```

- En tid som inte går att boka ger `409 slot_unavailable`, med orsaken i `details.reason`: `closed`, `notice`, `advance` eller `busy`.
- Tjänster med manuell bekräftelse ger status `pending` tills företaget har bekräftat.
- Bekräftelse-SMS följer företagets inställning. Skicka `"notify_customer": false` för att stänga av det för en bokning.
- Företag som bokar i ett journalsystem får `409 pms_managed` på lediga tider och bokningar. Läsning fungerar som vanligt.
- Två samtidiga anrop mot samma tid kan i sällsynta fall båda gå igenom. Kontrollera svaret om ni bokar i stora volymer.
- Ombokning görs genom att avboka och skapa en ny bokning.

## Webhooks

Lägg till en https-adress under **Integrationer → API** och välj händelser. Menodi skickar en POST när något händer. Med knappen *Skicka testhändelse* får ni en `webhook.ping` att testa mot.

| Händelse | När |
| --- | --- |
| `call.completed` | Ett samtal är klart och sparat. Transkriptet hämtas med `GET /calls/{id}`. |
| `booking.created` | En bokning skapades, oavsett kanal. |
| `booking.updated` | Tid, status, tjänst, personal, kunduppgifter eller anteckning ändrades. |
| `booking.cancelled` | En bokning avbokades. |
| `booking.deleted` | En bokning raderades. Bara `id` skickas. |

```
{
  "id": "e2f1c7a4-...",
  "type": "booking.created",
  "created_at": "2026-11-05T10:15:00.000Z",
  "api_version": "v1beta",
  "data": { "id": "...", "start": "2026-12-01T09:00:00.000Z", "status": "confirmed" }
}
```

- **Svara med 2xx inom 10 sekunder.** Allt annat, även en omdirigering, räknas som misslyckat.
- **Omförsök** sker efter 1 minut, 5 minuter, 30 minuter, 2 timmar, 12 timmar och 24 timmar.
- **Samma händelse kan komma mer än en gång, och i annan ordning.** Använd `id` för att bara hantera den en gång. Innehållet visar läget vid leveransen, inte när händelsen inträffade.

### Kontrollera signaturen

Headern `Menodi-Signature` har formen `t=<tidsstämpel>,v1=<signatur>`. Signaturen är HMAC-SHA256 av `tidsstämpel.råtext` med signeringshemligheten, som visas när adressen läggs till. Kontrollera den mot den råa texten, innan den tolkas som JSON.

```
import { createHmac, timingSafeEqual } from 'node:crypto';

function isFromMenodi(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  // Avvisa gamla händelser, så att en uppfångad begäran inte kan spelas upp igen.
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  const given = Buffer.from(parts.v1 ?? '', 'utf8');
  const wanted = Buffer.from(expected, 'utf8');
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}
```

## Referens

### Konto

#### GET /me

Hämta organisation och nyckel

Bra första anrop för att kontrollera att nyckeln fungerar.

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.organization.id` | uuid | Ja | Organisationens id |
| `data.organization.name` | string | Ja |  |
| `data.key.id` | uuid | Ja | Nyckelns id |
| `data.key.scopes` | lista av calls:read, bookings:read, contacts:read, catalog:read, bookings:write | Ja |  |
| `data.api_version` | v1beta | Ja |  |

Möjliga fel: 401 (invalid_api_key), 403 (api_access_disabled), 429 (rate_limited), 500 (internal_error)

### Samtal

#### GET /calls

Lista samtal

Nyast först. Listan innehåller inte transkript och aldrig testsamtal.

Behörighet: `calls:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | Nej | Antal rader per sida, 1 till 100 |
| `cursor` | query | string | Nej | next_cursor från föregående sida |
| `created_after` | query | date-time | Nej | Bara samtal efter denna tid |
| `created_before` | query | date-time | Nej | Bara samtal före denna tid |
| `direction` | query | inbound, outbound | Nej |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].id` | uuid | Ja | Samtalets id |
| `data[].created_at` | date-time | Ja | När samtalet registrerades |
| `data[].direction` | inbound, outbound, other | Ja |  |
| `data[].outcome` | booked, qualified, missed, voicemail, transferred, transfer_failed, cancelled, other | Ja | booked = bokning gjordes. qualified = samtalet besvarades och hanterades. missed = uppringaren lade på under hälsningen eller samtalet kom aldrig igång. voicemail = telefonsvarare (utgående). transferred = kopplades vidare. transfer_failed = vidarekopplingen gick inte fram. cancelled = bokning avbokades. other = värde som ännu inte är dokumenterat. |
| `data[].duration_seconds` | integer (kan vara null) | Ja |  |
| `data[].caller_name` | string (kan vara null) | Ja |  |
| `data[].caller_phone` | string (kan vara null) | Ja | Telefonnummer, normalt i E.164-format |
| `data[].summary` | string (kan vara null) | Ja |  |
| `data[].has_recording` | boolean | Ja | Sant om inspelning kan hämtas via /calls/{id}/recording |
| `next_cursor` | string (kan vara null) | Ja | Skicka som cursor för nästa sida. null = inga fler |

Möjliga fel: 400 (invalid_request, invalid_cursor), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 429 (rate_limited), 500 (internal_error)

#### GET /calls/{id}

Hämta ett samtal med transkript

Behörighet: `calls:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `id` | path | uuid | Ja |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.id` | uuid | Ja | Samtalets id |
| `data.created_at` | date-time | Ja | När samtalet registrerades |
| `data.direction` | inbound, outbound, other | Ja |  |
| `data.outcome` | booked, qualified, missed, voicemail, transferred, transfer_failed, cancelled, other | Ja | booked = bokning gjordes. qualified = samtalet besvarades och hanterades. missed = uppringaren lade på under hälsningen eller samtalet kom aldrig igång. voicemail = telefonsvarare (utgående). transferred = kopplades vidare. transfer_failed = vidarekopplingen gick inte fram. cancelled = bokning avbokades. other = värde som ännu inte är dokumenterat. |
| `data.duration_seconds` | integer (kan vara null) | Ja |  |
| `data.caller_name` | string (kan vara null) | Ja |  |
| `data.caller_phone` | string (kan vara null) | Ja | Telefonnummer, normalt i E.164-format |
| `data.summary` | string (kan vara null) | Ja |  |
| `data.has_recording` | boolean | Ja | Sant om inspelning kan hämtas via /calls/{id}/recording |
| `data.transcript` | string (kan vara null) | Ja |  |

Möjliga fel: 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 404 (not_found), 429 (rate_limited), 500 (internal_error)

#### GET /calls/{id}/recording

Hämta länk till inspelningen

Länken är signerad och gäller i 15 minuter. Spara filen om den ska behållas.

Behörighet: `calls:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `id` | path | uuid | Ja |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.url` | uri | Ja | Signerad länk till inspelningen |
| `data.expires_at` | date-time | Ja | När länken slutar gälla |

Möjliga fel: 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 404 (not_found, recording_unavailable), 429 (rate_limited), 500 (internal_error)

### Bokningar

#### GET /bookings

Lista bokningar

Nyast först. Med updated_after sorteras listan i stället på senaste ändring, äldst först, vilket passar stegvis synk.

Behörighet: `bookings:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | Nej | Antal rader per sida, 1 till 100 |
| `cursor` | query | string | Nej | next_cursor från föregående sida |
| `updated_after` | query | date-time | Nej | Stegvis synk: bara bokningar ändrade efter denna tid, äldst först |
| `start_after` | query | date-time | Nej | Bara bokningar som startar efter denna tid |
| `start_before` | query | date-time | Nej | Bara bokningar som startar före denna tid |
| `status` | query | pending, confirmed, completed, cancelled, no_show | Nej |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].id` | uuid | Ja | Bokningens id |
| `data[].created_at` | date-time | Ja | När bokningen skapades |
| `data[].updated_at` | date-time | Ja | När bokningen senast ändrades |
| `data[].start` | date-time | Ja | Starttid |
| `data[].duration_minutes` | integer (kan vara null) | Ja |  |
| `data[].status` | pending, confirmed, completed, cancelled, no_show, other | Ja |  |
| `data[].source` | phone_agent, chat_widget, website, manual, api, other | Ja |  |
| `data[].service_id` | uuid (kan vara null) | Ja |  |
| `data[].staff_id` | uuid (kan vara null) | Ja |  |
| `data[].contact_id` | uuid (kan vara null) | Ja |  |
| `data[].customer_name` | string (kan vara null) | Ja |  |
| `data[].customer_phone` | string (kan vara null) | Ja |  |
| `data[].notes` | string (kan vara null) | Ja |  |
| `next_cursor` | string (kan vara null) | Ja | Skicka som cursor för nästa sida. null = inga fler |

Möjliga fel: 400 (invalid_request, invalid_cursor), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 429 (rate_limited), 500 (internal_error)

#### POST /bookings

Skapa en bokning

Tiden måste vara en tid som GET /availability skulle visa. Headern Idempotency-Key krävs: samma nyckel ger samma svar utan att bokningen skapas två gånger. Svaret sparas i 24 timmar. Tjänster med manuell bekräftelse ger status pending.

Behörighet: `bookings:write`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | Ja | Unikt värde per bokning, till exempel ett UUID. Använd samma värde vid omförsök. |

Innehåll i begäran:

| Fält | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- |
| `start` | date-time | Ja | Starttid från GET /availability |
| `service_id` | uuid | Nej | Krävs om företaget har tjänster |
| `customer.name` | string | Ja |  |
| `customer.phone` | string | Ja | E.164 eller svenskt nummer |
| `notes` | string | Nej |  |
| `notify_customer` | boolean | Nej | false stänger av bekräftelse-SMS för den här bokningen. Annars följs företagets inställning |

Svar 201:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.id` | uuid | Ja | Bokningens id |
| `data.created_at` | date-time | Ja | När bokningen skapades |
| `data.updated_at` | date-time | Ja | När bokningen senast ändrades |
| `data.start` | date-time | Ja | Starttid |
| `data.duration_minutes` | integer (kan vara null) | Ja |  |
| `data.status` | pending, confirmed, completed, cancelled, no_show, other | Ja |  |
| `data.source` | phone_agent, chat_widget, website, manual, api, other | Ja |  |
| `data.service_id` | uuid (kan vara null) | Ja |  |
| `data.staff_id` | uuid (kan vara null) | Ja |  |
| `data.contact_id` | uuid (kan vara null) | Ja |  |
| `data.customer_name` | string (kan vara null) | Ja |  |
| `data.customer_phone` | string (kan vara null) | Ja |  |
| `data.notes` | string (kan vara null) | Ja |  |

Möjliga fel: 400 (invalid_request, idempotency_key_required), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 409 (idempotency_in_progress, slot_unavailable, pms_managed), 422 (idempotency_key_conflict, invalid_phone, service_required, invalid_service), 429 (rate_limited), 500 (internal_error)

#### GET /bookings/{id}

Hämta en bokning

Behörighet: `bookings:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `id` | path | uuid | Ja |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.id` | uuid | Ja | Bokningens id |
| `data.created_at` | date-time | Ja | När bokningen skapades |
| `data.updated_at` | date-time | Ja | När bokningen senast ändrades |
| `data.start` | date-time | Ja | Starttid |
| `data.duration_minutes` | integer (kan vara null) | Ja |  |
| `data.status` | pending, confirmed, completed, cancelled, no_show, other | Ja |  |
| `data.source` | phone_agent, chat_widget, website, manual, api, other | Ja |  |
| `data.service_id` | uuid (kan vara null) | Ja |  |
| `data.staff_id` | uuid (kan vara null) | Ja |  |
| `data.contact_id` | uuid (kan vara null) | Ja |  |
| `data.customer_name` | string (kan vara null) | Ja |  |
| `data.customer_phone` | string (kan vara null) | Ja |  |
| `data.notes` | string (kan vara null) | Ja |  |

Möjliga fel: 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 404 (not_found), 429 (rate_limited), 500 (internal_error)

#### POST /bookings/{id}/cancel

Avboka en bokning

Ofarligt att upprepa: en redan avbokad bokning returneras oförändrad. Inget SMS skickas till kunden.

Behörighet: `bookings:write`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `id` | path | uuid | Ja |  |

Innehåll i begäran:

| Fält | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- |
| `reason` | string | Nej |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.id` | uuid | Ja | Bokningens id |
| `data.created_at` | date-time | Ja | När bokningen skapades |
| `data.updated_at` | date-time | Ja | När bokningen senast ändrades |
| `data.start` | date-time | Ja | Starttid |
| `data.duration_minutes` | integer (kan vara null) | Ja |  |
| `data.status` | pending, confirmed, completed, cancelled, no_show, other | Ja |  |
| `data.source` | phone_agent, chat_widget, website, manual, api, other | Ja |  |
| `data.service_id` | uuid (kan vara null) | Ja |  |
| `data.staff_id` | uuid (kan vara null) | Ja |  |
| `data.contact_id` | uuid (kan vara null) | Ja |  |
| `data.customer_name` | string (kan vara null) | Ja |  |
| `data.customer_phone` | string (kan vara null) | Ja |  |
| `data.notes` | string (kan vara null) | Ja |  |

Möjliga fel: 400 (invalid_request), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 404 (not_found), 409 (booking_not_cancellable, pms_managed), 429 (rate_limited), 500 (internal_error)

#### GET /availability

Lediga tider

Upp till sju dagar med lediga tider, enligt samma regler som företagets bokningssida.

Behörighet: `bookings:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `service_id` | query | uuid | Nej | Krävs om företaget har tjänster |
| `from` | query | date-time | Nej | Börja leta från denna tid. Standard är nu |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].date` | date | Ja | Datum i Stockholmstid |
| `data[].slots[].time` | string | Ja | Klockslag i Stockholmstid, HH:mm |
| `data[].slots[].start` | date-time | Ja | Exakt starttid att skicka till POST /bookings |

Möjliga fel: 400 (invalid_request), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 409 (pms_managed), 422 (service_required, invalid_service), 429 (rate_limited), 500 (internal_error)

### Kontakter

#### GET /contacts

Lista kontakter

Senast ändrade först. Med updated_after sorteras listan äldst först, vilket passar stegvis synk.

Behörighet: `contacts:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | Nej | Antal rader per sida, 1 till 100 |
| `cursor` | query | string | Nej | next_cursor från föregående sida |
| `updated_after` | query | date-time | Nej | Stegvis synk: bara kontakter ändrade efter denna tid, äldst först |
| `phone` | query | string | Nej | Exakt telefonnummer. Svenska nummer normaliseras automatiskt |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].id` | uuid | Ja | Kontaktens id |
| `data[].updated_at` | date-time | Ja | När kontakten senast ändrades |
| `data[].name` | string (kan vara null) | Ja |  |
| `data[].phone` | string (kan vara null) | Ja |  |
| `data[].email` | string (kan vara null) | Ja |  |
| `data[].tags` | lista av string | Ja |  |
| `data[].notes` | string (kan vara null) | Ja |  |
| `data[].total_bookings` | integer | Ja |  |
| `data[].last_call_at` | date-time (kan vara null) | Ja |  |
| `next_cursor` | string (kan vara null) | Ja | Skicka som cursor för nästa sida. null = inga fler |

Möjliga fel: 400 (invalid_request, invalid_cursor), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 429 (rate_limited), 500 (internal_error)

#### GET /contacts/{id}

Hämta en kontakt

Behörighet: `contacts:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `id` | path | uuid | Ja |  |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data.id` | uuid | Ja | Kontaktens id |
| `data.updated_at` | date-time | Ja | När kontakten senast ändrades |
| `data.name` | string (kan vara null) | Ja |  |
| `data.phone` | string (kan vara null) | Ja |  |
| `data.email` | string (kan vara null) | Ja |  |
| `data.tags` | lista av string | Ja |  |
| `data.notes` | string (kan vara null) | Ja |  |
| `data.total_bookings` | integer | Ja |  |
| `data.last_call_at` | date-time (kan vara null) | Ja |  |

Möjliga fel: 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 404 (not_found), 429 (rate_limited), 500 (internal_error)

### Katalog

#### GET /services

Lista tjänster

Behörighet: `catalog:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | Nej | Antal rader per sida, 1 till 100 |
| `cursor` | query | string | Nej | next_cursor från föregående sida |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].id` | uuid | Ja | Tjänstens id |
| `data[].name` | string | Ja |  |
| `data[].description` | string (kan vara null) | Ja |  |
| `data[].duration_minutes` | integer (kan vara null) | Ja |  |
| `data[].price_sek` | number (kan vara null) | Ja |  |
| `data[].vat_rate` | number (kan vara null) | Ja |  |
| `data[].price_includes_vat` | boolean | Ja |  |
| `data[].requires_confirmation` | boolean | Ja | Sant om bokningar blir pending tills företaget bekräftar |
| `data[].active` | boolean | Ja |  |
| `next_cursor` | string (kan vara null) | Ja | Skicka som cursor för nästa sida. null = inga fler |

Möjliga fel: 400 (invalid_request, invalid_cursor), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 429 (rate_limited), 500 (internal_error)

#### GET /staff

Lista personal

Behörighet: `catalog:read`

Parametrar:

| Namn | Var | Typ | Krävs | Beskrivning |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | Nej | Antal rader per sida, 1 till 100 |
| `cursor` | query | string | Nej | next_cursor från föregående sida |

Svar 200:

| Fält | Typ | Alltid med | Beskrivning |
| --- | --- | --- | --- |
| `data[].id` | uuid | Ja | Personens id |
| `data[].name` | string | Ja |  |
| `data[].role` | string (kan vara null) | Ja |  |
| `data[].active` | boolean | Ja |  |
| `next_cursor` | string (kan vara null) | Ja | Skicka som cursor för nästa sida. null = inga fler |

Möjliga fel: 400 (invalid_request, invalid_cursor), 401 (invalid_api_key), 403 (api_access_disabled, insufficient_scope), 429 (rate_limited), 500 (internal_error)

## Felkoder

Fel svarar med `{ "error": { "code", "message", "request_id", "details" } }`. Bygg logik på `code`, som inte byter betydelse. `message` är text som kan ändras.

| Kod | Status | Betydelse |
| --- | --- | --- |
| `invalid_api_key` | 401 | API-nyckeln saknas, är ogiltig eller har återkallats. |
| `api_access_disabled` | 403 | API:t är inte aktiverat för den här organisationen. |
| `insufficient_scope` | 403 | Nyckeln saknar behörighet för den här åtgärden. |
| `not_found` | 404 | Resursen finns inte. |
| `method_not_allowed` | 405 | Metoden stöds inte för den här sökvägen. |
| `invalid_request` | 400 | Begäran är ogiltig. |
| `invalid_cursor` | 400 | Ogiltig cursor. Använd next_cursor från föregående svar. |
| `idempotency_key_required` | 400 | Headern Idempotency-Key krävs. |
| `idempotency_key_conflict` | 422 | Idempotency-Key har redan använts med en annan begäran. |
| `idempotency_in_progress` | 409 | En begäran med samma Idempotency-Key pågår redan. |
| `invalid_phone` | 422 | Telefonnumret kunde inte tolkas. |
| `service_required` | 422 | service_id krävs eftersom företaget har tjänster. |
| `invalid_service` | 422 | Tjänsten finns inte eller är inte aktiv. |
| `slot_unavailable` | 409 | Tiden går inte att boka. |
| `pms_managed` | 409 | Företagets bokningar hanteras i ett journalsystem och kan inte ändras via API:t. |
| `booking_not_cancellable` | 409 | Bokningen kan inte längre avbokas. |
| `recording_unavailable` | 404 | Samtalet har ingen inspelning som kan hämtas. |
| `rate_limited` | 429 | För många anrop. Försök igen om en stund. |
| `internal_error` | 500 | Ett internt fel uppstod. |
