
# API kampanii

> **Przestarzałe.** Kampanie zostały zastąpione przez [Agentów AI](agents.md) (bot: instrukcje, wiedza, narzędzia, godziny pracy, działania następcze), [Wiadomości](broadcasts.md) (wysyłka: odbiorcy, otwarcie, harmonogram) oraz [Punkty wejścia](entry-points.md) (który Agent przejmuje nową konwersację). Punkty końcowe na tej stronie nadal działają w przypadku istniejących integracji, jednak nowe integracje nie powinny z nich korzystać. Każda odpowiedź zawiera teraz nagłówek `Deprecation: true` oraz nagłówek `Sunset` z datą, po której te punkty końcowe przestaną odpowiadać; o tej dacie informujemy każde konto z dostępem do API z dużym wyprzedzeniem. Zobacz [Kampanie zostały przeniesione do Wiadomości i Agentów](../moving-from-campaigns.md), aby uzyskać mapowanie.

## Dokąd trafił każdy punkt końcowy

Nagłówek `Sunset` obecnie informuje o dacie **31 grudnia 2026**. Po tej dacie każdy punkt końcowy na tej stronie będzie odpowiadał `410 Gone` z komunikatem odsyłającym tutaj. Przed tym terminem przenieś każde wywołanie, którego używasz, na zamiennik wskazany w wierszu; wszystkie zamienniki akceptują `sub_account_id` dla agencji dokładnie tak samo, jak wywołania kampanii.

| Wywołanie API kampanii | Użyj zamiast tego |
|---|---|
| `GET /campaigns` | [`GET /agents`](agents.md#list-agents) dla botów, [`GET /broadcasts`](broadcasts.md#list-broadcasts) dla wysyłek. |
| `GET /campaigns/{campaignId}` | [`GET /agents/{agentId}`](agents.md#get-an-agent) lub [`GET /broadcasts/{broadcastId}`](broadcasts.md#get-a-broadcast). |
| `POST /campaigns` (Przychodzące / Połączone) | [`POST /agents`](agents.md#create-an-agent), a następnie [`PUT /entry-points/channel-defaults`](entry-points.md#point-a-channel-at-an-agent), aby docierały do nich nowe konwersacje. |
| `POST /campaigns` (Wychodzące) | [`POST /broadcasts`](broadcasts.md#create-a-broadcast) z Agentem, który powinien odpowiadać na odpowiedzi. |
| `PUT /campaigns/{campaignId}` | [`PUT /agents/{agentId}`](agents.md#update-an-agent) dla pól bota (`bot.*`, `anthropic_model`, działania następcze), [`PUT /broadcasts/{broadcastId}`](broadcasts.md#update-a-broadcast) dla odbiorców, otwieracza i harmonogramu. |
| `PUT /campaigns/{campaignId}` z `status` | [`PATCH /agents/{agentId}/active`](agents.md#pause-or-resume-an-agent), aby wstrzymać lub wznowić bota; [wstrzymaj i wznów](broadcasts.md#pause-and-resume) lub [uruchom](broadcasts.md#launch-a-broadcast) w transmisji. |
| `PATCH /campaigns/{campaignId}/enabled` | [`PATCH /agents/{agentId}/active`](agents.md#pause-or-resume-an-agent). |
| `PATCH /campaigns/{campaignId}/archived` | Wstrzymaj Agenta lub [`DELETE /agents/{agentId}`](agents.md#delete-an-agent), gdy nie jest już potrzebny. |
| `DELETE /campaigns/{campaignId}` | [`DELETE /agents/{agentId}`](agents.md#delete-an-agent) lub [`DELETE /broadcasts/{broadcastId}`](broadcasts.md#delete-a-broadcast). |
| `POST /campaigns/{campaignId}/duplicate` | [`POST /agents/{agentId}/duplicate`](agents.md#duplicate-an-agent) lub [`POST /broadcasts/{broadcastId}/duplicate`](broadcasts.md#duplicate-a-broadcast). |
| `POST /subaccounts/campaigns/copy` | [`POST /subaccounts/agents/copy`](agents.md#copy-an-agent-into-a-sub-account-agencies). |
| `PUT /campaigns/{campaignId}/bot-config` | [`PUT /agents/{agentId}/bot-config`](agents.md#update-bot-settings). |
| `PUT /campaigns/{campaignId}/active-hours` | [`PUT /agents/{agentId}/active-hours`](agents.md#set-active-hours). |
| `/campaigns/{campaignId}/custom-functions` | [`/agents/{agentId}/custom-functions`](agents.md#custom-functions). |
| `/campaigns/{campaignId}/kb-sources` | [`/agents/{agentId}/kb-sources`](agents.md#knowledge-sources). |
| `/campaigns/{campaignId}/mcp-servers` | [`/agents/{agentId}/mcp-servers`](agents.md#mcp-servers). |
| `/campaigns/{campaignId}/media-library` | [`/agents/{agentId}/media-library`](agents.md#media-library). |
| `/campaigns/{campaignId}/tags` oraz `tags` w kampanii | [`/agents/{agentId}/tags`](agents.md#tagging-rules). |
| `POST /campaigns/{campaignId}/channels`, `/incoming-routing`, `/reactivate`, `/stop-incoming` | [`PUT /entry-points/channel-defaults`](entry-points.md#point-a-channel-at-an-agent); jeden Agent na kanał, brak konfliktów do zatrzymania. |
| Ustawienia komentarz-do-DM w kampanii | Reguła `comment` przez [`POST /agents/{agentId}/entry-points`](entry-points.md#add-a-narrower-rule). |
| `POST /campaigns/{campaignId}/optimize` | [`POST /agents/{agentId}/optimize`](agents.md#optimize-an-agent-with-ai). |
| `POST /campaigns/{campaignId}/contacts/{contactId}/assign` | [`POST /contacts/{contactId}/assign-agent`](contacts.md#assign-an-ai-agent-to-a-contact), aby przekazać konwersację Agentowi. Aby wysłać zatwierdzony szablon kampanii do jednego kontaktu, dodaj go najpierw do biblioteki za pomocą [`POST /whatsapp-templates/from-campaign`](templates.md#bring-a-campaigns-approved-template-into-the-library), a następnie [`POST /whatsapp-templates/{templateId}/send-to-contact`](templates.md#send-a-template-to-an-existing-contact). |
| `DELETE /campaigns/{campaignId}/contacts/{contactId}` | Przypisz innego Agenta za pomocą [`POST /contacts/{contactId}/assign-agent`](contacts.md#assign-an-ai-agent-to-a-contact); nic nie musi być odłączane. |
| `POST /whatsapp-templates/campaign/{campaignId}` | [`POST /broadcasts/{broadcastId}/template`](broadcasts.md#submit-a-template-for-approval) lub [utwórz go w bibliotece](templates.md#create-a-template) i [wybierz](broadcasts.md#use-a-template-you-already-had-approved) w transmisji. |
| `GET /campaigns/{campaignId}/template-cost-estimate`, `/sms-cost-estimate` | [`POST /broadcasts/{broadcastId}/estimate-cost`](broadcasts.md#estimate-the-cost). |
| `GET /campaigns/{campaignId}/limits/*`, `GET /campaigns/limits/*` | Brak osobnego wywołania. [Uruchomienie transmisji](broadcasts.md#launch-a-broadcast) zwróci odmowę z podaniem przyczyny, jeśli limit zostanie przekroczony. |
| `GET /campaigns/stats/totals` | `GET /agents/stats/totals` — te same sumy przypisane do identyfikatora Agenta. |
| `POST /campaigns/{campaignId}/try-out/*` | Przetestuj Agenta w kroku **Test** w edytorze w panelu nawigacyjnym. |
| `POST /campaigns/{campaignId}/template-generation` | [`POST /agents/{agentId}/template-generation`](agents.md#generate-follow-up-messages). |


Kampania łączy w sobie wszystko, czego bot AI potrzebuje do rozmowy z Twoimi kontaktami: instrukcje, kanały, na których działa, godziny aktywności oraz zachowanie w ramach działań następczych. API kampanii umożliwia wyświetlanie, tworzenie, aktualizowanie, duplikowanie, włączanie, archiwizowanie i dostrajanie kampanii bezpośrednio z poziomu Twojego kodu, zamiast korzystać z pulpitu nawigacyjnego.

Wszystkie poniższe punkty końcowe są relatywne względem bazowego adresu URL `https://api.dmchamp.com/v1`. Każde żądanie musi być uwierzytelnione — zobacz [Dostęp do API](../integrations/api-access.md) oraz [Uwierzytelnianie](authentication.md), aby dowiedzieć się, jak uzyskać i przekazać klucz API. Dostęp do API jest funkcją płatną; bez niego żądania są odrzucane z błędem `403`.

> **Uwaga:** Niektóre przykłady pokazują prosty formularz zapytań `?apiKey=YOUR_API_KEY`, inne używają nagłówka `X-API-Key`. Oba działają wszędzie — użyj tego, który lepiej pasuje do Twojej konfiguracji.

---

## Typy kampanii

Podczas tworzenia kampanii musisz wybrać jeden z poniższych typów:

| Typ | Przeznaczenie |
|---|---|
| `Incoming from Unknown Contacts` | Bot odpowiada osobom, które piszą do Ciebie po raz pierwszy. |
| `Outgoing` | Bot rozpoczyna rozmowy z kontaktami dodanymi do kampanii. |
| `Keywords` | **Nieaktywny – nie używaj.** Kampania typu `Keywords` jest nieaktywna: jest nadal akceptowana ze względu na wsteczną kompatybilność, ale jest niewidoczna dla routingu przychodzącego na każdym kanale i żadne słowa kluczowe wyzwalające nie są przez nią odczytywane. Zamiast tego użyj punktu wejścia (Entry Point) typu **Słowo kluczowe** (Keyword) w agencie AI. |
| `Combined` | Mieszanka zachowań przychodzących i wychodzących. |

**Wielkość liter nie ma znaczenia.** `type`, `status`, `booking_provider`, `first_response_mode`, `bot.anthropic_model` oraz `bot.ai_speed` akceptują dowolną wielkość liter — `"live"`, `"Live"` oraz `"LIVE"` oznaczają to samo — a wartość jest przechowywana w swojej kanonicznej formie, która jest zwracana podczas odczytu kampanii. Jedynym wyjątkiem jest para wstrzymania: `"Paused"` oraz `"paused"` to dwa faktycznie różne stany, więc niejednoznaczna pisownia, taka jak `"PAUSED"`, jest odrzucana z błędem `400`, informującym o konieczności wyboru jednej z nich.

### Dwa stany wstrzymania

| Status | Kto go ustawia | Co oznacza |
|---|---|---|
| `Paused` | Własne mechanizmy bezpieczeństwa platformy (niskie zaangażowanie, powtarzające się błędy wysyłania, osiągnięcie limitu) oraz nowsze interfejsy Agentów i Transmisji | Kampania jest wstrzymana. Zaplanowane sprawdzenie może automatycznie cofnąć wstrzymanie bezpieczeństwa, gdy przyczyna ustąpi. |
| `paused` | Przycisk Wstrzymaj na pulpicie nawigacyjnym, w parze z `resumed` przy Wznów | Osoba wstrzymała kampanię ręcznie. Zaplanowane wysyłki są usuwane i tworzone ponownie po wznowieniu. |

Oba stany zatrzymują kampanię: routing przychodzący działa tylko wtedy, gdy status jest dokładnie równy `Live`. **Z poziomu API użyj `Paused`, aby wstrzymać, oraz `Live`, aby wznowić** — para pisana małymi literami istnieje dla przycisku na pulpicie nawigacyjnym i jest utrzymywana w celu jego poprawnego działania.

Żaden z tych stanów nie jest tym, co dzieje się, gdy AI przestaje odpowiadać w ramach jednej rozmowy. Jest to przełącznik dla konkretnego kontaktu, `is_bot_active` przy kontakcie — ustawiany, gdy kontrolę przejmuje człowiek, gdy kontakt rezygnuje z subskrypcji lub gdy AI kończy czat. Status samej kampanii pozostaje nienaruszony, a wszystkie inne rozmowy w jej ramach działają dalej. Zobacz [wstrzymywanie lub wznawianie AI dla jednego kontaktu](messages.md#pause-or-resume-the-ai-for-one-contact).

> **Utworzenie kampanii nie decyduje o tym, kto odpowiada na kanale.** Routing jest obsługiwany przez **punkty wejścia** (Entry Points) w agencie AI, a nie przez kampanie. Każdy kanał ma jeden domyślny punkt wejścia wskazujący agenta, który odpowiada na nowe, nieznane kontakty: ustaw go za pomocą `PUT /entry-points/channel-defaults`, sprawdź, czy drabinka jest aktywna dla konta za pomocą `GET /entry-points/routing-status`, wyczyść go za pomocą `DELETE /entry-points/channel-defaults`. `POST /channels/campaign` nadal zapisuje starszą mapę routingu kampanii dla poszczególnych kanałów, ale mapa ta nie jest już używana do routingu przychodzącego na żadnym koncie; jest zachowana wyłącznie w celu wycofania zmian. Nie opieraj na niej żadnych rozwiązań. Zobacz [Skieruj kanał do kampanii](channels.md#route-a-channel-to-a-campaign), aby porównać oba podejścia.

---

## Wyświetlanie kampanii

`GET /campaigns`

Zwraca Twoje kampanie, zaczynając od najnowszych. Zarchiwizowane kampanie są wykluczone, chyba że przekażesz `archived=true`.

**Parametry zapytania**

| Parametr | Wymagany | Opis |
|---|---|---|
| `limit` | Nie | Maksymalna liczba zwracanych kampanii. Domyślnie `50`, maksimum `100`. |
| `cursor` | Nie | Kursor stronicowania. Przekaż wartość `next_cursor` z poprzedniej odpowiedzi, aby pobrać następną stronę. |
| `archived` | Nie | Ustaw na `true`, aby uwzględnić zarchiwizowane kampanie. |

**cURL**

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

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.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.dmchamp.com/v1/campaigns",
    params={"limit": 20},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["campaigns"], data["next_cursor"])
```

**Odpowiedź**

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

Gdy `next_cursor` ma wartość `null`, oznacza to, że dotarłeś do ostatniej strony.

---

## Pobierz kampanię

`GET /campaigns/{campaignId}`

Zwraca pełny dokument kampanii, w tym konfigurację aktywnego bota (`bot`), ustawienia działań następczych, włączone kanały oraz wszelkie słowa kluczowe. Sygnatury czasowe są zwracane w milisekundach czasu epoch.

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
campaign = res.json()["campaign"]
```

**Odpowiedź**

```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
**Uwaga:** Kampania należąca do innego konta zwraca `404 Campaign not found` (nie `403`), więc nie można stwierdzić, czy dany identyfikator istnieje na innym koncie.
:::


---

## Utwórz kampanię

`POST /campaigns`

Tworzy nową kampanię. `name` oraz `type` są wymagane; wszystko inne jest opcjonalne. Możesz dołączyć dowolne inne pole kampanii w tym samym żądaniu — na przykład `language`, `ai_mode` lub pełny obiekt konfiguracji `bot` — a zostanie ono zapisane wraz z nową kampanią. Właściciel i czas utworzenia są ustawiane automatycznie.

**Pola żądania**

| Pole | Wymagane | Opis |
|---|---|---|
| `name` | Tak | Nazwa kampanii. |
| `type` | Tak | Jeden z czterech powyższych typów kampanii. |
| `language` | Nie | Język, w którym odpowiada bot (np. `"en"`). |
| `ai_mode` | Nie | Czy tryb AI jest włączony (`true`/`false`). W przypadku kampanii obsługiwanej przez agenta AI, odczyty zwracają przełącznik **Aktywny** agenta, a nie zapisaną wartość — zobacz uwagę poniżej dotyczącą aktualizacji. |
| `bot` | Nie | Obiekt konfiguracji bota (zobacz [Pola konfiguracji bota](#bot-configuration-fields)). |
| `list_id` | Nie | ID listy kontaktów do dołączenia. |
| `event_id` | Nie | ID typu wydarzenia, które AI może zarezerwować. |
| `event_ids` | Nie | Kilka typów wydarzeń jednocześnie, jako tablica ID typów wydarzeń — pierwszy z nich jest domyślny. Wyślij `event_id` lub `event_ids`, nie oba jednocześnie. |

**cURL**

```bash
curl -X POST "https://api.dmchamp.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.dmchamp.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.dmchamp.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"]
```

**Odpowiedź**

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

---

## Aktualizacja kampanii

`PUT /campaigns/{campaignId}`

Częściowo aktualizuje kampanię — wyślij tylko te pola, które chcesz zmienić. Jest to jedyna ogólna metoda aktualizacji; nie istnieje `PATCH /campaigns/{campaignId}` (dwie trasy `PATCH` to wąskie przełączniki [włącz](#enable-or-disable-a-campaign) i [archiwizuj](#archive-or-restore-a-campaign)).

**Pola, które możesz zmienić.** Wszystko, co zapisuje edytor kampanii, w tym `name`, `status`, `type`, `language`, `ai_mode`, `enabled_channels`, ustawienia wyzwalacza i sekwencji (drip), flagi rezerwacji i działań następczych, pola monitorowania Instagrama/Facebooka oraz cała konfiguracja `bot`. Tożsamość i własność są zablokowane na czas trwania kampanii: `user`, `id` oraz `created_at` są odrzucane, podobnie jak każda nazwa pola, której punkt końcowy nie rozpoznaje. Odrzucenie dotyczy całego żądania, a nie poszczególnych pól — jeden nieznany klucz zwraca `400` i **nic** w tym żądaniu nie zostaje zapisane.

**`ai_mode` w kampanii obsługiwanej przez agenta odzwierciedla stan agenta.** Gdy na kampanię odpowiada agent AI, odczyt kampanii zwraca `ai_mode` pochodzące z przełącznika **Aktywny** tego agenta — jest to jedyny przełącznik, który faktycznie decyduje o tym, czy AI odpowiada. Zapisanie `ai_mode` w takiej kampanii jest akceptowane, ale nie zmieni wartości zwracanej przy odczycie; zamiast tego należy włączyć lub wyłączyć przełącznik Aktywny agenta (w panelu nawigacyjnym lub za pośrednictwem API agentów). W klasycznych kampaniach bez agenta, `ai_mode` odczytuje i zapisuje przechowywaną wartość tak jak dotychczas.

**Pola bota są scalane, a nie nadpisywane.** Wysyłaj ustawienia bota jako klucze kropkowe (`"bot.instructions": "..."`) lub jako zagnieżdżony obiekt (`"bot": { "instructions": "..." }`) — oba sposoby zapisują dane element po elemencie, więc pola, których nie wyślesz, zachowują swoje bieżące wartości. `bot.instructions`, `bot.goal`, `bot.rules` oraz `bot.personality` można edytować w ten sposób, podobnie jak każde inne ustawienie bota wymienione w sekcji [Pola konfiguracji bota](#bot-configuration-fields). To samo dotyczy `test_bot`, `frequency` oraz `follow_up_config`.

Aby całkowicie zastąpić konfigurację bota — usuwając każde pole, którego nie wyślesz — użyj `bot_replace` (lub `test_bot_replace`) z pełnym obiektem. Nie można łączyć zastępowania i scalania dla tego samego obiektu w jednym żądaniu; zwraca to `400`.

::: note
**Uwaga:** Zapisywanie `bot.*` przez API odnosi skutek **natychmiast** w aktywnej kampanii. Edytor w panelu działa inaczej: zmiany są tam zapisywane jako wersja robocza i stają się aktywne dopiero po kliknięciu przez klienta przycisku Opublikuj. Jeśli więc klient ma nieopublikowane zmiany w panelu, pozostają one w `test_bot`, a odczyt API `bot` poprawnie pokazuje to, czego AI używa w tej chwili.
:::


Kilka pól ustawia się za pomocą dedykowanego klucza, zamiast zapisywać je bezpośrednio: użyj `list_id` dla listy kontaktów, `event_id` dla typu wydarzenia (lub `event_ids`, uporządkowanej tablicy ID typów wydarzeń, aby pozwolić AI na rezerwację kilku — pierwszy jest domyślny; pusta tablica usuwa powiązania ze wszystkimi), oraz `contact_ids` (tablicy ID kontaktów) dla kontaktów kampanii. Wpisy w bazie wiedzy są zarządzane przez [API FAQ](faqs.md), a nie przez ten punkt końcowy.

**Tagi zastępują, nie scalają.** Wyślij `tags` jako kompletną tablicę, a stanie się ona zestawem tagów kampanii — zobacz [Tagi kampanii](#campaign-tags), aby poznać pola oraz punkty końcowe służące do dodawania lub edycji pojedynczego tagu.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.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.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"name": "Spring Promo v2", "enabled_channels": ["whatsapp"]},
)
data = res.json()
```

**Odpowiedź**

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

---

## Usuwanie kampanii

`DELETE /campaigns/{campaignId}`

Trwale usuwa kampanię. Tej operacji nie można cofnąć — jeśli kampania może być jeszcze potrzebna, [zarchiwizuj ją](#archive-or-restore-a-campaign).

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Odpowiedź**

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

---

## Duplikowanie kampanii

`POST /campaigns/{campaignId}/duplicate`

Tworzy kopię kampanii z zachowaniem wszystkich jej ustawień. Kopia jest domyślnie **wyłączona**, a jej nazwa otrzymuje przyrostek `(copy)`, dzięki czemu nie wysyła żadnych wiadomości, dopóki jej wyraźnie nie włączysz.

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
new_campaign_id = res.json()["campaign_id"]
```

**Odpowiedź**

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

> Zduplikowane kopie **w ramach jednego konta**.

---

::: master-only
## Kopiowanie kampanii na subkonto (agencje)

`POST /subaccounts/campaigns/copy`

Kopiuje kampanię z konta agencji (lub jednego z subkont) na inne subkonto. Jest to punkt końcowy, którego należy użyć, jeśli przechowujesz główną kampanię szablonową i chcesz, aby każdy nowy klient rozpoczął pracę z już skonfigurowanym szablonem.

W przeciwieństwie do [duplikowania](#duplicate-a-campaign), ta operacja odbywa się między kontami i wykonuje głęboką kopię zależności kampanii, dzięki czemu kopia działa samodzielnie na koncie docelowym, zamiast odwoływać się do Twojego konta.

**Pola żądania**

| Pole | Wymagane | Opis |
|---|---|---|
| `campaignId` | Tak | Źródłowa kampania, z której ma zostać wykonana kopia. |
| `targetUserId` | Tak | Identyfikator subkonta, na które ma zostać wykonana kopia. |
| `newName` | Nie | Nazwa kopii. Domyślnie jest to nazwa źródłowa z przyrostkiem `(copy)`. |
| `copyFaqs` | Nie | Skopiuj również FAQ i bazę wiedzy. Wartość domyślna to `true`. |
| `copyCustomFunctions` | Nie | Skopiuj również funkcje niestandardowe i serwery MCP. Wartość domyślna to `false`. |

**Co jest przenoszone:** konfiguracja bota, instrukcje i ustawienia; źródła FAQ i bazy wiedzy (w tym przesłane pliki), gdy `copyFaqs` jest włączone; funkcje niestandardowe i serwery MCP, gdy `copyCustomFunctions` jest włączone; biblioteka mediów.

**Co celowo nie jest przenoszone:** szablony WhatsApp konta źródłowego (są one zatwierdzane dla konkretnego konta i muszą zostać przesłane ponownie), wszelkie połączone posty na Instagramie lub Facebooku, adresy URL webhooków dla poszczególnych tagów, kontakty i listy oraz cała historia użycia. Żadne dane przypisane do konta nie wyciekają między klientami.

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts/campaigns/copy?apiKey=YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "TEMPLATE_CAMPAIGN_ID",
    "targetUserId": "abc123def456",
    "newName": "Inbound Instagram Leads",
    "copyFaqs": true,
    "copyCustomFunctions": false
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/subaccounts/campaigns/copy", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_AGENCY_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaignId: "TEMPLATE_CAMPAIGN_ID",
    targetUserId: "abc123def456",
    newName: "Inbound Instagram Leads",
    copyFaqs: true,
  }),
});
const { data } = await res.json();
console.log(data.newCampaignId);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/subaccounts/campaigns/copy",
    headers={"X-API-Key": "YOUR_AGENCY_API_KEY"},
    json={
        "campaignId": "TEMPLATE_CAMPAIGN_ID",
        "targetUserId": "abc123def456",
        "newName": "Inbound Instagram Leads",
        "copyFaqs": True,
    },
)
new_campaign_id = res.json()["data"]["newCampaignId"]
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "newCampaignId": "aZ9plnewCopyId01234",
    "message": "Campaign copied with 12 FAQs, 2 knowledge sources.",
    "counts": {
      "faqs": 12,
      "kbSources": 2,
      "customFunctions": 0,
      "mcpServers": 0,
      "mediaFiles": 3,
      "mediaLibraryItems": 1
    }
  }
}
```

> **Ta operacja wymaga `targetUserId`, a nie `sub_account_id`.** Punkt końcowy sam identyfikuje oba konta — właścicielem kampanii jest źródło, `targetUserId` to miejsce docelowe — więc nie używa standardowego parametru [`sub_account_id`](../agency/api-for-agencies.md). Uwierzytelnij się za pomocą klucza **agencji**; klucz inny niż agencji zwróci `403`. Zarówno źródło, jak i cel muszą należeć do Twojej agencji.

**Kopia zawsze trafia jako `Draft` bez routingu kanałów**, więc nigdy nie zacznie wysyłać wiadomości do kontaktów klienta, zanim nie będziesz gotowy. Aby zakończyć udostępnianie:

1. `PUT /campaigns/{newCampaignId}` z `{ "status": "Live", "sub_account_id": "abc123def456" }`
2. `POST /channels/campaign` z `{ "campaign_id": "...", "channels": ["instagram"], "sub_account_id": "abc123def456" }`

Jeśli którykolwiek krok kopiowania nie powiedzie się, wszystko, co zostało utworzone, zostanie wycofane — nigdy nie otrzymasz częściowo skopiowanej kampanii.

---
:::

## Włączanie lub wyłączanie kampanii

`PATCH /campaigns/{campaignId}/enabled`

Włącza lub wyłącza kampanię. Wyłączona kampania przestaje angażować kontakty, ale zachowuje całą swoją konfigurację.

**Pola żądania**

| Pole | Wymagane | Opis |
|---|---|---|
| `enabled` | Tak | `true` aby włączyć, `false` aby wyłączyć. Musi być wartością logiczną (boolean). |

**cURL**

```bash
curl -X PATCH "https://api.dmchamp.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.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"enabled": True},
)
data = res.json()
```

**Odpowiedź**

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

---

## Archiwizowanie lub przywracanie kampanii

`PATCH /campaigns/{campaignId}/archived`

Archiwizuje lub przywraca kampanię. Zarchiwizowane kampanie są ukryte na domyślnej liście kampanii, ale zachowują wszystkie swoje dane i można je przywrócić w dowolnym momencie.

**Pola żądania**

| Pole | Wymagane | Opis |
|---|---|---|
| `archived` | Tak | `true` aby zarchiwizować, `false` aby przywrócić. Musi być wartością logiczną (boolean). |

**cURL**

```bash
curl -X PATCH "https://api.dmchamp.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.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"archived": True},
)
data = res.json()
```

**Odpowiedź**

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

---

## Aktualizacja konfiguracji bota

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

To bezpieczny sposób na zmianę poszczególnych ustawień bota. Każde wysłane pole jest **scalane** z istniejącą konfiguracją bota, więc wszystkie pominięte pola zostają zachowane. Używaj tego zamiast punktu końcowego aktualizacji kampanii, gdy chcesz jedynie zmodyfikować część bota.

Klucze pól mogą zawierać tylko litery, cyfry, podkreślniki i myślniki.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.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.dmchamp.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.dmchamp.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()
```

**Odpowiedź**

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

### Pola konfiguracji bota

Wszystkie pola bota są opcjonalne. Wyślij tylko te, które chcesz ustawić. Wszelkie dodatkowe pola bota wykraczające poza wymienione tutaj są akceptowane i przechowywane w niezmienionej formie.

| Pole | Typ | Opis |
|---|---|---|
| `instructions` | string | Główne instrukcje sterujące sposobem, w jaki bot rozmawia z kontaktami. |
| `rules` | string | Sztywne zasady, których bot musi zawsze przestrzegać. |
| `goal` | string | Cel, do którego bot powinien dążyć w każdej rozmowie. |
| `personality` | string | Opis tonu głosu i osobowości bota. |
| `ai_speed` | string | Poziom rozumowania stosowany przez AI przed udzieleniem odpowiedzi. Jeden z `fast`, `fast_thinker`, `balanced`, `thorough`. |
| `anthropic_model` | string | Poziom jakości AI używany do odpowiedzi w tej kampanii. Jeden z `standard`, `economy` (przestarzałe), `max`, `mini`. `max` i `mini` działają tylko na kontach uprawnionych do korzystania z tych poziomów. |
| `max_messages` | integer | Maksymalna liczba wiadomości bota w jednej rozmowie. |
| `alert_human_when` | string | Warunki, w których bot powinien powiadomić członka zespołu. |
| `availability` | object | Harmonogram godzin aktywności bota. Możesz ustawić go tutaj lub użyć dedykowanego [punktu końcowego godzin aktywności](#set-the-bot-active-hours). |
| `follow_up_config` | object | Konfiguracja zachowania po zakończeniu rozmowy, przechowywana w podanej formie. |

---

## Ustaw godziny aktywności bota

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

Ustawia harmonogram dostępności bota. Poza skonfigurowanymi oknami czasowymi bot nie odpowiada automatycznie. Zapisuje to pole `availability` w konfiguracji bota.

**Pola żądania**

| Pole | Wymagane | Opis |
|---|---|---|
| `availability` | Tak | Obiekt z kluczami odpowiadającymi dniom tygodnia. Dozwolone klucze to `monday` do `sunday`; każdy inny klucz zwróci `400`. Dni, które pominiesz, pozostaną bez zmian. |

Każdy dzień tygodnia zawiera pojedyncze okno czasowe lub tablicę okien. Okno posiada `start_time` i `end_time` w 24-godzinnym formacie `HH:MM`.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.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.dmchamp.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.dmchamp.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()
```

**Odpowiedź**

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

---

## Wyświetl listę niestandardowych funkcji kampanii

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

Zwraca funkcje niestandardowe powiązane z tą kampanią, rozwiązane do pełnych definicji. Funkcje niestandardowe to zewnętrzne akcje HTTP, które bot może wywołać podczas rozmowy — na przykład sprawdzenie stanu magazynowego w Twoim sklepie lub utworzenie rekordu w systemie CRM.

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
custom_functions = res.json()["custom_functions"]
```

**Odpowiedź**

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

---

## Powiąż funkcję niestandardową z kampanią

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

Powiązuje istniejącą [funkcję niestandardową](../ai-automation/custom-functions.md) z tą kampanią, aby bot mógł ją wywoływać podczas rozmowy. Powiązanie funkcji, która jest już powiązana, nie powoduje żadnej akcji.

| Pole | Wymagane | Opis |
|---|---|---|
| `custom_function_id` | Tak | Identyfikator funkcji niestandardowej do powiązania. |

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

**Odpowiedź**

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

---

## Odwiąż funkcję niestandardową od kampanii

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

Odwiązanie funkcji, która nie jest powiązana, nie powoduje żadnej akcji.

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

**Odpowiedź**

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

---

## Powiąż źródło bazy wiedzy z kampanią

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

Powiązuje źródło bazy wiedzy (utworzone za pomocą [interfejsu API FAQ](faqs.md)) z tą kampanią, aby bot mógł z niego korzystać podczas udzielania odpowiedzi. Powiązanie źródła, które jest już powiązane, nie powoduje żadnej akcji.

| Pole | Wymagane | Opis |
|---|---|---|
| `kb_source_id` | Tak | Identyfikator źródła bazy wiedzy do powiązania. |

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

**Odpowiedź**

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

---

## Odwiąż źródło bazy wiedzy od kampanii

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

Odwiązanie źródła, które nie jest powiązane, nie powoduje żadnej akcji.

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

**Odpowiedź**

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

---

## Powiąż serwer MCP z kampanią

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

Łączy serwer MCP z tą kampanią, dając botowi dostęp do narzędzi tego serwera podczas rozmowy. Połączenie serwera, który jest już połączony, nie powoduje żadnej akcji.

| Pole | Wymagane | Opis |
|---|---|---|
| `mcp_server_id` | Tak | Identyfikator serwera MCP do połączenia. |

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

**Odpowiedź**

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

---

## Odłącz serwer MCP od kampanii

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

Odłączenie serwera, który nie jest połączony, nie powoduje żadnej akcji.

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

**Odpowiedź**

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

---

## Biblioteka mediów kampanii

Biblioteka mediów przechowuje obrazy, filmy, dokumenty i notatki głosowe, które bot może wysyłać podczas rozmowy.

### Wyświetl bibliotekę mediów kampanii

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

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

**Odpowiedź**

```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` to podpisany adres URL przechwycony w momencie przesyłania — może być już nieważny w momencie odczytu; pulpit nawigacyjny podpisuje go ponownie na żądanie.

### Prześlij element multimedialny

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

| Pole | Wymagane | Opis |
|---|---|---|
| `base64Data` | Tak | Plik zakodowany w formacie base64 (bez prefiksu data-URL). |
| `mimeType` | Tak | Typ MIME pliku (np. `image/png`). |
| `title` | Tak | Krótka etykieta wyświetlana w bibliotece i w monicie AI. |
| `description` | Tak | Instrukcja informująca bota, **kiedy** wysłać ten element. |
| `fileName` | Nie | Oryginalna nazwa pliku, używana do utworzenia nazwy obiektu w pamięci masowej. |
| `sendMessage` | Nie | Preferowane sformułowanie, którego bot powinien użyć podczas wysyłania tego elementu. |
| `maxSendsPerConversation` | Nie | Maksymalna liczba wysłania tego elementu przez bota do jednego kontaktu w ramach rozmowy. Wartość domyślna to `1`. |
| `sendAsVoiceNote` | Nie | W przypadku przesłania dźwięku, przekoduj go na notatkę głosową WhatsApp. Wartość domyślna to `false` (zapisywany jako zwykły plik audio). |

```bash
curl -X POST "https://api.dmchamp.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."
  }'
```

**Odpowiedź**

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

### Aktualizacja elementu multimedialnego

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

Edytuje tylko metadane elementu — aby zastąpić sam plik, usuń element i prześlij nowy.

| Pole | Opis |
|---|---|
| `title` | Krótka etykieta. |
| `description` | Instrukcja dotycząca czasu wysyłki. |
| `send_message` | Preferowane sformułowanie, którego ma używać bot. |
| `max_sends_per_conversation` | Nieujemna liczba całkowita lub `null`, aby usunąć limit. |

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

**Odpowiedź**

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

### Usuwanie elementu multimedialnego

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

Usunięcie elementu, który już nie istnieje, jest operacją bez efektu (no-op).

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

**Odpowiedź**

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

---

## Tagi kampanii

Tag kampanii to etykieta, której uczysz bota, aby przypisywał ją do kontaktu podczas rozmowy — `hot-lead`, `not-interested`, `booked-a-call`. Każdy tag składa się z trzech części:

| Pole | Typ | Opis |
|---|---|---|
| `name` | ciąg znaków, wymagane | Sama etykieta. To właśnie ją bot przypisuje do kontaktu i to na jej podstawie dokonujesz późniejszego dopasowania, więc dbaj o to, by była krótka i stała. |
| `description` | ciąg znaków | Instrukcja mówiąca botowi, **kiedy** przypisać ten tag. To ta część wykonuje pracę — "osoba potwierdza dołączenie do społeczności" zostanie użyte, "gorący lead" nie. |
| `webhook` | ciąg znaków | Adres URL, który otrzymuje `POST` w momencie przypisania tagu do kontaktu. Pozostaw puste, jeśli go nie potrzebujesz. |
| `tag_id` | ciąg znaków | Opcjonalne. Łączy ten wpis z istniejącym tagiem na Twoim koncie zamiast tworzyć nowy. Podaj go, jeśli chcesz później odwołać się do tego konkretnego tagu za pomocą poniższych punktów końcowych dla pojedynczych tagów. |

Nazwy tagów muszą być unikalne w ramach kampanii. Bot przypisuje tagi **według nazwy**, więc w przypadku dwóch wpisów o tej samej nazwie wynik nie jest określony.

### Ustaw wszystkie tagi kampanii

`PUT /campaigns/{campaignId}` z tablicą `tags`.

To zastępuje tagi kampanii dokładnie tym, co wyślesz, co jest tym samym, co robi karta Tagi w panelu nawigacyjnym po zapisaniu zmian. **Za każdym razem wysyłaj kompletną tablicę** — tag, który pominiesz, zostanie usunięty. Wysłanie `[]` usuwa je wszystkie.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.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.dmchamp.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.dmchamp.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()
```

**Odpowiedź**

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

Odczytaj tagi za pomocą [`GET /campaigns/{campaignId}`](#get-a-campaign).

### Dodaj jeden tag

`POST /campaigns/{campaignId}/tags`

Dodaje pojedynczy tag bez konieczności ponownego wysyłania reszty. Użyj tego, gdy dodajesz tagi do zestawu, którego nie utworzyłeś w tym żądaniu.

```bash
curl -X POST "https://api.dmchamp.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." } }'
```

Wysłanie dokładnie tego samego tagu dwukrotnie nie powoduje żadnego efektu za drugim razem. Wysłanie tego samego `tag_id` z inną nazwą lub opisem spowoduje dodanie **drugiego** wpisu zamiast edycji pierwszego — użyj poniższego punktu końcowego, aby edytować istniejący tag.

### Zaktualizuj lub usuń jeden tag

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

Adresują one jeden wpis za pomocą jego `tag_id`, więc działają tylko na tagach, które zostały z nim utworzone. Jeśli tag nie ma `tag_id`, zmień go za pomocą powyższego `PUT /campaigns/{campaignId}` dla całej tablicy.

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

`tagId`, którego nie ma w kampanii, zwraca `404` z `"Tag not found in campaign tags"`.

---

## Przełączanie kanałów kampanii

`POST /campaigns/{campaignId}/channels`

Dodaje lub usuwa kanały z tablicy `enabled_channels` kampanii bez konieczności ponownego przesyłania całej tablicy — jest to bezpieczniejsze niż [`PUT /campaigns/{campaignId}`](#update-a-campaign), gdy w tym samym czasie kampanię może edytować ktoś inny.

Wyślij pojedyncze przełączenie lub partię — nie oba w tym samym żądaniu:

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

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

| Pole | Opis |
|---|---|
| `channel` | Jeden kanał do przełączenia. Użyj w parze z `action`. |
| `action` | `"add"` lub `"remove"`. Użyj w parze z `channel`. |
| `add` | Tablica kanałów do dodania. Format wsadowy — użyj zamiast `channel`/`action`. |
| `remove` | Tablica kanałów do usunięcia. Format wsadowy. |

Prawidłowe kanały: `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.dmchamp.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/channels?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "action": "add" }'
```

**Odpowiedź**

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

> To zmienia tylko kanały, w których kampania jest reklamowana — nie decyduje o tym, kto odpowiada na dany kanał. Zobacz [Typy kampanii](#campaign-types) powyżej oraz [Kierowanie kampanii do kanałów przychodzących](#route-a-campaign-to-incoming-channels) poniżej, aby uzyskać więcej informacji.

---

## Komentarz do wiadomości prywatnej (Instagram i Facebook)

Funkcja „Komentarz do wiadomości prywatnej” zamienia komentarz pod Twoim postem w prywatną rozmowę: ktoś dodaje komentarz, bot wysyła mu wiadomość prywatną (DM), a kampania przejmuje dalszą część konwersacji. Jest ona konfigurowana w całości za pomocą obiektu kampanii, więc nie ma w niej żadnych elementów dostępnych wyłącznie w interfejsie użytkownika.

Najpierw połącz stronę na Facebooku — zobacz [Połączenie kanału](channels.md#instagram--messenger-meta). Następnie ustaw poniższe pola za pomocą [`PUT /campaigns/{campaignId}`](#update-a-campaign).

> **Kampania musi być `Live`.** Monitorowanie komentarzy wykrywa tylko te kampanie, których `status` to `Live` (wielkość liter nie ma znaczenia — zobacz [Typy kampanii](#campaign-types)). Każdy inny status wyłącza tę funkcję bez powiadomienia, a wymyślony status, taki jak `"Active"`, jest teraz odrzucany z błędem `400` zamiast zapisywany. Prawidłowe statusy to `Draft`, `Pending Approval`, `Scheduled`, `Live`, `Paused`, `Completed`, `Sent` oraz `Failed`.

**Pola**

| Pole | Typ | Opis |
|---|---|---|
| `monitor_instagram_posts` | boolean | Obserwuj każdy post na Instagramie na połączonej stronie. |
| `instagram_post_ids` | string[] | Obserwuj tylko te posty na Instagramie. Pozostaw puste, gdy `monitor_instagram_posts` jest włączone. |
| `instagram_comment_delay_minutes` | number | Odczekaj tyle minut po komentarzu przed wysłaniem wiadomości DM. |
| `monitor_facebook_posts` | boolean | Obserwuj każdy post na Facebooku na połączonej stronie. |
| `facebook_post_ids` | string[] | Obserwuj tylko te posty na Facebooku. |
| `facebook_comment_delay_minutes` | number | Opóźnienie przed wysłaniem wiadomości DM, w minutach. |
| `public_comment_reply_instructions` | string | Wskazówki dotyczące widocznej odpowiedzi pozostawionej pod samym komentarzem. Zastępuje domyślne sformułowanie „sprawdź swoje wiadomości DM”. |
| `first_response_mode` | string | `"ai"` (domyślnie) generuje pierwszą wiadomość DM i odpowiedź publiczną. `"exact_text"` wysyła Twoje sformułowanie dosłownie, bez generowania przez AI i bez pobierania kredytów. |
| `first_response_exact_text` | string | Dosłowna pierwsza wiadomość DM, używana, gdy `first_response_mode` to `"exact_text"`. Wymagane, aby ten tryb zadziałał. |
| `first_response_exact_text_variants` | string[] | Dodatkowe sformułowania dla pierwszej wiadomości DM. Jedno jest wybierane losowo przy każdej wysyłce, więc powtarzające się wiadomości DM nie są identyczne. |
| `public_comment_reply_exact_text` | string | Dosłowna odpowiedź publiczna w trybie `"exact_text"`. Pozostaw puste, aby pominąć odpowiedź publiczną i wysłać tylko wiadomość DM. |
| `public_comment_reply_exact_text_variants` | string[] | Dodatkowe sformułowania dla odpowiedzi publicznej. |
| `monitor_instagram_followers` | boolean | Traktuj nowego obserwującego jako wyzwalacz i wyślij powitalną wiadomość DM (konta osobiste na Instagramie). |
| `follower_outreach_instructions` | string | Wskazówki dotyczące tej powitalnej wiadomości DM dla nowego obserwującego. |
| `respond_to_instagram_story_replies` | boolean | Czy AI odpowiada na odpowiedzi do Twoich relacji na Instagramie. Domyślnie `true`. Ustaw `false`, aby odpowiedzi do relacji trafiały na czat (z załączoną relacją) bez odpowiedzi AI. Ustawienie na żywo — nie jest częścią wersji roboczej, więc nie wymaga publikacji. |

**Czyszczenie pola**

Te pola są usuwane, a nie ustawiane na `null`, gdy wysyłasz `null`, dzięki czemu bot przywraca ustawienia domyślne: `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`.

> **Jeden nieznany klucz odrzuca całe żądanie.** `PUT /campaigns/{campaignId}` weryfikuje całą treść względem listy dozwolonych elementów. Klucz, który nie zostanie rozpoznany, zwraca `400` dla całego żądania — nie jest on ignorowany bez powiadomienia, a żadne inne pola w tej treści nie zostają zapisane.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.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.dmchamp.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.dmchamp.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()
```

**Odpowiedź**

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

> Widoczna odpowiedź pozostawiona pod komentarzem wymaga funkcji odpowiedzi na komentarze w Twoim planie. Bez niej wiadomość prywatna nadal jest wysyłana, a odpowiedź publiczna jest pomijana.

---

## Optymalizacja kampanii za pomocą AI

`POST /campaigns/{campaignId}/optimize`

Uruchamia to samo przepisywanie przez AI, co funkcje „Optymalizuj” i przesyłanie opinii po kliknięciu łapki w dół w panelu nawigacyjnym: pobiera Twoją opinię, przepisuje instrukcje bota i przygotowuje wynik jako nową wersję roboczą do sprawdzenia.

| Pole | Wymagane | Opis |
|---|---|---|
| `user_feedback` | Wymagane jedno z dwóch | Dowolna opinia opisująca, co należy poprawić. |
| `thumbs_down_feedback` | Wymagane jedno z dwóch | Opinia zebrana po kliknięciu łapki w dół przy konkretnej odpowiedzi bota. |
| `thumbs_down_message` | Nie | Wiadomość bota, której dotyczy opinia z łapką w dół. |

```bash
curl -X POST "https://api.dmchamp.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." }'
```

**Odpowiedź** (`202` — przepisywanie odbywa się w tle)

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

Odpytuj [`GET /campaigns/{campaignId}`](#get-a-campaign) i obserwuj `test_bot.status`: zmienia się na `"Optimizing"` natychmiast, a następnie z powrotem na `"Draft"`, gdy wynik przepisywania trafi do `test_bot`. Od tego momentu zachowuje się jak każda wersja robocza w panelu — przejrzyj ją, a następnie opublikuj w panelu, aby zaczęła działać. `409` oznacza, że optymalizacja dla tej kampanii jest już w toku.

> Optymalizacja kosztuje kredyty, tak samo jak każda inna operacja AI na Twoim koncie.

---

## Przypisz kontakt do kampanii

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

Dodaje istniejący kontakt do kampanii i, jeśli o to poprosisz, natychmiast wysyła wiadomość powitalną kampanii. Jest to sposób na wysłanie zatwierdzonego szablonu WhatsApp kampanii do jednego kontaktu: szablon, z którym kampania została zatwierdzona, należy do tej kampanii, więc nie pojawia się w bibliotece [Templates API](templates.md) i nie może zostać wysłany przez `/whatsapp-templates/send`.

| Pole | Wymagane | Opis |
|---|---|---|
| `sendOpeningMessage` | Nie | `true` wysyła wiadomość powitalną kampanii (zatwierdzony szablon WhatsApp w kampanii WhatsApp) natychmiast po przypisaniu kontaktu. Domyślnie `false`. |
| `triggerAIResponse` | Nie | `true` pozwala sztucznej inteligencji na napisanie własnej pierwszej wiadomości. Domyślnie `false`. |

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

**Odpowiedź**

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

> **Kredyty:** Wysłanie wiadomości powitalnej w kampanii WhatsApp jest rozliczane tak samo jak wysyłka szablonu, wyceniane według kraju odbiorcy i kategorii szablonu. W innych kanałach wiadomość powitalna jest zwykłą wiadomością wychodzącą.

---

## Usuwanie kontaktu z kampanii

`DELETE /campaigns/{campaignId}/contacts/{contactId}`

Usuwa kontakt z kampanii: czyści bieżącą kampanię kontaktu, jeśli jest nią właśnie ta, usuwa kampanię z historii kampanii kontaktu oraz, jeśli kampania nadal istnieje, usuwa kontakt z listy kontaktów kampanii. Nic nie jest wysyłane, a przypisany do kontaktu agent AI, stan włączenia/wyłączenia AI oraz przynależność do list pozostają bez zmian. Kontakt, którego bieżąca kampania została wyczyszczona, od następnej wiadomości będzie obsługiwany przez przypisanego agenta AI lub trafi do skrzynki odbiorczej zespołu, jeśli nie ma przypisanego agenta.

Działa to również w przypadku kampanii, która została już usunięta, więc jest to sposób na wyczyszczenie nieaktualnego odniesienia do kampanii z kontaktu. Usunięcie kampanii teraz automatycznie usuwa ją z każdego kontaktu; to wywołanie służy do usuwania odniesień pozostałych z wcześniejszego okresu lub do usuwania kontaktu z kampanii, która jest nadal aktywna.

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

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "contactId": "contact_abc123",
    "campaignId": "NBCXrhqGPSFsd6MV7pRo",
    "clearedCurrentCampaign": true,
    "removedFromCampaigns": true,
    "campaignExists": false
  }
}
```

`clearedCurrentCampaign` informuje, czy była to bieżąca kampania kontaktu, `removedFromCampaigns` czy znajdowała się ona w historii kontaktu, a `campaignExists` ma wartość `false`, gdy kampania została już usunięta. Wywołanie można bezpiecznie powtarzać: kontakt, który nigdy nie był w kampanii, zwraca `200` ze wszystkimi flagami ustawionymi na `false`.

---

## Kierowanie kampanii do kanałów przychodzących

Te punkty końcowe zarządzają tym, która kampania odpowiada nowym, nieznanym kontaktom w danym kanale. **Preferuj punkty wejścia (Entry Points)** dla nowych integracji (zobacz notatkę w sekcji [Typy kampanii](#campaign-types)) — pozostają one przydatne do pracy z kampaniami, które korzystają ze starszego sposobu kierowania, oraz do rozwiązywania konfliktów własności kanału między dwiema kampaniami przychodzącymi.

### Przypisywanie kampanii do kanałów przychodzących

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

| Pole | Wymagane | Opis |
|---|---|---|
| `channels` | Tak | Tablica kanałów, które ta kampania powinna obsługiwać dla nowych, nieznanych kontaktów. |

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

**Odpowiedź**

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

`channels` wyświetla tylko te kanały, które faktycznie zostały skierowane do tej kampanii; `failed` wyświetla te, które nie zostały skierowane. Jeśli wszystkie żądane kanały zawiodą, samo żądanie również zakończy się niepowodzeniem.

### Usuwanie kierowania przychodzącego kampanii

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

| Pole | Wymagane | Opis |
|---|---|---|
| `channelToUnassign` | Nie | Usuń kierowanie tylko dla tego jednego kanału. Pomiń, aby usunąć wszystkie kanały, które ta kampania obecnie obsługuje. |

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

**Odpowiedź**

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

### Reaktywacja uśpionej kampanii

`POST /campaigns/{campaignId}/reactivate`

Przywraca kampanię ze stanu `Ended`, `Completed`, `Paused` lub `Draft` i odzyskuje jej kanały. Działa tylko w przypadku kampanii `Incoming from Unknown Contacts` lub `Combined` — kampania, która jest już `Live`, jest traktowana jako zakończona sukcesem i nie wymaga żadnych działań.

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

**Odpowiedź**

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

Kanał zajęty już przez agenta innej kampanii pojawi się w `channelsBlockedByConflict` zamiast powodować niepowodzenie całego wywołania — użyj [zatrzymania kolidującej kampanii przychodzącej](#stop-a-conflicting-incoming-campaign) poniżej, aby najpierw go zwolnić, jeśli chcesz, aby ta kampania go przejęła. Zwracany jest `400` dla typu kampanii, który nie obsługuje reaktywacji, lub statusu, który nie jest jednym z powyższych stanów uśpienia.

### Zatrzymaj kolidującą kampanię przychodzącą

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

Zwalnia kanały tej kampanii z INNEJ kampanii, która obecnie je zajmuje, dzięki czemu ta kampania może je przejąć jako następna. Jest to wersja REST tego, co pulpit nawigacyjny robi automatycznie, gdy uruchamiasz kampanię przychodzącą w kanale, który ktoś inny już obsługuje.

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

**Odpowiedź**

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

`released_channels` zwraca pustą wartość, gdy ta kampania posiada już wszystkie kanały, które reklamuje — nie ma nic do przejęcia.

---

## Szacunkowe koszty

Oszacuj koszt uruchomienia kampanii przed jej wysłaniem.

### Szacunkowy koszt szablonu WhatsApp

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

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

**Odpowiedź**

```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` wynosi `"credits"` w zarządzanym kanale WhatsApp. W kanale, w którym Meta obciąża bezpośrednio Twoje własne konto WhatsApp Business, `costPerContact`, `subtotal` oraz `totalTemplateCost` zwracają `null` — nigdy `0`, co byłoby odczytane jako bezpłatne — ponieważ nie ma kwoty kredytu do raportowania.

### Szacunkowy koszt SMS

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

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

**Odpowiedź**

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

Wiadomości SMS są zawsze wysyłane za pośrednictwem Twojego własnego konta Twilio (zobacz [dostawca SMS](../settings/sms-provider.md)), więc są one zawsze rozliczane bezpośrednio przez Twilio — `estimatedCostUsd` to szacunkowa wartość tego rachunku Twilio, a nie opłata kredytowa.

---

## Sprawdzanie limitów

Sprawdź limit przed uruchomieniem, zamiast dowiadywać się o nim po nieudanej wysyłce.

### Sprawdzanie w zakresie kampanii

`GET /campaigns/{campaignId}/limits/ai-credit-messaging` — czy uruchomienie lub zaplanowanie tej kampanii przekroczyłoby limit wiadomości AI-credit Twojego konta.

`GET /campaigns/{campaignId}/limits/messaging` — czy przekroczyłoby to dzienny limit wiadomości Twojego konta.

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

**Odpowiedź** (limit nieprzekroczony)

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

W przypadku przekroczenia limitu zwracany jest `400`, a powód znajduje się w `error`.

### Sprawdzanie w zakresie konta

`GET /campaigns/limits/campaigns` — czy osiągnięto miesięczny limit tworzenia kampanii w ramach subskrypcji.

`GET /campaigns/limits/contacts` — czy osiągnięto limit kontaktów w ramach subskrypcji.

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

**Odpowiedź**

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

---

## Sumy statystyk kampanii

`GET /campaigns/stats/totals`

Suma wysłanych i otrzymanych odpowiedzi dla każdej kampanii ORAZ każdego agenta AI na Twoim koncie w określonym oknie czasowym — te same liczby, które strona listy kampanii pokazuje obok każdego wiersza, dostępne w jednym wywołaniu zamiast jednego żądania na kampanię.

| Parametr zapytania | Opis |
|---|---|
| `days` | Rozmiar okna czasowego, 1-365. Domyślnie 90. |

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

**Odpowiedź**

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

`byAgent` stanowi własne podsumowanie, a nie sumę `byCampaign` — ruch na koncie natywnym dla agenta AI może w ogóle nie dotyczyć żadnej kampanii, więc w przeciwnym razie byłby tutaj niewidoczny.

---

## Testowanie kampanii w środowisku testowym (playground)

Plac zabaw pozwala na prowadzenie rozmowy z botem kampanii bez korzystania z rzeczywistego kanału lub kontaktu. Jest to ten sam piaskownica, co panel testowy w pulpicie nawigacyjnym, i jest w pełni dostępny przez API.

Przebieg jest następujący: utwórz ukryty kontakt testowy, wyślij wiadomość, a następnie odpytaj kampanię o odpowiedź bota. Odpowiedzi są generowane asynchronicznie, więc trafiają do `test_messages` w kampanii, a nie w treści odpowiedzi.

> **Działanie placu zabaw przez API wiąże się z kosztami kredytów.** Rozmowa testowa rozpoczęta przy użyciu klucza API jest rozliczana według standardowej stawki za wiadomość AI, tak samo jak rzeczywista odpowiedź, i pojawia się w historii użycia jako zwykły wpis. Testowanie z poziomu pulpitu nawigacyjnego pozostaje bezpłatne. Różnica jest zamierzona: test wykonuje tę samą pracę AI, co działanie na żywo, więc nielimitowany plac zabaw API byłby sposobem na korzystanie z nieograniczonej liczby operacji AI na koszt kogoś innego.

### Krok 1 - Utwórz kontakt testowy

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

Tworzy ukryty kontakt testowy i łączy go z kampanią. Wszystkie pola treści są opcjonalne; wszystko, co pominiesz, zostanie zastąpione wbudowaną przykładową tożsamością (John Doe).

| Pole | Wymagane | Opis |
|---|---|---|
| `first_name` | Nie | Imię kontaktu testowego. |
| `last_name` | Nie | Nazwisko kontaktu testowego. |
| `email` | Nie | Adres e-mail kontaktu testowego. |
| `phone` | Nie | Numer telefonu kontaktu testowego. |

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

**Odpowiedź**

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

### Krok 2 - Zarejestruj przychodzącą wiadomość

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

Dodaje wiadomości do wątku testowego. Wyślij tutaj najpierw wiadomość odwiedzającego, aby pojawiła się w historii rozmowy, którą czyta bot.

| Pole | Wymagane | Opis |
|---|---|---|
| `messages` | Tak | Tablica obiektów wiadomości, maks. 200 na żądanie. |
| `messages[].body` | Tak | Treść wiadomości. |
| `messages[].direction` | Tak | `"inbound"` dla odwiedzającego, `"outbound"` dla bota. |
| `messages[].timestamp` | Nie | Ciąg znaków ISO-8601 lub milisekundy epoki. |
| `messages[].role` | Nie | Opcjonalna etykieta roli. |
| `messages[].name` | Nie | Opcjonalna nazwa wyświetlana. |
| `ignoreCounter` | Nie | Liczba całkowita. Resetuje licznik ignorowania kampanii w tym samym zapisie. |

```bash
curl -X POST "https://api.dmchamp.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"
      }
    ]
  }'
```

**Odpowiedź**

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

### Krok 3 - Poproś bota o odpowiedź

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

Wysyła wiadomość do potoku AI. Jest to wywołanie, które faktycznie generuje odpowiedź bota.

| Pole | Wymagane | Opis |
|---|---|---|
| `message` | Tak | Tekst najnowszej wiadomości odwiedzającego. |

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

**Odpowiedź**

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

`"Published"` oznacza, że wiadomość trafiła do potoku AI. `"Ignored"` oznacza, że nowsza wiadomość testowa zastąpiła tę poprzednią — środowisko testowe łączy szybką serię wiadomości w jedną odpowiedź, mniej więcej cztery sekundy po ostatniej wiadomości, podobnie jak w prawdziwej rozmowie czeka się, aż ktoś skończy pisać. Ze względu na to okno łączenia, to wywołanie zwraca wynik po kilku sekundach.

### Krok 4 - Odczytanie odpowiedzi

`GET /campaigns/{campaignId}`

Odpowiedź bota jest dodawana do tablicy `test_messages` kampanii. Odpytuj kampanię, aż pojawi się nowy wpis `outbound`.

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

### Resetowanie środowiska testowego

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

Czyści całą piaskownicę: usuwa kontakt testowy, czyści `test_messages` i zwalnia blokady odpowiedzi bota. Używaj tego między uruchomieniami testów.

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

**Odpowiedź**

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

### Inne punkty końcowe środowiska testowego

| Punkt końcowy | Co robi |
|---|---|
| `DELETE /campaigns/{campaignId}/try-out/contact` | Usuwa tylko bieżący kontakt testowy i odłącza go, pozostawiając `test_messages` nienaruszone. Działa nawet wtedy, gdy żaden kontakt nie jest powiązany. |
| `POST /campaigns/{campaignId}/try-out/transfer` | Uruchamia nowe środowisko testowe z istniejącą rozmową w jednym żądaniu: zastępuje kontakt testowy i nadpisuje `test_messages`. Treść przyjmuje `first_name`, `last_name`, `messages` (może być puste) oraz `ignoreCounter`. Preferuj to rozwiązanie zamiast usuwania, tworzenia i dodawania, co trzykrotnie zwiększa zużycie limitu zapytań. |
| `POST /campaigns/{campaignId}/try-out/messages/replace` | Nadpisuje `test_messages` w całości zamiast dodawać do niej. Używaj do skracania lub przewijania wątku. |
| `POST /campaigns/{campaignId}/try-out/contact/reset-ignore-counter` | Resetuje tylko licznik ignorowania kontaktu testowego, dla przepływów ponownego wykonania i powtórzeń po wysłaniu. |

---

## Błędy API kampanii

Punkty końcowe kampanii zwracają standardową kopertę błędu:

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

| Status | Kiedy występuje w punkcie końcowym kampanii |
|---|---|
| `400` | Wymagane pole jest brakujące lub nieprawidłowe (na przykład błędny `type`, wartość niebędąca wartością logiczną `enabled` lub nieznany klucz dnia tygodnia). Zwracane również przez punkt końcowy [sprawdzania limitu](#limit-checks), gdy limit zostałby przekroczony, oraz przez [reaktywację](#reactivate-a-dormant-campaign) dla typu lub statusu kampanii, który tego nie obsługuje. |
| `404` | Nie znaleziono kampanii — albo nie istnieje, albo należy do innego konta. |
| `409` | [Optymalizacja](#optimize-a-campaign-with-ai) jest już uruchomiona dla tej kampanii. |

Wspólne kody, które może zwrócić każdy punkt końcowy — `401`, `403` (Twój plan nie obejmuje dostępu do API), `429` (limit szybkości) oraz `500` — zostały wymienione wraz ze wskazówkami dotyczącymi ponawiania prób w sekcji [Błędy i stronicowanie](errors-and-pagination.md).

---

## Powiązane

- [Skieruj kanał do kampanii](channels.md#route-a-channel-to-a-campaign) — przypisz Instagram, WhatsApp lub dowolny inny kanał do Agenta AI, który ma go obsługiwać, korzystając z Punktów Wejścia (Entry Points).
- [Generuj szablony wiadomości uzupełniających za pomocą AI](templates.md#generate-follow-up-templates-with-ai) — uruchom zadanie w tle, które przygotuje szablony wiadomości uzupełniających dla kampanii w WhatsApp.
- [API FAQ](faqs.md) — zarządzaj wpisami pytań i odpowiedzi używanymi w Twoich kampaniach.
- [Dostęp do API](../integrations/api-access.md) — wygeneruj swój klucz API.
- [Uwierzytelnianie](authentication.md) — wszystkie sposoby przekazywania klucza.
