
# Yhteystieto-API

Yhteystieto on yksittäinen henkilö, jolle lähetät viestejä – sisältäen nimen, puhelinnumeron, sähköpostin, kanavan, tunnisteet, mukautetut kentät sekä listat ja kampanjat, joihin he kuuluvat. Yhteystieto-API:n avulla voit luoda, etsiä, päivittää, merkitä, tuoda massana ja poistaa yhteystietoja ilman hallintapaneelin käyttöä.

Kaikki tämän sivun polut ovat suhteessa perus-URL-osoitteeseen:

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

Joten `/contacts` tarkoittaa `https://api.youraiconnector.com/v1/contacts`.

> **Oletko uusi API:n käyttäjä?** Lue ensin [API-käyttöoikeus](../integrations/api-access.md) – se kattaa API-avaimen luomisen, kolme tunnistautumistapaa, nopeusrajoitukset ja virhemuodon. Kaikki tällä sivulla olettaa, että sinulla on jo toimiva API-avain.

---

## Tietoa yhteystietojen tunnisteista (ID)

Jokaisella yhteystiedolla on yksilöllinen tunniste (ID). Tunniste, jonka saat **luodessasi** yhteystiedon (kohdassa `data.contactId`), on sama tunniste, jota käytät kaikkialla muualla – yhteystiedon hakemiseen, päivittämiseen, merkitsemiseen, viestin lähettämiseen tai poistamiseen. Tallenna se kerran ja käytä uudelleen.

Sinun ei tarvitse luoda yhteystietoa saadaksesi sen tunnisteen. Voit myös etsiä sen puhelinnumeron tai sähköpostin perusteella (katso [Hae yhteystieto](#get-a-contact-by-phone-or-email)) tai selata kaikkia yhteystietojasi (katso [Listaa yhteystiedot](#list-contacts)). Jokainen näistä palauttaa saman tunnisteen.

---

## Luo yhteystieto

`POST /contacts`

Lisää uuden yhteystiedon tilillesi. **Puhelinnumero maakohtaisella suuntanumerolla on pakollinen** – pelkkä sähköpostiosoite ei riitä. Kaikki muu on valinnaista.

Voit halutessasi lisätä uuden yhteystiedon suoraan yhteen tai useampaan listaan käyttämällä `listId` (yksittäinen lista) tai `listIds` (taulukko). Jos molemmat lähetetään, `listIds` on ensisijainen.

Kaikki lähettämäsi kentät, jotka eivät ole alla olevassa **Luo yhteystieto** -kenttätaulukossa (`phoneNumber`, `firstName`, `lastName`, `email`, `channel`, `is_bot_active`, `is_private`, `lead_profile`, `listId`, `listIds`, `custom_fields`) mainittuja vakiokenttiä, tallennetaan automaattisesti **mukautetuiksi kentiksi** — joten Make- tai Zapier-työkalun kaltaisista työkaluista tuleva tasainen hyötykuorma toimii ilman sisäkkäisyyttä. Voit myös välittää eksplisiittisen `custom_fields`-objektin.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `phoneNumber` | Kyllä | Yhteystiedon puhelinnumero maakohtaisella suuntanumerolla (esim. `+15551234567`). |
| `firstName` | Ei | Etunimi. |
| `lastName` | Ei | Sukunimi. |
| `email` | Ei | Sähköpostiosoite. |
| `channel` | Ei | Viestintäkanava. Yksi seuraavista: `whatsapp`, `sms`, `whatsapp_web`. Oletusarvo on `whatsapp`. |
| `is_bot_active` | Ei | Vastaako tekoälyavustaja tälle yhteystiedolle. Oletusarvo on `true`. |
| `is_private` | Ei | Merkitse yhteystieto yksityiseksi. Kun `true`, tekoälyavustaja on poistettu käytöstä heidän kohdallaan. Oletusarvo on `false`. |
| `lead_profile` | Ei | Vapaamuotoisia muistiinpanoja liidistä. |
| `listId` | Ei | Yksittäisen listan tunniste, johon yhteystieto lisätään. |
| `listIds` | Ei | Taulukko listatunnisteista, joihin yhteystieto lisätään (ohittaa kohdan `listId`). |
| `custom_fields` | Ei | Objekti omista avain/arvo-kentistäsi. Voit myös välittää nämä ylimmän tason avaimina. |

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

**Vastaus**

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

Uuden yhteystiedon tunniste on kohdassa `data.contactId`. Listat, joihin se lisättiin, palautetaan kohdassa `data.listsAdded`.

> **Kopioita ei luoda.** Jos yhteystieto samalla puhelinnumerolla on jo olemassa, luontikutsu **ei** luo tai palauta sitä. Vastaus palautuu HTTP-tilalla `200` ja `error_code`-arvolla `409` rungossa, joten tee haarautuminen `error_code`-arvon perusteella HTTP-tilan sijaan:
>
> ```json
> { "success": false, "error_code": 409, "error": "A contact with this phone number already exists for the current user." }
> ```
>
> Jos haluat käsitellä olemassa olevaa yhteystietoa `error_code`-arvon `409` jälkeen, etsi se [Hae yhteystieto puhelinnumeron tai sähköpostin perusteella](#get-a-contact-by-phone-or-email) -toiminnolla — `GET /contacts?phoneNumber=...` — ja käytä sen palauttamaa ID:tä uudelleen.

> **Vastaavat WhatsApp-kirjoitusasut lasketaan samaksi numeroksi.** Joissakin maissa samalle matkapuhelinliittymälle on kaksi hyväksyttyä kirjoitusasua, ja WhatsApp saattaa ilmoittaa kumman tahansa: Meksiko (`+52…` ja vanha `+521…`), Brasilia (yhdeksännen numeron kanssa tai ilman) ja Argentiina (merkinnän `9` kanssa tai ilman kohdan `+54` jälkeen). Kaksoiskappaleiden tarkistus luotaessa ja `GET /contacts?phoneNumber=` täsmäävät molempien kirjoitusasujen välillä, joten saat takaisin olemassa olevan yhteystiedon riippumatta siitä, kummassa muodossa lähetät sen. Yhteystietoon tallennettua `phone_number` ei koskaan kirjoiteta uudelleen.

---

## Hae yhteystieto puhelinnumeron tai sähköpostin perusteella

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

Etsii yksittäisen yhteystiedon ja palauttaa täydellisen, rikastetun yhteystieto-objektin – mukaan lukien sen listat, tunnisteet ja kampanjat ratkaistuna `{ id, name }`-pareiksi sekä viimeisimmän viestinvaihdon.

Anna **joko** `phoneNumber` (kansainvälisessä muodossa) **tai** `email`. Jos et anna kumpaakaan, tämä sama päätepiste vaihtaa tilaan [Listaa yhteystiedot](#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"])
```

**Vastaus**

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

Yhteystiedon tunniste palautetaan sekä ylimmällä tasolla (`contactId`) että objektin sisällä (`contact.id`). Jos mitään ei löydy, saat vastauksen `404`, jossa on `{ "success": false, "message": "Contact not found" }`.

> **`avatarUrl`** on yhteyshenkilön profiilikuva, joka on haettu WhatsAppista tai Metasta, kun he lähettävät sinulle viestin. Se on vain luku -muodossa: et voi asettaa sitä, ja se on `null` yhteyshenkilöille, joilla ei ole kuvaa tai jotka tavoittavat sinut kanavan kautta, joka ei jaa kuvaa. Käsittele linkkiä väliaikaisena sen sijaan, että tallentaisit sen, sillä jotkin näistä kuvalinkeistä vanhenevat ja päivittyvät automaattisesti. (Alla olevassa luettelon päätepisteessä samaa arvoa kutsutaan nimellä `avatar_url`.)

> **Puhelinnumerot URL-osoitteissa.** URL-osoitteen kyselymerkkijonossa oleva `+`-merkki on koodattava muodossa `%2B`, muuten se tulkitaan välilyönniksi. Yllä olevat esimerkit tekevät tämän puolestasi.

---

## Hae yhteystieto tunnisteen (ID) perusteella

`GET /contacts/{contactId}`

Kun tiedät jo yhteystiedon tunnisteen, voit hakea sen suoraan. Vastaus on muodoltaan samanlainen kuin yllä olevassa haussa.

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

Jos yhteystiedon tunniste ei löydy tililtäsi, palautetaan `404`.

---

## Hae yhteystiedon tilastot

`GET /contacts/{contactId}/stats`

Palauttaa yhden yhteystiedon kootut viestitilastot: kokonaismäärät, tekoälyn ja ihmisen vastausten määrät, käytetyt krediitit sekä ensimmäisen ja viimeisen viestin aikaleimat.

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

**Vastaus**

```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` on sama tekoälyviestien laskuri, jonka sovelluksen sisäinen "nollaa"-painike nollaa yhteystiedon kohdalla. `creditsUsed` on tämän yhteystiedon juokseva krediittien kokonaismäärä, ei vain tämän vastauksen luvut. Jos yhteystiedon ID:tä ei löydy tililtäsi, palautetaan `404`.

---

## Listaa yhteystiedot

`GET /contacts`

Kutsu `GET /contacts` **ilman** `phoneNumber`- tai `email`-parametreja selataksesi kaikkia yhteystietojasi uusimmasta alkaen. Jokainen sivu palauttaa tiivistetyt yhteystiedot (listat, tunnisteet ja kampanjat palautetaan tunnisteiden taulukoina kokonaisten objektien sijaan) sekä `next_cursor`-arvon.

| Kyselyparametri | Kuvaus |
|---|---|
| `limit` | Sivun koko. Oletusarvo on 50, enimmäismäärä 100. |
| `cursor` | `next_cursor`-arvo edelliseltä sivulta. Jätä pois ensimmäisellä sivulla. |
| `listId` | Valinnainen. Palauta vain tähän listaan kuuluvat yhteystiedot. |

Käydäksesi läpi kaikki sivut: tee ensimmäinen kutsu ilman kursoria ja jatka sitten palautetun `next_cursor`-arvon välittämistä `cursor`-parametrina. **Lopeta, kun `next_cursor` on `null`** — tämä tarkoittaa, ettei tuloksia ole enempää.

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

**Vastaus**

```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
**Huomautus:** Suodattaminen `listId`-arvolla, jota ei ole tililläsi, palauttaa `404`-vastauksen. Virheellinen `cursor` palauttaa `400`-vastauksen.
:::


---

## Laske yhteystiedot

`GET /contacts/count`

Palauttaa tiedon siitä, kuinka monta yhteystietoa vastaa suodatinta, sekä jaottelun kanavittain ilman sivutusta. Tämä on oikea kutsu kaikkiin "kuinka monta" -kysymyksiin – koontinäytön ruutuun, automaatioon tai Champille kysyttäväksi. Kaikki suodattimet ovat valinnaisia, ja usean yhdistäminen kaventaa tulosta (yhteystiedon on vastattava jokaista lähettämääsi suodatinta).

| Kyselyparametri | Kuvaus |
|---|---|
| `agentId` | Vain tälle tekoälyagentille määritetyt yhteystiedot. Käytä `none`, jos haluat yhteystiedot, joilla ei ole määritettyä agenttia (nämä vastataan kanavan oletusagentin toimesta). |
| `channel` | Vain tämän kanavan yhteystiedot, esim. `whatsapp`, `messenger`, `instagram`, `sms`, `email`, `chat_widget`. |
| `tag` | Vain yhteystiedot, joilla on tämä tunniste, tunnisteen **nimen** perusteella (kirjainkoolla ei ole merkitystä). Tunnisteen nimi, jota sinulla ei ole, palauttaa `404`. |
| `listId` | Vain tämän listan yhteystiedot. |
| `botActive` | `true` tai `false` — vain yhteystiedot, joiden tekoälyavustaja on päällä tai pois päältä. |
| `status` | Vain yhteystiedot, joilla on tämä tila, esim. `Lead`. |
| `rules` | URL-koodattu JSON-sääntöobjekti, joka käyttää samaa muotoa kuin älykäs lista (katso [Muoto `smart_rules`](#the-smart_rules-shape) alempana). Ei voida yhdistää muihin suodattimiin. |

Jos et lähetä mitään suodatinta, saat tilisi yhteystietojen kokonaismäärän.

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

**Vastaus**

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

`by_channel` jakaa saman kokonaismäärän kanavittain; yhteystiedot, jotka eivät ole millään kanavalla, lasketaan kohdassa `none`. `filters` toistaa käytetyt suodattimet, jotta voit tarkistaa, että kutsu teki sen, mitä tarkoitit.

::: note
**Huomautus:** Jos lähetät `rules` yhdessä minkä tahansa muun suodattimen kanssa tai `rules`-arvon, joka ei ole kelvollista JSON-muotoa, palautetaan `400`. Tunnisteen nimi tai listan ID, jota ei ole tililläsi, palauttaa `404`.
:::


---

## Päivitä yhteystieto

`PUT /contacts/{contactId}`

Päivittää olemassa olevan yhteystiedon. Vain sisällyttämäsi kentät muuttuvat – jätä pois kaikki, mitä et halua muuttaa. Sinun on lähetettävä vähintään yksi kenttä, muuten saat `400`-vastauksen ("No fields to update").

| Kenttä | Kuvaus |
|---|---|
| `firstName` | Etunimi. |
| `lastName` | Sukunimi. |
| `email` | Sähköpostiosoite. |
| `is_bot_active` | Vastaako tekoälyavustaja tälle yhteyshenkilölle. |
| `is_private` | Merkitse yksityiseksi. Tämän asettaminen tilaan `true` kytkee myös tekoälyavustajan pois päältä. |
| `do_not_disturb` | Keskeytä automaattinen yhteydenotto tähän yhteyshenkilöön. Estää myös tekoälyä vastaamasta. |
| `follow_ups_disabled` | Pysäytä kaikki automaattiset jatkotoimenpiteet tälle yhteyshenkilölle (pikaviestit, syklit ja kylmäliidit), samalla kun tekoäly jatkaa vastaamista heidän lähettämiinsä viesteihin. Hyödyllinen, kun joku on tehnyt ostoksen. Pysyy pois päältä, kunnes asetat sen takaisin tilaan `false`. |
| `lead_profile` | Vapaamuotoiset liidimuistiinpanot. |
| `custom_fields` | Mukautettujen kenttien objekti. **Yhdistetään avainkohtaisesti** – vain lähettämäsi avaimet kirjoitetaan, loput olemassa olevista mukautetuista kentistä säilytetään. Voit myös välittää mukautettujen kenttien avaimia ylimmällä tasolla. |

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

**Vastaus**

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

> **Mukautetut kentät yhdistetään, ei korvata.** Lähettämällä `{ "custom_fields": { "tier": "gold" } }` asetetaan vain `tier` – kaikki muut yhteystiedon mukautetut kentät pysyvät täsmälleen ennallaan. Jos haluat poistaa mukautetun kentän kokonaan kaikilta yhteystiedoilta, käytä [Poista mukautettu kenttä](#delete-a-custom-field)-toimintoa.

---

## Lisää tai poista tunnisteita

`POST /contacts/{contactId}/tags`

Lisää ja/tai poistaa tunnisteita yksittäiseltä yhteystiedolta yhdellä kutsulla. Välitä tunnisteiden **ID-tunnukset** kohdissa `addTagIds` ja `removeTagIds`. Vähintään toisen näistä on oltava ei-tyhjä.

Tunnisteiden on oltava jo olemassa tililläsi – luo ne ensin [tunnisteiden päätepisteen](reference.md) kautta. Jos yhteystietoa tai mitään viitattua tunnistetta ei ole olemassa, saat `404`-vastauksen.

| Kenttä | Kuvaus |
|---|---|
| `addTagIds` | Taulukko tunnisteiden ID-numeroista, jotka lisätään yhteystietoon. |
| `removeTagIds` | Taulukko tunnisteiden ID-numeroista, jotka poistetaan yhteystiedosta. |

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

**Vastaus**

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

---

## Hallitse tunnisteiden kirjastoa

Nämä päätepisteet hallitsevat itse tunnistetta — sen nimeämistä uudelleen tai poistamista tililtäsi — toisin kuin tunnisteen lisäämistä tai poistamista yhdeltä yhteystiedolta (katso [Lisää tai poista tunnisteita](#add-or-remove-tags) yllä). Jokaisella tilisi tunnisteella on ID (`tagId`): se, joka näkyy hallintapaneelisi tunnisteiden hallinnassa, ja se, joka palautetaan muodossa `data.tag_id`, kun luot tunnisteen käyttämällä `POST /tags` ja JSON-runkoa `{ "name": "..." }` (ei `phoneNumber`, `email` tai `contactId`).

### Päivitä tunniste

`PUT /tags/{tagId}`

Lähetä vain ne kentät, joita olet muuttamassa.

| Kenttä | Kuvaus |
|---|---|
| `name` | Tunnisteen nimi. |

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

**Vastaus**

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

Jos `tagId` ei löydy tililtäsi, palautetaan `404`.

### Poista tunniste

`DELETE /tags/{tagId}`

Poistaa yhden tunnisteen ID:n perusteella. **Tätä ei voi kumota** — tunnisteen sisältävät yhteystiedot menettävät sen yksinkertaisesti. Jo poistetun (tai olemattoman) tunnisteen poistaminen palauttaa `200` ja `deleted: 0`, eikä `404`, koska poistettavaa ei ole.

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

**Vastaus**

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

### Poista useita tunnisteita kerralla

`DELETE /tags`

| Kenttä | Kuvaus |
|---|---|
| `tagIds` | Taulukko poistettavista tunnisteiden ID-tunnuksista (enintään 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"] }'
```

**Vastaus**

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

ID-tunnukset, joita ei ole olemassa tai jotka kuuluvat toiselle tilille, ohitetaan hiljaisesti, eikä niitä lasketa mukaan kohtaan `deleted`.

---

## Aseta lippu joukkona

`POST /contacts/bulk-flag`

Asettaa yhden totuusarvolipun useille yhteystiedoille kerralla. Enintään 500 yhteystiedon ID-numeroa per pyyntö. ID-numerot, joita ei löydy tililtäsi, ohitetaan ja lasketaan mukaan kohtaan `skipped`.

| Kenttä | Kuvaus |
|---|---|
| `contactIds` | Taulukko päivitettävien yhteystietojen ID-numeroista (enintään 500). |
| `field` | Asetettava lippu. Yksi seuraavista: `bot_active` (AI-avustaja päällä/pois), `dnd` (keskeytä automaattinen yhteydenotto), `spam`, `private`. |
| `value` | Totuusarvo, johon lippu asetetaan. |

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

**Vastaus**

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

---

## Tuo yhteystietoja joukkona

`POST /contacts/import`

Luo jopa 500 yhteystietoa yhdellä kutsulla JSON-taulukosta. Jokainen tietue vaatii `phone_number`-kentän kansainvälisessä muodossa; kaikki muu on valinnaista. Tietueet, joissa on virheellisiä puhelinnumeroita tai tukemattomia kanavia, **ohitetaan** (niitä ei luoda), ja jokaisesta ohitetusta tietueesta raportoidaan sen indeksi ja syy – joten voit korjata vain epäonnistuneet kohdat ja yrittää uudelleen.

Puhelinnumerot, jotka ovat jo olemassa tililläsi, ohitetaan oletusarvoisesti `duplicate`-tunnisteella. Lähetä `updateExisting: true`, jos haluat sen sijaan **päivittää** kyseiset yhteystiedot: tietueessa olevat kentät korvaavat yhteystiedon tiedot (`first_name`, `last_name`, `email`, `lead_profile` ja `custom_fields` yhdistetään avain avaimelta), `tags` lisätään ja yhteystieto lisätään `listId`-kohteeseen. Kanavaa, puhelinnumeroa tai bottilippuja ei koskaan muuteta olemassa olevassa yhteystiedossa.

Voit halutessasi lisätä jokaisen tuodun (tai päivitetyn) yhteystiedon listaan `listId`-kentällä, asettaa `defaultChannel`-arvon tietueille, joissa sitä ei ole määritetty, sekä merkitä tietueet `tags`-tunnisteilla (tunnisteiden nimet – puuttuvat tunnisteet luodaan, olemassa olevat täsmäytetään kirjainkoosta riippumatta).

**Ylätason kentät**

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `contacts` | Kyllä | Taulukko yhteystietueista (enintään 500). |
| `listId` | Ei | Lista, johon jokainen tuotu (ja päivitetty) yhteystieto lisätään. On oltava tililläsi oleva lista. |
| `defaultChannel` | Ei | Kanava, jota käytetään tietueissa, joista puuttuu `channel`. Yksi seuraavista: `whatsapp`, `sms`, `whatsapp_web`. Oletusarvo on `whatsapp`. |
| `updateExisting` | Ei | `true` päivittääksesi yhteystiedot, joiden puhelinnumero on jo olemassa, sen sijaan että ne ohitettaisiin `duplicate`-tunnisteella. Oletusarvo on `false`. |

**Tietuekohtaiset kentät**

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `phone_number` | Kyllä | Puhelinnumero kansainvälisessä muodossa (etuliite `+` lisätään, jos se puuttuu). |
| `first_name` | Ei | Etunimi. |
| `last_name` | Ei | Sukunimi. |
| `email` | Ei | Sähköpostiosoite. |
| `channel` | Ei | Yksi seuraavista: `whatsapp`, `sms`, `whatsapp_web`. Käyttää oletuksena arvoa `defaultChannel`. |
| `is_bot_active` | Ei | Vastaako tekoälyavustaja. Oletusarvo on `true`. |
| `is_private` | Ei | Merkitse yksityiseksi. Oletusarvo on `false`. |
| `lead_profile` | Ei | Vapaamuotoiset liidimuistiinpanot. |
| `custom_fields` | Ei | Objekti, joka sisältää mukautettujen kenttien avaimet ja arvot. |
| `tags` | Ei | Taulukko tunnisteiden nimistä (myös yksittäinen `"a; b"`-merkkijono toimii). Tunnisteet, joita ei ole olemassa, luodaan; olemassa olevat täsmäytetään kirjainkokoa huomioimatta. Enintään 25 per tietue. |

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

**Vastaus**

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

Jos joitakin tietueita ei voida luoda, ne näkyvät `skipped`-kohdassa syyn kera (tässä ilman `updateExisting`-arvoa, joten olemassa oleva numero ohitetaan):

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

`updateExisting: true`-arvolla sama pyyntö raportoi olemassa olevan yhteystiedon kohdassa `updated` / `updated_contact_ids`.

Mahdolliset ohitussyyt: `invalid_record`, `missing_phone_number`, `invalid_phone_number`, `invalid_channel`, `duplicate_in_request`, `duplicate`, `contact_limit_reached`, `create_failed`.

> **Pakettirajoitukset.** Jos tilauksesi yhteystietoraja ei salli näin montaa uutta yhteystietoa, koko pyyntö hylätään heti virheellä `403`. Jos raja täyttyy kesken kaiken, jäljellä olevat tietueet palautetaan ohitettuina syyllä `contact_limit_reached`.

---

## Yhteystietojen tuominen CSV-tiedostosta

Jos tuonti on suurempi kuin mitä [massatuonti](#bulk-import-contacts) tukee (enintään noin 50 000 riviä), aseta asynkroninen tuontityö jonoon tilisi tallennustilassa olevalle CSV-tiedostolle ja kyselytilaa sitä, kunnes se on valmis.

### Aloita tuonti

`POST /contacts/import-csv`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `csvStoragePath` | Kyllä | CSV-tiedoston tallennuspolku kohdassa `users/{your account id}/imports/`, päättyen tunnisteeseen `.csv`. |
| `listName` | Kyllä | Luo (tai käyttää uudelleen) listan tällä nimellä ja lisää siihen jokaisen tuodun yhteystiedon. |
| `existingListRefs` | Ei | Taulukko olemassa olevista listojen ID-tunnuksista, joihin jokainen tuotu yhteystieto lisätään myös. |
| `defaultChannel` | Ei | Kanava, jota käytetään riveille, joissa sitä ei ole määritetty. |

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

**Vastaus** (`202` — tuonti on jonossa, ei vielä valmis)

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

> **Tiedoston siirtäminen tallennustilaan.** Tämä päätepiste käynnistää ja seuraa tuontityötä; se ei hyväksy itse latausta. CSV-tiedoston on oltava jo paikassa `csvStoragePath` ennen kuin kutsut sitä — hallintapaneelin oma CSV-tuontitoiminto tekee tämän ensimmäisenä vaiheenaan.

### Tuontityön kysely

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

**Vastaus**

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

`status` etenee tilojen `queued` → `processing` → `completed` kautta tai päätyy tilaan `failed`, jolloin syy näkyy kohdassa `error_message`. Jos `jobId` ei ole olemassa tililläsi, palautetaan `404`.

---

## Yhteystietojen vienti

Käynnistää yhteystietojesi asynkronisen CSV-viennin ja palauttaa työn, jonka valmistumista voit seurata kyselyillä.

### Aloita vienti

`POST /contacts/export`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `listId` | Ei | Vie vain tähän luetteloon kuuluvat yhteystiedot. |
| `contactIds` | Ei | Vie vain nämä tietyt yhteystietojen tunnisteet. |

Jos jätät molemmat tyhjiksi, tilisi kaikki yhteystiedot viedään.

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

**Vastaus** (`202` — vienti on jonossa)

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

### Kysy vientityön tilaa

`GET /contacts/export/{jobId}`

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

**Vastaus**

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

> Kun `status` on `"completed"`, saat `export_id` ja `contact_count`. Luodun CSV-tiedoston lataaminen tapahtuu hallintapaneelisi Vienti-sivulta.

---

## Lähetä viesti yhteystiedolle

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

Lähettää viestin olemassa olevalle yhteyshenkilölle kanavassa, jota hän jo käyttää. Viesti asetetaan jonoon ja toimitetaan taustalla — vastaus vahvistaa, että se on otettu vastaan, ei sitä, että se on jo toimitettu.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `body` | Kyllä | Lähetettävän viestin teksti. |
| `mediaUrl` | Ei | Liitettävän mediatiedoston URL-osoite. |
| `mediaContentType` | Ei | Liitetyn median MIME-tyyppi (esim. `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"])
```

**Vastaus**

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

> **Etkö voi lähettää juuri nyt?** Jos yhteyshenkilöllä on käytössä älä häiritse -tila tai yksityinen tila, tai hän ei ole kanavalla, joka voi vastaanottaa lähteviä viestejä, pyyntö hylätään `422`-koodilla ja selittävällä `error`-viestillä.

Jos haluat lähettää viestin puhelinnumeron, Instagram-tunnuksen tai muun kanavatunnisteen perusteella yhteyshenkilön tunnisteen sijaan – ja lukeaksesi lisää viestinnästä yleisesti – katso [Messages API](messages.md).

---

## Määritä tekoälyagentti yhteyshenkilölle

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

Siirtää olemassa olevan keskustelun toiselle tekoälyagentille seuraavasta viestistä alkaen. Tämä vastaa keskusteluvalikon **Määritä tekoälyagentti** -toimintoa ja samaa vaihetta, jota automaatioiden **Määritä tekoälyagentti tai kampanja** -toiminto käyttää.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `agentId` | Kyllä | Sen tekoälyagentin tunniste (ID), jonka tulisi ottaa keskustelu haltuun, tai `null` määrityksen poistamiseksi, jolloin keskustelu palaa tiimisi saapuneet-kansioon. |
| `triggerAIResponse` | Ei | `true` saa vastikään määritetyn agentin vastaamaan yhteyshenkilön uusimpiin vastaamattomiin viesteihin välittömästi. Oletusarvo on `false`. |

> **Ole varovainen `triggerAIResponse: true` kanssa** – se lähettää yhteystiedolle viestin välittömästi, joten käytä sitä vain, kun haluat viestin menevän perille heti. Messengerissä ja Instagramissa viesti epäonnistuu, jos yhteystieto on viimeksi kirjoittanut sinulle yli 24 tuntia sitten.

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

**Vastaus**

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

> Agentin on kuuluttava samaan tiliin kuin yhteyshenkilön; muussa tapauksessa pyyntö hylätään virheellä `404` tai `403`. Löydät agenttien tunnisteet (ID) Tekoälyagentit-sivulta (jokaisen agentin URL-osoite päättyy sen tunnisteeseen).

---

## Määritä tekoälyagentti useille yhteystiedoille

`POST /contacts/bulk-assign-agent`

Siirtää useita keskusteluja toiselle tekoälyagentille yhdellä kutsulla – tai tyhjentää määrityksen kaikilta käyttämällä `null`. Kyseessä on puhtaasti reititysmuutos: **viestiä ei lähetetä, eikä agentti vastaa kenellekään**. Jokainen yhteystieto saa yksinkertaisesti uuden agentin seuraavan kerran, kun he kirjoittavat. (Siksi tässä ei ole `triggerAIResponse`.)

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `agentId` | Kyllä | Tekoälyagentti, jonka tulisi ottaa vastuu, tai `null` määrityksen tyhjentämiseksi. |
| `contactIds` | Yksi kolmesta | Enintään 500 siirrettävää yhteystieto-ID:tä. |
| `filter` | Yksi kolmesta | Valitse yhteystiedot palvelimelta listaamisen sijaan, uusimmasta alkaen. Käyttää samoja avaimia kuin laskentapäätepisteen suodattimet: `agentId` (tai `none`), `channel`, `tag`, `listId`, `botActive`, `status`. |
| `rules` | Yksi kolmesta | Älykkään listan sääntöobjekti – katso [Muoto `smart_rules`](#the-smart_rules-shape). |
| `limit` | Ei | Kuinka monta yhteystietoa siirretään tässä kutsussa, kun valitset kohdassa `filter` tai `rules`. 1–500, oletusarvo on 500. |

Lähetä täsmälleen yksi seuraavista: `contactIds`, `filter` tai `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"])
```

**Vastaus**

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

`matched` on yhteystietojen kokonaismäärä, jonka valinta löysi, `updated` on niiden määrä, jotka siirrettiin tällä kutsulla, `skipped` on niiden ID-tunnusten määrä, joita ei löytynyt tililtäsi, ja `remaining` on niiden määrä, jotka vastaavat edelleen ehtoja kutsun päätyttyä.

**Kaikkien siirtäminen.** Koska kutsu siirtää enintään 500 yhteystietoa, suuri ryhmä vaatii useita kutsuja. Käytä suodatinta, joka lakkaa vastaamasta yhteystietoon, kun se on siirretty – esimerkiksi `filter: { "agentId": "agent_abc123" }` määritettäessä agentille `agent_xyz789` – ja toista täsmälleen sama kutsu, kunnes `remaining` palauttaa arvon `0`. Kun lähetät sen sijaan `contactIds`, `remaining` on aina `0`.

---

## Yhteystiedon määrittäminen osastolle

`POST /contacts/{contactId}/department`

"Määritä tämä liidi myyntiosastolle" — tallentaa yhteystiedon nimetyn osaston alle ja antaa sen oletusarvoisesti sille osaston henkilölle, jolla on tällä hetkellä vähiten yhteystietoja. Tämä on erillinen [AI-agentin määrittämisestä](#assign-an-ai-agent-to-a-contact): osasto vastaa kysymykseen "mikä tiimi omistaa tämän", agentti vastaa kysymykseen "mikä tekoäly vastaa tästä", ja toisen asettaminen ei koskaan poista toista.

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `department_id` | Kyllä | Osasto, jonka alle yhteystieto tallennetaan. Tyhjennä kenttä välittämällä `null`. |
| `hand_to_member` | Ei | Anna yhteystieto myös osaston vähiten kuormitetulle henkilölle. Oletusarvo on `true`. Ei koskaan määritä uudelleen yhteystietoa, joka on jo jonkun omistuksessa. |

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

**Vastaus**

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

`assigned_to` on `null`, kun joku muu omisti yhteystiedon jo valmiiksi tai välitit arvon `hand_to_member: false`.

---

## Yhteystiedon linkittäminen kanavien välillä

"Jatka WhatsAppissa" (tai tekstiviestillä) etsii tai luo tämän henkilön yhteystiedon toisessa puhelinpohjaisessa kanavassa ja linkittää ne toisiinsa, jotta sovellus tunnistaa heidät samaksi henkilöksi.

### Linkitä toiseen kanavaan

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

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `channel` | Kyllä | Kanava, johon linkitetään. Yksi seuraavista: `whatsapp`, `whatsapp_web`, `sms`. |
| `phoneNumber` | Ei | Uudella kanavalla käytettävä puhelinnumero. Oletuksena käytetään lähdeyhteystiedon omaa numeroa. |

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

**Vastaus**

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

`created` kertoo, luotiinko kohdekanavalle uusi yhteystieto vai löytyikö ja linkitettiinkö olemassa oleva. Tämän kutsuminen toisen kerran on turvallista — se palauttaa saman `contact_id`-arvon `created: false`-tunnuksella sen sijaan, että loisi kaksoiskappaleen.

`422` tarkoittaa, että tili ei voi suorittaa tätä linkitystä juuri nyt: yhteystieto on jo kyseisessä kanavaperheessä, sillä ei ole käytettävissä olevaa puhelinnumeroa tai kohdekanavalle ei ole yhdistettyä lähettäjää. `409` tarkoittaa, että kaksi yhteystietoa on jo linkitetty kahdelle eri henkilölle — poista linkitys ensin toisesta.

### Listaa yhteystiedon linkitetyt keskustelut

`GET /contacts/{contactId}/linked`

Palauttaa muut keskustelut, jotka kuuluvat samalle henkilölle kuin tämä yhteystieto. Linkittämätön yhteystieto palauttaa tyhjän taulukon, ei `404`-virhettä — "tällä henkilöllä ei ole muita kanavia" on normaali tila.

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

**Vastaus**

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

### Poista yhteystiedon linkitys

`DELETE /contacts/{contactId}/link`

Poistaa tämän yhteystiedon henkilöltä yksipuolisesti — kaikki muut kyseiseen henkilöön linkitetyt yhteystiedot säilyttävät linkityksensä, joten yhden linkityksen poistaminen kolmen ryhmästä ei hajota ryhmää.

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

**Vastaus**

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

---

## Hae yhteystiedon profiilikuva

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

Hakee (ja välimuistiin tallentaa) yhteystiedon WhatsApp- tai Meta-profiilikuvan pyynnöstä — sama kuva, joka palautetaan `avatarUrl`-kentässä kohdassa [Hae yhteystieto](#get-a-contact-by-phone-or-email), päivitettynä.

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

**Vastaus**

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

`cached: true` tarkoittaa, että URL-osoite on peräisin tuoreesta hausta eikä uudesta palveluntarjoajan kyselystä — kuvat tallennetaan välimuistiin 7 päiväksi, ja yhteystieto, jolla palveluntarjoajan mukaan ei ole saatavilla olevaa kuvaa, tallennetaan välimuistiin ei-saatavilla-tilassa 24 tunniksi. Kun haettavaa kuvaa ei ole, `avatar_url` jätetään pois ja `message` selittää syyn.

---

## Automerkitse yhteystiedot tekoälyllä

Suorittaa tilisi tunnistesäännöt yhden tai useamman yhteystiedon koko keskusteluhistorian läpi ja lisää (tai poistaa) tunnisteita täsmälleen samalla tavalla kuin reaaliaikainen merkitseminen live-chatin aikana — samat säännöt, sama krediittikustannus per tunniste.

### Aloita suoritus

`POST /contacts/auto-tag`

| Kenttä | Pakollinen | Kuvaus |
|---|---|---|
| `scope` | Kyllä | `"contacts"` tiettyjen yhteystietojen merkitsemiseen tai `"agent"` kaikkien sellaisten keskustelujen merkitsemiseen, joita yksi tekoälyagentti parhaillaan käsittelee. |
| `contact_ids` | Pakollinen, kun `scope` on `"contacts"` | Taulukko yhteystietojen ID-tunnuksista, 1–500 kappaletta. |
| `agent_id` | Pakollinen, kun `scope` on `"agent"` | Tekoälyagentti, jonka keskustelut merkitään. Kun `scope` on `"contacts"`, tämä on valinnainen ja rajaa vain sitä, mitkä agentin tunnistesäännöt suoritetaan. |

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

**Yksittäinen** yhteystieto suoritetaan rivinsisäisesti ja palauttaa tuloksen välittömästi:

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

**Kaksi tai useampi** yhteystieto (tai `scope: "agent"`) suoritetaan taustatyönä ja palauttaa `202` välittömästi:

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

### Kysely suorituksen tilasta

`GET /contacts/auto-tag/run`

Palauttaa tilin nykyisen (tai uusimman) suorituksen, joten voit kysellä edistymistä ilman, että sinun tarvitsee seurata `run_id` itse.

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

**Vastaus**

```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` on `null`, kun tili ei ole koskaan aloittanut suoritusta. `status` siirtyy tilasta `"running"` tilaan `"completed"` tai `"failed"`.

Vain yksi massasuoritus voi olla käynnissä tiliä kohden kerrallaan — toisen aloittaminen toisen ollessa käynnissä palauttaa `409` ja `error_code: "auto_tag_run_in_progress"`. Krediittien loppuminen yksittäisen yhteystiedon suorituksessa palauttaa `402` ja `error_code: "insufficient_credits"`; massasuoritus sen sijaan pysähtyy ennenaikaisesti ja raportoi edistymisensä kohdassa `run`.

---

## Poista yhteystieto

`DELETE /contacts/{contactId}`

Poistaa pysyvästi yhden yhteystiedon ID-tunnuksen perusteella, mukaan lukien sen viestihistorian. **Tätä ei voi kumota.** Jos haluat poistaa useita yhteystietoja yhdellä kutsulla, käytä alla olevaa [Poista yhteystiedot](#delete-contacts) -toimintoa.

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

**Vastaus**

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

Yhteystunnus, jota ei ole tililläsi tai joka kuuluu toiselle tilille, palauttaa virheen `404`.

---

## Poista yhteyshenkilöitä

`DELETE /contacts`

Poistaa pysyvästi yhden tai useamman yhteyshenkilön tunnisteen perusteella yhdellä kutsulla (enintään 500 tunnisteen ID:tä). Tunnisteet, joita ei ole tililläsi, ohitetaan ja lasketaan mukaan `skipped`-kohtaan. **Tätä toimintoa ei voi kumota.**

| Kenttä | Kuvaus |
|---|---|
| `contactIds` | Taulukko poistettavista yhteyshenkilöiden tunnisteista (enintään 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']}")
```

**Vastaus**

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

---

## Mukautetun kentän poistaminen

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

Poistaa yhden mukautetun kentän avaimen **jokaiselta** tilisi yhteystiedolta. Käytä tätä siivoamiseen mukautetun kentän uudelleennimeämisen tai käytöstä poistamisen jälkeen. Avain saa sisältää vain kirjaimia, numeroita, alaviivoja ja yhdysviivoja. Palauttaa tiedon siitä, kuinka monta yhteystietoa päivitettiin. **Tätä toimintoa ei voi kumota.**

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

**Vastaus**

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

::: note
**Huomautus:** Kentän avain, joka sisältää tukemattomia merkkejä, palauttaa `400`-vastauksen.
:::


---

## Listat

Listat ryhmittelevät yhteystietoja. Lista on joko **staattinen** (päätät itse, kuka siinä on) tai **älykäs** (jäsenyys lasketaan sääntöjen perusteella ja pidetään automaattisesti ajan tasalla — katso [Listojen ja yhteystietojen järjestäminen](../get-started/list-and-contact-management.md#smart-lists-auto-updating)).

| Kenttä | Kuvaus |
|---|---|
| `name` | Pakollinen luotaessa. Enintään 100 merkkiä. |
| `status` | `live` (oletus) tai `draft`. Pienillä kirjaimilla. |
| `contact_ids` | Taulukko yhteystietojen tunnuksista, jotka lisätään listalle. **Vain staattiset listat.** |
| `type` | `static` (oletus) tai `smart`. |
| `smart_rules` | Sääntöjoukko — pakollinen, kun `type` on `smart`. Katso alta. |

### Listan luominen

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

**Vastaus**

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

Älykäs lista arvioidaan **inline**-muodossa samassa pyynnössä, joten `evaluation` kertoo tarkalleen, ketkä päätyivät sille. Staattisella listalla `evaluation` on `null`.

### Listan päivittäminen

`PUT /lists/{listId}`

Lähetä vain ne kentät, joita muutat. Kentän `smart_rules` muuttaminen arvioi listan välittömästi uudelleen ja palauttaa saman `evaluation`-objektin.

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

Voit vaihtaa listan tyyppiä näiden kahden välillä:

- **Staattinen → älykäs**: lähetä `{ "type": "smart", "smart_rules": { … } }`. Säännöt astuvat voimaan välittömästi.
- **Älykäs → staattinen**: lähetä `{ "type": "static" }`. Säännöt poistetaan, ja listalla olevat henkilöt pysyvät siellä.

### `smart_rules`-rakenne

```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` (jokaisen ehdon on oltava tosi) tai `any` (vähintään yksi).
- `conditions` — 1–20 ehtoa, kussakin enintään 100 arvoa, merkkijonot enintään 200 merkkiä.

| `field` | `op` | `value` |
|---|---|---|
| `tags` | `has_any`, `has_all`, `has_none` | tunniste-ID-taulukko |
| `lists` | `in_any`, `not_in_any` | lista-ID-taulukko (**vain staattiset listat** — älykästä listaa ei voi muodostaa toisesta älykkäästä listasta) |
| `channel` | `is_any`, `is_none` | kanavataulukko |
| `status` | `is_any`, `is_none` | yhteystietojen tilataulukko |
| `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" }` |
| samat päivämääräkentät | `before`, `after` | ISO-päivämäärä (`"2026-01-01"`, verrataan kokonaisina päivinä) tai täysi ISO-päivämäärä ja -aika (`"2026-01-01T14:30:00Z"`, verrataan tarkkaan hetkeen) |
| samat päivämääräkentät | `is_set`, `not_set` | — |
| `has_interacted_with_ai` | `is` | `true` / `false` — `true` täsmää yhteystietoihin, joille tekoäly on lähettänyt viestin vähintään kerran (koskaan) |
| `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` | merkkijono `contains`-lomakkeille |
| `current_campaign_id`, `assigned_agent` | `is_any`, `is_none`, `is_set`, `not_set` | ID-taulukko `is_any` / `is_none` -lomakkeille |
| `custom_field` (sekä `key`) | `eq`, `neq`, `contains`, `not_contains`, `is_set`, `not_set` | merkkijono arvolomakkeille |

`not_within_last` täsmää myös yhteystietoihin, joille päivämäärää ei ole koskaan asetettu ("enemmän kuin N sitten, **tai ei koskaan**"), ja tekstivertailut eivät huomioi kirjainkokoa.

**Tekoälyn sitouttaminen.** `has_interacted_with_ai` on elinkaaren lippu: `true` jokaiselle yhteystiedolle, jolle tekoälysi on lähettänyt vähintään yhden viestin, `false` kaikille muille (mukaan lukien yhteystiedot, joihin vain tiimisi on vastannut). Se leimataan tekoälyn ensimmäiseen viestiin yhteystiedolle, eikä sitä koskaan poisteta, joten tekoälyn vastausten kytkeminen pois päältä tai yhteystiedon siirtäminen toiseen kampanjaan ei nollaa sitä. Jos kyseessä on *ajanjakso* — "yhteystiedot, joita tekoälyni käsitteli tässä kuussa", mikä on tyypillinen laskutuskysymys — käytä sen sijaan väliä `last_ai_interaction_at`:

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

Älä sekoita kumpaakaan näistä kohtiin `is_bot_active` (tekoälyllä on *lupa* vastata, ei sitä, että se olisi vastannut) tai `has_ever_responded` (*yhteystieto* vastasi kenelle tahansa). Samat kaksi leimaa palautetaan jokaisesta yhteystiedosta muodossa `first_ai_interaction_at` / `last_ai_interaction_at`, ja koko sääntöjoukko toimii myös kohteessa `GET /contacts?rules=`, joten voit laskea täsmäykset luomatta listaa.

### Esikatsele sääntöjoukkoa

`POST /lists/preview`

Laskee ja näyttää otoksen yhteystiedoista, jotka sääntöjoukko täsmäisi, luomatta tai muuttamatta mitään. Käytä tätä sääntöjen tarkistamiseen ennen niiden tallentamista.

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

**Vastaus**

```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` sisältää enintään 10 yhteystietoa, uusimmasta aktiivisimmasta alkaen.

### Suorita älykäs luettelo uudelleen nyt

`POST /lists/{listId}/evaluate`

Pakottaa välittömän uudelleenarvioinnin (sama toiminto kuin **Päivitä nyt** hallintapaneelissa). Älykkäät luettelot päivittyvät jo valmiiksi, kun yhteystieto muuttuu, sekä 15 minuutin välein aikaperusteisten sääntöjen osalta, joten tätä tarvitaan vain, kun haluat tuloksen *heti nyt*.

**Vastaus**

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

`evaluation.skipped: true` tarkoittaa, että saman luettelon arviointi oli jo käynnissä, eikä tämä kutsu tehnyt mitään.

### Älykkäät luettelot eivät hyväksy käsin valittuja jäseniä

Jäsenyyspäätepisteet palauttavat **`409`** ja `"This is a smart list — its members are computed from its rules. Edit the rules instead."`, kun kohdeluettelo on älykäs. Tämä koskee `POST /contacts/lists`, `DELETE /contacts/lists`, `POST /contacts/lists/batch`, `contact_ids` kohteessa `POST /lists` ja `PUT /lists/{listId}`, sekä älykkään luettelon valitsemista CSV-tuonnin kohteeksi. Muuta sen sijaan sääntöjä.

Kutsun `POST /lists/{listId}/evaluate` tekeminen **staattiselle** luettelolle on myös `409` — siinä ei ole sääntöjä suoritettavaksi.

---

## Contacts API -virheet

Yhteystietojen päätepisteet palauttavat vakioituun virhemuotoon:

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

Jotkin päätepisteet sisältävät myös `error_code`-arvon, joka yleensä vastaa HTTP-tilaa — ainoa poikkeus on alla oleva kaksoiskappaletapaus, jossa HTTP-tila on `200` ja vain `error_code` sisältää `409`-arvon. Yhteystietojen päätepisteille ominaiset koodit:

| Koodi | Milloin se tapahtuu yhteystieto-päätepisteessä |
|---|---|
| `400` | Virheellinen pyyntö — puuttuva/virheellinen kenttä, tyhjä runko, virheellinen kohdistin tai yli 500 tunnusta erässä. |
| `402` | Ei riittävästi krediittejä tekoälytunnisteiden suorittamiseen yhdelle yhteystiedolle (`error_code: "insufficient_credits"`). |
| `404` | Yhteystietoa, listaa tai tunnistetta ei löytynyt tililtäsi. |
| `409` | Kyseisellä puhelinnumerolla varustettu yhteystieto on jo olemassa (luotaessa). Palautetaan rungossa muodossa `error_code` HTTP-tilakoodilla `200`, joten tee haarautuminen `error_code` kohdassa. Palautetaan myös, kun joukkoautomaattinen tunnistus on jo käynnissä (`error_code: "auto_tag_run_in_progress"`), tai kun yhteystiedon linkittäminen toiseen kanavaan yhdistäisi kaksi yhteystietoa, jotka on jo linkitetty kahdelle eri henkilölle. |
| `422` | Yhteystieto ei voi vastaanottaa viestiä juuri nyt (älä häiritse -tila, yksityinen tai tukematon kanava). Kanavan linkityksen päätepisteessä tämä kattaa myös puuttuvan puhelinnumeron, tukemattoman kanavaparin tai sen, ettei kohdekanavalle ole yhdistettyä lähettäjää. |

`403` yhteystietojen päätepisteessä voi tarkoittaa myös yhteystietorajoitusta tai listan käyttöoikeusongelmaa sen sijaan, että kyse olisi tilausoikeudesta. Jaetut koodit, joita jokainen päätepiste voi palauttaa — `401`, `403` (tilauksesi ei sisällä API-käyttöoikeutta), `429` (nopeusrajoitus) ja `500` — on lueteltu uudelleenyritysohjeiden kera kohdassa [Virheet ja sivutus](errors-and-pagination.md).

---

## Seuraavat vaiheet

- [Viestien API](messages.md) — lähetä viestejä kanavan tunnisteen perusteella ja hallinnoi keskusteluja.
- [API-viite](reference.md) — täydellinen päätepisteluettelo, mukaan lukien tunnisteet ja listat.
- [API-käyttöoikeus](../integrations/api-access.md) — todennus, nopeusrajoitukset ja virheiden käsittely.
