
# Contacts API

En kontakt är en enskild person som du skickar meddelanden till – deras namn, telefonnummer, e-post, kanal, taggar, anpassade fält samt de listor och kampanjer de tillhör. Contacts API låter dig skapa kontakter, söka upp dem, uppdatera dem, tagga dem, importera dem i bulk och ta bort dem, allt utan att använda kontrollpanelen.

Alla sökvägar på denna sida är relativa till bas-URL:en:

```
https://api.youraiconnector.com/v1
```

Så `/contacts` betyder `https://api.youraiconnector.com/v1/contacts`.

> **Ny med API:et?** Läs [API-åtkomst](../integrations/api-access.md) först – den täcker hur du genererar din API-nyckel, de tre sätten att autentisera, hastighetsbegränsningar och felformatet. Allt på denna sida förutsätter att du redan har en fungerande API-nyckel.

---

## Om kontakt-ID:n

Varje kontakt har ett unikt ID. Det ID du får tillbaka när du **skapar** en kontakt (i `data.contactId`) är samma ID som du använder överallt annars – för att hämta, uppdatera, tagga, skicka ett meddelande till eller ta bort den kontakten. Spara det en gång och återanvänd det.

Du behöver inte skapa en kontakt för att få dess ID. Du kan också söka upp ett ID via telefonnummer eller e-post (se [Hämta en kontakt](#get-a-contact-by-phone-or-email)), eller bläddra igenom alla dina kontakter (se [Lista kontakter](#list-contacts)). Var och en av dessa returnerar samma ID.

---

## Skapa en kontakt

`POST /contacts`

Lägger till en ny kontakt i ditt konto. Ett **telefonnummer med landskod krävs** – enbart e-post räcker inte. Allt annat är valfritt.

Du kan valfritt lägga till den nya kontakten direkt i en eller flera listor med `listId` (en enskild lista) eller `listIds` (en array). Om båda skickas, vinner `listIds`.

Alla fält du skickar som inte är ett av standardfälten för skapande som listas i tabellen **Skapa en kontakt** nedan (`phoneNumber`, `firstName`, `lastName`, `email`, `channel`, `is_bot_active`, `is_private`, `lead_profile`, `listId`, `listIds`, `custom_fields`) sparas automatiskt som ett **anpassat fält** — så en platt nyttolast från ett verktyg som Make eller Zapier fungerar utan nästling. Du kan också skicka ett explicit `custom_fields`-objekt.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `phoneNumber` | Ja | Kontaktens telefonnummer, med landskod (t.ex. `+15551234567`). |
| `firstName` | Nej | Förnamn. |
| `lastName` | Nej | Efternamn. |
| `email` | Nej | E-postadress. |
| `channel` | Nej | Meddelandekanal. En av `whatsapp`, `sms`, `whatsapp_web`. Standard är `whatsapp`. |
| `is_bot_active` | Nej | Huruvida AI-assistenten svarar på denna kontakt. Standard är `true`. |
| `is_private` | Nej | Markera kontakten som privat. När `true`, är AI-assistenten avstängd för dem. Standard är `false`. |
| `lead_profile` | Nej | Fritextanteckningar om leadet. |
| `listId` | Nej | Ett enskilt list-ID att lägga till kontakten i. |
| `listIds` | Nej | En array av list-ID:n att lägga till kontakten i (har företräde framför `listId`). |
| `custom_fields` | Nej | Ett objekt med dina egna nyckel/värde-fält. Du kan även skicka dessa som nycklar på toppnivå. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+15551234567",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "is_bot_active": true,
    "listIds": ["list123", "list456"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/contacts", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phoneNumber: "+15551234567",
    firstName: "Jane",
    lastName: "Smith",
    email: "jane@example.com",
    is_bot_active: true,
    listIds: ["list123", "list456"],
  }),
});
const data = await res.json();
console.log(data.data.contactId);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phoneNumber": "+15551234567",
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane@example.com",
        "is_bot_active": True,
        "listIds": ["list123", "list456"],
    },
)
print(res.json()["data"]["contactId"])
```

**Svar**

```json
{
  "success": true,
  "data": {
    "message": "Successfully created new contact",
    "contactId": "contact_abc123",
    "listsAdded": ["list123", "list456"]
  }
}
```

Den nya kontaktens ID finns i `data.contactId`. Listorna den lades till i återspeglas i `data.listsAdded`.

> **Duplikat skapas inte.** Om en kontakt med samma telefonnummer redan finns, skapar eller returnerar anropet för att skapa **inte** kontakten. Svaret kommer tillbaka med HTTP-status `200` och en `error_code` på `409` i brödtexten, så förgrena baserat på `error_code` snarare än på HTTP-statusen:
>
> ```json
> { "success": false, "error_code": 409, "error": "A contact with this phone number already exists for the current user." }
> ```
>
> För att arbeta med en befintlig kontakt efter en `error_code` på `409`, slå upp den med [Hämta en kontakt via telefon eller e-post](#get-a-contact-by-phone-or-email) — `GET /contacts?phoneNumber=...` — och återanvänd ID:t som returneras.

> **Motsvarande WhatsApp-stavningar räknas som samma nummer.** Vissa länder har två giltiga stavningar för samma mobilnummer och WhatsApp kan rapportera vilket som helst av dem: Mexiko (`+52…` och det äldre `+521…`), Brasilien (med eller utan den nionde siffran) och Argentina (med eller utan `9` efter `+54`). Dubblettkontrollen vid skapande och `GET /contacts?phoneNumber=` matchar mot båda stavningarna, så du får tillbaka den befintliga kontakten oavsett vilken form du skickar. Det `phone_number` som lagras på kontakten skrivs aldrig över.

---

## Hämta en kontakt via telefon eller e-post

`GET /contacts?phoneNumber=...` eller `GET /contacts?email=...`

Slår upp en enskild kontakt och returnerar det fullständiga, berikade kontaktobjektet — inklusive dess listor, taggar och kampanjer upplösta till `{ id, name }`-par, plus det senast utväxlade meddelandet.

Skicka **antingen** `phoneNumber` (i internationellt format) **eller** `email`. Om du inte skickar något av dem växlar samma slutpunkt istället till läget [Lista kontakter](#list-contacts).

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?phoneNumber=%2B15551234567&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const phone = encodeURIComponent("+15551234567");
const res = await fetch(`https://api.youraiconnector.com/v1/contacts?phoneNumber=${phone}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"phoneNumber": "+15551234567"},
)
print(res.json()["contact"])
```

**Svar**

```json
{
  "success": true,
  "contactId": "contact_abc123",
  "contact": {
    "id": "contact_abc123",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "phoneNumber": "+15551234567",
    "channel": "whatsapp",
    "isBotActive": true,
    "isPrivate": false,
    "doNotDisturb": false,
    "lead_profile": null,
    "avatarUrl": "https://example.com/photo.jpg",
    "customFields": {},
    "lists": [{ "id": "list123", "name": "VIP customers" }],
    "tags": [{ "id": "tagHotLead", "name": "Hot lead" }],
    "campaigns": [{ "id": "campaign789", "name": "Spring promo" }],
    "currentCampaign": { "id": "campaign789", "name": "Spring promo" },
    "lastMessage": {
      "direction": "inbound",
      "body": "Sounds good, thanks!",
      "status": "received",
      "timestamp": "2026-06-09T10:21:00.000Z"
    }
  }
}
```

Kontakt-ID:t returneras både på toppnivå (`contactId`) och inuti objektet (`contact.id`). Om inget matchar får du ett `404` med `{ "success": false, "message": "Contact not found" }`.

> **`avatarUrl`** är kontaktens profilfoto, hämtat från WhatsApp eller Meta när de skickar ett meddelande till dig. Det är skrivskyddat: du kan inte ställa in det, och det är `null` för kontakter som inte har något foto eller som når dig via en kanal som inte delar ett. Behandla länken som tillfällig istället för att lagra den, eftersom vissa av dessa fotolänkar löper ut och uppdateras automatiskt. (I list-slutpunkten nedan kallas samma värde för `avatar_url`.)

> **Telefonnummer i URL:er.** Ett `+`-tecken i en frågesträng måste vara URL-kodat som `%2B`, annars läses det som ett mellanslag. Exemplen ovan gör detta åt dig.

---

## Hämta en kontakt med ID

`GET /contacts/{contactId}`

När du redan har en kontakts ID kan du hämta den direkt. Svarsformatet är identiskt med sökningen ovan.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["contact"])
```

Ett kontakt-ID som inte finns på ditt konto returnerar ett `404`.

---

## Hämta kontaktstatistik

`GET /contacts/{contactId}/stats`

Returnerar sammanställd meddelandestatistik för en kontakt: totaler, AI- kontra mänskliga svar, förbrukade krediter samt tidsstämplar för första och sista meddelandet.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.totalMessages, data.creditsUsed);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["totalMessages"], data["creditsUsed"])
```

**Svar**

```json
{
  "success": true,
  "totalMessages": 48,
  "sent": 21,
  "received": 27,
  "aiReplies": 18,
  "humanReplies": 3,
  "creditsUsed": 34,
  "botMessageCount": 18,
  "firstMessageAt": "2026-05-01T09:00:00.000Z",
  "lastMessageAt": "2026-06-09T10:21:00.000Z"
}
```

`botMessageCount` är samma AI-meddelanderäknare som "återställ"-knappen i appen för en kontakt nollställer. `creditsUsed` är den löpande kredit-summan för denna kontakt, inte bara siffrorna för detta svar. Ett kontakt-ID som inte finns på ditt konto returnerar ett `404`.

---

## Lista kontakter

`GET /contacts`

Anropa `GET /contacts` **utan** varken `phoneNumber` eller `email` för att bläddra igenom alla dina kontakter, med de nyaste först. Varje sida returnerar kompakta kontaktsammanfattningar (listor, taggar och kampanjer returneras som ID-arrayer istället för fullständiga objekt) och en `next_cursor`.

| Frågeparameter | Beskrivning |
|---|---|
| `limit` | Sidstorlek. Standard är 50, max 100. |
| `cursor` | `next_cursor`-värdet från föregående sida. Utelämna på första sidan. |
| `listId` | Valfritt. Returnera endast kontakter som tillhör denna lista. |

För att gå igenom varje sida: gör det första anropet utan en markör (cursor), och fortsätt sedan att skicka tillbaka det returnerade `next_cursor`-värdet som `cursor`. **Stoppa när `next_cursor` är `null`** — det betyder att det inte finns några fler resultat.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&apiKey=YOUR_API_KEY"

# next page:
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&cursor=contact_abc123&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
async function listAllContacts() {
  const all = [];
  let cursor = null;
  do {
    const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
    const data = await res.json();
    all.push(...data.contacts);
    cursor = data.next_cursor;
  } while (cursor);
  return all;
}
```

**Python**

```python
import requests

def list_all_contacts():
    all_contacts = []
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        res = requests.get(
            "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
            headers={"X-API-Key": "YOUR_API_KEY"},
            params=params,
        )
        data = res.json()
        all_contacts.extend(data["contacts"])
        cursor = data["next_cursor"]
        if not cursor:
            break
    return all_contacts
```

**Svar**

```json
{
  "success": true,
  "contacts": [
    {
      "id": "contact_abc123",
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@example.com",
      "phone_number": "+15551234567",
      "channel": "whatsapp",
      "is_bot_active": true,
      "is_private": false,
      "do_not_disturb": false,
      "avatar_url": "https://example.com/photo.jpg",
      "custom_fields": {},
      "created_at": "2026-06-01T09:00:00.000Z",
      "list_ids": ["list123"],
      "tag_ids": ["tagHotLead"],
      "campaign_ids": ["campaign789"],
      "current_campaign_id": "campaign789"
    }
  ],
  "next_cursor": "contact_abc123"
}
```

::: note
**Obs:** Filtrering med en `listId` som inte finns på ditt konto returnerar en `404`. En ogiltig `cursor` returnerar en `400`.
:::


---

## Räkna kontakter

`GET /contacts/count`

Returnerar hur många kontakter som matchar ett filter, plus en uppdelning per kanal, utan att behöva bläddra igenom dem. Detta är rätt anrop för alla "hur många"-frågor — en instrumentpanel, en automatisering eller när du frågar Champ. Alla filter är valfria, och att kombinera flera begränsar antalet (en kontakt måste matcha alla du skickar med).

| Frågeparameter | Beskrivning |
|---|---|
| `agentId` | Endast kontakter tilldelade denna AI-agent. Skicka `none` för kontakter utan tilldelad agent (dessa besvaras av kanalens standardagent). |
| `channel` | Endast kontakter på denna kanal, t.ex. `whatsapp`, `messenger`, `instagram`, `sms`, `email`, `chat_widget`. |
| `tag` | Endast kontakter med denna tagg, baserat på taggens **namn** (skiftlägesoberoende). Ett taggnamn du inte har returnerar `404`. |
| `listId` | Endast kontakter på denna lista. |
| `botActive` | `true` eller `false` — endast kontakter vars AI-assistent är på eller av. |
| `status` | Endast kontakter med denna status, t.ex. `Lead`. |
| `rules` | Ett URL-kodat JSON-regelobjekt, med samma form som en smart lista (se [Formen för `smart_rules`](#the-smart_rules-shape) längre ner). Kan inte kombineras med de andra filtren. |

Skicka inget filter alls så får du det totala antalet kontakter på ditt konto.

**cURL**

```bash
# everything
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?apiKey=YOUR_API_KEY"

# only the contacts one agent handles on Messenger
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?agentId=agent_xyz789&channel=messenger&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count");
url.searchParams.set("agentId", "agent_xyz789");
url.searchParams.set("channel", "messenger");

const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
const data = await res.json();
console.log(data.total);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"agentId": "agent_xyz789", "channel": "messenger"},
)
data = res.json()
print(data["total"])
```

**Svar**

```json
{
  "success": true,
  "total": 3423,
  "by_channel": { "messenger": 2744, "instagram": 667, "none": 12 },
  "filters": { "agentId": "agent_xyz789" }
}
```

`by_channel` delar upp samma total per kanal; kontakter som inte finns på någon kanal räknas under `none`. `filters` återspeglar de filter som tillämpades, så att du kan kontrollera att anropet gjorde vad du avsåg.

::: note
**Obs:** Om du skickar `rules` tillsammans med något annat filter, eller ett `rules`-värde som inte är giltig JSON, returneras `400`. Ett taggnamn eller list-ID som inte finns på ditt konto returnerar `404`.
:::


---

## Uppdatera en kontakt

`PUT /contacts/{contactId}`

Uppdaterar en befintlig kontakt. Endast de fält du inkluderar ändras — utelämna allt du inte vill ändra. Du måste skicka minst ett fält, annars får du ett `400` ("Inga fält att uppdatera").

| Fält | Beskrivning |
|---|---|
| `firstName` | Förnamn. |
| `lastName` | Efternamn. |
| `email` | E-postadress. |
| `is_bot_active` | Huruvida AI-assistenten svarar på denna kontakt. |
| `is_private` | Markera som privat. Att ställa in detta till `true` stänger även av AI-assistenten. |
| `do_not_disturb` | Pausa automatiserad kontakt med denna person. Stoppar även AI:n från att svara. |
| `follow_ups_disabled` | Stoppa alla automatiserade uppföljningar för denna kontakt (snabba, cykel och kalla leads) medan AI:n fortsätter att svara på meddelanden de skickar. Användbart när någon har köpt. Förblir avstängt tills du ställer in det tillbaka till `false`. |
| `lead_profile` | Fritextanteckningar om leadet. |
| `custom_fields` | Ett objekt med anpassade fält. **Slås samman per nyckel** — endast nycklarna du skickar skrivs, resten av de befintliga anpassade fälten behålls. Du kan även skicka nycklar för anpassade fält på toppnivå. |

**cURL**

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "firstName": "Jane", "do_not_disturb": true }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ firstName: "Jane", do_not_disturb: true }),
});
const data = await res.json();
console.log(data.message);
```

**Python**

```python
import requests

res = requests.put(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"firstName": "Jane", "do_not_disturb": True},
)
print(res.json()["message"])
```

**Svar**

```json
{
  "success": true,
  "message": "Contact updated successfully"
}
```

> **Anpassade fält slås samman, de ersätts inte.** Att skicka `{ "custom_fields": { "tier": "gold" } }` sätter endast `tier` — alla andra anpassade fält på kontakten förblir precis som de var. För att ta bort ett anpassat fält helt från alla kontakter, använd [Ta bort ett anpassat fält](#delete-a-custom-field).

---

## Lägg till eller ta bort taggar

`POST /contacts/{contactId}/tags`

Lägger till och/eller tar bort taggar på en enskild kontakt i ett anrop. Skicka tagg-**ID:n** i `addTagIds` och `removeTagIds`. Minst en av de två måste vara icke-tom.

Taggarna måste redan finnas på ditt konto — skapa dem först via [tagg-slutpunkten](reference.md). Om kontakten eller någon refererad tagg inte finns, får du ett `404`.

| Fält | Beskrivning |
|---|---|
| `addTagIds` | Array med tagg-ID:n som ska läggas till kontakten. |
| `removeTagIds` | Array med tagg-ID:n som ska tas bort från kontakten. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    addTagIds: ["tagHotLead"],
    removeTagIds: ["tagColdLead"],
  }),
});
const data = await res.json();
console.log(data.added, data.removed);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"]},
)
data = res.json()
print(data["added"], data["removed"])
```

**Svar**

```json
{
  "success": true,
  "contact_id": "contact_abc123",
  "added": 1,
  "removed": 1
}
```

---

## Hantera ditt taggbibliotek

Dessa slutpunkter hanterar själva taggen — att byta namn på eller ta bort den från ditt konto — till skillnad från att lägga till eller ta bort en tagg på en enskild kontakt (se [Lägg till eller ta bort taggar](#add-or-remove-tags) ovan). Varje tagg på ditt konto har ett ID (`tagId`): det som visas i din kontrollpanels tagghanterare, och det som returneras som `data.tag_id` när du skapar en tagg med `POST /tags` och en JSON-kropp med `{ "name": "..." }` (inga `phoneNumber`, `email` eller `contactId`).

### Uppdatera en tagg

`PUT /tags/{tagId}`

Skicka endast de fält du ändrar.

| Fält | Beskrivning |
|---|---|
| `name` | Taggens namn. |

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagHotLead?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hot lead (Q3)" }'
```

**Svar**

```json
{ "success": true, "tag_id": "tagHotLead" }
```

Ett `tagId` som inte finns på ditt konto returnerar ett `404`.

### Ta bort en tagg

`DELETE /tags/{tagId}`

Tar bort en tagg via ID. **Detta kan inte ångras** — kontakter som har taggen förlorar den helt enkelt. Att ta bort en tagg som redan är borta (eller aldrig har funnits) returnerar `200` med `deleted: 0` istället för ett `404`, eftersom det inte finns något att räkna upp.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagColdLead?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{ "success": true, "deleted": 1 }
```

### Ta bort flera taggar samtidigt

`DELETE /tags`

| Fält | Beskrivning |
|---|---|
| `tagIds` | Array med tagg-ID:n som ska tas bort (max 1000). |

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tagIds": ["tagColdLead", "tagUnsubscribed"] }'
```

**Svar**

```json
{ "success": true, "deleted": 2 }
```

ID:n som inte existerar, eller som tillhör ett annat konto, hoppas över tyst och räknas inte i `deleted`.

---

## Ange flagga för flera kontakter

`POST /contacts/bulk-flag`

Anger en boolesk flagga för många kontakter samtidigt. Upp till 500 kontakt-ID:n per förfrågan. ID:n som inte finns på ditt konto hoppas över och räknas i `skipped`.

| Fält | Beskrivning |
|---|---|
| `contactIds` | Array med kontakt-ID:n som ska uppdateras (max 500). |
| `field` | Vilken flagga som ska anges. En av `bot_active` (AI-assistent på/av), `dnd` (pausa automatiserad kontakt), `spam`, `private`. |
| `value` | Det booleska värdet som flaggan ska sättas till. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contactIds": ["contactId1", "contactId2"],
    "field": "bot_active",
    "value": false
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contactIds: ["contactId1", "contactId2"],
    field: "bot_active",
    value: false,
  }),
});
const data = await res.json();
console.log(data.updated, data.skipped);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contactIds": ["contactId1", "contactId2"],
        "field": "bot_active",
        "value": False,
    },
)
data = res.json()
print(data["updated"], data["skipped"])
```

**Svar**

```json
{
  "success": true,
  "updated": 2,
  "skipped": 0
}
```

---

## Massimportera kontakter

`POST /contacts/import`

Skapar upp till 500 kontakter i ett anrop från en JSON-array. Varje post kräver ett `phone_number` i internationellt format; allt annat är valfritt. Poster med ogiltiga telefonnummer eller kanaler som inte stöds **hoppas över** (skapas inte), och varje post som hoppas över rapporteras med sitt index och orsak — så att du bara kan åtgärda felen och försöka igen.

Telefonnummer som redan finns på ditt konto hoppas som standard över som `duplicate`. Skicka `updateExisting: true` för att istället **uppdatera** dessa kontakter: fälten som finns i posten skriver över kontaktens (`first_name`, `last_name`, `email`, `lead_profile` och `custom_fields` slås samman nyckel för nyckel), `tags` läggs till och kontakten läggs till i `listId`. Kanal, telefonnummer och bot-flaggor ändras aldrig på en befintlig kontakt.

Du kan valfritt lägga till varje importerad (eller uppdaterad) kontakt i en lista med `listId`, ange en `defaultChannel` för poster som inte anger en, och tagga poster med `tags` (taggnamn — saknade taggar skapas, befintliga matchas oberoende av skiftläge).

**Fält på toppnivå**

| Fält | Krävs | Beskrivning |
|---|---|---|
| `contacts` | Ja | Array med kontaktposter (max 500). |
| `listId` | Nej | Lista att lägga till varje importerad (och uppdaterad) kontakt i. Måste vara en lista på ditt konto. |
| `defaultChannel` | Nej | Kanal som tillämpas på poster som utelämnar `channel`. En av `whatsapp`, `sms`, `whatsapp_web`. Standard är `whatsapp`. |
| `updateExisting` | Nej | `true` för att uppdatera kontakter vars telefonnummer redan finns istället för att hoppa över dem som `duplicate`. Standard är `false`. |

**Fält per post**

| Fält | Krävs | Beskrivning |
|---|---|---|
| `phone_number` | Ja | Telefonnummer i internationellt format (ett inledande `+` läggs till om det saknas). |
| `first_name` | Nej | Förnamn. |
| `last_name` | Nej | Efternamn. |
| `email` | Nej | E-postadress. |
| `channel` | Nej | En av `whatsapp`, `sms`, `whatsapp_web`. Faller tillbaka på `defaultChannel`. |
| `is_bot_active` | Nej | Om AI-assistenten svarar. Standard är `true`. |
| `is_private` | Nej | Markera som privat. Standard är `false`. |
| `lead_profile` | Nej | Fritextanteckningar för lead. |
| `custom_fields` | Nej | Objekt med nycklar och värden för anpassade fält. |
| `tags` | Nej | Array med taggnamn (en enskild `"a; b"`-sträng fungerar också). Taggar som inte finns skapas; befintliga matchas utan att ta hänsyn till skiftläge. Max 25 per post. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      { "phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"] },
      { "phone_number": "+12025551235", "first_name": "Bob" }
    ],
    "listId": "list123",
    "defaultChannel": "whatsapp_web",
    "updateExisting": true
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contacts: [
      { phone_number: "+12025551234", first_name: "Ann", last_name: "Lee", tags: ["vip", "newsletter"] },
      { phone_number: "+12025551235", first_name: "Bob" },
    ],
    listId: "list123",
    defaultChannel: "whatsapp_web",
    updateExisting: true,
  }),
});
const data = await res.json();
console.log(`Imported ${data.imported}, updated ${data.updated}, skipped ${data.skipped.length}`);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contacts": [
            {"phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"]},
            {"phone_number": "+12025551235", "first_name": "Bob"},
        ],
        "listId": "list123",
        "defaultChannel": "whatsapp_web",
        "updateExisting": True,
    },
)
data = res.json()
print(f"Imported {data['imported']}, updated {data['updated']}, skipped {len(data['skipped'])}")
```

**Svar**

```json
{
  "success": true,
  "imported": 2,
  "contact_ids": ["contact_abc123", "contact_def456"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": []
}
```

Om vissa poster inte kan skapas visas de i `skipped` med orsaken (här utan `updateExisting`, så det befintliga numret hoppas över):

```json
{
  "success": true,
  "imported": 1,
  "contact_ids": ["contact_abc123"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": [
    { "index": 1, "phone_number": "+12025551235", "reason": "duplicate" }
  ]
}
```

Med `updateExisting: true` rapporterar samma begäran den befintliga kontakten under `updated` / `updated_contact_ids` istället.

Möjliga orsaker till att poster hoppas över: `invalid_record`, `missing_phone_number`, `invalid_phone_number`, `invalid_channel`, `duplicate_in_request`, `duplicate`, `contact_limit_reached`, `create_failed`.

> **Planbegränsningar.** Om din plans kontaktgräns inte tillåter så här många nya kontakter, avvisas hela förfrågan direkt med ett `403`. Om gränsen nås halvvägs, returneras de återstående posterna som hoppade över med orsaken `contact_limit_reached`.

---

## Importera kontakter från en CSV-fil

För importer som är större än vad [massimport](#bulk-import-contacts) stöder (upp till cirka 50 000 rader), köa ett asynkront importjobb mot en CSV-fil som redan finns i ditt kontos lagring, och polla sedan jobbet tills det är slutfört.

### Starta importen

`POST /contacts/import-csv`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `csvStoragePath` | Ja | Lagringssökväg för CSV-filen, under `users/{your account id}/imports/`, som slutar på `.csv`. |
| `listName` | Ja | Skapar (eller återanvänder) en lista med detta namn och lägger till varje importerad kontakt i den. |
| `existingListRefs` | Nej | Array med befintliga list-ID:n som varje importerad kontakt också ska läggas till i. |
| `defaultChannel` | Nej | Kanal som tillämpas på rader som inte anger någon. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csvStoragePath": "users/abc123/imports/leads.csv",
    "listName": "Webinar signups"
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    csvStoragePath: "users/abc123/imports/leads.csv",
    listName: "Webinar signups",
  }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csvStoragePath": "users/abc123/imports/leads.csv",
        "listName": "Webinar signups",
    },
)
job_id = res.json()["job_id"]
```

**Svar** (`202` — importen är köad, inte färdigställd än)

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "queued"
}
```

> **Att få in filen i lagringen.** Denna slutpunkt startar och spårar importjobbet; den accepterar inte en uppladdning i sig. CSV-filen måste redan finnas på `csvStoragePath` innan du anropar den — instrumentpanelens egen CSV-importör gör detta som sitt första steg.

### Polla importjobbet

`GET /contacts/import-csv/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv/csvimp_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "completed",
  "imported": 812,
  "updated": 0,
  "skipped": 14,
  "errors": [],
  "error_message": null
}
```

`status` rör sig genom `queued` → `processing` → `completed`, eller `failed` med orsaken i `error_message`. Ett `jobId` som inte finns på ditt konto returnerar ett `404`.

---

## Exportera kontakter

Startar en asynkron CSV-export av dina kontakter och returnerar ett jobb som du pollar för slutförande.

### Starta exporten

`POST /contacts/export`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `listId` | Nej | Exportera endast kontakter som tillhör denna lista. |
| `contactIds` | Nej | Exportera endast dessa specifika kontakt-ID:n. |

Om båda utelämnas exporteras varje kontakt på ditt konto.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "listId": "list123" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ listId: "list123" }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"listId": "list123"},
)
job_id = res.json()["job_id"]
```

**Svar** (`202` — exporten ligger i kö)

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "queued"
}
```

### Fråga efter exportjobbets status

`GET /contacts/export/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export/export_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "completed",
  "export_id": "exp_xyz789",
  "contact_count": 812,
  "error_message": null
}
```

> När `status` är `"completed"` får du `export_id` och `contact_count`. Nedladdning av den genererade CSV-filen sker från din kontrollpanels sida för exporter.

---

## Skicka ett meddelande till en kontakt

`POST /contacts/{contactId}/send-message`

Skickar ett meddelande till en befintlig kontakt via den kanal de redan använder. Meddelandet läggs i kö och levereras i bakgrunden — svaret bekräftar att det har tagits emot, inte att det har levererats än.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `body` | Ja | Texten i meddelandet som ska skickas. |
| `mediaUrl` | Nej | URL till en mediefil som ska bifogas. |
| `mediaContentType` | Nej | MIME-typ för den bifogade filen (t.ex. `image/jpeg`). |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Hi! Your appointment is confirmed." }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ body: "Hi! Your appointment is confirmed." }),
});
const data = await res.json();
console.log(data.messageId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"body": "Hi! Your appointment is confirmed."},
)
print(res.json()["messageId"])
```

**Svar**

```json
{
  "success": true,
  "messageId": "aB3dE5fG7hI9jK1lM2nO",
  "contactId": "contact_abc123",
  "channel": "whatsapp",
  "message": "Message created successfully. Delivery is being processed."
}
```

> **Kan du inte skicka just nu?** Om kontakten har aktiverat stör ej-läge eller privat läge, eller inte befinner sig på en kanal som kan ta emot utgående meddelanden, avvisas begäran med en `422` och en förklarande `error`.

För att skicka via telefonnummer, Instagram-ID eller annan kanalidentitet istället för ett kontakt-ID — och för mer information om meddelanden i allmänhet — se [Messages API](messages.md).

---

## Tilldela en AI-agent till en kontakt

`POST /contacts/{contactId}/assign-agent`

Flyttar en befintlig konversation till en annan AI-agent, från och med nästa meddelande. Det är samma sak som **Tilldela AI-agent** i en chatts meny, och samma steg som åtgärden **Tilldela AI-agent eller kampanj** använder i Automatiseringar.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `agentId` | Ja | ID för den AI-agent som ska ta över, eller `null` för att rensa tilldelningen så att konversationen går tillbaka till din team-inkorg. |
| `triggerAIResponse` | Nej | `true` gör att den nyligen tilldelade agenten svarar på kontaktens senaste obesvarade meddelanden direkt. Standardvärdet är `false`. |

> **Var försiktig med `triggerAIResponse: true`** — det skickar ett meddelande till kontakten direkt, så använd det bara när du vill att de ska kontaktas nu. På Messenger och Instagram misslyckas meddelandet om kontakten senast skrev till dig för mer än 24 timmar sedan.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "agent_xyz789" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ agentId: "agent_xyz789" }),
});
const data = await res.json();
console.log(data.data.agentId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"agentId": "agent_xyz789"},
)
print(res.json()["data"]["agentId"])
```

**Svar**

```json
{
  "success": true,
  "data": {
    "contactId": "contact_abc123",
    "agentId": "agent_xyz789",
    "aiResponseTriggered": false
  }
}
```

> Agenten måste tillhöra samma konto som kontakten; annars avvisas begäran med ett `404` eller `403`. Hitta agent-ID:n på sidan för AI-agenter (varje agents URL slutar med dess ID).

---

## Tilldela en AI-agent till många kontakter

`POST /contacts/bulk-assign-agent`

Flyttar många konversationer till en annan AI-agent i ett anrop — eller rensar tilldelningen för alla med `null`. Det är en ren routingsändring: **inget meddelande skickas och agenten svarar inte någon**. Varje kontakt får helt enkelt den nya agenten nästa gång de skriver. (Det är därför det inte finns något `triggerAIResponse` här.)

| Fält | Krävs | Beskrivning |
|---|---|---|
| `agentId` | Ja | AI-agenten som ska ta över, eller `null` för att rensa tilldelningen. |
| `contactIds` | En av tre | Upp till 500 kontakt-ID:n att flytta. |
| `filter` | En av tre | Välj kontakterna på servern istället för att lista dem, de nyaste först. Använder samma nycklar som räkne-endpointens filter: `agentId` (eller `none`), `channel`, `tag`, `listId`, `botActive`, `status`. |
| `rules` | En av tre | Ett regelobjekt för smart lista — se [Formen för `smart_rules`](#the-smart_rules-shape). |
| `limit` | Nej | Hur många kontakter som ska flyttas i detta anrop när du väljer med `filter` eller `rules`. 1 till 500, standard är 500. |

Skicka exakt en av `contactIds`, `filter` eller `rules`.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent_xyz789",
    "filter": { "agentId": "agent_abc123", "channel": "messenger" }
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agentId: "agent_xyz789",
    filter: { agentId: "agent_abc123", channel: "messenger" },
  }),
});
const data = await res.json();
console.log(data.updated, data.remaining);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "agentId": "agent_xyz789",
        "filter": {"agentId": "agent_abc123", "channel": "messenger"},
    },
)
data = res.json()
print(data["updated"], data["remaining"])
```

**Svar**

```json
{
  "success": true,
  "agentId": "agent_xyz789",
  "matched": 3415,
  "updated": 500,
  "skipped": 0,
  "remaining": 2915,
  "filters": { "agentId": "agent_abc123" }
}
```

`matched` är hur många kontakter urvalet hittade totalt, `updated` hur många som flyttades av detta anrop, `skipped` hur många av de ID:n du skickade som inte hittades på ditt konto, och `remaining` hur många som fortfarande matchar nu när anropet är klart.

**Flytta alla.** Eftersom ett anrop flyttar högst 500 kontakter krävs några anrop för en stor grupp. Använd ett filter som slutar matcha en kontakt när den väl har flyttats — till exempel `filter: { "agentId": "agent_abc123" }` medan du tilldelar till `agent_xyz789` — och upprepa exakt samma anrop tills `remaining` returneras som `0`. När du skickar `contactIds` istället är `remaining` alltid `0`.

---

## Tilldela en kontakt till en avdelning

`POST /contacts/{contactId}/department`

"Tilldela detta lead till försäljningsavdelningen" — arkiverar en kontakt under en namngiven avdelning och ger den som standard till den person på avdelningen som för närvarande har färst kontakter. Detta är skilt från att [tilldela en AI-agent](#assign-an-ai-agent-to-a-contact): en avdelning svarar på "vilket team äger detta", en agent svarar på "vilken AI svarar på detta", och att ställa in den ena rensar aldrig den andra.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `department_id` | Ja | Avdelningen som kontakten ska arkiveras under. Skicka `null` för att rensa den. |
| `hand_to_member` | Nej | Ge även kontakten till den person på avdelningen som har minst arbetsbelastning. Standardvärdet är `true`. Omplacerar aldrig en kontakt som någon redan äger. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "department_id": "dept_sales" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ department_id: "dept_sales" }),
});
const data = await res.json();
console.log(data.assigned_to);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"department_id": "dept_sales"},
)
print(res.json()["assigned_to"])
```

**Svar**

```json
{
  "success": true,
  "department_id": "dept_sales",
  "assigned_to": "member_uid_123"
}
```

`assigned_to` är `null` när kontakten redan ägdes av någon, eller om du skickade `hand_to_member: false`.

---

## Länka en kontakt över kanaler

"Fortsätt på WhatsApp" (eller SMS) hittar eller skapar personens kontakt i en annan telefonbaserad kanal och länkar samman de två, så att resten av appen känner igen dem som samma person.

### Länka till en annan kanal

`POST /contacts/{contactId}/link-channel`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `channel` | Ja | Kanalen att länka till. En av `whatsapp`, `whatsapp_web`, `sms`. |
| `phoneNumber` | Nej | Telefonnummer som ska användas på den nya kanalen. Standard är källkontaktens eget nummer. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link-channel?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "sms" }'
```

**Svar**

```json
{
  "success": true,
  "data": {
    "contact_id": "contact_def456",
    "person_id": "person_xyz789",
    "created": true
  }
}
```

`created` anger om en ny kontakt skapades för målkanalen eller om en befintlig hittades och länkades. Det är säkert att anropa detta en andra gång — det returnerar samma `contact_id` med `created: false` istället för att skapa en dubblett.

Ett `422` innebär att kontot inte kan utföra denna länkning just nu: kontakten finns redan i den kanalfamiljen, den saknar telefonnummer att använda, eller så finns ingen ansluten avsändare för målkanalen. Ett `409` innebär att de två kontakterna redan är länkade till två olika personer — koppla bort en först.

### Lista en kontakts länkade konversationer

`GET /contacts/{contactId}/linked`

Returnerar de andra konversationer som tillhör samma person som denna kontakt. En olänkad kontakt returnerar en tom array, inte ett `404` — "denna person har inga andra kanaler" är ett normalt tillstånd.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/linked?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "data": [
    {
      "contact_id": "contact_def456",
      "channel": "sms",
      "custom_channel": null,
      "first_name": "Jane",
      "last_name": "Smith",
      "phone_number": "+15551234567",
      "last_message": "Sounds good, thanks!",
      "last_message_timestamp": "2026-06-09T10:21:00.000Z",
      "linked_from": {
        "contact_id": "contact_abc123",
        "channel": "whatsapp",
        "linked_at": "2026-06-01T09:00:00.000Z",
        "reason": "continue_on_channel"
      }
    }
  ]
}
```

### Koppla bort en kontakt

`DELETE /contacts/{contactId}/link`

Tar bort denna kontakt från sin person, ensidigt — alla andra kontakter som fortfarande är länkade till den personen behåller sin länk, så att koppla bort en av tre upplöser inte gruppen.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link?apiKey=YOUR_API_KEY"
```

**Svar**

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

---

## Hämta en kontakts profilbild

`POST /contacts/{contactId}/profile-pic`

Hämtar (och cachar) kontaktens WhatsApp- eller Meta-profilfoto på begäran — samma foto som returneras som `avatarUrl` vid [Hämta en kontakt](#get-a-contact-by-phone-or-email), uppdaterat.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/profile-pic?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "avatar_url": "https://example.com/photo.jpg",
  "cached": false
}
```

`cached: true` innebär att URL:en kom från en nyligen utförd hämtning snarare än en färsk sökning hos leverantören — bilder cachas i 7 dagar, och en kontakt som leverantören rapporterar saknar tillgängligt foto cachas som otillgänglig i 24 timmar. När det inte finns någon bild att hämta utelämnas `avatar_url` och `message` förklarar varför.

---

## Tagga kontakter automatiskt med AI

Kör kontots taggningsregler över en eller flera kontakters fullständiga konversationshistorik och lägger till (eller tar bort) taggar på exakt samma sätt som realtidstaggningsfunktionen som körs under en livechatt — samma regler, samma kreditkostnad per tagg.

### Starta en körning

`POST /contacts/auto-tag`

| Fält | Krävs | Beskrivning |
|---|---|---|
| `scope` | Ja | `"contacts"` för att tagga specifika kontakter, eller `"agent"` för att tagga varje konversation som för närvarande hanteras av en AI-agent. |
| `contact_ids` | Krävs när `scope` är `"contacts"` | Array av kontakt-ID:n, 1 till 500. |
| `agent_id` | Krävs när `scope` är `"agent"` | AI-agenten vars konversationer ska taggas. När `scope` är `"contacts"` är detta valfritt och begränsar bara vilka av agentens taggningsregler som körs. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "contacts", "contact_ids": ["contact_abc123", "contact_def456"] }'
```

En **enskild** kontakt körs inline och returnerar resultatet direkt:

```json
{ "success": true, "result": { "tags_applied": 2, "tags_removed": 0 } }
```

**Två eller fler** kontakter (eller `scope: "agent"`) körs som ett bakgrundsjobb och returnerar `202` omedelbart:

```json
{ "success": true, "run_id": "m1x2y3-a1b2c3d4", "total": 214 }
```

### Fråga efter status för en körning

`GET /contacts/auto-tag/run`

Returnerar kontots nuvarande (eller senaste) körning, så att du kan kontrollera förloppet utan att själv behöva spåra `run_id`.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag/run?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "run": {
    "run_id": "m1x2y3-a1b2c3d4",
    "status": "running",
    "total": 214,
    "processed": 58,
    "tagged_contacts": 12,
    "tags_applied": 15,
    "tags_removed": 2,
    "credits_charged": 15
  }
}
```

`run` är `null` när kontot aldrig har startat en körning. `status` flyttas från `"running"` till `"completed"` eller `"failed"`.

Endast en masskörning kan vara igång per konto åt gången — att starta en andra körning medan en annan pågår returnerar `409` med `error_code: "auto_tag_run_in_progress"`. Om krediter tar slut vid en körning för en enskild kontakt returneras `402` med `error_code: "insufficient_credits"`; en masskörning stoppar istället sig själv i förtid och rapporterar hur långt den kom i `run`.

---

## Ta bort en kontakt

`DELETE /contacts/{contactId}`

Tar permanent bort en kontakt via ID, tillsammans med dess meddelandehistorik. **Detta kan inte ångras.** För att ta bort flera kontakter i ett enda anrop, använd [Ta bort kontakter](#delete-contacts) nedan.

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  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(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**Svar**

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

Ett kontakt-ID som inte finns på ditt konto, eller som tillhör ett annat konto, returnerar ett `404`.

---

## Ta bort kontakter

`DELETE /contacts`

Tar permanent bort en eller flera kontakter via ID i ett enda anrop (upp till 500 ID:n). ID:n som inte finns på ditt konto hoppas över och räknas i `skipped`. **Detta kan inte ångras.**

| Fält | Beskrivning |
|---|---|
| `contactIds` | Array med kontakt-ID:n som ska tas bort (max 500). |

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["contactId1", "contactId2"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ contactIds: ["contactId1", "contactId2"] }),
});
const data = await res.json();
console.log(`Deleted ${data.deleted}, skipped ${data.skipped}`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactIds": ["contactId1", "contactId2"]},
)
data = res.json()
print(f"Deleted {data['deleted']}, skipped {data['skipped']}")
```

**Svar**

```json
{
  "success": true,
  "deleted": 2,
  "skipped": 0
}
```

---

## Ta bort ett anpassat fält

`DELETE /contacts/custom-fields/{fieldKey}`

Tar bort en anpassad fältnyckel från **varje** kontakt på ditt konto. Använd detta för att städa upp efter att ha döpt om eller tagit bort ett anpassat fält. Nyckeln får endast innehålla bokstäver, siffror, understreck och bindestreck. Returnerar hur många kontakter som uppdaterades. **Detta kan inte ångras.**

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(`Removed from ${data.updated} contacts`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(f"Removed from {res.json()['updated']} contacts")
```

**Svar**

```json
{
  "success": true,
  "updated": 42
}
```

::: note
**Obs:** En fältnyckel med tecken som inte stöds returnerar en `400`.
:::


---

## Listor

Listor grupperar kontakter. En lista är antingen **statisk** (du bestämmer vem som finns på den) eller **smart** (medlemskap beräknas utifrån regler och hålls automatiskt uppdaterat — se [Organisera listor & kontakter](../get-started/list-and-contact-management.md#smart-lists-auto-updating)).

| Fält | Beskrivning |
|---|---|
| `name` | Krävs vid skapande. Upp till 100 tecken. |
| `status` | `live` (standard) eller `draft`. Gemener. |
| `contact_ids` | Array med kontakt-ID:n att lägga till i listan. **Endast statiska listor.** |
| `type` | `static` (standard) eller `smart`. |
| `smart_rules` | Regeluppsättningen — krävs när `type` är `smart`. Se nedan. |

### Skapa en lista

`POST /lists`

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Hot leads (active)",
        "type": "smart",
        "smart_rules": {
          "match": "all",
          "conditions": [
            { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
            { "field": "last_activity_at", "op": "within_last", "value": { "amount": 90, "unit": "days" } }
          ]
        }
      }'
```

**Svar**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 3, "removed": 0, "total": 3 }
}
```

En smart lista utvärderas **inline**, i samma anrop, så `evaluation` visar exakt vilka som hamnade på den. För en statisk lista är `evaluation` `null`.

### Uppdatera en lista

`PUT /lists/{listId}`

Skicka endast de fält du ändrar. Om du ändrar `smart_rules` utvärderas listan omedelbart på nytt och returnerar samma `evaluation`-objekt.

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/list_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "any", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead", "tagWebinar"] } ] } }'
```

Du kan växla en lista mellan de två typerna:

- **Statisk → smart**: skicka `{ "type": "smart", "smart_rules": { … } }`. Reglerna tar över omedelbart.
- **Smart → statisk**: skicka `{ "type": "static" }`. Reglerna tas bort och de som finns på listan stannar kvar.

### Strukturen för `smart_rules`

```json
{
  "match": "all",
  "conditions": [
    { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
    { "field": "channel", "op": "is_any", "value": ["whatsapp", "sms"] },
    { "field": "last_incoming_message_at", "op": "not_within_last", "value": { "amount": 7, "unit": "days" } },
    { "field": "created_at", "op": "after", "value": "2026-01-01" },
    { "field": "is_bot_active", "op": "is", "value": true },
    { "field": "email", "op": "is_set" },
    { "field": "custom_field", "key": "Plan", "op": "eq", "value": "pro" }
  ]
}
```

- `match` — `all` (alla villkor måste vara sanna) eller `any` (minst ett).
- `conditions` — 1 till 20 villkor, varje med högst 100 värden, strängar på upp till 200 tecken.

| `field` | `op` | `value` |
|---|---|---|
| `tags` | `has_any`, `has_all`, `has_none` | matris med tagg-ID:n |
| `lists` | `in_any`, `not_in_any` | matris med list-ID:n (**endast statiska listor** — en smart lista kan inte byggas från en annan smart lista) |
| `channel` | `is_any`, `is_none` | matris med kanaler |
| `status` | `is_any`, `is_none` | matris med kontaktstatusar |
| `created_at`, `last_activity_at`, `last_incoming_message_at`, `last_outgoing_message_at`, `first_ai_interaction_at`, `last_ai_interaction_at` | `within_last`, `not_within_last` | `{ "amount": 1–3650, "unit": "hours" \| "days" }` |
| samma datumfält | `before`, `after` | ISO-datum (`"2026-01-01"`, jämförs som hela dagar) eller fullständigt ISO-datum/tid (`"2026-01-01T14:30:00Z"`, jämförs med det exakta ögonblicket) |
| samma datumfält | `is_set`, `not_set` | — |
| `has_interacted_with_ai` | `is` | `true` / `false` — `true` matchar kontakter som AI:n har skickat meddelanden till minst en gång (någonsin) |
| `is_bot_active`, `do_not_disturb`, `is_private`, `has_ever_responded` | `is` | `true` / `false` |
| `email`, `phone_number`, `first_name`, `last_name` | `is_set`, `not_set`, `contains`, `not_contains` | sträng för `contains`-formulären |
| `current_campaign_id`, `assigned_agent` | `is_any`, `is_none`, `is_set`, `not_set` | matris med ID:n för `is_any` / `is_none`-formulären |
| `custom_field` (plus ett `key`) | `eq`, `neq`, `contains`, `not_contains`, `is_set`, `not_set` | sträng för värdeformulären |

`not_within_last` matchar även kontakter där datumet aldrig har angetts ("mer än N sedan, **eller aldrig**"), och textjämförelser ignorerar skiftläge.

**AI-engagemang.** `has_interacted_with_ai` är livstidsflaggan: `true` för varje kontakt som din AI har skickat minst ett meddelande till, `false` för alla andra (inklusive kontakter som bara ditt team någonsin har svarat). Den stämplas vid AI:ns första meddelande till en kontakt och rensas aldrig, så att stänga av AI-svar för kontakten eller flytta dem till en annan kampanj återställer den inte. För en *period* — "kontakterna min AI hanterade denna månad", den vanliga faktureringsfrågan — använd istället intervallet `last_ai_interaction_at`:

```json
{ "field": "last_ai_interaction_at", "op": "within_last", "value": { "amount": 30, "unit": "days" } }
```

Blanda inte ihop någon av dem med `is_bot_active` (AI:n *får* svara, inte att den har gjort det) eller `has_ever_responded` (kontakten *svarade*, till vem som helst). Samma två stämplar returneras för varje kontakt som `first_ai_interaction_at` / `last_ai_interaction_at`, och hela regeluppsättningen fungerar även på `GET /contacts?rules=`, så du kan räkna matchningar utan att skapa en lista.

### Förhandsgranska en regeluppsättning

`POST /lists/preview`

Räknar och visar exempel på de kontakter som en regeluppsättning skulle matcha, utan att skapa eller ändra någonting. Använd detta för att kontrollera regler innan du sparar dem.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/preview?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "all", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead"] } ] } }'
```

**Svar**

```json
{
  "success": true,
  "count": 3,
  "sample": [
    {
      "id": "contact_abc123",
      "first_name": "Sofia",
      "last_name": "Martinez",
      "phone_number": "+31600000000",
      "email": "sofia@example.com",
      "channel": "whatsapp"
    }
  ]
}
```

`sample` innehåller upp till 10 kontakter, sorterade efter senast aktiva först.

### Kör om en smart lista nu

`POST /lists/{listId}/evaluate`

Tvingar fram en omedelbar omvärdering (samma sak som **Uppdatera nu** gör i kontrollpanelen). Smarta listor uppdateras redan när en kontakt ändras, samt var 15:e minut för tidsbaserade regler, så detta behövs bara när du vill ha resultatet *direkt*.

**Svar**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 2, "removed": 1, "total": 4 }
}
```

`evaluation.skipped: true` innebär att en annan utvärdering av samma lista redan kördes och att detta anrop inte gjorde någonting.

### Smarta listor tillåter inte manuellt tillagda medlemmar

Medlemskaps-endpoints returnerar **`409`** med `"This is a smart list — its members are computed from its rules. Edit the rules instead."` när mållistan är smart. Detta omfattar `POST /contacts/lists`, `DELETE /contacts/lists`, `POST /contacts/lists/batch`, `contact_ids` på `POST /lists` och `PUT /lists/{listId}`, samt att välja en smart lista som mål för CSV-import. Ändra reglerna istället.

Att anropa `POST /lists/{listId}/evaluate` på en **statisk** lista är också ett `409` — den har inga regler att köra.

---

## Fel i Contacts API

Kontakt-endpoints returnerar standardfel-kuvertet:

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

Vissa slutpunkter inkluderar även `error_code`, vilket vanligtvis matchar HTTP-statusen — det enda undantaget är fallet med dubblettkontakt nedan, där HTTP-statusen är `200` och endast `error_code` bär på `409`. Koderna som är specifika för kontaktslutpunkter:

| Kod | När det inträffar på en kontakt-endpoint |
|---|---|
| `400` | Felaktig förfrågan — ett saknat/ogiltigt fält, tom brödtext, felaktig markör eller över 500 ID:n i en batch. |
| `402` | Inte tillräckligt med krediter för att slutföra en AI-taggningskörning på en kontakt (`error_code: "insufficient_credits"`). |
| `404` | Kontakten, listan eller taggen hittades inte på ditt konto. |
| `409` | En kontakt med det telefonnumret finns redan (vid skapande). Returneras som `error_code` i brödtexten med en HTTP-status på `200`, så förgrena på `error_code` här. Returneras även när en automatisk mass-taggningskörning redan pågår (`error_code: "auto_tag_run_in_progress"`), eller när länkning av en kontakt till en annan kanal skulle slå samman två kontakter som redan är länkade till två olika personer. |
| `422` | Kontakten kan inte ta emot ett meddelande just nu (stör ej, privat eller kanal som inte stöds). På endpointen för kanallänkning täcker det även inget telefonnummer, en parkoppling av kanal som inte stöds, eller ingen ansluten avsändare för målkanalen. |

Ett `403` på en kontaktslutpunkt kan också innebära ett problem med kontaktgräns eller listbehörighet snarare än abonnemangsåtkomst. De delade koderna som varje slutpunkt kan returnera — `401`, `403` (ditt abonnemang inkluderar inte API-åtkomst), `429` (hastighetsbegränsning) och `500` — listas med vägledning för återförsök i [Fel & Sidnumrering](errors-and-pagination.md).

---

## Nästa steg

- [Messages API](messages.md) — skicka meddelanden via kanalidentitet och hantera konversationer.
- [API-referens](reference.md) — fullständig lista över endpoints, inklusive taggar och listor.
- [API-åtkomst](../integrations/api-access.md) — autentisering, hastighetsgränser och felhantering.
