
# Kampagne-API

En kampagne samler alt det, som AI-botten skal bruge for at tale med dine kontakter: dens instruktioner, de kanaler den kører på, dens aktive timer og dens opfølgningsadfærd. Kampagne-API'et giver dig mulighed for at liste, oprette, opdatere, duplikere, aktivere, arkivere og finjustere kampagner fra din egen kode i stedet for fra dashboardet.

Alle slutpunkter herunder er relative til basis-URL'en `https://api.youraiconnector.com/v1`. Hver anmodning skal godkendes — se [API-adgang](../integrations/api-access.md) og [Godkendelse](authentication.md) for hvordan du får og sender din API-nøgle. API-adgang er en betalt funktion; uden den afvises anmodninger med en `403`.

> **Bemærk:** Nogle eksempler viser den simple `?apiKey=YOUR_API_KEY` forespørgselsform, andre bruger `X-API-Key` headeren. Begge virker overalt — brug den, der passer bedst til din opsætning.

---

## Kampagnetyper

Når du opretter en kampagne, skal du vælge en af disse typer:

| Type | Hvad den bruges til |
|---|---|
| `Incoming from Unknown Contacts` | Botten svarer folk, der sender dig en besked for første gang. |
| `Outgoing` | Botten starter samtaler med kontakter, du tilføjer til kampagnen. |
| `Keywords` | **Inaktiv - må ikke bruges.** En `Keywords`-kampagne er inaktiv: den accepteres stadig af hensyn til bagudkompatibilitet, men den er usynlig for indgående routing på alle kanaler, og intet læser dens trigger-søgeord. Brug i stedet et indgangspunkt af typen **Søgeord** på en AI-agent. |
| `Combined` | En blanding af indgående og udgående adfærd. |

**Store og små bogstaver er underordnet.** `type`, `status`, `booking_provider`, `first_response_mode`, `bot.anthropic_model` og `bot.ai_speed` accepterer alle store og små bogstaver — `"live"`, `"Live"` og `"LIVE"` er det samme — og værdien gemmes i sin kanoniske form, hvilket er det, du får tilbage, når du læser kampagnen. Den eneste undtagelse er pause-parret: `"Paused"` og `"paused"` er to reelt forskellige tilstande, så en tvetydig stavemåde som `"PAUSED"` afvises med en `400`, der beder dig om at vælge én.

### De to pausetilstande

| Status | Hvem skriver den | Hvad det betyder |
|---|---|---|
| `Paused` | Platformens egne sikkerhedstjek (lavt engagement, gentagne sendefejl, grænse nået) og de nyere Agents- og Broadcasts-flader | Kampagnen er sat på hold. En planlagt gennemgang kan automatisk ophæve en sikkerhedspause, når årsagen er løst. |
| `paused` | Dashboardets Pause-knap, parret med `resumed` ved Genoptag | En person har sat den på pause manuelt. Planlagte afsendelser nedbrydes og genopbygges ved genoptagelse. |

Begge stopper kampagnen: Indgående routing kører kun, mens status er præcis `Live`. **Fra API'et skal du bruge `Paused` til at sætte på pause og `Live` til at genoptage** — parret med små bogstaver findes til dashboard-knappen og holdes i drift til dette formål.

Ingen af disse er, hvad der sker, når AI'en holder op med at svare i en samtale. Det er en kontakt-specifik kontakt, `is_bot_active` på kontakten — indstilles når et menneske overtager, når kontakten fravælger, eller når AI'en afslutter chatten. Kampagnens egen status forbliver uberørt, og alle andre samtaler i den fortsætter med at køre. Se [sæt AI på pause eller genoptag for én kontakt](messages.md#pause-or-resume-the-ai-for-one-contact).

> **Oprettelse af en kampagne afgør ikke, hvem der besvarer en kanal.** Routing håndteres af **indgangspunkter** på en AI-agent, ikke af kampagner. Hver kanal har ét kanal-standardindgangspunkt, der angiver den agent, som besvarer nye, ukendte kontakter på den: indstil det med `PUT /entry-points/channel-defaults`, tjek om stigen er live for kontoen med `GET /entry-points/routing-status`, ryd det med `DELETE /entry-points/channel-defaults`. `POST /channels/campaign` skriver stadig det ældre kampagnerouting-kort pr. kanal, men det kort konsulteres ikke længere for indgående routing på nogen konto; det bevares kun til rollback. Byg ikke mod det. Se [Route en kanal til en kampagne](channels.md#route-a-channel-to-a-campaign) for begge flader side om side.

---

## List kampagner

`GET /campaigns`

Returnerer dine kampagner, nyeste først. Arkiverede kampagner er udelukket, medmindre du sender `archived=true`.

**Forespørgselsparametre**

| Parameter | Påkrævet | Beskrivelse |
|---|---|---|
| `limit` | Nej | Maksimalt antal kampagner, der skal returneres. Standard `50`, maksimum `100`. |
| `cursor` | Nej | Sidenummereringsmarkør. Send `next_cursor`-værdien fra det forrige svar for at få den næste side. |
| `archived` | Nej | Sæt til `true` for at inkludere arkiverede kampagner. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns?limit=20&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/campaigns?limit=20", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.campaigns, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns",
    params={"limit": 20},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["campaigns"], data["next_cursor"])
```

**Svar**

```json
{
  "success": true,
  "campaigns": [
    {
      "id": "NBCXrhqGPSFsd6MV7pRo",
      "name": "Inbound WhatsApp Leads",
      "type": "Incoming from Unknown Contacts",
      "status": "Live",
      "enabled": true,
      "archived": false,
      "created_at": 1700000000000,
      "ai_mode": true,
      "language": "en",
      "enabled_channels": ["whatsapp", "instagram"]
    }
  ],
  "next_cursor": "NBCXrhqGPSFsd6MV7pRo"
}
```

Når `next_cursor` er `null`, har du nået den sidste side.

---

## Hent en kampagne

`GET /campaigns/{campaignId}`

Returnerer det fulde kampagnedokument, inklusive live-bot-konfigurationen (`bot`), opfølgningsindstillinger, aktiverede kanaler og eventuelle nøgleord. Tidsstempler returneres som epoch-millisekunder.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { campaign } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
campaign = res.json()["campaign"]
```

**Svar**

```json
{
  "success": true,
  "campaign": {
    "id": "NBCXrhqGPSFsd6MV7pRo",
    "name": "Inbound WhatsApp Leads",
    "type": "Incoming from Unknown Contacts",
    "status": "Live",
    "language": "en",
    "ai_mode": true,
    "enabled": true,
    "archived": false,
    "created_at": 1700000000000,
    "enabled_channels": ["whatsapp", "instagram"],
    "bot": {
      "instructions": "Greet warmly and ask about their goals.",
      "goal": "Book a discovery call.",
      "ai_speed": "balanced",
      "anthropic_model": "standard",
      "max_messages": 20
    }
  }
}
```

::: note
**Bemærk:** En kampagne, der ejes af en anden konto, returnerer `404 Campaign not found` (ikke `403`), så du kan ikke se, om et ID findes på en anden konto.
:::


---

## Opret en kampagne

`POST /campaigns`

Opretter en ny kampagne. `name` og `type` er påkrævede; alt andet er valgfrit. Du kan inkludere ethvert andet kampagnefelt i samme anmodning — for eksempel `language`, `ai_mode` eller et fuldt `bot`-konfigurationsobjekt — og det vil blive gemt sammen med den nye kampagne. Ejer og oprettelsestidspunkt indstilles automatisk.

**Anmodningsfelter**

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `name` | Ja | Kampagnenavnet. |
| `type` | Ja | En af de fire kampagnetyper ovenfor. |
| `language` | Nej | Sprog, som botten svarer på (f.eks. `"en"`). |
| `ai_mode` | Nej | Hvorvidt AI-tilstand er aktiveret (`true`/`false`). Ved en kampagne, der besvares af en AI-agent, returnerer læsninger agentens **Aktiv**-til/fra-knap frem for en gemt værdi – se bemærkningen under opdatering nedenfor. |
| `bot` | Nej | Bot-konfigurationsobjektet (se [Bot-konfigurationsfelter](#bot-configuration-fields)). |
| `list_id` | Nej | ID på kontaktlisten, der skal tilknyttes. |
| `event_id` | Nej | ID på den begivenhedstype, som AI'en kan booke. |
| `event_ids` | Nej | Flere begivenhedstyper på én gang som et array af begivenhedstype-ID'er – den første er standarden. Send enten `event_id` eller `event_ids`, ikke begge. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring Promo",
    "type": "Outgoing",
    "language": "en",
    "ai_mode": true,
    "bot": {
      "instructions": "Greet warmly and ask about their goals.",
      "goal": "Book a discovery call."
    }
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/campaigns", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Spring Promo",
    type: "Outgoing",
    language: "en",
    ai_mode: true,
    bot: {
      instructions: "Greet warmly and ask about their goals.",
      goal: "Book a discovery call.",
    },
  }),
});
const { campaign_id } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/campaigns",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "Spring Promo",
        "type": "Outgoing",
        "language": "en",
        "ai_mode": True,
        "bot": {
            "instructions": "Greet warmly and ask about their goals.",
            "goal": "Book a discovery call.",
        },
    },
)
campaign_id = res.json()["campaign_id"]
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## Opdater en kampagne

`PUT /campaigns/{campaignId}`

Opdaterer delvist en kampagne — send kun de felter, du ønsker at ændre. Dette er det eneste generelle opdaterings-verb; der findes ikke `PATCH /campaigns/{campaignId}` (de to `PATCH`-ruter er de snævre [enable](#enable-or-disable-a-campaign) og [archive](#archive-or-restore-a-campaign) skifteknapper).

**Hvilke felter du kan ændre.** Alt hvad kampagneditoren skriver, inklusive `name`, `status`, `type`, `language`, `ai_mode`, `enabled_channels`, trigger- og dryp-indstillinger, flag for booking og opfølgning, felter til overvågning af Instagram/Facebook og hele `bot`-konfigurationen. Identitet og ejerskab er låst i kampagnens levetid: `user`, `id` og `created_at` afvises, og det samme gør ethvert feltnavn, som slutpunktet ikke genkender. Afvisning sker pr. anmodning, ikke pr. felt — én ukendt nøgle returnerer en `400`, og **intet** i den anmodning bliver skrevet.

**`ai_mode` på en kampagne understøttet af en agent afspejler agenten.** Når en kampagne besvares af en AI-agent, returnerer læsning af kampagnen `ai_mode` afledt af den agents **Aktiv**-til/fra-knap – den ene kontakt, der rent faktisk afgør, om AI'en svarer. Skrivning af `ai_mode` på en sådan kampagne accepteres, men ændrer ikke det, du læser tilbage; slå i stedet agentens Aktiv-knap til eller fra (i dashboardet eller via Agents API). På klassiske kampagner uden en agent læser og skriver `ai_mode` den lagrede værdi som før.

**Bot-felter flettes, de overskrives ikke.** Send bot-indstillinger enten som prikkede nøgler (`"bot.instructions": "..."`) eller som et indlejret objekt (`"bot": { "instructions": "..." }`) — begge skriver blad for blad, så de felter, du udelader, beholder deres nuværende værdier. `bot.instructions`, `bot.goal`, `bot.rules` og `bot.personality` kan alle redigeres på denne måde, ligesom enhver anden bot-indstilling angivet under [Bot-konfigurationsfelter](#bot-configuration-fields). Det samme gælder for `test_bot`, `frequency` og `follow_up_config`.

For at erstatte en bot-konfiguration fuldstændigt — og slette ethvert felt, du ikke sender — skal du bruge `bot_replace` (eller `test_bot_replace`) med det komplette objekt. Du kan ikke kombinere en erstatning og en fletning for det samme objekt i én anmodning; det returnerer en `400`.

::: note
**Bemærk:** Skrivning til `bot.*` via API'et træder i kraft **øjeblikkeligt** på den aktive kampagne. Dashboard-editoren fungerer anderledes: rettelser der gemmes som et udkast og går først live, når klienten klikker på Udgiv. Så hvis en klient har upublicerede dashboard-ændringer, ligger de i `test_bot`, og en API-læsning af `bot` viser korrekt, hvad AI'en bruger lige nu.
:::


Et par felter angives via en dedikeret nøgle i stedet for at blive skrevet direkte: brug `list_id` til kontaktlisten, `event_id` til begivenhedstypen (eller `event_ids`, et sorteret array af begivenhedstype-ID'er, for at lade AI'en booke flere – den første er standarden; et tomt array fjerner tilknytningen til dem alle), og `contact_ids` (et array af kontakt-ID'er) til kampagnens kontakter. Vidensbase-poster administreres via [FAQ-API'et](faqs.md), ikke dette slutpunkt.

**Tags erstatter, de fletter ikke.** Send `tags` som det komplette array, og det bliver kampagnens tag-sæt — se [Kampagne-tags](#campaign-tags) for felterne og for slutpunkterne, der tilføjer eller redigerer et enkelt tag.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Spring Promo v2", "enabled_channels": ["whatsapp"] }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Spring Promo v2",
      enabled_channels: ["whatsapp"],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"name": "Spring Promo v2", "enabled_channels": ["whatsapp"]},
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## Slet en kampagne

`DELETE /campaigns/{campaignId}`

Sletter en kampagne permanent. Dette kan ikke fortrydes — hvis du får brug for kampagnen igen senere, bør du [arkivere den](#archive-or-restore-a-campaign) i stedet.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Svar**

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

---

## Dupliker en kampagne

`POST /campaigns/{campaignId}/duplicate`

Opretter en kopi af kampagnen, hvor alle indstillinger bevares. Kopien starter som **deaktiveret**, og dens navn får suffikset `(copy)`, så den aldrig sender beskeder, før du eksplicit aktiverer den.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { campaign_id } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
new_campaign_id = res.json()["campaign_id"]
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "aZ9plnewCopyId01234"
}
```

> Dubletkopier **inden for én konto**.

---


## Aktivér eller deaktivér en kampagne

`PATCH /campaigns/{campaignId}/enabled`

Slår en kampagne til eller fra. En deaktiveret kampagne stopper med at interagere med kontakter, men beholder hele sin konfiguration.

**Anmodningsfelter**

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `enabled` | Ja | `true` for at aktivere, `false` for at deaktivere. Skal være en boolsk værdi. |

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled",
  {
    method: "PATCH",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ enabled: true }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.patch(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"enabled": True},
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "enabled": true
}
```

---

## Arkiver eller gendan en kampagne

`PATCH /campaigns/{campaignId}/archived`

Arkiverer eller gendanner en kampagne. Arkiverede kampagner skjules fra standardlisten over kampagner, men beholder alle deres data og kan gendannes når som helst.

**Anmodningsfelter**

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `archived` | Ja | `true` for at arkivere, `false` for at gendanne. Skal være en boolsk værdi. |

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "archived": true }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived",
  {
    method: "PATCH",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ archived: true }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.patch(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"archived": True},
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "archived": true
}
```

---

## Opdater bot-konfigurationen

`PUT /campaigns/{campaignId}/bot-config`

Dette er den sikre måde at ændre individuelle bot-indstillinger på. Hvert felt, du sender, **flettes** ind i den eksisterende bot-konfiguration, så alle felter, du udelader, bevares. Brug dette i stedet for kampagne-opdaterings-endpointet, når du kun vil justere en del af botten.

Feltnøgler må kun indeholde bogstaver, tal, understregninger og bindestreger.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": "Always answer in a friendly, concise tone.",
    "ai_speed": "balanced"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      instructions: "Always answer in a friendly, concise tone.",
      ai_speed: "balanced",
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "instructions": "Always answer in a friendly, concise tone.",
        "ai_speed": "balanced",
    },
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

### Bot-konfigurationsfelter

Alle bot-felter er valgfrie. Send kun dem, du ønsker at indstille. Eventuelle yderligere bot-felter ud over dem, der er anført her, accepteres og gemmes, som de er.

| Felt | Type | Beskrivelse |
|---|---|---|
| `instructions` | string | De primære instruktioner, der styrer, hvordan botten taler med kontakter. |
| `rules` | string | Hårde regler, som botten altid skal følge. |
| `goal` | string | Det resultat, botten skal arbejde hen imod i hver samtale. |
| `personality` | string | Beskrivelse af bottens tonefald og personlighed. |
| `ai_speed` | string | Hvor meget ræsonnement AI'en anvender, før den svarer. En af `fast`, `fast_thinker`, `balanced`, `thorough`. |
| `anthropic_model` | string | Det AI-kvalitetsniveau, der bruges til denne kampagnes svar. En af `standard`, `economy` (forældet), `max`, `mini`. `max` og `mini` træder kun i kraft på konti, der er berettigede til disse niveauer. |
| `max_messages` | integer | Maksimalt antal bot-beskeder pr. samtale. |
| `alert_human_when` | string | Betingelser for, hvornår botten skal advare et menneskeligt teammedlem. |
| `availability` | object | Bottens tidsplan for aktive timer. Du kan indstille dette her eller bruge det dedikerede [endpoint for aktive timer](#set-the-bot-active-hours). |
| `follow_up_config` | object | Konfiguration af opfølgningsadfærd, gemt som angivet. |

---

## Indstil bottens aktive timer

`PUT /campaigns/{campaignId}/active-hours`

Angiver bottens tilgængelighedsplan. Uden for de konfigurerede tidsvinduer svarer botten ikke automatisk. Dette skriver til `availability`-feltet i bot-konfigurationen.

**Anmodningsfelter**

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `availability` | Ja | Et objekt indekseret efter ugedag. Tilladte nøgler er `monday` til `sunday`; enhver anden nøgle returnerer en `400`. Dage, du udelader, forbliver uændrede. |

Hver ugedag indeholder enten et enkelt tidsvindue eller en række af vinduer. Et vindue har en `start_time` og `end_time` i 24-timers `HH:MM`-format.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "availability": {
      "monday": { "start_time": "09:00", "end_time": "17:00" },
      "tuesday": [
        { "start_time": "09:00", "end_time": "12:00" },
        { "start_time": "13:00", "end_time": "17:00" }
      ]
    }
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      availability: {
        monday: { start_time: "09:00", end_time: "17:00" },
        tuesday: [
          { start_time: "09:00", end_time: "12:00" },
          { start_time: "13:00", end_time: "17:00" },
        ],
      },
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "availability": {
            "monday": {"start_time": "09:00", "end_time": "17:00"},
            "tuesday": [
                {"start_time": "09:00", "end_time": "12:00"},
                {"start_time": "13:00", "end_time": "17:00"},
            ],
        }
    },
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## List en kampagnes brugerdefinerede funktioner

`GET /campaigns/{campaignId}/custom-functions`

Returnerer de brugerdefinerede funktioner, der er knyttet til denne kampagne, opløst til fulde definitioner. Brugerdefinerede funktioner er eksterne HTTP-handlinger, som botten kan kalde under en samtale — for eksempel at tjekke lagerstatus i din butik eller oprette en post i dit CRM-system.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { custom_functions } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
custom_functions = res.json()["custom_functions"]
```

**Svar**

```json
{
  "success": true,
  "custom_functions": [
    {
      "id": "fn_abc123",
      "name": "check_stock",
      "description": "Looks up whether a product is in stock.",
      "url": "https://example.com/api/stock",
      "method": "POST",
      "input": [
        { "name": "sku", "type": "string" }
      ],
      "ai_action": "Tell the customer whether the item is available.",
      "created_at": 1700000000000,
      "updated_at": 1700000500000
    }
  ]
}
```

---

## Knyt en brugerdefineret funktion til en kampagne

`POST /campaigns/{campaignId}/custom-functions`

Knytter en eksisterende [brugerdefineret funktion](../ai-automation/custom-functions.md) til denne kampagne, så botten kan kalde den under en samtale. Hvis man knytter en funktion, der allerede er tilknyttet, sker der intet.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `custom_function_id` | Ja | ID på den brugerdefinerede funktion, der skal tilknyttes. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "custom_function_id": "fn_abc123" }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "custom_function_id": "fn_abc123"
}
```

---

## Fjern tilknytning af en brugerdefineret funktion fra en kampagne

`DELETE /campaigns/{campaignId}/custom-functions/{customFunctionId}`

Hvis man fjerner tilknytningen af en funktion, der ikke er tilknyttet, sker der intet.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions/fn_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "custom_function_id": "fn_abc123"
}
```

---

## Knyt en vidensbasekilde til en kampagne

`POST /campaigns/{campaignId}/kb-sources`

Knytter en vidensbasekilde (oprettet via [FAQ-API'et](faqs.md)) til denne kampagne, så botten kan bruge den, når den svarer. Hvis man knytter en kilde, der allerede er tilknyttet, sker der intet.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `kb_source_id` | Ja | ID på den vidensbasekilde, der skal tilknyttes. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/kb-sources?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kb_source_id": "kb_abc123" }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "kb_source_id": "kb_abc123"
}
```

---

## Fjern tilknytning af en vidensbasekilde fra en kampagne

`DELETE /campaigns/{campaignId}/kb-sources/{kbSourceId}`

Hvis man fjerner tilknytningen af en kilde, der ikke er tilknyttet, sker der intet.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/kb-sources/kb_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "kb_source_id": "kb_abc123"
}
```

---

## Knyt en MCP-server til en kampagne

`POST /campaigns/{campaignId}/mcp-servers`

Linker en MCP-server til denne kampagne, hvilket giver botten adgang til serverens værktøjer under en samtale. Hvis man linker en server, der allerede er linket, sker der intet.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `mcp_server_id` | Ja | ID på den MCP-server, der skal linkes. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/mcp-servers?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mcp_server_id": "mcp_abc123" }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "mcp_server_id": "mcp_abc123"
}
```

---

## Fjern link til en MCP-server fra en kampagne

`DELETE /campaigns/{campaignId}/mcp-servers/{mcpServerId}`

Hvis man fjerner linket til en server, der ikke er linket, sker der intet.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/mcp-servers/mcp_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "mcp_server_id": "mcp_abc123"
}
```

---

## Kampagnens mediebibliotek

Mediebiblioteket indeholder billeder, videoer, dokumenter og stemmenoter, som botten kan sende under en samtale.

### Vis en kampagnes mediebibliotek

`GET /campaigns/{campaignId}/media-library`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "media_items": [
    {
      "id": "media_abc123",
      "item_id": "media_abc123",
      "title": "Pricing sheet",
      "description": "Send when the contact asks about pricing.",
      "media_url": "https://example.com/pricing.pdf",
      "media_content_type": "application/pdf",
      "type": "document",
      "agent_id": "",
      "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
      "media_home": "campaign"
    }
  ]
}
```

`media_url` er en signeret URL, der blev oprettet ved upload – den kan være udløbet, når du læser den igen; dashboardet gen-signerer den efter behov.

### Upload et medieelement

`POST /campaigns/{campaignId}/media-library`

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `base64Data` | Ja | Filen, base64-kodet (uden data-URL-præfiks). |
| `mimeType` | Ja | MIME-type for filen (f.eks. `image/png`). |
| `title` | Ja | Kort etiket, der vises i biblioteket og i AI-prompten. |
| `description` | Ja | Instruktion til botten om, **hvornår** dette element skal sendes. |
| `fileName` | Nej | Oprindeligt filnavn, bruges til at oprette navnet på lagringsobjektet. |
| `sendMessage` | Nej | Foretrukken ordlyd, som botten skal bruge, når den sender dette element. |
| `maxSendsPerConversation` | Nej | Maksimalt antal gange botten må sende dette element til én kontakt i en samtale. Standard er `1`. |
| `sendAsVoiceNote` | Nej | Ved lyd-upload, transkod den til en WhatsApp-stemmenote. Standard er `false` (gemmes som en almindelig lydfil). |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "base64Data": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mimeType": "image/png",
    "title": "Product photo",
    "description": "Send when the contact asks what the product looks like."
  }'
```

**Svar**

```json
{
  "success": true,
  "itemId": "media_abc123",
  "mediaUrl": "https://example.com/product.png",
  "storagePath": "ai_media/campaigns/NBCXrhqGPSFsd6MV7pRo/media_abc123.png",
  "mediaContentType": "image/png",
  "type": "image",
  "isVoiceNote": false
}
```

### Opdater et medieelement

`PATCH /campaigns/{campaignId}/media-library/{itemId}`

Redigerer kun elementets metadata — for at erstatte selve filen skal du slette elementet og uploade et nyt.

| Felt | Beskrivelse |
|---|---|
| `title` | Kort etiket. |
| `description` | Instruktion om hvornår der skal sendes. |
| `send_message` | Foretrukken ordlyd som botten skal bruge. |
| `max_sends_per_conversation` | Ikke-negativt heltal, eller `null` for at rydde grænsen. |

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library/media_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Updated pricing sheet" }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "item_id": "media_abc123"
}
```

### Slet et medieelement

`DELETE /campaigns/{campaignId}/media-library/{itemId}`

Sletning af et element, der allerede er væk, er en no-op.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library/media_abc123?apiKey=YOUR_API_KEY"
```

**Svar**

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

---

## Kampagne-tags

Et kampagne-tag er en etiket, du lærer botten at anvende på en kontakt under en samtale — `hot-lead`, `not-interested`, `booked-a-call`. Hvert tag består af tre dele:

| Felt | Type | Beskrivelse |
|---|---|---|
| `name` | string, påkrævet | Selve etiketten. Dette er, hvad botten anvender på kontakten, og hvad du matcher på senere, så hold det kort og stabilt. |
| `description` | string | Instruktionen, der fortæller botten, **hvornår** dette tag skal anvendes. Dette er den del, der udfører arbejdet — "personen bekræfter, at de har tilmeldt sig fællesskabet" bruges, "varm emne" gør ikke. |
| `webhook` | string | En URL, der modtager en `POST` i det øjeblik, tagget lander på en kontakt. Udelad den, hvis du ikke har brug for en. |
| `tag_id` | string | Valgfri. Linker denne post til et eksisterende tag på din konto i stedet for et nyt. Angiv den, hvis du vil adressere dette specifikke tag senere med slutpunkterne for enkelte tags nedenfor. |

Tag-navne skal være unikke inden for en kampagne. Botten anvender tags **efter navn**, så to poster, der deler samme navn, har ingen defineret vinder.

### Angiv alle en kampagnes tags

`PUT /campaigns/{campaignId}` med et `tags` array.

Dette erstatter kampagnens tags med præcis det, du sender, hvilket er det samme, som dashboardets Tags-fane gør, når du gemmer. **Send det komplette array hver gang** — et tag, du udelader, er et tag, du har slettet. Ved at sende `[]` sletter du dem alle.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": [
      {
        "name": "hot-lead",
        "description": "The person confirms they want to buy, or asks how to get started right away.",
        "webhook": "https://example.com/hooks/campaign-events"
      },
      {
        "name": "not-interested",
        "description": "The person declines the offer or says they are not a fit."
      }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      tags: [
        {
          name: "hot-lead",
          description:
            "The person confirms they want to buy, or asks how to get started right away.",
          webhook: "https://example.com/hooks/campaign-events",
        },
        {
          name: "not-interested",
          description: "The person declines the offer or says they are not a fit.",
        },
      ],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "tags": [
            {
                "name": "hot-lead",
                "description": "The person confirms they want to buy, or asks how to get started right away.",
                "webhook": "https://example.com/hooks/campaign-events",
            },
            {
                "name": "not-interested",
                "description": "The person declines the offer or says they are not a fit.",
            },
        ]
    },
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

Læs tags tilbage med [`GET /campaigns/{campaignId}`](#get-a-campaign).

### Tilføj ét tag

`POST /campaigns/{campaignId}/tags`

Tilføjer et enkelt tag uden at skulle sende resten igen. Brug dette, når du tilføjer til et sæt, som du ikke har bygget i denne anmodning.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tag": { "name": "booked-a-call", "description": "The person confirms a booked time." } }'
```

At poste det nøjagtig samme tag to gange gør intet anden gang. At poste det samme `tag_id` med et andet navn eller en anden beskrivelse tilføjer en **anden** post i stedet for at redigere den første — brug slutpunktet nedenfor til at redigere på stedet.

### Opdater eller fjern ét tag

`PUT /campaigns/{campaignId}/tags/{tagId}`
`DELETE /campaigns/{campaignId}/tags/{tagId}`

Disse adresserer én post via dens `tag_id`, så de virker kun på tags, der er oprettet med en. Hvis et tag ikke har nogen `tag_id`, skal du ændre det med hele-arrayet `PUT /campaigns/{campaignId}` ovenfor.

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/tags/tag_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tag": { "name": "hot-lead", "description": "Updated instruction." } }'
```

Et `tagId`, der ikke er på kampagnen, returnerer `404` med `"Tag not found in campaign tags"`.

---

## Skift en kampagnes kanaler

`POST /campaigns/{campaignId}/channels`

Tilføjer eller fjerner kanaler fra kampagnens `enabled_channels`-array uden at skulle sende hele arrayet igen — mere sikkert end [`PUT /campaigns/{campaignId}`](#update-a-campaign), når noget andet muligvis redigerer kampagnen på samme tid.

Send enten et enkelt skift eller en batch — ikke begge dele i samme anmodning:

```json
{ "channel": "whatsapp", "action": "add" }
```

```json
{ "add": ["whatsapp", "instagram"], "remove": ["sms"] }
```

| Felt | Beskrivelse |
|---|---|
| `channel` | Én kanal der skal skiftes. Par med `action`. |
| `action` | `"add"` eller `"remove"`. Par med `channel`. |
| `add` | Array af kanaler der skal tilføjes. Batch-form — brug i stedet for `channel`/`action`. |
| `remove` | Array af kanaler der skal fjernes. Batch-form. |

Gyldige kanaler: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `messenger`, `facebook`, `chat_widget`, `custom_channel`, `imessage`, `telegram`, `instagram_private`, `line`, `viber`, `tiktok`, `email`, `linkedin`, `skool`.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/channels?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "action": "add" }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "added": ["whatsapp"],
  "removed": []
}
```

> Dette ændrer kun, hvilke kanaler kampagnen annoncerer på — det afgør ikke, hvem der besvarer en kanal. Se [Kampagnetyper](#campaign-types) ovenfor og [Ruter en kampagne til indgående kanaler](#route-a-campaign-to-incoming-channels) nedenfor for dette.

---

## Kommentar-til-DM (Instagram og Facebook)

Kommentar-til-DM forvandler en kommentar på et af dine opslag til en privat samtale: nogen kommenterer, botten sender dem en DM, og kampagnen tager samtalen derfra. Den konfigureres udelukkende gennem kampagneobjektet, så der er intet ved den, der kun findes i brugerfladen.

Forbind Facebook-siden først — se [Kanalforbindelse](channels.md#instagram--messenger-meta). Indstil derefter felterne nedenfor med [`PUT /campaigns/{campaignId}`](#update-a-campaign).

> **Kampagnen skal være `Live`.** Kommentarovervågning opsamler kun kampagner, hvis `status` er `Live` (alle kombinationer af store og små bogstaver — se [Kampagnetyper](#campaign-types)). Enhver anden status deaktiverer den lydløst, og en opdigtet status som `"Active"` afvises nu med en `400` i stedet for at blive gemt. Gyldige statusser inkluderer `Draft`, `Pending Approval`, `Scheduled`, `Live`, `Paused`, `Completed`, `Sent` og `Failed`.

**Felter**

| Felt | Type | Beskrivelse |
|---|---|---|
| `monitor_instagram_posts` | boolean | Overvåg hvert Instagram-opslag på den tilknyttede side. |
| `instagram_post_ids` | string[] | Overvåg kun disse Instagram-opslag. Lad stå uindstillet, når `monitor_instagram_posts` er slået til. |
| `instagram_comment_delay_minutes` | number | Vent dette antal minutter efter en kommentar, før DM'en sendes. |
| `monitor_facebook_posts` | boolean | Overvåg hvert Facebook-opslag på den tilknyttede side. |
| `facebook_post_ids` | string[] | Overvåg kun disse Facebook-opslag. |
| `facebook_comment_delay_minutes` | number | Forsinkelse før DM'en, i minutter. |
| `public_comment_reply_instructions` | string | Vejledning til det synlige svar, der efterlades på selve kommentaren. Tilsidesætter standardformuleringen "tjek dine DM'er". |
| `first_response_mode` | string | `"ai"` (standard) genererer den første DM og det offentlige svar. `"exact_text"` sender din ordlyd ordret, uden AI-generering og uden kreditforbrug. |
| `first_response_exact_text` | string | Den ordrette første DM, der bruges, når `first_response_mode` er `"exact_text"`. Påkrævet for at denne tilstand træder i kraft. |
| `first_response_exact_text_variants` | string[] | Ekstra formuleringer til den første DM. Én vælges tilfældigt pr. afsendelse, så gentagne DM'er ikke er byte-identiske. |
| `public_comment_reply_exact_text` | string | Det ordrette offentlige svar i `"exact_text"`-tilstand. Lad stå blank for at springe det offentlige svar over og kun sende DM'en. |
| `public_comment_reply_exact_text_variants` | string[] | Ekstra formuleringer til det offentlige svar. |
| `monitor_instagram_followers` | boolean | Behandl en ny følger som en udløser og send en åbnings-DM (Instagram-personlige konti). |
| `follower_outreach_instructions` | string | Vejledning til den åbnings-DM til nye følgere. |
| `respond_to_instagram_story_replies` | boolean | Om AI'en skal besvare svar på dine Instagram Stories. Standard `true`. Sæt `false` for at lade Story-svar lande i chatten (med Story'en vedhæftet) uden et AI-svar. Live-indstilling — ikke en del af udkastet, så den behøver ikke publicering. |

**Rydning af et felt**

Disse felter fjernes i stedet for at blive sat til `null`, når du sender `null`, så botten falder tilbage på sine standardindstillinger: `instagram_post_ids`, `facebook_post_ids`, `instagram_comment_delay_minutes`, `facebook_comment_delay_minutes`, `public_comment_reply_instructions`, `follower_outreach_instructions`, `first_response_exact_text`, `first_response_exact_text_variants`, `public_comment_reply_exact_text`, `public_comment_reply_exact_text_variants`.

> **Én ukendt nøgle afviser hele anmodningen.** `PUT /campaigns/{campaignId}` validerer hele kroppen mod en tilladelsesliste. En nøgle, der ikke genkendes, returnerer `400` for hele anmodningen — den ignoreres ikke lydløst, og ingen af de andre felter i den krop skrives.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "Live",
    "monitor_instagram_posts": true,
    "instagram_comment_delay_minutes": 2,
    "first_response_mode": "exact_text",
    "first_response_exact_text": "Hey! Sending the details over now.",
    "first_response_exact_text_variants": [
      "Hi there, here are the details you asked for.",
      "Thanks for commenting, here is what you need."
    ],
    "public_comment_reply_exact_text": "Just sent you a DM."
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      status: "Live",
      monitor_instagram_posts: true,
      instagram_comment_delay_minutes: 2,
      first_response_mode: "ai",
      public_comment_reply_instructions:
        "Tell them to check their message requests folder too.",
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "status": "Live",
        "monitor_facebook_posts": True,
        "facebook_post_ids": None,
        "facebook_comment_delay_minutes": 5,
    },
)
data = res.json()
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

> Det synlige svar, der efterlades på kommentaren, kræver funktionen til kommentarsvar i dit abonnement. Uden den sendes DM'en stadig, og det offentlige svar springes over.

---

## Optimer en kampagne med AI

`POST /campaigns/{campaignId}/optimize`

Kører den samme AI-omskrivning som dashboardets Optimize- og thumbs-down-feedback-flows: tager din feedback, omskriver bottens instruktioner og klargør resultatet som en ny kladdeversion, som du kan gennemse.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `user_feedback` | Et af disse to er påkrævet | Fri feedback, der beskriver, hvad der skal forbedres. |
| `thumbs_down_feedback` | Et af disse to er påkrævet | Feedback indsamlet fra en thumbs-down på et specifikt botsvar. |
| `thumbs_down_message` | Nej | Den botbesked, som thumbs-down-feedbacken refererer til. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/optimize?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_feedback": "Make the tone more casual and mention the free trial earlier." }'
```

**Svar** (`202` — omskrivningen kører i baggrunden)

```json
{ "success": true, "campaign_id": "NBCXrhqGPSFsd6MV7pRo" }
```

Pol [`GET /campaigns/{campaignId}`](#get-a-campaign) og hold øje med `test_bot.status`: den skifter med det samme til `"Optimizing"`, og derefter tilbage til `"Draft"`, når omskrivningen lander i `test_bot`. Derfra opfører den sig som enhver anden dashboard-kladde — gennemse den, og publicer den derefter i dashboardet for at gøre den aktiv. En `409` betyder, at en optimering allerede kører for denne kampagne.

> Optimering koster credits, ligesom enhver anden AI-handling på din konto.

---

## Tildel en kontakt til en kampagne

`POST /campaigns/{campaignId}/contacts/{contactId}/assign`

Placerer en eksisterende kontakt i en kampagne og sender, hvis du anmoder om det, kampagnens åbningsbesked med det samme. Dette er måden at sende en kampagnes godkendte WhatsApp-skabelon til én kontakt på: den skabelon, som en kampagne blev godkendt med, tilhører den pågældende kampagne, så den vises ikke i [Templates API](templates.md)-biblioteket og kan ikke sendes via `/whatsapp-templates/send`.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `sendOpeningMessage` | Nej | `true` sender kampagnens åbningsbesked (den godkendte WhatsApp-skabelon på en WhatsApp-kampagne), så snart kontakten er tildelt. Standard er `false`. |
| `triggerAIResponse` | Nej | `true` lader AI'en skrive sin egen første besked i stedet. Standard er `false`. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/contacts/contact_abc123/assign?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sendOpeningMessage": true }'
```

**Svar**

```json
{
  "success": true,
  "data": { "contactId": "contact_abc123", "campaignId": "NBCXrhqGPSFsd6MV7pRo" }
}
```

> **Kreditter:** Afsendelse af åbningsbeskeden på en WhatsApp-kampagne afregnes som enhver anden skabelonafsendelse, prissat efter modtagerens land og skabelonens kategori. På andre kanaler er åbningsbeskeden en normal udgående besked.

---

## Ruter en kampagne til indgående kanaler

Disse endpoints styrer, hvilken kampagne der besvarer nye, ukendte kontakter på en kanal. **Foretræk Entry Points** til nye integrationer (se noten under [Kampagnetyper](#campaign-types)) — disse forbliver nyttige til arbejde med kampagner, der ruter på den ældre måde, og til at løse en konflikt om kanalejerskab mellem to indgående kampagner.

### Tildel en kampagne til indgående kanaler

`POST /campaigns/{campaignId}/incoming-routing`

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `channels` | Ja | Liste over kanaler, som denne kampagne skal besvare for nye, ukendte kontakter. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/incoming-routing?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channels": ["whatsapp", "instagram"] }'
```

**Svar**

```json
{
  "success": true,
  "uid": "abc123",
  "campaignId": "NBCXrhqGPSFsd6MV7pRo",
  "channels": ["whatsapp", "instagram"],
  "failed": []
}
```

`channels` viser kun de kanaler, der rent faktisk blev rutet til denne kampagne; `failed` viser alle dem, der ikke blev det. Hvis alle anmodede kanaler fejler, fejler selve anmodningen.

### Ryd en kampagnes indgående ruting

`DELETE /campaigns/{campaignId}/incoming-routing`

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `channelToUnassign` | Nej | Ryd ruting for kun denne ene kanal. Udelad for at rydde alle kanaler, som denne kampagne i øjeblikket besvarer. |

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/incoming-routing?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channelToUnassign": "instagram" }'
```

**Svar**

```json
{
  "success": true,
  "uid": "abc123",
  "campaignId": "NBCXrhqGPSFsd6MV7pRo",
  "channelsRemoved": ["instagram"]
}
```

### Genaktiver en dvalende kampagne

`POST /campaigns/{campaignId}/reactivate`

Bring en kampagne tilbage fra `Ended`, `Completed`, `Paused` eller `Draft` og genindtag dens kanaler. Virker kun på `Incoming from Unknown Contacts` eller `Combined` kampagner — en kampagne, der allerede er `Live`, behandles som en succes, hvor der ikke er mere at gøre.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/reactivate?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "data": {
    "success": true,
    "channelsReactivated": ["whatsapp"],
    "channelsBlockedByConflict": [],
    "campaignType": "Incoming from Unknown Contacts"
  }
}
```

En kanal, der allerede er optaget af en anden kampagnes agent, vises i `channelsBlockedByConflict` i stedet for at hele kaldet fejler — brug [stop en modstridende indgående kampagne](#stop-a-conflicting-incoming-campaign) nedenfor for at frigøre den først, hvis du ønsker, at denne kampagne skal overtage den. En `400` returneres for en kampagnetype, der ikke understøtter genaktivering, eller en status, der ikke er en af de dvaletilstande, der er nævnt ovenfor.

### Stop en modstridende indgående kampagne

`POST /campaigns/{campaignId}/stop-incoming`

Frigør denne kampagnes kanaler fra den ANDEN kampagne, der i øjeblikket holder dem, så denne kampagne kan overtage dem derefter. Dette er REST-versionen af det, som dashboardet gør automatisk, når du starter en indgående kampagne i en kanal, som en anden allerede besvarer.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/stop-incoming?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "ended_campaign_ids": [],
  "released_channels": ["whatsapp"],
  "cleared_entire_field": false
}
```

`released_channels` returneres tom, når denne kampagne allerede ejer alle de kanaler, den annoncerer for — der er intet at overtage.

---

## Omkostningsoverslag

Estimer hvad det vil koste at starte en kampagne, før du sender den.

### Omkostningsoverslag for WhatsApp-skabelon

`GET /campaigns/{campaignId}/template-cost-estimate`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/template-cost-estimate?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "billing_mode": "credits",
  "data": {
    "countries": [
      {
        "countryCode": "1",
        "name": "United States",
        "iso": "US",
        "flag": "🇺🇸",
        "contactCount": 120,
        "costPerContact": 2,
        "subtotal": 240
      }
    ],
    "totalContacts": 120,
    "totalTemplateCost": 240,
    "templateCategory": "marketing",
    "billing_mode": "credits",
    "service_messages_billable_soon": false
  }
}
```

`billing_mode` er `"credits"` på den administrerede WhatsApp-linje. På en linje, hvor Meta fakturerer din egen WhatsApp Business-konto direkte, returneres `costPerContact`, `subtotal` og `totalTemplateCost` som `null` — aldrig `0`, hvilket ville blive læst som gratis — da der ikke er noget kreditbeløb at rapportere.

### Omkostningsoverslag for SMS

`GET /campaigns/{campaignId}/sms-cost-estimate`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/sms-cost-estimate?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "billing_mode": "twilio_direct",
  "data": {
    "totalContacts": 120,
    "messageLength": 87,
    "segmentsPerMessage": 1,
    "totalSegments": 120,
    "estimatedCostUsd": 0.96,
    "priceUnit": "USD per segment",
    "billedByTwilio": true
  }
}
```

SMS sendes altid via din egen Twilio-konto (se [SMS-udbyder](../settings/sms-provider.md)), så dette faktureres altid direkte af Twilio — `estimatedCostUsd` er et estimat af den Twilio-regning, ikke et kreditgebyr.

---

## Grænsekontroller

Kontrollér en grænse, før du sender, i stedet for at opdage det via en mislykket afsendelse.

### Kampagne-omfattende kontroller

`GET /campaigns/{campaignId}/limits/ai-credit-messaging` — om lancering eller planlægning af denne kampagne ville overskride din kontos AI-kredit-beskedgrænse.

`GET /campaigns/{campaignId}/limits/messaging` — om det ville overskride din kontos daglige beskedgrænse.

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/limits/messaging?apiKey=YOUR_API_KEY"
```

**Svar** (grænsen er ikke overskredet)

```json
{
  "success": true,
  "data": "Campaign is within the daily messaging limit."
}
```

En `400` returneres i stedet, når grænsen overskrides, med årsagen i `error`.

### Kontobaserede tjek

`GET /campaigns/limits/campaigns` — om du har nået din abonnementsgrænse for månedlig oprettelse af kampagner.

`GET /campaigns/limits/contacts` — om du har nået dit abonnements kontaktgrænse.

```bash
curl "https://api.youraiconnector.com/v1/campaigns/limits/campaigns?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "data": "You can create 3 more campaigns this month."
}
```

---

## Samlede kampagnestatistikker

`GET /campaigns/stats/totals`

Samlede antal sendte og besvarede beskeder for hver kampagne OG hver AI-agent på din konto over et rullende tidsvindue — de samme tal, som kampagnelistesiden viser ud for hver række, i ét kald i stedet for én anmodning pr. kampagne.

| Forespørgselsparameter | Beskrivelse |
|---|---|
| `days` | Størrelsen på det rullende tidsvindue, 1-365. Standard er 90. |

```bash
curl "https://api.youraiconnector.com/v1/campaigns/stats/totals?days=30&apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "byCampaign": {
    "NBCXrhqGPSFsd6MV7pRo": { "sent": 1204, "replied": 318 }
  },
  "byAgent": {
    "agent_abc123": { "sent": 1204, "replied": 318 }
  },
  "windowDays": 30
}
```

`byAgent` er sin egen opsummering, ikke en sum af `byCampaign` — trafikken på en konto, der er indfødt AI-agent, kan være uden kampagner, så den ville ellers være usynlig her.

---

## Test en kampagne i legepladsen

Legepladsen giver dig mulighed for at føre en samtale med en kampagnes bot uden at røre en rigtig kanal eller en rigtig kontakt. Det er den samme sandkasse som kontrolpanelets testpanel, og den er fuldt tilgængelig via API'et.

Flowet er: opret en skjult testkontakt, send en besked, og forespørg derefter kampagnen om bottens svar. Svar genereres asynkront, så de ankommer i `test_messages` på kampagnen i stedet for i svarteksten.

> **Playground kører over API-omkostningskreditter.** En test-samtale, der startes med en API-nøgle, debiteres til den normale AI-beskedtakst, ligesom et rigtigt svar, og vises i din forbrugshistorik som en almindelig post. Test fra dashboardet forbliver gratis. Forskellen er tilsigtet: en testkørsel udfører det samme AI-arbejde som en live-kørsel, så en ubegrænset API-playground ville være en måde at køre ubegrænset AI på andres regning.

### Trin 1 - Opret testkontakten

`POST /campaigns/{campaignId}/try-out/contact`

Opretter den skjulte testkontakt og linker den til kampagnen. Alle brødtekstfelter er valgfrie; alt, hvad du udelader, falder tilbage på en indbygget eksempelidentitet (John Doe).

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `first_name` | Nej | Testkontaktens fornavn. |
| `last_name` | Nej | Testkontaktens efternavn. |
| `email` | Nej | Testkontaktens e-mail. |
| `phone` | Nej | Testkontaktens telefonnummer. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/contact?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Maria", "last_name": "Lopez" }'
```

**Svar**

```json
{
  "success": true,
  "contactId": "8kQx1vNbA2fLpR7d"
}
```

### Trin 2 - Registrer den indgående besked

`POST /campaigns/{campaignId}/try-out/messages`

Tilføjer beskeder til testtråden. Send den besøgendes besked hertil først, så den optræder i samtaleloggen, som botten læser.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `messages` | Ja | Array af beskedobjekter, maks. 200 pr. anmodning. |
| `messages[].body` | Ja | Beskedteksten. |
| `messages[].direction` | Ja | `"inbound"` for den besøgende, `"outbound"` for botten. |
| `messages[].timestamp` | Nej | ISO-8601-streng eller epoch-millisekunder. |
| `messages[].role` | Nej | Valgfri rolle-etiket. |
| `messages[].name` | Nej | Valgfrit visningsnavn. |
| `ignoreCounter` | Nej | Heltal. Nulstiller kampagnens ignorerings-tæller i samme skrivning. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/messages?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "body": "Do you ship to Belgium?",
        "direction": "inbound",
        "timestamp": "2026-07-22T09:30:00Z"
      }
    ]
  }'
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "appended": 1
}
```

### Trin 3 - Bed botten om at svare

`POST /campaigns/{campaignId}/try-out/test-message`

Sender beskeden videre til AI-pipelinen. Dette er kaldet, der rent faktisk genererer et botsvar.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `message` | Ja | Den besøgendes seneste beskedtekst. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/test-message?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Do you ship to Belgium?" }'
```

**Svar**

```json
{
  "success": true,
  "data": "Published"
}
```

`"Published"` betyder, at beskeden blev sendt til AI-pipelinen. `"Ignored"` betyder, at en nyere testbesked har erstattet denne — legepladsen samler en hurtig serie af beskeder til ét svar, cirka fire sekunder efter den sidste besked, på samme måde som en rigtig samtale venter på, at nogen er færdige med at skrive. På grund af dette tidsvindue tager dette kald et par sekunder om at returnere.

### Trin 4 - Læs svaret

`GET /campaigns/{campaignId}`

Bottens svar tilføjes til kampagnens `test_messages`-array. Pol kampagnen, indtil en ny `outbound`-post optræder.

```json
{
  "success": true,
  "campaign": {
    "id": "NBCXrhqGPSFsd6MV7pRo",
    "test_messages": [
      { "body": "Do you ship to Belgium?", "direction": "inbound" },
      { "body": "Yes, we ship across the EU.", "direction": "outbound" }
    ]
  }
}
```

### Nulstil legepladsen

`POST /campaigns/{campaignId}/try-out/reset`

Rydder hele sandkassen: sletter testkontakten, tømmer `test_messages` og frigiver bottens svarlåse. Brug denne mellem testkørsler.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/reset?apiKey=YOUR_API_KEY"
```

**Svar**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

### Andre playground-endepunkter

| Endepunkt | Hvad det gør |
|---|---|
| `DELETE /campaigns/{campaignId}/try-out/contact` | Sletter kun den aktuelle testkontakt og fjerner linket til den, mens `test_messages` forbliver intakt. Lykkes selv når ingen kontakt er linket. |
| `POST /campaigns/{campaignId}/try-out/transfer` | Starter en frisk playground med en eksisterende samtale i én anmodning: erstatter testkontakten og overskriver `test_messages`. Body tager `first_name`, `last_name`, `messages` (kan være tom) og `ignoreCounter`. Foretræk denne frem for slet-derefter-opret-derefter-tilføj, som tredobler dit forbrug af hastighedsbegrænsningen. |
| `POST /campaigns/{campaignId}/try-out/messages/replace` | Overskriver `test_messages` fuldstændigt i stedet for at tilføje. Brug denne til at trunkere eller spole en tråd tilbage. |
| `POST /campaigns/{campaignId}/try-out/contact/reset-ignore-counter` | Nulstiller kun testkontaktens ignorerings-tæller til brug for gentagelses-flows efter en afsendelse. |

---

## Fejl i kampagne-API

Kampagne-endpoints returnerer standardfejlkuverten:

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

| Status | Hvornår det sker på et kampagne-endpoint |
|---|---|
| `400` | Et påkrævet felt mangler eller er ugyldigt (for eksempel en forkert `type`, en ikke-boolsk `enabled` eller en ukendt ugedagsnøgle). Returneres også af et [limit check](#limit-checks)-endpoint, når grænsen ville blive overskredet, og af [reactivate](#reactivate-a-dormant-campaign) for en kampagnetype eller status, der ikke understøtter det. |
| `404` | Kampagnen blev ikke fundet — enten eksisterer den ikke, eller også tilhører den en anden konto. |
| `409` | En [optimering](#optimize-a-campaign-with-ai) kører allerede for denne kampagne. |

De delte koder, som ethvert endpoint kan returnere — `401`, `403` (din plan inkluderer ikke API-adgang), `429` (rate limit) og `500` — er angivet med vejledning om genforsøg i [Errors & Pagination](errors-and-pagination.md).

---

## Relateret

- [Rout en kanal til en kampagne](channels.md#route-a-channel-to-a-campaign) — peg Instagram, WhatsApp eller enhver anden kanal mod den AI-agent, der skal besvare den, ved hjælp af indgangspunkter (Entry Points).
- [Generer opfølgningsskabeloner med AI](templates.md#generate-follow-up-templates-with-ai) — start et baggrundsjob, der skriver en kampagnes WhatsApp-opfølgningsskabeloner.
- [FAQs API](faqs.md) — administrer de spørgsmål-og-svar-poster, som dine kampagner bruger.
- [API-adgang](../integrations/api-access.md) — generer din API-nøgle.
- [Autentificering](authentication.md) — alle måder at angive din nøgle på.
