
# API dla agencji

Jako agencja możesz korzystać z tego samego interfejsu REST API, z którego korzystają Twoi klienci, ale kierować poszczególne żądania do jednego ze swoich zarządzanych **subkont**, zamiast do własnego konta. Pozwala to na tworzenie narzędzi, które kompleksowo wdrażają klienta — od tworzenia kampanii, przez trenowanie sztucznej inteligencji na bazie wiedzy, importowanie kontaktów, łączenie kanałów komunikacji, aż po zakup numerów telefonów — wszystko to bez konieczności ręcznego logowania się na każde subkonto.

Ta strona opisuje wyłącznie zachowanie specyficzne dla agencji: jak działać w imieniu subkonta za pomocą parametru `sub_account_id`. Podstawowe informacje (generowanie klucza, uwierzytelnianie, bazowy adres URL, format błędów, limity zapytań) znajdziesz w przewodniku [Dostęp do API](../integrations/api-access.md). Wszystkie zawarte tam informacje mają zastosowanie również tutaj — uwierzytelniasz się za pomocą klucza API **swojego konta agencyjnego**.

::: note
**Uwaga:** Ta strona ma charakter techniczny. Jeśli nie jesteś programistą, udostępnij ją osobie tworzącej Twoją integrację.
:::


***

## Jak działa „działanie w imieniu”

Domyślnie każde żądanie API jest wykonywane w ramach konta, do którego należy klucz API — czyli Twojego konta agencyjnego. Aby działać w imieniu zarządzanego konta klienta, dodaj do żądania opcjonalny parametr `sub_account_id`, ustawiając go na identyfikator konta tego klienta.

- **Pomiń `sub_account_id`** → żądanie zostanie wykonane w ramach Twojego konta agencyjnego.
- **Dołącz `sub_account_id`** → żądanie zostanie wykonane w ramach tego subkonta, ale tylko po tym, jak platforma potwierdzi, że subkonto rzeczywiście należy do Ciebie.

Zawsze uwierzytelniasz się za pomocą klucza API **swojego konta agencyjnego**. Nigdy nie potrzebujesz własnego klucza subkonta i nigdy nie obsługujesz danych uwierzytelniających subkonta.

### Gdzie go umieścić

- **Punkty końcowe GET / DELETE** → przekaż go jako parametr zapytania: `?sub_account_id=THE_SUB_ACCOUNT_ID` (obok `apiKey`, jeśli uwierzytelniasz się przez zapytanie).
- **Punkty końcowe POST / PUT / PATCH** → dołącz go w treści żądania JSON jako `"sub_account_id": "THE_SUB_ACCOUNT_ID"`.
- **Asystenci AI** → nie ma nic do skonfigurowania. [Serwer MCP](../integrations/connect-ai-clients.md) obsługuje to samo ustawienie w swoich narzędziach odczytu, więc jedno połączenie z kluczem agencji może raportować dla każdego klienta: wystarczy wymienić klienta w zapytaniu („ile kontaktów ma Bella's Bistro?”). Dostępne są również akcje zapisu: każdy punkt końcowy, który akceptuje `sub_account_id`, jest udostępniany jako narzędzie, dzięki czemu możesz tworzyć, zmieniać i wysyłać w imieniu klienta z tego samego połączenia.

### Znajdowanie identyfikatora subkonta

`sub_account_id` to unikalny identyfikator konta klienta. Listę swoich subkont oraz ich identyfikatory możesz uzyskać z punktów końcowych API **SubAccounts** (zobacz przewodnik [Sub-konta](sub-accounts.md)) lub ze strony **Subkonta** na pasku bocznym.

***

## Własność jest zawsze weryfikowana

Gdy przekazujesz `sub_account_id`, platforma sprawdza, czy konto jest rzeczywistym subkontem **oraz** czy należy do Twojej agencji. Dopiero wtedy żądanie jest realizowane.

Jeśli identyfikator jest nieznany, nie jest subkontem lub należy do innej agencji, żądanie zakończy się niepowodzeniem z odpowiedzią **`404`**:

```json
{
  "success": false,
  "error_code": 404,
  "error": "Sub-account not found."
}
```

> **Dlaczego 404, a nie 403?** Odpowiedź „zabronione” (forbidden) poinformowałaby osobę z zewnątrz, że identyfikator istnieje, ale nie należy do niej. Zwracanie tego samego `404` dla przypadków „nie istnieje” oraz „nie jest twoje” oznacza, że punkt końcowy nie może zostać użyty do odkrycia, które identyfikatory kont należą do innych agencji. Traktuj `404` w tym miejscu jako „to nie jest subkonto, którym zarządzasz”.

***

## Gdzie `sub_account_id` jest obsługiwane

`sub_account_id` jest akceptowane w zasadzie w każdym punkcie końcowym **zasobów** — każdym wywołaniu, które tworzy, odczytuje, aktualizuje lub usuwa własne dane konta. W praktyce możesz skonfigurować i uruchomić całe środowisko subkonta za pomocą klucza agencji:

- **Konfiguracja AI** — kampanie, agenci, FAQ, źródła bazy wiedzy (skanowanie stron **oraz** przesyłanie dokumentów), grupy bazy wiedzy, transmisje, funkcje niestandardowe, serwery MCP
- **Kontakty i CRM** — kontakty (w tym import), listy, tagi, zadania, transakcje, spotkania, wydarzenia
- **Kanały i numery** — podłączanie WhatsApp / WhatsApp Web / Telegram / Instagram i Messenger / LINE, wyszukiwanie / kupowanie / zarządzanie numerami telefonów, szablony WhatsApp, routing kanałów
- **Wiadomości i treści** — wysyłanie wiadomości, sesje czatu, eksporty czatów, codzienne podsumowania
- **Ustawienia i integracje** — webhooki, konfiguracja widżetu czatu, konfiguracja white-label, BYOK SMS i inne ustawienia konta, analityka

W każdym z tych przypadków parametr jest **opcjonalny** — jeśli go pominiesz, wywołanie zostanie wykonane na Twoim własnym koncie agencji, dzięki czemu jedna integracja obsługuje oba scenariusze. Kredyty i wykorzystanie zawsze pochodzą z konta, którego dotyczy operacja: opłaty za kampanie, wiadomości, tagi i numery subkonta są naliczane z salda **subkonta**.

### Gdzie to NIE ma zastosowania

Kilka punktów końcowych działa na poziomie agencji lub odnosi się do samego siebie i ignoruje `sub_account_id`:

- **Zarządzanie samymi subkontami** — punkty końcowe SubAccounts (tworzenie / wyświetlanie listy / aktualizacja subkonta) oraz punkt końcowy limitu wydatków BYOK już wskazują subkonto w swojej ścieżce URL. [Punkty końcowe dotyczące cen i zasad](#set-per-client-ai-pricing-and-policy) oraz [punkty końcowe monitorowania czatu](#read-a-sub-accounts-conversations) postępują zgodnie z tym samym schematem.
- **Kopiowanie Agenta między kontami** — `POST /v1/subaccounts/agents/copy` wskazuje oba konta, przyjmując konto docelowe jako `targetUserId`. Zobacz [przykład praktyczny](#worked-example-ship-a-template-agent-into-every-new-client) poniżej. (Starszy punkt `POST /v1/subaccounts/campaigns/copy` działa w ten sam sposób, ale jest przestarzały, podobnie jak reszta [Campaigns API](../api/campaigns.md).)
- **Dostosowywanie kredytów i dwa podsumowania dla całej agencji** — [`POST /v1/subaccounts/credits`](#grant-or-deduct-credits-directly) identyfikuje subkonto za pomocą `email`; [`GET /v1/subaccounts/credit-usage`](#read-credit-usage-and-campaign-health-across-your-book) oraz `GET /v1/subaccounts/campaign-status` generują raporty dla wszystkich subkont jednocześnie, więc nie ma jednego konkretnego konta, do którego można się odwołać.
- **Własne konto Twojej agencji** — zarządzanie kluczami API, raportowanie użycia przez agencję, zarządzanie zespołem oraz Twoje [plany cenowe](#manage-your-pricing-tiers-over-the-api) zawsze dotyczą konta Twojej agencji.
- **Webhooki wiadomości przychodzących** — punkty końcowe, do których systemy zewnętrzne wysyłają dane, są powiązane z kontem, którego poświadczenia je skonfigurowały, więc nie ma czego przekierowywać.

> Zawsze aktualna, czytelna dla maszyn lista parametrów akceptowanych przez każdy punkt końcowy znajduje się w Twoim panelu w dokumentacji API (**Ustawienia → Integracje → Klucz API**) oraz w specyfikacji OpenAPI pod adresem `GET /v1/docs/openapi.yaml`. Często wprowadzamy zmiany w API — traktuj je jako jedyne źródło prawdy.

::: master-only
<figure><img src="../.gitbook/assets/v2-api-access-key-section.png" alt="Strona ustawień klucza API z zamaskowanym kluczem i kontrolką regeneracji"><figcaption><p>Ustawienia → Integracje → Klucz API — tutaj znajduje się klucz Twojej agencji, obok linku do pełnej dokumentacji API.</p></figcaption></figure>
:::

***

## Przykład praktyczny: podłączanie Instagrama i Messengera dla subkonta

Podłączanie Instagrama i Messengera to proces oparty na przeglądarce. Rozpoczynasz go za pomocą API, przekazujesz klientowi zwrócony adres URL zgody (lub otwierasz go dla niego), czekasz, aż autoryzuje się w swojej przeglądarce, a następnie wybierasz stronę do podłączenia — wszystko to, kierując operację na jego subkonto za pomocą `sub_account_id`.

### Krok 1 — Rozpoczęcie połączenia

Wywołaj punkt końcowy połączenia z `sub_account_id` klienta w treści żądania. Nie są tu przesyłane żadne poświadczenia; platforma zwraca adres URL zgody, który klient musi otworzyć w przeglądarce, oraz jednorazowy token korelacji.

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/channels/meta/connect?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sub_account_id": "abc123def456"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/channels/meta/connect", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    sub_account_id: "abc123def456",
  }),
});

const data = await res.json();
// data.oauth_url -> open this in the client's browser
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/channels/meta/connect",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "sub_account_id": "abc123def456",
    },
)

data = res.json()
# data["oauth_url"] -> open this in the client's browser
```

**Odpowiedź:**

```json
{
  "success": true,
  "oauth_url": "https://www.facebook.com/v21.0/dialog/oauth?client_id=...&state=...",
  "state_token": "8sFq2yV0kQ7m4n1pZr3tWb6cXe9hJl2aD5gK7uN0oI",
  "expires_at": "2026-06-10T12:30:00.000Z"
}
```

Skieruj klienta do `oauth_url` w przeglądarce, aby udzielił autoryzacji. Parametr `state_token` koreluje tę próbę i jest krótkotrwałym sekretem — nie loguj go. Próba wygasa o `expires_at`; jeśli termin minie, rozpocznij ponownie.

### Krok 2 — Odpytuj, aż strony się załadują

Po autoryzacji przez klienta odpytuj punkt końcowy statusu (używając tego samego `sub_account_id`, tym razem jako parametru zapytania), aż pojawią się strony, z którymi można nawiązać połączenie.

**cURL**

```bash
curl "https://api.dmchamp.com/v1/channels/meta/status?apiKey=YOUR_API_KEY&sub_account_id=abc123def456"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.com/v1/channels/meta/status?sub_account_id=abc123def456",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);

const data = await res.json();
// Wait until data.status === "pages_loaded", then read data.pages
```

**Python**

```python
import requests

res = requests.get(
    "https://api.dmchamp.com/v1/channels/meta/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"sub_account_id": "abc123def456"},
)

data = res.json()
# Wait until data["status"] == "pages_loaded", then read data["pages"]
```

**Odpowiedź:**

```json
{
  "success": true,
  "status": "pages_loaded",
  "pages": [
    {
      "id": "1098765432101234",
      "name": "Acme Studio",
      "category": "Hair Salon",
      "instagram_business_account": {
        "id": "17841400000000000",
        "username": "acme.studio"
      }
    }
  ],
  "selected_page": null
}
```

Pole `status` przechodzi przez stany `pending` → `token_received` → `pages_loaded` → `connected`. Poczekaj na `pages_loaded` przed wybraniem strony. Zamiast przejścia do kolejnego etapu mogą również wystąpić dwa końcowe stany błędu: `failed` oraz `expired` (klient odmówił zgody lub upłynął około 30-minutowy czas ważności tokena stanu) — w obu przypadkach dołączane jest pole `reason`. Jeśli zobaczysz któryś z nich, przerwij odpytywanie i wróć do kroku 1; nie czekaj w nieskończoność na `pending`. Tokeny dostępu do strony nigdy nie są zwracane.

### Krok 3 — Wybierz stronę do połączenia

Wybierz jeden z identyfikatorów stron z kroku 2 i zaznacz go. Wybranie strony łączy zarówno Instagram, jak i Messenger dla tej strony. Ponownie dołącz `sub_account_id` w treści żądania.

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/channels/meta/select-page?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "page_id": "1098765432101234",
    "sub_account_id": "abc123def456"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/channels/meta/select-page", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    page_id: "1098765432101234",
    sub_account_id: "abc123def456",
  }),
});

const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/channels/meta/select-page",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "page_id": "1098765432101234",
        "sub_account_id": "abc123def456",
    },
)

data = res.json()
```

**Odpowiedź:**

```json
{
  "success": true,
  "page_id": "1098765432101234",
  "instagram_business_account_id": "17841400000000000"
}
```

To wszystko — Instagram i Messenger są teraz połączone z subkontem klienta. Dostarczyłeś jedynie `page_id`; podstawowe poświadczenie jest rozwiązywane po stronie serwera i nigdy nie przechodzi przez Twoją integrację.

***

## Przykład: zakup numeru dla subkonta

Zakup numeru przebiega w ten sam sposób: wyszukaj za pomocą `sub_account_id` w zapytaniu, a następnie dokonaj zakupu, używając go w treści. Środki są pobierane z salda **subkonta**, a numer jest przypisywany do subkonta.

**Wyszukiwanie (cURL):**

```bash
curl "https://api.dmchamp.com/v1/phone-numbers/available?apiKey=YOUR_API_KEY&country_code=US&sub_account_id=abc123def456"
```

**Zakup (JavaScript):**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/phone-numbers", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phone_number: "+14155551234",
    country_code: "US",
    display_name: "Support line",
    sub_account_id: "abc123def456",
  }),
});

const data = await res.json();
```

**Zakup (Python):**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/phone-numbers",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "phone_number": "+14155551234",
        "country_code": "US",
        "display_name": "Support line",
        "sub_account_id": "abc123def456",
    },
)

data = res.json()
```

**Odpowiedź:**

```json
{
  "success": true,
  "phone_number": "+14155551234",
  "channel": "whatsapp",
  "whatsapp_status": "PURCHASED",
  "outgoing_status": "PURCHASED",
  "status": "PURCHASED",
  "purchase_credits": 11.5,
  "monthly_credits": 11.5
}
```

Numer jest udostępniany w stanie `PURCHASED`, a rejestracja nadawcy WhatsApp jest kontynuowana w tle. Przed wysłaniem wiadomości odpytuj `GET /v1/phone-numbers/{phoneNumber}/status?sub_account_id=abc123def456`, aż status zmieni się na `ONLINE`.

***

## Przykład praktyczny: wdrożenie szablonu Agenta u każdego nowego klienta

Typowy model pracy agencji polega na utrzymywaniu jednego głównego Agenta na koncie agencji, skonfigurowanego tak, jak chcesz, aby każdy klient zaczynał, a następnie kopiowaniu go na każde nowe subkonto w momencie jego tworzenia. Wymaga to trzech wywołań i później nie trzeba nic powtarzać: kopia zachowuje swoje ustawienia, dopóki ich nie zmienisz.

### Krok 1 — Skopiowanie Agenta

`POST /v1/subaccounts/agents/copy`

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts/agents/copy?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "YOUR_TEMPLATE_AGENT_ID",
    "targetUserId": "abc123def456",
    "newName": "Inbound Instagram Leads",
    "copyFaqs": true
  }'
```

Odpowiedź zawiera identyfikator nowego Agenta w `data.agent_id`. Często zadawane pytania, baza wiedzy i biblioteka mediów są kopiowane; szablony WhatsApp, połączone posty w mediach społecznościowych oraz kontakty z konta źródłowego celowo nie są przenoszone. Pełna lista pól znajduje się w [AI Agents API](../api/agents.md#copy-an-agent-into-a-sub-account-agencies).

Zwróć uwagę, że ten punkt końcowy przyjmuje `targetUserId` zamiast `sub_account_id` — sam wskazuje oba konta. Dwa poniższe wywołania używają normalnego parametru `sub_account_id`.

### Krok 2 — Włączenie go

Kopia zawsze dociera w stanie wstrzymanym, więc nie może wysyłać wiadomości do nikogo, dopóki tego nie zatwierdzisz. To również moment na przypisanie poziomu AI, na którym ma pracować klient; ustawienie to pozostaje aktywne, więc nie ma potrzeby ponownego stosowania go według harmonogramu.

```bash
curl -X PATCH "https://api.dmchamp.com/v1/agents/NEW_AGENT_ID/active?apiKey=YOUR_API_KEY&sub_account_id=abc123def456" \
  -H "Content-Type: application/json" \
  -d '{ "active": true }'

curl -X PUT "https://api.dmchamp.com/v1/agents/NEW_AGENT_ID?apiKey=YOUR_API_KEY&sub_account_id=abc123def456" \
  -H "Content-Type: application/json" \
  -d '{ "anthropic_model": "max" }'
```

Aby uniemożliwić klientowi późniejszą zmianę poziomu, [zablokuj dozwolone poziomy](#set-per-client-ai-pricing-and-policy) na subkoncie zamiast ponownie wysyłać tę wartość.

### Krok 3 — Skierowanie kanałów klienta na kampanię

Kopia dociera również bez żadnego routingu, więc nic do niej nie dotrze, dopóki nie uczynisz jej osobą odpowiadającą na kanałach połączonych przez klienta. Jedno wywołanie na kanał:

```bash
curl -X PUT "https://api.dmchamp.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY&sub_account_id=abc123def456" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "instagram", "agent_id": "NEW_AGENT_ID" }'
```

Od tego momentu pierwsza wiadomość od nieznanego kontaktu na tym kanale jest automatycznie odbierana przez skopiowanego Agenta. Zobacz [Skieruj kanał na Agenta](../api/entry-points.md#point-a-channel-at-an-agent), aby dowiedzieć się więcej o innych kanałach i routingu dla poszczególnych numerów.

> **Ustaw strefę czasową klienta podczas tworzenia subkonta.** Przekaż `time_zone_id` w `POST /v1/subaccounts`. Aktywne godziny kampanii są oceniane w strefie czasowej subkonta, więc klient utworzony bez niej ma harmonogram odczytywany według czasu UTC — co po cichu zmienia czas, w którym asystent może odpowiadać.

***

## Pomiń kreator konfiguracji dla klienta, którego konfigurujesz samodzielnie

`POST /v1/subaccounts`

Domyślnie, przy pierwszym logowaniu nowego właściciela subkonta, jest on prowadzony przez kreator konfiguracji (Setup Wizard). W przypadku klientów, dla których wykonujesz usługę w pełni — gdzie tworzysz kampanię i łączysz kanały, zanim klient się zaloguje — przekaż `guided_onboarding: false` podczas tworzenia konta. Zostaną oni przekierowani bezpośrednio do pulpitu nawigacyjnego, a pozycja **Kreator konfiguracji** zostanie ukryta na ich pasku bocznym.

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "client@example.com",
    "first_name": "Alex",
    "last_name": "Client",
    "business_name": "Client Co",
    "guided_onboarding": false,
    "usage_limits": { "monthly_credits": 500 }
  }'
```

Pomiń to pole (lub wyślij `true`), a kreator będzie zachowywał się dokładnie tak, jak zawsze, więc istniejące integracje nie wymagają zmian. Aby później przywrócić klientowi kreator, ponownie pokaż element `guided_onboarding` za pomocą `PUT /v1/subaccounts/{subAccountUid}/menu-visibility` (poniżej) — widoczność menu kontroluje, czy kreator jest dostępny, a `guided_onboarding` kontroluje tylko przekierowanie przy pierwszym logowaniu.

***

## Wyłączanie zadań, podsumowań dziennych lub biblioteki mediów dla klienta

`POST /v1/subaccounts`

Te trzy funkcje są włączone dla każdego nowego klienta, chyba że określisz inaczej, i działają inaczej niż wszystkie pozostałe funkcje opisane w tym przewodniku: wymagają **rezygnacji** (opt-out), a nie aktywacji (opt-in). Pominięcie ich w `features` nie wystarczy, ponieważ lista `features` starszej integracji po prostu o nich nie wspominała — nie jesteśmy w stanie odróżnić sytuacji, w której „agencja wyłączyła tę funkcję”, od sytuacji, w której „lista została napisana, zanim ta opcja istniała”.

Dlatego należy to wyraźnie określić za pomocą `feature_settings`:

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "client@example.com",
    "first_name": "Alex",
    "last_name": "Client",
    "business_name": "Client Co",
    "feature_settings": {
      "tasks": false,
      "daily_summaries": false,
      "ai_media_library": true
    }
  }'
```

Każdy klucz jest opcjonalny; wszystko, co pominiesz, pozostanie włączone. Dzięki `tasks: false` sztuczna inteligencja przestaje tworzyć zadania dla danego klienta i nie są wysyłane żadne wiadomości e-mail o treści „Utworzono nowe zadanie”; z kolei przy `daily_summaries: false` nocne podsumowanie nigdy nie jest generowane ani wysyłane pocztą elektroniczną.

`feature_settings` to jedyny sposób, aby wyłączyć te trzy funkcje w momencie tworzenia. Pominięcie ich w `features` samo w sobie nic nie zmienia, niezależnie od tego, jak wygląda reszta Twojej listy — jest to celowe działanie, aby starsza integracja nie straciła wszystkich trzech funkcji w sposób niezauważalny.

Aby zmienić którekolwiek z tych ustawień później, wyślij pełną listę `features` do `PUT /v1/subaccounts/{subAccountUid}/features` — tam obecność na liście włącza funkcję, a jej brak ją wyłącza.

***

## Automatyczne logowanie klientów do ich subkont (SSO)

`POST /v1/subaccounts/{subAccountUid}/sso-link`

Jedno wywołanie z użyciem klucza API Twojej agencji zwraca gotowy do otwarcia adres URL, który loguje klienta bezpośrednio do jego subkonta — bez ekranu logowania, bez hasła, bez konieczności budowania czegokolwiek dodatkowego. Otwórz go w nowej karcie, przekierowaniu lub ramce iframe wewnątrz własnego produktu.

| Pole | Wymagane | Opis |
|---|---|---|
| `redirect` | Nie | Strona wewnątrz aplikacji, na której ma się znaleźć klient, np. `"/chats"` lub `"/agents"`. Zwracane jako `deep_link_url` w odpowiedzi. |
| `app_base_url` | Nie | Host panelu nawigacyjnego dla linku. Domyślnie jest to domena Twojej aplikacji typu white-label (lub domena platformy, jeśli jej nie posiadasz). Musi być `https`. |

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/sso-link" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "redirect": "/chats" }'
```

**Odpowiedź**

```json
{
  "success": true,
  "url": "https://app.yourdomain.com/auth?redirect=%2Fchats#token=eyJhbGciOi…",
  "deep_link_url": "https://app.yourdomain.com/chats",
  "expires_at": "2026-07-22T15:04:05.000Z",
  "sub_account_uid": "SUB_ACCOUNT_UID"
}
```

Jak dobrze z tego korzystać:

- **Jeden przeskok.** Otwarcie `url` loguje klienta i przenosi go bezpośrednio na stronę `redirect` panelu nawigacyjnego — bez ekranu logowania i stron pośrednich. `deep_link_url` wskazuje ten sam cel dla integratorów, którzy wolą nawigować po ramce jawnie po zalogowaniu; gdy sesja istnieje, każda ścieżka panelu działa w tym kontekście przeglądarki.
- **Generuj na żądanie, otwieraj natychmiast.** Link zawiera poświadczenia logowania i wygasa po około godzinie. Żądaj go po stronie serwera w momencie, gdy klient klika, i nigdy go nie przechowuj ani nie wysyłaj e-mailem.
- Token logowania przesyłany jest we fragmencie adresu URL (`#…`), którego przeglądarki nigdy nie wysyłają do serwerów, i jest usuwany z paska adresu w momencie użycia.
- **Tylko Twoje własne subkonta.** Punkt końcowy odrzuca każde konto, którego Twoja agencja nie jest właścicielem.
- Wygasły link wyświetla czytelny błąd ze ścieżką ponowienia — wygeneruj nowy.

***

## Ukrywanie elementów nawigacji na subkoncie

`PUT /v1/subaccounts/{subAccountUid}/menu-visibility`

Kontroluje, które elementy paska bocznego i ustawień widzi subkonto — przydatne, gdy osadzasz panel i chcesz, aby widoczne były tylko te obszary, których Twój produkt jeszcze nie obejmuje. Wszystko, co nie jest wymienione, pozostaje widoczne; wyślij `null` jako całą wartość `menuVisibility`, aby zresetować wszystko do stanu widocznego. Ukrycie elementu ukrywa wpis w menu — połącz to z funkcjami, które przyznajesz subkontu w celu twardej blokady dostępu.

**cURL**

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/menu-visibility" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "menuVisibility": {
      "side_nav": { "Dashboard": false, "Campaigns": false, "Automations": false },
      "settings_nav": { "team": false }
    }
  }'
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "subAccountUid": "SUB_ACCOUNT_UID",
    "menuVisibility": {
      "side_nav": { "Dashboard": false, "Campaigns": false, "Automations": false },
      "settings_nav": { "team": false }
    }
  }
}
```

`side_nav` akceptuje te 13 kluczy, które odpowiadają nazwom elementów paska bocznego: `Dashboard`, `DailySummaries`, `Chats`, `Contacts`, `Deals`, `Tasks`, `Automations`, `Campaigns`, `Appointments`, `Settings`, `Help`, `CreditsCounter` (saldo kredytowe widoczne na pasku bocznym) oraz `guided_onboarding` (Kreator konfiguracji). Trzy dodatkowe klucze — `AiInsights`, `Sub Accounts` oraz `Agency Reselling` — są akceptowane, ale nie wykonują żadnych działań: miały one zastosowanie wyłącznie do wycofanego klasycznego pulpitu nawigacyjnego, więc ich ustawienie nie ma wpływu na Twoje subkonta. Brakujące klucze oznaczają widoczność; gdy logujesz się na subkonto samodzielnie, ukryte elementy są tymczasowo wyświetlane, dzięki czemu zawsze możesz przywrócić poprzednie ustawienia.

Ukrycie strony w menu nigdy nie przyznaje do niej dostępu. `Automations` wymaga funkcji `automations` przyznanej na subkoncie — jeśli ustawisz klucz na `true` bez niej, strona nadal się nie pojawi. `Tasks` i `DailySummaries` działają odwrotnie: są włączone dla każdego klienta, dopóki ich nie wyłączysz (zobacz [Wyłączanie zadań, podsumowań dziennych lub biblioteki mediów dla klienta](#turn-tasks-daily-summaries-or-the-media-library-off-for-a-client)).

***

## Wybierz typy kanałów, które klient może połączyć

`PUT /v1/subaccounts/{subAccountUid}/features`

Przełączniki **Typy kanałów** widoczne w planie to zwykłe identyfikatory funkcji, więc możesz ustawić je dla każdego klienta za pomocą API zamiast pulpitu nawigacyjnego. Jest to jeden z punktów końcowych, który zawiera nazwę subkonta w swoim adresie URL, więc nie wymaga `sub_account_id`.

| Identyfikator funkcji | Kanał |
|---|---|
| `channel_chat_widget` | Widżet czatu na stronie |
| `channel_whatsapp_api` | WhatsApp Business API |
| `channel_whatsapp_web` | WhatsApp Web (numer powiązany kodem QR) |
| `channel_instagram` | Instagram |
| `channel_messenger` | Facebook Messenger |
| `channel_telegram` | Telegram |
| `channel_line` | LINE |
| `channel_viber` | Viber |
| `channel_email` | Skrzynka e-mail |
| `channel_sms` | SMS |
| `channel_imessage` | iMessage |

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/features" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "features": [
      "channels_3",
      "channel_chat_widget",
      "channel_whatsapp_web",
      "channel_instagram",
      "image_understanding",
      "contact_tagging",
      "incoming_campaigns",
      "webhooks"
    ]
  }'
```

Trzy rzeczy, o których należy pamiętać:

- **To wywołanie zastępuje całą listę funkcji.** Wyślij wszystkie funkcje, które klient ma zachować, a nie tylko te, które zmieniasz. Te same identyfikatory działają jako `features` w `POST /v1/subaccounts` podczas tworzenia konta.
- **Typy kanałów i liczba kanałów to oddzielne ograniczenia i oba mają zastosowanie.** `channels_1` / `channels_3` / `channels_unlimited` kontrolują *liczbę* połączeń; identyfikatory `channel_*` kontrolują *które typy*. Powyższy przykład oznacza „do 3 połączeń i tylko Chat Widget, WhatsApp Web lub Instagram”.
- **Nieprzesłanie żadnych identyfikatorów `channel_*` oznacza brak ograniczeń kanałów.** Jest to oryginalne zachowanie, dlatego istniejący klienci nie odczuli zmian po wprowadzeniu tej funkcji. Wyślij jeden lub więcej, a wszystko inne będzie wyświetlane jako zablokowane na stronie Kanały klienta z notatką o aktualizacji zamiast przycisku Połącz. Kanały, które klient już połączył, nadal działają.

> Ustawienie listy kanałów dla **poziomu planu**, dzięki czemu każdy klient, który kupi ten plan, dziedziczy te ustawienia, odbywa się w panelu sterowania w ustawieniach planu agencji. Ten punkt końcowy ustawia to dla jednego konkretnego subkonta.

***

## Ustaw dokładny limit członków zespołu dla klienta

`PUT /v1/subaccounts/{subAccountUid}/limits`

Funkcje `team_seats_*` oferują tylko wstępnie ustawione progi (3 / 5 / 10 / bez limitu). Aby nadać klientowi **dokładną** liczbę miejsc w zespole — 2, 7, 15, cokolwiek innego — ustaw `usage_limits.team_seats_limit`. Ma to pierwszeństwo przed ustawieniami wstępnymi, a platforma egzekwuje to przy każdym zaproszeniu, bezpośrednim dodaniu i zaakceptowaniu zaproszenia: po osiągnięciu limitu kolejne zaproszenia są odrzucane po stronie serwera.

- Liczba całkowita dodatnia to dokładny limit.
- `0` oznacza, że członkowie zespołu **nie są uwzględnieni** — klient nie może nikogo zaprosić.
- `-1` oznacza brak limitu.
- `null` usuwa niestandardowy limit i przywraca ustawienie wstępne `team_seats_*` znajdujące się na liście funkcji.

Obniżenie limitu nigdy nie usuwa istniejących członków zespołu; jedynie blokuje możliwość dodawania nowych.

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/limits" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "usageLimits": { "team_seats_limit": 7 }
  }'
```

Możesz to również ustawić w momencie tworzenia: `POST /v1/subaccounts` akceptuje `usage_limits.team_seats_limit` z tą samą semantyką. Aby odczytać bieżącą wartość, pobierz subkonto za pomocą `GET /v1/subaccounts?email=...` i sprawdź `usage_limits.team_seats_limit` (brak/`null` = decydują ustawienia wstępne). Ten sam punkt końcowy aktualizuje również `credits`, `monthly_credits`, `roll_over_to_next_month`, `rollover_cap_months`, `rollover_expiry_days` oraz `byok_monthly_limit_usd` — wyślij tylko te klucze, które chcesz zmienić.

**Jak to wpływa na limity miejsc w planach SaaS.** Twoje plany SaaS mogą mieć własny przydział miejsc (ustawiany w edytorze planów — zobacz [Miejsca w zespole w ramach planu](agency-accounts.md#step-3--set-up-pricing-tiers)), który jest stosowany automatycznie, gdy klient wykupuje subskrypcję. Limit ustawiony za pomocą tego punktu końcowego jest liczony jako przyznanie **ręczne**: zakup planu zastępuje go własnym przydziałem miejsc planu (ten zakup jest wyraźnym wyborem planu), ale automatyczne **odnowienia miesięczne nigdy nie nadpisują limitu ręcznego** — dzięki temu jednorazowy wyjątek przyznany klientowi pozostaje aktywny po zakończeniu cyklu rozliczeniowego. Usunięcie ręcznego limitu za pomocą `null` przywraca kontrolę nad tym polem planowi przy jego następnym odnowieniu.

**Ogranicz to, co klient przenosi między okresami rozliczeniowymi.** Dwa kolejne klucze `usage_limits` znajdują się obok `roll_over_to_next_month`. Oba są również akceptowane przez `POST /v1/subaccounts` w momencie tworzenia, a `null` czyści oba z nich.

| Klucz | Działanie |
|---|---|
| `rollover_cap_months` | Liczba miesięcy limitu, które klient może zachować. Liczba od 0 do 120, dozwolone ułamki (`0.5` = pół miesiąca). Przy każdym odnowieniu niewykorzystane saldo jest przycinane do maksymalnie tej wielokrotności limitu przyznawanego w danym odnowieniu, zanim zostaną dodane nowe kredyty; `0` nie przenosi niczego. |
| `rollover_expiry_days` | Całkowita liczba dni, od 1 do 3650. Kredyty pozostawione niewykorzystane tak długo są usuwane przy pierwszym odnowieniu po osiągnięciu tego wieku. Wydatki zawsze są odejmowane od najstarszych kredytów, więc klient, który co miesiąc wykorzystuje swój limit, nigdy niczego nie traci. |

Jeśli pozostaną nieustawione, oba wracają do wartości z planu klienta; wartość wysłana tutaj ma pierwszeństwo przed wartością z planu. Dotyczy to tylko kredytów cyklicznych (miesięczny limit i kredyty z planu): doładowania, automatyczne doładowania i dodatki jednorazowe nigdy nie są ograniczane ani nie wygasają. Każde przycięcie jest zapisywane w historii kredytów klienta jako **Korekta kredytu z tytułu limitu przeniesienia** lub **Korekta kredytu z tytułu wygasłych kredytów** i nigdy nie liczy się jako zużycie. Odpowiedniki na poziomie planu to `rollover_cap_months` i `rollover_expiry_days` w poziomie cenowym — zobacz [Pola na poziomie cenowym](#the-fields-on-a-tier) oraz [Ograniczanie tego, co przechodzi na kolejny okres](sub-accounts.md#capping-what-rolls-over).

***

## Ustawianie cen i zasad AI dla poszczególnych klientów

`PUT /v1/subaccounts/{subAccountUid}/max-tier` · `/ai-tiers` · `/max-rate` · `/action-pricing` · `/insider-rate` · `/locked-bot-fields` · `/notifications` · `/zero-credit-reply`

Osiem kolejnych przełączników dla każdego klienta, obok `/limits`, `/features` i `/menu-visibility` powyżej. Każdy z nich przyjmuje uid subkonta w adresie URL (brak parametru `sub_account_id` w treści/zapytaniu — cel jest już nazwany w ścieżce) i ma ten sam zakres: Twój klucz agencji, a subkonto musi należeć do Twojej agencji.

**Z jakich modeli AI może korzystać klient**

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/max-tier" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

`{ "enabled": boolean }` włącza (lub wyłącza) klienta w planie Max AI — naszej infrastrukturze w cenie katalogowej platformy. Włączenie tego dla klienta BYOK zmienia koszt AI z „bezpłatny przy użyciu własnego klucza” na „pobierany z mojej puli kredytów”, więc jest to świadoma decyzja dla każdego klienta, a nie domyślne ustawienie dla całej agencji.

Aby ograniczyć, z KTÓRYCH poziomów mogą w ogóle korzystać kampanie i agenci klienta (zamiast tylko blokować Max), użyj `ai-tiers`:

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/ai-tiers" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "allowed_ai_tiers": ["standard", "economy"] }'
```

`allowed_ai_tiers` to tablica pobrana z `standard`, `economy`, `max`, `mini` — ZASTĘPUJE ona listę dozwolonych elementów klienta. Wyślij `null` (lub `[]`), aby wyczyścić ograniczenie i pozwolić im wybrać dowolny poziom. Ma to znaczenie, ponieważ subkonto wybierające własny poziom AI korzysta z **Twojej** puli kredytów, więc jest to dźwignia do określenia, które modele mogą generować koszty na Twoim rachunku w przypadku klienta-resellera.

**Odpowiedź zastępcza, gdy klientowi skończą się kredyty**

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/zero-credit-reply" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "message": "Thanks for your message, we will get back to you shortly." }'
```

Gdy saldo klienta (lub Twoja pula) jest puste, AI nie może odpowiedzieć, a kontakt nic nie słyszy. Dzięki `enabled: true` każdy kontakt, który napisze w trakcie przerwy, otrzyma `message` jednorazowo (maks. 500 znaków, wysyłane w niezmienionej formie na każdym kanale), a AI odpowie na te rozmowy w rzeczywistości, gdy kredyty wrócą. `enabled: false` przechowuje zapisany tekst na później; `enabled: false` bez `message` usuwa to ustawienie. Ten sam przełącznik co **Odpowiedź zastępcza, gdy skończą się kredyty** w oknie edycji subkonta — zobacz [Odpowiedź zastępcza, gdy klientowi skończą się kredyty](sub-accounts.md#a-holding-reply-while-a-client-is-out-of-credits).

**Ile klient płaci za akcję AI oraz marża opłaty za WhatsApp**

Dwa sposoby ustawienia stawki dla klienta, od najprostszego do najbardziej szczegółowego:

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/max-rate" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "rate": 0.35 }'
```

`rate` to cena w kredytach, którą spala WŁASNE saldo subkonta za każdą akcję AI modelu Max — Twoja marża dla klienta nałożona na to, co faktycznie płaci Twoja pula. `null` usuwa nadpisanie, przywracając cenę katalogową platformy. Stawka musi wynosić co najmniej tyle, ile kosztuje akcja Max dla Twojej puli (abyś nigdy nie mógł wycenić klienta poniżej kosztów) i nie więcej niż 10 kredytów; żądanie spoza tego zakresu zostanie odrzucone z informacją o obliczonym progu minimalnym w komunikacie o błędzie.

Aby zastosować ceny dla poszczególnych typów akcji zamiast jednej stałej stawki Max, użyj `action-pricing`:

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/action-pricing" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "actionPricing": {
      "AI_MESSAGE": 0.6,
      "CHAT_SUMMARY": 0.15,
      "wa_carrier_multiplier": null
    }
  }'
```

`actionPricing` to SCALANIE z istniejącą mapą klienta — klucz, o którym nie wspomnisz, pozostaje bez zmian, a `null` przywraca ten klucz do wartości domyślnej. Rozpoznawane klucze:

| Klucz | Ceny |
|---|---|
| `AI_MESSAGE` | Odpowiedź AI |
| `AI_TOOL_USE` | Wywołanie narzędzia AI |
| `EVALUATION_CALL` | Przejście oceny czatu |
| `INTERRUPTION_HANDLING` | Obsługa przerwania w trakcie odpowiedzi |
| `CONTACT_TAG` | Tag kontaktu przypisany przez AI |
| `CHAT_SUMMARY` | Podsumowanie czatu |
| `wa_carrier_multiplier` | Mnożnik marży stosowany do każdej opłaty za WhatsApp niezwiązanej z AI, którą płaci klient: miesięczny czynsz za numer, opłaty za dostarczenie w zarządzanym kanale oraz koszty szablonów Meta/Twilio. |

Stawki dla poszczególnych akcji muszą być liczbą większą od 0 i nieprzekraczającą 10; `wa_carrier_multiplier` musi wynosić co najmniej `1` (brak zniżki poniżej kosztów) i nie więcej niż 10. Przesłanie nierozpoznanego klucza lub wartości spoza zakresu spowoduje odrzucenie CAŁEGO żądania wraz z wymienieniem każdego błędnego klucza, dzięki czemu literówka nigdy nie spowoduje cichego zapisania ceny, która w rzeczywistości nie jest stosowana.

Jeśli jesteś członkiem [Champions Circle](https://skool.com/dm-champions), `insider-rate` przekazuje Twoją stawkę 20% zniżki na Max/Lead Finder jednemu klientowi, zamiast stosować ją w całej agencji:

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/insider-rate" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

Włączenie tej opcji wymaga, aby Twoje konto agencji faktycznie posiadało członkostwo Circle; wyłączenie jej nigdy tego nie wymaga, więc członek, któremu wygasło członkostwo, zawsze może przywrócić ustawienia klienta.

**Blokowanie sekcji podręcznika klienta**

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/locked-bot-fields" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "locked_bot_fields": ["instructions", "rules"] }'
```

`locked_bot_fields` to tablica pobrana z `instructions`, `goal`, `rules`, `personality`, `conclude_unless` — ZASTĘPUJE ona listę zablokowanych elementów klienta. Zablokowana sekcja jest odrzucana po stronie serwera, jeśli samo PODKONTO próbuje ją zmienić (bezpośrednio lub za pomocą klucza API), podczas gdy Ty (poprzez `sub_account_id`) oraz administrator panelu klienta możecie nadal edytować wszystko. Wyślij `null` (lub `[]`), aby odblokować wszystko. Przydatne w przypadku klientów typu „done-for-you”, gdzie jesteś właścicielem podręcznika i jesteś oceniany na podstawie wyników.

**Ustawianie preferencji powiadomień klienta w jego imieniu**

```bash
curl -X PUT "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/notifications" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notifications": {
      "settings": {
        "credit_alerts": { "enabled": true, "channels": ["email", "in_app"] },
        "new_contacts": { "enabled": false }
      }
    }
  }'
```

`notifications` zastępuje cały zestaw preferencji powiadomień klienta (nie jest to scalanie poszczególnych kluczy — wyślij każdą kategorię, którą chcesz zachować, zgodnie z tym, jak zapisuje je strona Ustawień podkonta). Każda kategoria w `settings` akceptuje `enabled` (wartość logiczna) oraz maksymalnie trzy `channels` z `email`, `in_app`, `webhook`. Wyślij `null`, aby przywrócić ustawienia domyślne platformy.

Wszystkie siedem punktów końcowych odpowiada `{ "success": true, "data": { "subAccountUid": "...", ...the field(s) you set... } }` i jest rejestrowanych w dzienniku audytu z wartością przed/po. Typowe błędy: `403`, jeśli Twoje konto nie jest kontem agencji/dewelopera lub podkonto nie należy do Ciebie, `400`, jeśli nie jest to podkonto agencji lub wartość jest poza zakresem.

***

## Wstrzymanie klienta, który zawiesił swoją subskrypcję

`POST /v1/subaccounts/{subAccountUid}/pause` · `POST /v1/subaccounts/{subAccountUid}/unpause`

Gdy klient zawiesza u Ciebie subskrypcję, wstrzymaj jego konto zamiast je usuwać: wszystko, co wysyła, zostaje natychmiast zatrzymane — wiadomości wychodzące, transmisje, odpowiedzi AI we wszystkich kanałach — a po zalogowaniu klient widzi pełnoekranową blokadę **Konto wstrzymane** (z Twoją opcjonalną wiadomością) zamiast aplikacji. Nic nie jest usuwane ani rozłączane: agenci, kampanie, podłączone kanały, kontakty i historia czatów pozostają dokładnie w takim stanie, w jakim były, więc wznowienie konta przywraca klienta dokładnie w miejsce, w którym przerwał — bez konieczności ponownej konfiguracji.

| Pole | Wymagane | Opis |
|---|---|---|
| `message` | Nie | Wyświetlane klientowi na ekranie blokady. Pozostaw puste, aby użyć domyślnego sformułowania. |
| `reason` | Nie | Notatka wewnętrzna agencji przechowywana wraz ze wstrzymaniem i w dzienniku audytu — nigdy nie jest pokazywana klientowi. |

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/pause" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Your account is on hold — contact us to reactivate it.", "reason": "Subscription suspended per client email" }'
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "subAccountUid": "SUB_ACCOUNT_UID",
    "paused": true,
    "level": "hard_blocked"
  }
}
```

Gdy klient wraca, `POST /v1/subaccounts/SUB_ACCOUNT_UID/unpause` (bez treści) zdejmuje blokadę — wysyłanie wiadomości i odpowiedzi AI zostają natychmiast wznowione.

Warto wiedzieć:

- **To ten sam stan, co przełącznik twardej blokady w pulpicie nawigacyjnym** ([Blokowanie / Wstrzymywanie subkonta](sub-accounts.md#blocking-pausing-a-sub-account)) — klient wstrzymany przez API jest widoczny jako zablokowany w pulpicie nawigacyjnym i odwrotnie, a wznowienie usuwa blokadę nałożoną z obu stron. Bieżący stan można odczytać z pola `agency_block` w `GET /v1/subaccounts` (`level` z `"none"`, `"soft_blocked"` lub `"hard_blocked"`).
- **Oba wywołania są idempotentne.** Wstrzymanie już wstrzymanego klienta jedynie odświeża wiadomość, powód i znacznik czasu; wznowienie aktywnego klienta nic nie zmienia.
- **Klient nie otrzymuje automatycznie wiadomości e-mail** — wiele agencji korzysta z white-labelingu, więc powiadomienie klienta pozostaje w Twojej gestii.
- **Twoje własne rozliczenia w DM Champ pozostają nienaruszone.** Wstrzymanie klienta wpływa tylko na Twoją relację z nim.
- **Asystenci AI również mogą to zrobić**: [serwer MCP](../integrations/connect-ai-clients.md) udostępnia te punkty końcowe jako narzędzia `pause_subaccount` i `unpause_subaccount`.

***

## Przyznawanie lub odejmowanie kredytów bezpośrednio

`POST /v1/subaccounts/credits`

Dodaje lub usuwa dokładną liczbę kredytów z salda jednego subkonta — to odpowiednik API dla ręcznej korekty kredytów w panelu nawigacyjnym. Jest to jednorazowa zmiana salda, odrębna od cyklicznych ustawień `monthly_credits`, `roll_over_to_next_month`, `rollover_cap_months` i `rollover_expiry_days` w [`PUT /v1/subaccounts/{subAccountUid}/limits`](#set-an-exact-team-member-limit-for-a-client).

Jest to jedyny punkt końcowy na tej stronie, który identyfikuje podkonto za pomocą **adresu e-mail**, a nie `sub_account_id`.

| Pole | Wymagane | Opis |
|---|---|---|
| `email` | Tak | Adres e-mail podkonta, tak jak istnieje w ramach Twojej agencji. |
| `amount` | Tak | Niezerowa liczba kredytów. Wartość dodatnia dodaje, ujemna odejmuje. |
| `description` | Nie | Wyświetlane przy korekcie w historii kredytów klienta. Domyślnie ustawia ogólny wpis "Skorygowano przez agencję za pomocą API". |

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/subaccounts/credits" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "client@example.com", "amount": 500, "description": "Q3 bonus credits" }'
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "email": "client@example.com",
    "previous_balance": 1200,
    "adjustment": 500,
    "new_balance": 1700
  }
}
```

Ujemna wartość `amount`, która spowodowałaby spadek salda poniżej zera, jest odrzucana z `400`, informując o dostępnym saldzie i kwocie, którą próbowano odliczyć. Jeśli klient korzysta z własnego rozliczenia Stripe (tryb odsprzedaży), dodana kwota liczy się również jako kredyty, które zakupił, więc przetrwa kolejne miesięczne resetowanie w taki sam sposób, jak prawdziwe doładowanie; w przypadku standardowego klienta z przydzielonymi środkami jest ona traktowana jako część jego cyklicznego limitu. Tak czy inaczej, są to dodatki jednorazowe, więc limit przeniesienia lub wygaśnięcie ustawione na koncie (lub jego planie) nigdy ich nie przycina — podlegają im tylko cykliczny limit i kredyty z planu.

***

## Odczytywanie konwersacji podkonta

`GET /v1/subaccounts/{subAccountUid}/chats` · `GET /v1/subaccounts/{subAccountUid}/chats/{contactId}/messages`

Pozwala na utworzenie widoku monitorowania lub wsparcia rozmów klienta bez konieczności logowania się na jego konto. Najpierw wyświetl listę kontaktów z podglądem ostatniej wiadomości, a następnie odczytaj pełną historię wiadomości wybranego kontaktu.

**Lista kontaktów**

```bash
curl "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/chats?apiKey=YOUR_AGENCY_API_KEY&pageSize=25"
```

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `pageSize` | Nie | Liczba kontaktów na stronę. Domyślnie 25, maksymalnie 50. |
| `lastActivityAt` | Nie | Kursor stronicowania — przekaż `lastActivityAt` z poprzedniej strony, aby kontynuować. |
| `searchQuery` | Nie | Filtruj według nazwy kontaktu lub numeru telefonu. |

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "contacts": [
      {
        "contactId": "contact456",
        "firstName": "Jamie",
        "lastName": "Lee",
        "phoneNumber": "+14155551234",
        "email": "jamie@example.com",
        "channel": "whatsapp",
        "lastActivityAt": "2026-08-30T14:22:00.000Z",
        "lastMessage": { "body": "Thanks, that fixed it!", "direction": "inbound", "timestamp": "2026-08-30T14:22:00.000Z" },
        "isBotActive": true,
        "markChatClosed": false
      }
    ],
    "subAccountName": "Client Co",
    "subAccountEmail": "client@example.com",
    "hasMore": true,
    "lastActivityAt": "2026-08-30T14:22:00.000Z"
  }
}
```

Kontakty są sortowane według najnowszej aktywności. Kontynuuj stronicowanie za pomocą `lastActivityAt`, dopóki `hasMore` ma wartość `true`.

**Odczyt wiadomości jednego kontaktu**

```bash
curl "https://api.dmchamp.com/v1/subaccounts/SUB_ACCOUNT_UID/chats/contact456/messages?apiKey=YOUR_AGENCY_API_KEY&pageSize=30"
```

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `pageSize` | Nie | Liczba wiadomości na stronę. Domyślnie 30, maksymalnie 100. |
| `beforeTimestamp` | Nie | Kursor stronicowania — pobierz wiadomości starsze niż ten znacznik czasu ISO. |

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "messages": [
      {
        "messageId": "msg789",
        "body": "Thanks, that fixed it!",
        "direction": "inbound",
        "timestamp": "2026-08-30T14:22:00.000Z",
        "status": "received",
        "channel": "whatsapp",
        "botReply": false,
        "mediaUrl": null,
        "mediaContentType": null,
        "name": "Jamie Lee",
        "role": null
      }
    ],
    "contactInfo": { "firstName": "Jamie", "lastName": "Lee", "phoneNumber": "+14155551234", "channel": "whatsapp" },
    "hasMore": false,
    "oldestTimestamp": "2026-08-30T14:22:00.000Z"
  }
}
```

Wiadomości są zwracane od najnowszych; przeglądaj historię wstecz za pomocą `beforeTimestamp`.

***

## Odczyt zużycia kredytów i kondycji kampanii w całym portfelu

`GET /v1/subaccounts/credit-usage` · `GET /v1/subaccounts/campaign-status`

Dwa podsumowania w stylu pulpitu nawigacyjnego dla wszystkich zarządzanych subkont, służące do tworzenia własnych raportów agencyjnych zamiast klikania w każdego klienta z osobna.

**Zużycie kredytów**

```bash
curl "https://api.dmchamp.com/v1/subaccounts/credit-usage?apiKey=YOUR_AGENCY_API_KEY&from=2026-08-01&to=2026-08-31"
```

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `from` / `to` | Tak | Zakres dat ISO. |
| `subAccountId` | Nie | Pomiń, aby uzyskać podsumowanie dla całej agencji, jeden wiersz na subkonto. Uwzględnij, aby przełączyć się w tryb szczegółowy: podsumowanie danego subkonta oraz jego surowe, stronicowane rekordy użycia. |
| `limitCount` | Nie | Tylko tryb szczegółowy. Domyślnie 500, maksymalnie 2000. |
| `startAfterTimestamp` | Nie | Tylko tryb szczegółowy — kursor stronicowania. |

```json
{
  "success": true,
  "data": {
    "subAccounts": [
      {
        "subAccountId": "abc123def456",
        "subAccountName": "Client Co",
        "subAccountEmail": "client@example.com",
        "totalCreditsUsed": 842,
        "totalCostUsd": 3.15,
        "byReason": { "AI reply": 620, "Chat summary": 80 },
        "topCampaigns": [{ "campaignName": "Inbound Leads", "creditsUsed": 500 }]
      }
    ],
    "totals": { "totalCreditsUsed": 842, "totalCostUsd": 3.15, "totalRecords": 214 },
    "dateRange": { "from": "2026-08-01", "to": "2026-08-31" },
    "hasMore": false,
    "lastTimestamp": null
  }
}
```

Przekaż `subAccountId`, a ta sama odpowiedź będzie zawierać również `records`: poszczególne opłaty z `amount`, `reason`, `campaignName`, `contactName` oraz `timestamp`. W przypadku klienta korzystającego z własnego klucza BYOK zamiast Twoich kredytów, dane o kosztach/tokenach są ukryte (`costsRedacted: true`) — to telemetria kosztów platformy, a nie dane do wyświetlenia dla resellera.

**Status kampanii**

```bash
curl "https://api.dmchamp.com/v1/subaccounts/campaign-status?apiKey=YOUR_AGENCY_API_KEY&pageSize=20"
```

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `pageSize` | Nie | Liczba subkont na stronę. Domyślnie 10, maksymalnie 50. |
| `lastDocumentId` | Nie | Kursor stronicowania. |
| `searchQuery` | Nie | Filtrowanie według nazwy subkonta lub adresu e-mail. |

```json
{
  "success": true,
  "data": {
    "totalSubAccounts": 34,
    "subAccountsWithIssues": 3,
    "totalLiveCampaigns": 51,
    "totalPausedCampaigns": 6,
    "subAccounts": [
      {
        "userId": "abc123def456",
        "email": "client@example.com",
        "displayName": "Jamie Lee",
        "businessName": "Client Co",
        "totalCampaigns": 2,
        "liveCampaigns": 1,
        "pausedCampaigns": 1,
        "hasIssues": true,
        "issueDetails": ["1 campaign paused"],
        "lastCampaignActivity": "2026-08-29T09:00:00.000Z"
      }
    ],
    "hasMore": true,
    "lastDocumentId": "abc123def456",
    "pageSize": 20
  }
}
```

Flaga `hasIssues` / `issueDetails` wskazuje subkonta, na które warto zwrócić uwagę — na przykład wstrzymaną kampanię lub taką, do której nie przypisano żadnego kanału. Użyj tego, aby zbudować pulpit nawigacyjny sprawdzający stan wszystkich klientów, zamiast otwierać profil każdego z nich, aby zauważyć zatrzymaną kampanię.

> Aby uzyskać dostęp do szeregów czasowych dotyczących wiadomości i aktywności kredytowej dla każdego klienta (dane gotowe do wykresów, a nie migawka w czasie), zobacz `GET /analytics/agency-rollup` w przewodniku po API analitycznym.

***

## Udostępnij konto klienta, które jest już skonfigurowane

`PUT /v1/snapshots/default` · `POST /v1/snapshots/{snapshotId}/apply`

[Migawka](snapshots.md) to szablon wielokrotnego użytku: jeden lub więcej agentów AI wraz z ich bazą wiedzy, narzędziami i mediami, przechwyconymi z Twojego własnego konta. Dwa punkty końcowe umieszczają go w Twoim procesie udostępniania.

**Automatycznie — każdy nowy klient otrzymuje go od razu.** Oznacz migawkę jako domyślną raz, a każde konto, które od tego momentu utworzysz, będzie miało ją zainstalowaną. Dotyczy to kont utworzonych przez `POST /v1/subaccounts`, kont utworzonych w panelu sterowania oraz kont utworzonych automatycznie, gdy klient płaci za pośrednictwem Twojego linku do kasy.

Najpierw znajdź identyfikator migawki:

```bash
curl "https://api.dmchamp.com/v1/snapshots" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY"
```

Następnie ustaw ją jako domyślną:

```bash
curl -X PUT "https://api.dmchamp.com/v1/snapshots/default" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "snapshot_id": "SNAPSHOT_ID" }'
```

To cała integracja. Wyślij `{"snapshot_id": null}`, aby ją wyłączyć. Możesz zrobić to samo z poziomu panelu sterowania, klikając gwiazdkę na stronie **Migawki**.

Aby odczytać, co jest obecnie oznaczone gwiazdką (na przykład przed podjęciem decyzji przez skrypt aprowizacyjny), `GET /v1/snapshots/default` zwraca `{ "success": true, "data": { "default_snapshot_id": "SNAPSHOT_ID" } }` — `null`, gdy nic nie jest oznaczone gwiazdką. `GET /v1/snapshots` (używane do znalezienia powyższego identyfikatora) zwraca to samo `default_snapshot_id` wraz z pełną tablicą `snapshots`, więc większość integracji wymaga tylko tego jednego wywołania. Pola pełnego obiektu migawki znajdują się w przewodniku [Migawki](snapshots.md).

**Na żądanie — zainstaluj na jednym koncie.** Przydatne podczas wdrażania istniejącego klienta lub późniejszego udostępniania klientowi drugiego szablonu.

```bash
curl -X POST "https://api.dmchamp.com/v1/snapshots/SNAPSHOT_ID/apply" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sub_account_id": "SUB_ACCOUNT_UID" }'
```

Pomiń `sub_account_id`, a zostanie ona zainstalowana na Twoim własnym koncie agencji. Podobnie jak w przypadku punktów końcowych `/subaccounts`, wskazują one konto docelowe w ścieżce lub treści, a nie za pomocą parametru `sub_account_id`.

Warto wiedzieć przed rozpoczęciem budowy:

- **Zainstalowani agenci są domyślnie wstrzymani.** Najpierw połącz kanały klienta, a następnie aktywuj agenta. Dotyczy to zarówno ścieżki automatycznej, jak i na żądanie.
- **Udostępnianie nigdy nie kończy się niepowodzeniem z powodu migawki.** Jeśli instalacja nie może zostać ukończona, konto klienta nadal jest tworzone i użyteczne — po prostu jest puste, a migawkę można zastosować później.
- **Kanały, kalendarze i połączenia OAuth nigdy nie są kopiowane.** Każde konto łączy własne. Narzędzia korzystające ze zwykłego klucza API działają natychmiast.
- **Dwukrotne zastosowanie tworzy drugą kopię.** Nic nie jest nadpisywane.

***

## Tworzenie szablonu za pomocą API

`POST /v1/snapshots` · agenci, funkcje niestandardowe i multimedia przez API

Powyższa sekcja opisuje dystrybucję migawki utworzonej w panelu nawigacyjnym. Część dotycząca tworzenia jest również dostępna, dzięki czemu cały cykl — jednorazowe przygotowanie głównej konfiguracji, jej przechwycenie i przekazanie każdemu klientowi — można zrealizować za pomocą kodu.

Elementy w kolejności, w jakiej używa ich skrypt udostępniający:

1. **Tworzenie funkcji niestandardowych.** `POST /v1/custom-functions` tworzy funkcję; `GET /v1/custom-functions` wyświetla listę posiadanych funkcji, a `GET`, `PUT` i `DELETE` w `/v1/custom-functions/{customFunctionId}` służą do odczytu, aktualizacji i usuwania. `POST /v1/custom-functions/test` wykonuje testowe uruchomienie definicji przed jej zapisaniem.
2. **Tworzenie i konfigurowanie agenta.** `POST /v1/agents` tworzy agenta, `PUT /v1/agents/{agentId}` aktualizuje go, a `PATCH /v1/agents/{agentId}/active` z `{ "active": false }` utrzymuje go w stanie wstrzymania podczas pracy (to samo wywołanie z `true` uruchamia go). `GET /v1/agents` wyświetla listę agentów.
3. **Nadawanie agentowi umiejętności.** `POST /v1/agents/{agentId}/custom-functions` z `{ "custom_function_id": "..." }` przypisuje funkcję do agenta; odpowiadające mu `DELETE /v1/agents/{agentId}/custom-functions/{customFunctionId}` odłącza ją.
4. **Wypełnianie biblioteki multimediów.** `POST /v1/agents/{agentId}/media-library` przesyła element (JSON z `base64Data`, `mimeType`, `title`, `description`); `GET` wyświetla listę elementów agenta, a `PATCH`/`DELETE` w `/{itemId}` aktualizują lub usuwają wybrany element.
5. **Przechwytywanie jako migawka.**

```bash
curl -X POST "https://api.dmchamp.com/v1/snapshots" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Master setup v1",
    "agent_ids": ["AGENT_ID"],
    "include_knowledge": true,
    "include_tools": true,
    "include_media": true
  }'
```

Stąd postępujemy zgodnie z poprzednią sekcją: oznaczamy ją jako domyślną, aby każdy nowy klient był z nią tworzony, lub stosujemy ją na żądanie. Zarządzanie odbywa się równolegle: `PATCH /v1/snapshots/{snapshotId}` z `{ "name": "..." }` zmienia nazwę, `DELETE /v1/snapshots/{snapshotId}` usuwa migawkę (i odznacza ją, jeśli była domyślna), a `GET /v1/snapshots/apply-targets` wyświetla listę wszystkich kont, na których można przeprowadzić instalację.

Punkty końcowe agenta, funkcji niestandardowych i multimediów obsługują `sub_account_id`, więc te same wywołania mogą również zarządzać agentem bezpośrednio na koncie klienta. Wywołania migawek zawsze działają na koncie agencji — szablon jest przechowywany u Ciebie. Pełne schematy żądań i odpowiedzi dla wszystkich tych elementów znajdują się w [Dokumentacji API](../api/reference.md).

***

## Zarządzaj swoimi poziomami cenowymi przez API

`GET /v1/agency/pricing-tiers` · `POST /v1/agency/pricing-tiers` · `PATCH /v1/agency/pricing-tiers/{tierIndex}` · `DELETE /v1/agency/pricing-tiers/{tierIndex}`

Plany, które sprzedajesz w **Trybie SaaS → Poziomy cenowe**, można odczytywać i zmieniać za pomocą kodu, dzięki czemu własny panel administracyjny lub skrypt udostępniający może dodać plan, dostosować cenę lub udostępnić link do płatności bez konieczności otwierania pulpitu nawigacyjnego. Uwierzytelnij się za pomocą klucza API agencji, tak jak w przypadku każdego innego wywołania na tej stronie; te punkty końcowe są na poziomie agencji, więc nie wymagają `sub_account_id`. Każdy zapis uruchamia tę samą walidację i tę samą synchronizację produktów i cen Stripe, co zapis w pulpicie nawigacyjnym, więc plan utworzony w ten sposób jest nieodróżnialny od planu skonfigurowanego ręcznie.

### Wyświetl listę swoich poziomów

```bash
curl "https://api.dmchamp.com/v1/agency/pricing-tiers" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY"
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "tiers": [
      {
        "tierIndex": 0,
        "credits": 1000,
        "price_cents": 2900,
        "currency": "usd",
        "label": "Starter",
        "billing_interval": "month",
        "trial_days": 14,
        "trial_credits": 250,
        "trial_card_required": false,
        "trial_hard_expiry": true,
        "stripe_price_id": "price_1PxAbC…",
        "stripe_product_id": "prod_QxAbC…",
        "checkout_url": "https://app.yourdomain.com/v1/checkout?id=YOUR_AGENCY_UID&tierIndex=0"
      }
    ],
    "count": 1,
    "max_tiers": 20
  }
}
```

Każdy poziom zwracany jest wraz ze swoim **`tierIndex`** — pozycją na liście planów, za pomocą której adresują go pozostałe trzy wywołania — oraz gotowym do udostępnienia **`checkout_url`**, tym samym linkiem, który udostępnia karta **Płatności**, wskazującym już na [domenę white label](white-labeling.md), na której sprzedawany jest dany plan.

### Dodaj poziom

Treść żądania to jeden obiekt poziomu; jest on dołączany na końcu listy.

```bash
curl -X POST "https://api.dmchamp.com/v1/agency/pricing-tiers" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Starter",
    "credits": 1000,
    "price_cents": 2900,
    "currency": "usd",
    "billing_interval": "month",
    "trial_days": 14,
    "trial_credits": 250,
    "trial_card_required": false,
    "trial_hard_expiry": true,
    "features": ["channels_3", "channel_whatsapp_web", "webhooks"]
  }'
```

Odpowiedź zawiera utworzony poziom, w tym `tierIndex`, na którym został umieszczony, oraz jego `checkout_url`.

### Edytuj poziom

Wyślij tylko te pola, które chcesz zmienić; wszystko inne w planie pozostanie bez zmian.

```bash
curl -X PATCH "https://api.dmchamp.com/v1/agency/pricing-tiers/0" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "price_cents": 3900, "trial_hard_expiry": true }'
```

Pole, którego API nie rozpoznaje, jest odrzucane, a nie ignorowane, a błąd wskazuje jego nazwę — dzięki temu literówka nigdy nie spowoduje cichego zapisania ustawienia, które wygląda na aktywne, ale nie działa. Zmiana ceny, liczby kredytów, waluty lub cyklu rozliczeniowego tworzy nową cenę w Stripe; klienci, którzy już zasubskrybowali, pozostają przy planie, na który się zapisali.

### Usuń poziom

```bash
curl -X DELETE "https://api.dmchamp.com/v1/agency/pricing-tiers/2" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY"
```

Ta sama zasada co w pulpicie nawigacyjnym: plan, który nadal ma aktywnych subskrybentów, nie może zostać usunięty. Żądanie zostanie odrzucone z informacją, ilu subskrybentów z niego korzysta — najpierw anuluj ich subskrypcje lub przenieś ich na inny plan. Pomyślne usunięcie zwraca pozostałe poziomy, już z przeliczoną numeracją.

### Pola poziomu

| Pole | Opis |
|---|---|
| `label` / `description` | Nazwa planu oraz opcjonalny wiersz wyświetlany na stronie płatności. |
| `credits` | Kredyty, które klient otrzymuje **miesięcznie** w planie miesięcznym lub rocznym oraz **na okres rozliczeniowy** w planie tygodniowym. |
| `price_cents` | Cena za okres rozliczeniowy w najmniejszej jednostce waluty (`2900` = 29,00 $). W planie rocznym jest to cena za cały rok. |
| `currency` | Kod ISO małymi literami — `usd`, `eur`, `gbp` i tak dalej. |
| `billing_interval` / `billing_interval_count` | `month` (domyślnie), `year` lub `week` z liczbą 1–52 dla „co N tygodni”. |
| `trial_days` | Długość bezpłatnego okresu próbnego, od 0 do 90. `0` (lub pominięcie) oznacza brak okresu próbnego. |
| `trial_credits` | Kredyty, z którymi klient rozpoczyna okres próbny. Domyślnie `credits` planu. |
| `trial_card_required` | `false` pozwala klientowi rozpocząć okres próbny bez podawania karty. Domyślnie `true`. |
| `trial_hard_expiry` | `true` zwraca niewykorzystane kredyty próbne do Twojej puli i blokuje konto klienta, gdy okres próbny kończy się bez aktualizacji planu. Domyślnie `false` — zobacz [Twarde wygaśnięcie po okresie próbnym](agency-accounts.md#step-3--set-up-pricing-tiers). |
| `rollover_cap_months` | Liczba miesięcy limitu, które klienci w tym planie mogą przenieść między odnowieniami — liczba od 0 do 120, dozwolone ułamki. `0` nie przenosi niczego; `null` (domyślnie) oznacza brak limitu. Zobacz [Ograniczanie tego, co przechodzi na kolejny okres](sub-accounts.md#capping-what-rolls-over). |
| `rollover_expiry_days` | Liczba dni, po których niewykorzystane kredyty są usuwane przy następnym odnowieniu — liczba całkowita od 1 do 3650. `null` (domyślnie) oznacza, że nigdy nie wygasają. |
| `features` / `feature_settings` | Co otrzymują klienci w tym planie — te same identyfikatory funkcji co w [Wybierz, które typy kanałów klient może połączyć](#choose-which-channel-types-a-client-can-connect). |
| `team_seats_limit` | Miejsca w zespole przyznawane przez plan: dokładna liczba, `0` dla braku, `-1` dla nieograniczonej liczby. |
| `white_label_config` | Na której z Twoich [domen white label](white-labeling.md#up-to-three-white-labels) sprzedawany jest plan. |

Pola okresu próbnego mają znaczenie tylko w planie, który go posiada: zapisz poziom z `trial_days: 0`, a zostaną one usunięte. Identyfikatory produktu i ceny w Stripe dla planu są zarządzane automatycznie i nie można ich ustawić ręcznie.

Trzy rzeczy, o których należy pamiętać:

- **Indeksy planów to pozycje, a nie stałe identyfikatory.** Usunięcie planu przesuwa wszystkie kolejne plany o jedną pozycję w górę, dlatego po każdej zmianie należy ponownie pobrać listę — oraz ponownie skopiować opublikowane linki do kasy, dokładnie tak samo, jak w przypadku usunięcia planu w panelu nawigacyjnym.
- **Tryb SaaS musi zostać skonfigurowany w pierwszej kolejności.** Te punkty końcowe wymagają konta agencji z funkcją white labeling oraz zapisanym kluczem Stripe; bez tego nie istnieje konto Stripe, na którym mogłyby znajdować się produkt i cena planu.
- **Limit wynosi dwadzieścia planów**, tak samo jak w panelu nawigacyjnym. Pole `max_tiers` w odpowiedzi listy informuje o aktualnym limicie.

Pełne schematy żądań i odpowiedzi znajdują się w [Dokumentacji API](../api/reference.md), w sekcji **Agencja**.

***

## Ustawianie ceny za kredyt przez API

`GET /v1/agency/credit-price` · `PATCH /v1/agency/credit-price`

Cenę, którą klienci płacą za doładowania ad-hoc (**Tryb SaaS → Cennik za kredyt**), można również odczytywać i zmieniać z poziomu kodu. Funkcja ta została stworzona z myślą o sytuacjach, w których cena musi zmieniać się automatycznie: agencja sprzedająca kredyty w jednej walucie, a rozliczająca w innej, może zlecić zaplanowanemu zadaniu aktualizację ceny wraz ze zmianą kursu walut, zamiast ręcznej edycji co tydzień.

### Odczyt bieżącej ceny

```bash
curl "https://api.dmchamp.com/v1/agency/credit-price" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY"
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "price_per_credit_cents": 125,
    "price_per_credit_currency": "brl",
    "note": "USD 0.25 per credit at our reference rate",
    "minimum_cents": 60
  }
}
```

`minimum_cents` to najniższa cena dozwolona przez platformę w danej walucie, dzięki czemu zadanie może sprawdzić nową cenę przed jej wysłaniem. Wszystkie trzy wartości mają postać `null`, dopóki cena nie zostanie ustawiona.

### Zmiana ceny

```bash
curl -X PATCH "https://api.dmchamp.com/v1/agency/credit-price" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "price_per_credit_cents": 130, "currency": "brl" }'
```

Wyślij tylko te dane, które chcesz zmienić. `price_per_credit_cents` to cena w najmniejszej jednostce walutowej (`130` = 1,30 R$); `currency` to kod ISO pisany małymi literami; `note` to opcjonalny wiersz o długości do 200 znaków, wyświetlany klientom bezpośrednio pod ceną za kredyt na ich stronie rozliczeniowej — przydatny do podania ceny referencyjnej w innej walucie, np. *"0,25 USD za kredyt według naszego kursu referencyjnego"*. Wyślij `"note": ""`, aby go usunąć. Odpowiedź ma taką samą strukturę jak odczyt powyżej, więc zadanie może porównać wartości i pominąć zapis, jeśli nic się nie zmieniło.

Obowiązują te same zasady co w panelu nawigacyjnym: cena nie może być niższa niż minimum platformy dla danej waluty, a konto musi posiadać funkcję white labeling. W przeciwieństwie do punktów końcowych poziomów cenowych, do odczytu lub zmiany tej wartości nie jest wymagany klucz Stripe.

### Nadawanie zadaniu klucza o ograniczonych uprawnieniach

Umieszczanie pełnego klucza agencji w harmonogramie to szerszy dostęp, niż jest potrzebny do aktualizacji ceny. Zamiast tego utwórz **klucz o ograniczonym zakresie** (scoped key) ograniczony do obszaru **Agency Credit Price**: taki klucz może odczytywać i zmieniać cenę za kredyt i nic więcej — nie ma dostępu do subkont, planów, kredytów ani połączenia ze Stripe.

```bash
curl -X POST "https://api.dmchamp.com/v1/api-keys" \
  -H "X-API-Key: YOUR_AGENCY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "FX price updater", "scopes": { "read_only": false, "tags": ["Agency Credit Price"] } }'
```

Odpowiedź zawiera nowy klucz w `api_key` tylko **raz** — nigdy więcej nie zostanie wyświetlony, więc zapisz go od razu. Ustaw `"read_only": true` dla klucza, który musi tylko odczytywać cenę, i dodaj `"expires_at"` (datę w formacie ISO), jeśli chcesz, aby przestał działać automatycznie. Tylko klucz właściciela konta może tworzyć klucze o ograniczonym zakresie; możesz je wyświetlić lub unieważnić za pomocą `GET /v1/api-keys` i `DELETE /v1/api-keys/{id}`.

***

## Pozwól subkontu odczytać Twoje ceny

`GET /v1/subaccounts/agency-pricing`

Każdy inny punkt końcowy na tej stronie jest wywoływany za pomocą **Twojego klucza agencji**, opcjonalnie wskazując klienta poprzez `sub_account_id`. Ten działa odwrotnie: jest wywoływany za pomocą **własnego klucza API subkonta**, bez `sub_account_id`, dzięki czemu własna strona doładowań klienta (lub zbudowana dla niego integracja) może wyświetlać Twoje stawki bez konieczności wglądu w Twoje konto agencji.

```bash
curl "https://api.dmchamp.com/v1/subaccounts/agency-pricing" \
  -H "X-API-Key: THE_SUB_ACCOUNTS_OWN_API_KEY"
```

**Odpowiedź**

```json
{
  "success": true,
  "data": {
    "tiers": [{ "credits": 1000, "price_cents": 2900, "currency": "usd" }],
    "price_per_credit_cents": 125,
    "price_per_credit_currency": "brl",
    "price_per_credit_note": "USD 0.25 per credit at our reference rate",
    "agency_display_name": "Client Co's Growth Partner"
  }
}
```

To dokładnie odzwierciedla to, co zwracają dla Ciebie jako agencji [`GET /v1/agency/pricing-tiers`](#list-your-tiers) oraz [`GET /v1/agency/credit-price`](#read-the-current-price), z wyłączeniem danych, których klient nie musi widzieć (identyfikatory Stripe, `max_tiers` itp.). Działa to tylko w przypadku konta, które jest faktycznie subkontem z powiązaną agencją — wywołanie tego z poziomu własnego konta agencji spowoduje błąd uprawnień.

***

## Rzeczy, o których warto pamiętać

- **Użyj swojego klucza agencji.** Uwierzytelniaj każde wywołanie kluczem API swojego konta agencji — nie subkonta. Parametr `sub_account_id` przekierowuje działanie.
- **Kredyty pochodzą z subkonta.** Zakupy i opłaty cykliczne obciążają saldo kredytowe docelowego subkonta, a nie Twoje.
- **`404` oznacza „nie Twoje subkonto”.** Sprawdź dokładnie identyfikator i upewnij się, że jest to konto, którym zarządzasz.
- **Parametr jest opcjonalny wszędzie tam, gdzie jest akceptowany.** Pomiń go, a ten sam punkt końcowy zadziała na Twoim koncie agencji, dzięki czemu możesz użyć jednej integracji do obu celów.

***

## Powiązane

- [Dostęp do API](../integrations/api-access.md) — uwierzytelnianie, podstawowy adres URL, błędy, limity szybkości.
- [Subkonta](sub-accounts.md) — lista i zarządzanie kontami, które możesz obsługiwać.
- [Automatyczne doładowanie subkonta](sub-account-auto-recharge.md) — przyznawanie kredytów subkontu za pomocą webhooka + API.
- [API kampanii](../api/campaigns.md) — tworzenie, aktualizowanie i kopiowanie kampanii, w tym pełna dokumentacja pól.
- [API połączeń kanałów](../api/channels.md) — łączenie kanałów klienta i kierowanie ich do kampanii.
- Przewodnik po API analitycznym (w sekcji API) — podsumowanie subkont agencji i wszystkie inne punkty końcowe raportowania.
- [Migawki](snapshots.md) — co rejestruje migawka i jak ją zbudować w panelu nawigacyjnym.
