
# API voor bureaus

Als bureau kun je dezelfde REST API gebruiken als je klanten, maar individuele verzoeken richten op een van je beheerde **sub-accounts** in plaats van op je eigen account. Hiermee kun je tools bouwen die een klant volledig onboarden — hun campagnes aanmaken, hun AI trainen op een kennisbank, hun contacten importeren, hun berichtenkanalen verbinden en telefoonnummers kopen — allemaal zonder handmatig in elk sub-account in te loggen.

Deze pagina behandelt alleen het bureau-specifieke gedrag: hoe je namens een sub-account handelt met de `sub_account_id` parameter. Voor de basis (het genereren van een sleutel, authenticatie, basis-URL, foutopmaak, tarieflimieten), begin met de [API-toegang](../integrations/api-access.md) gids. Alles wat daar staat, is hier ook van toepassing — je authenticeert met de API-sleutel van **jouw bureau-account**.

::: note
**Let op:** Deze pagina is technisch. Als je geen ontwikkelaar bent, deel deze dan met de persoon die je integratie bouwt.
:::


***

## Hoe "handelen namens" werkt

Standaard handelt elk API-verzoek namens het account dat de API-sleutel bezit — jouw bureau-account. Om in plaats daarvan namens een beheerd klantaccount te handelen, voeg je de optionele `sub_account_id` parameter toe aan het verzoek, ingesteld op het account-id van die klant.

- **Laat `sub_account_id` weg** → het verzoek wordt uitgevoerd namens je eigen bureau-account.
- **Voeg `sub_account_id` toe** → het verzoek wordt uitgevoerd namens dat sub-account, maar pas nadat het platform heeft bevestigd dat het sub-account daadwerkelijk van jou is.

Je authenticeert altijd met de API-sleutel van je **bureau-account**. Je hebt nooit de eigen sleutel van het sub-account nodig en je beheert nooit de inloggegevens van het sub-account.

### Waar je het plaatst

- **GET / DELETE-eindpunten** → geef het door als queryparameter: `?sub_account_id=THE_SUB_ACCOUNT_ID` (naast je `apiKey`, als je via query authenticeert).
- **POST / PUT / PATCH-eindpunten** → voeg het toe aan de JSON-requestbody als `"sub_account_id": "THE_SUB_ACCOUNT_ID"`.
- **AI-assistenten** → niets te configureren. De [MCP-server](../integrations/connect-ai-clients.md) voert dezelfde instelling uit op zijn leestools, dus één verbinding met je agency-sleutel kan rapporteren over elke klant: noem de klant simpelweg in je verzoek ("hoeveel contacten heeft Bella's Bistro?"). Schrijfacties zijn ook beschikbaar: elk eindpunt dat `sub_account_id` accepteert, wordt blootgesteld als een tool, zodat je namens een klant kunt aanmaken, wijzigen en verzenden via dezelfde verbinding.

### Het id van een sub-account vinden

De `sub_account_id` is het unieke id van het klantaccount. U kunt de lijst met uw subaccounts en hun id's verkrijgen via de **SubAccounts** API-endpoints (zie de [Sub-Accounts](sub-accounts.md) handleiding) of via de pagina **Sub Accounts** in de zijbalk.

***

## Eigendom wordt altijd gecontroleerd

Wanneer je een `sub_account_id` doorgeeft, controleert het platform of het account een echt sub-account is **en** of het bij jouw bureau hoort. Pas dan wordt het verzoek verwerkt.

Als het id onbekend is, geen sub-account is of bij een ander bureau hoort, mislukt het verzoek met een **`404`** respons:

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

> **Waarom 404 en geen 403?** Een "forbidden"-antwoord zou een buitenstaander vertellen dat het id bestaat, maar niet van hen is. Het retourneren van dezelfde `404` voor "bestaat niet" en "is niet van jou" betekent dat het eindpunt niet kan worden gebruikt om te ontdekken welke account-id's bij andere bureaus horen. Behandel een `404` hier als "dit is geen sub-account dat jij beheert."

***

## Waar `sub_account_id` wordt ondersteund

`sub_account_id` wordt geaccepteerd op vrijwel elk **resource**-endpoint — elke aanroep die gegevens van een account aanmaakt, leest, bijwerkt of verwijdert. In de praktijk kun je de volledige configuratie van een sub-account inrichten en uitvoeren met je bureausleutel:

- **AI-configuratie** — campagnes, agents, veelgestelde vragen, bronnen voor de kennisbank (website-crawl **en** document-upload), kennisbankgroepen, uitzendingen, aangepaste functies, MCP-servers
- **Contacten & CRM** — contacten (inclusief import), lijsten, tags, taken, deals, afspraken, evenementen
- **Kanalen & nummers** — WhatsApp / WhatsApp Web / Telegram / Instagram & Messenger / LINE verbinden, telefoonnummers zoeken / kopen / beheren, WhatsApp-sjablonen, kanaalroutering
- **Berichten & inhoud** — berichten verzenden, chatsessies, chat-exports, dagelijkse samenvattingen
- **Instellingen & integraties** — webhooks, configuratie van chat-widgets, white-label-configuratie, BYOK SMS en andere accountinstellingen, analyses

Bij elk van deze is de parameter **optioneel** — laat je deze weg, dan werkt de aanroep op je eigen bureau-account, zodat één integratie voor beide dient. Credits en verbruik komen altijd van het account waarop je je richt: kosten voor de campagnes, berichten, tags en nummers van een sub-account worden in rekening gebracht op **het saldo van het sub-account**.

### Waar het NIET van toepassing is

Een paar endpoints zijn op bureauniveau of zelfgericht en negeren `sub_account_id`:

- **Het beheren van de sub-accounts zelf** — de SubAccounts-endpoints (aanmaken / weergeven / bijwerken van een sub-account) en het BYOK-bestedingslimiet-endpoint benoemen het sub-account al in hun eigen URL-pad. De [prijs- en beleids-endpoints](#set-per-client-ai-pricing-and-policy) en [chat-monitoring-endpoints](#read-a-sub-accounts-conversations) volgen hetzelfde patroon.
- **Een Agent kopiëren tussen accounts** — `POST /v1/subaccounts/agents/copy` benoemt beide accounts zelf, waarbij de bestemming wordt opgegeven als `targetUserId`. Zie het [uitgewerkte voorbeeld](#worked-example-ship-a-template-agent-into-every-new-client) hieronder. (De oudere `POST /v1/subaccounts/campaigns/copy` werkt op dezelfde manier, maar is verouderd samen met de rest van de [Campaigns API](../api/campaigns.md).)
- **Credits aanpassen en de twee agency-brede overzichten** — [`POST /v1/subaccounts/credits`](#grant-or-deduct-credits-directly) identificeert het sub-account in plaats daarvan via `email`; [`GET /v1/subaccounts/credit-usage`](#read-credit-usage-and-campaign-health-across-your-book) en `GET /v1/subaccounts/campaign-status` rapporteren over elk sub-account tegelijk, dus er is geen enkel account om op te richten.
- **Het account van uw eigen agency** — API-sleutelbeheer, rapportage over agency-gebruik, teambeheer en uw [prijscategorieën](#manage-your-pricing-tiers-over-the-api) hebben altijd betrekking op uw agency-account.
- **Webhooks voor inkomende berichten** — endpoints waar externe systemen *naar* posten, zijn gekoppeld aan het account waarvan de inloggegevens ze hebben geconfigureerd, dus er is niets om om te leiden.

> De altijd actuele, machineleesbare lijst van welke parameters elk endpoint accepteert, vindt u in de API-referentie van uw dashboard (**Instellingen → Integraties → API-sleutel**) en de OpenAPI-specificatie op `GET /v1/docs/openapi.yaml`. We brengen vaak API-wijzigingen door — beschouw die als de bron van waarheid.

::: master-only
<figure><img src="../.gitbook/assets/v2-api-access-key-section.png" alt="Pagina met API-sleutelinstellingen met gemaskeerde sleutel en de knop Opnieuw genereren"><figcaption><p>Instellingen → Integraties → API-sleutel — de sleutel van uw bureau staat hier, naast de link naar de volledige API-referentie.</p></figcaption></figure>
:::

***

## Uitgewerkt voorbeeld: Instagram & Messenger verbinden voor een sub-account

Het verbinden van Instagram & Messenger is een browsergebaseerd proces. Je start dit met de API, geeft de geretourneerde toestemmings-URL aan de klant (of opent deze voor hen), wacht tot ze autoriseren in hun browser en kiest vervolgens welke pagina moet worden verbonden — dit alles terwijl je hun sub-account target met `sub_account_id`.

### Stap 1 — De verbinding starten

Roep het verbindings-eindpunt aan met de `sub_account_id` van de klant in de body. Hier worden geen inloggegevens verzonden; het platform retourneert een toestemmings-URL die de klant in een browser moet openen, plus een eenmalig correlatietoken.

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

**Antwoord:**

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

Stuur de klant naar `oauth_url` in een browser om te autoriseren. De `state_token` correleert deze poging en is een kortstondig geheim — log deze niet. De poging verloopt op `expires_at`; als deze verloopt, begin dan opnieuw.

### Stap 2 — Pollen totdat de pagina's zijn geladen

Nadat de client toestemming heeft gegeven, pols je het status-eindpunt (met dezelfde `sub_account_id`, ditmaal als queryparameter) totdat de verbindbare pagina's verschijnen.

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

**Antwoord:**

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

Het `status`-veld doorloopt `pending` → `token_received` → `pages_loaded` → `connected`. Wacht op `pages_loaded` voordat u een pagina selecteert. Er kunnen ook twee terminale foutstatussen verschijnen in plaats van dat het proces vordert: `failed` en `expired` (de client heeft toestemming geweigerd, of het venster van ~30 minuten van het state-token is verstreken) — een `reason`-veld is inbegrepen wanneer een van beide optreedt. Stop met pollen en begin opnieuw bij stap 1 als u er een ziet; wacht niet eeuwig op `pending`. Pagina-toegangstokens worden nooit geretourneerd.

### Stap 3 — Selecteer de pagina om te verbinden

Kies een van de pagina-id's uit Stap 2 en selecteer deze. Het selecteren van een pagina verbindt zowel Instagram als Messenger voor die pagina. Voeg `sub_account_id` opnieuw toe aan de body.

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

**Antwoord:**

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

Dat is alles — Instagram en Messenger zijn nu verbonden met het sub-account van de client. Je hebt alleen de `page_id` verstrekt; de onderliggende inloggegevens worden op de server opgelost en gaan nooit door je integratie heen.

***

## Uitgewerkt voorbeeld: een nummer kopen voor een sub-account

Het kopen van een nummer werkt op dezelfde manier: zoek met `sub_account_id` in de query en koop het vervolgens met diezelfde waarde in de body. Credits worden afgeschreven van het saldo van **het sub-account** en het nummer wordt ingericht op het sub-account.

**Zoeken (cURL):**

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

**Kopen (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();
```

**Aankoop (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()
```

**Antwoord:**

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

Het nummer wordt ingericht in de `PURCHASED`-status en de registratie van de WhatsApp-afzender gaat op de achtergrond door. Pol `GET /v1/phone-numbers/{phoneNumber}/status?sub_account_id=abc123def456` totdat de status `ONLINE` bereikt voordat u gaat verzenden.

***

## Uitgewerkt voorbeeld: een sjabloon-Agent naar elke nieuwe klant sturen

Het gebruikelijke agency-patroon is om één master-Agent op uw agency-account te houden, afgesteld zoals u wilt dat elke klant begint, en bij het inrichten een kopie ervan in elk nieuw sub-account te plaatsen. Dat zijn drie aanroepen en daarna hoeft er niets meer herhaald te worden: de kopie behoudt zijn instellingen totdat u ze wijzigt.

### Stap 1 — De Agent kopiëren

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

Het antwoord bevat het id van de nieuwe Agent op `data.agent_id`. De veelgestelde vragen, kennisbank en mediabibliotheek worden meegekopieerd; de WhatsApp-sjablonen, gekoppelde sociale berichten en contacten van het bronaccount worden bewust niet meegekopieerd. Volledige veldenlijst in de [AI Agents API](../api/agents.md#copy-an-agent-into-a-sub-account-agencies).

Let op: dit endpoint gebruikt `targetUserId` in plaats van `sub_account_id` — het benoemt beide accounts zelf. De twee aanroepen hieronder gebruiken de normale `sub_account_id`-parameter.

### Stap 2 — Inschakelen

De kopie komt altijd gepauzeerd aan, dus hij kan niemand berichten sturen totdat u dat aangeeft. Dit is ook het moment om de AI-laag vast te leggen waarop u de klant wilt hebben; deze blijft daar staan, dus het is niet nodig om deze volgens een schema opnieuw toe te passen.

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

Om te voorkomen dat de klant de laag daarna wijzigt, kunt u [de toegestane lagen vergrendelen](#set-per-client-ai-pricing-and-policy) op het sub-account in plaats van de waarde opnieuw te verzenden.

### Stap 3 — De kanalen van de klant hieraan koppelen

De kopie komt ook zonder routering aan, dus niets bereikt de Agent totdat u deze instelt als de beantwoorder op de kanalen die de klant heeft verbonden. Eén aanroep per kanaal:

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

Vanaf hier wordt een eerste bericht van een onbekend contact op dat kanaal automatisch opgepikt door de gekopieerde Agent. Zie [Een kanaal naar een Agent wijzen](../api/entry-points.md#point-a-channel-at-an-agent) voor de andere kanalen en routering per nummer.

> **Stel de tijdzone van de klant in wanneer u het subaccount aanmaakt.** Geef `time_zone_id` door op `POST /v1/subaccounts`. De actieve uren van een campagne worden geëvalueerd in de eigen tijdzone van het subaccount, dus bij een klant die zonder tijdzone is aangemaakt, wordt het schema gelezen op basis van UTC — wat stilletjes verschuift wanneer de assistent mag antwoorden.

***

## Sla de setup wizard over voor een klant die u zelf configureert

`POST /v1/subaccounts`

Standaard wordt de eigenaar van een nieuw sub-account de eerste keer dat deze inlogt door de begeleide Setup Wizard geleid. Voor 'done-for-you'-klanten — waarbij u de campagne opbouwt en de kanalen koppelt voordat de klant ooit inlogt — geeft u `guided_onboarding: false` door wanneer u het account aanmaakt. Zij komen in plaats daarvan op het dashboard terecht en het item **Setup Wizard** is verborgen in hun zijbalk.

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

Laat het veld weg (of stuur `true`) en de wizard gedraagt zich precies zoals altijd, dus bestaande integraties hoeven niet te worden gewijzigd. Om een klant later de wizard terug te geven, laat u het `guided_onboarding`-item opnieuw zien met `PUT /v1/subaccounts/{subAccountUid}/menu-visibility` (hieronder) — menu-zichtbaarheid bepaalt of de wizard bereikbaar is, `guided_onboarding` regelt alleen de omleiding bij de eerste aanmelding.

***

## Taken, dagelijkse samenvattingen of de mediabibliotheek uitschakelen voor een klant

`POST /v1/subaccounts`

Deze drie staan standaard aan voor elke nieuwe klant, tenzij je anders aangeeft, en ze gedragen zich anders dan alle andere functies in deze handleiding: ze zijn **opt-out**, niet opt-in. Ze weglaten uit `features` is op zichzelf niet genoeg, omdat de `features`-lijst van een oudere integratie ze simpelweg nooit vermeldde — we kunnen niet zien of "het bureau dit heeft uitgeschakeld" of dat "deze lijst is geschreven voordat de optie bestond".

Geef het dus expliciet aan met `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
    }
  }'
```

Elke sleutel is optioneel; alles wat je weglaat, blijft aanstaan. Met `tasks: false` stopt de AI met het aanmaken van taken voor die klant en worden er geen "Nieuwe taak aangemaakt"-e-mails verzonden; met `daily_summaries: false` wordt de dagelijkse samenvatting nooit gegenereerd of gemaild.

`feature_settings` is het enige dat deze drie uitschakelt bij aanmaak. Ze weglaten uit `features` doet op zichzelf niets, ongeacht hoe de rest van je lijst eruitziet — dat is bewust gedaan, zodat een oudere integratie niet stilletjes alle drie verliest.

Om dit achteraf te wijzigen, stuur je de volledige `features`-lijst naar `PUT /v1/subaccounts/{subAccountUid}/features` — daar zorgt aanwezigheid in de lijst ervoor dat een functie wordt ingeschakeld en afwezigheid dat deze wordt uitgeschakeld.

***

## Automatisch inloggen van uw klanten in hun sub-account (SSO)

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

Eén aanroep met uw agency API-sleutel levert een URL op die direct klaar is voor gebruik en de klant rechtstreeks in zijn eigen sub-account inlogt — geen inlogscherm, geen wachtwoordstap, niets om zelf te bouwen. Open het in een nieuw tabblad, via een redirect of in een iframe binnen uw eigen product.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `redirect` | Nee | In-app pagina waar de client moet eindigen, bijv. `"/chats"` of `"/agents"`. Wordt geretourneerd als `deep_link_url` in het antwoord. |
| `app_base_url` | Nee | Dashboard-host voor de link. Standaard ingesteld op uw white-label app-domein (of het platformdomein als u er geen heeft). Moet `https` zijn. |

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

**Antwoord**

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

Hoe u dit goed gebruikt:

- **Eén stap.** Het openen van `url` logt de klant in en brengt hen direct naar de `redirect` pagina van het dashboard — geen inlogscherm, geen tussenliggende pagina. `deep_link_url` benoemt dezelfde bestemming, voor integrators die er de voorkeur aan geven om na het inloggen expliciet naar een frame te navigeren; zodra de sessie bestaat, werkt elk dashboardpad in die browsercontext.
- **Op aanvraag aanmaken, direct openen.** De link bevat inloggegevens en verloopt na ongeveer een uur. Vraag deze aan de serverzijde aan op het moment dat de klant klikt, en sla deze nooit op en verstuur deze nooit per e-mail.
- Het inlogtoken reist mee in het URL-fragment (`#…`), dat browsers nooit naar servers sturen, en het wordt uit de adresbalk verwijderd zodra het is verbruikt.
- **Alleen uw eigen subaccounts.** Het eindpunt weigert elk account dat niet van uw bureau is.
- Een verlopen link toont een duidelijke foutmelding met een pad om het opnieuw te proberen — maak een nieuwe aan.

***

## Navigatie-items verbergen op een sub-account

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

Bepaalt welke zijbalk- en instellingsitems een sub-account ziet — handig wanneer u het dashboard insluit en alleen de onderdelen wilt tonen die uw product nog niet dekt. Alles wat niet wordt vermeld, blijft zichtbaar; stuur `null` als de volledige `menuVisibility` waarde om alles weer zichtbaar te maken. Het verbergen van een item verbergt het menu-item — combineer dit met de functies die u aan het sub-account verleent voor strikte toegangsbepaling.

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

**Antwoord**

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

`side_nav` accepteert deze 13 sleutels, die overeenkomen met de namen van de zijbalkitems: `Dashboard`, `DailySummaries`, `Chats`, `Contacts`, `Deals`, `Tasks`, `Automations`, `Campaigns`, `Appointments`, `Settings`, `Help`, `CreditsCounter` (het kredietsaldo dat in de zijbalk wordt getoond) en `guided_onboarding` (de configuratiewizard). Drie verdere sleutels — `AiInsights`, `Sub Accounts` en `Agency Reselling` — worden geaccepteerd maar doen niets: ze waren alleen van toepassing op het stopgezette klassieke dashboard, dus het instellen ervan heeft geen effect op uw subaccounts. Ontbrekende sleutels betekenen zichtbaar; wanneer u zelf inlogt op het subaccount, worden verborgen items tijdelijk getoond zodat u de instellingen altijd weer kunt wijzigen.

Een pagina verbergen in het menu geeft nooit toegang tot de pagina. `Automations` vereist dat de `automations`-functie is verleend op het subaccount — zet de sleutel op `true` zonder deze functie en de pagina zal nog steeds niet verschijnen. `Tasks` en `DailySummaries` werken andersom: ze staan aan voor elke klant tenzij je ze uitschakelt (zie [Taken, dagelijkse samenvattingen of de mediabibliotheek uitschakelen voor een klant](#turn-tasks-daily-summaries-or-the-media-library-off-for-a-client)).

***

## Kies welke kanaaltypen een klant kan verbinden

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

De **Kanaaltypen**-schakelaars die je ziet op een abonnementsniveau zijn gewone functie-ID's, dus je kunt ze per klant instellen via de API in plaats van via het dashboard. Dit is een van de eindpunten die het sub-account in zijn eigen URL benoemt, dus het vereist geen `sub_account_id`.

| Functie-ID | Kanaal |
|---|---|
| `channel_chat_widget` | Website Chat Widget |
| `channel_whatsapp_api` | WhatsApp Business API |
| `channel_whatsapp_web` | WhatsApp Web (QR-gekoppeld nummer) |
| `channel_instagram` | Instagram |
| `channel_messenger` | Facebook Messenger |
| `channel_telegram` | Telegram |
| `channel_line` | LINE |
| `channel_viber` | Viber |
| `channel_email` | E-mail mailbox |
| `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"
    ]
  }'
```

Drie dingen om goed te krijgen:

- **De aanroep vervangt de volledige functielijst.** Stuur elke functie die de klant moet behouden, niet alleen degene die je wijzigt. Dezelfde ID's werken als `features` op `POST /v1/subaccounts` wanneer je het account aanmaakt.
- **Kanaaltypen en kanaalaantal zijn afzonderlijke beperkingen, en beide zijn van toepassing.** `channels_1` / `channels_3` / `channels_unlimited` bepalen *hoeveel* verbindingen; de `channel_*` ID's bepalen *welke typen*. Het bovenstaande voorbeeld betekent "maximaal 3 verbindingen, en alleen Chat Widget, WhatsApp Web of Instagram".
- **Helemaal geen `channel_*` ID's sturen betekent geen kanaalbeperking.** Dat is het oorspronkelijke gedrag, en daarom werden bestaande klanten niet beïnvloed toen dit werd uitgebracht. Stuur er een of meer en al het andere wordt als vergrendeld weergegeven op de Kanalen-pagina van de klant met een upgrade-notitie in plaats van een Verbind-knop. Kanalen die de klant al heeft verbonden, blijven werken.

> Het instellen van de kanaallijst op een **abonnementsniveau**, zodat elke klant die dat niveau koopt het overneemt, gebeurt in het dashboard onder je agency-abonnementinstellingen. Dit eindpunt stelt het in op één specifiek sub-account.

***

## Stel een exact limiet voor teamleden in voor een klant

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

De `team_seats_*`-functies bieden alleen vooraf ingestelde stappen (3 / 5 / 10 / onbeperkt). Om een klant een **exact** aantal teamzetels te geven — 2, 7, 15, wat dan ook — stel in plaats daarvan `usage_limits.team_seats_limit` in. Dit heeft voorrang op de voorinstellingen en het platform dwingt dit af bij elke uitnodiging, directe toevoeging en acceptatie van een uitnodiging: zodra de limiet is bereikt, worden verdere uitnodigingen aan de serverzijde geweigerd.

- Een positief geheel getal is het exacte maximum.
- `0` betekent dat teamleden **niet zijn inbegrepen** — de klant kan niemand uitnodigen.
- `-1` betekent onbeperkt.
- `null` wist de aangepaste limiet en valt terug op welke `team_seats_*`-voorinstelling dan ook in de functielijst staat.

Het verlagen van de limiet verwijdert nooit bestaande teamleden; het stopt alleen het toevoegen van nieuwe leden.

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

Je kunt dit ook instellen bij het aanmaken: `POST /v1/subaccounts` accepteert `usage_limits.team_seats_limit` met dezelfde semantiek. Om de huidige waarde te lezen, haal je het sub-account op met `GET /v1/subaccounts?email=...` en kijk je naar `usage_limits.team_seats_limit` (afwezig/`null` = de voorinstellingen bepalen het). Hetzelfde eindpunt werkt ook `credits`, `monthly_credits`, `roll_over_to_next_month`, `rollover_cap_months`, `rollover_expiry_days` en `byok_monthly_limit_usd` bij — stuur alleen de sleutels die je wilt wijzigen.

**Hoe dit samenwerkt met de stoellimieten van SaaS-abonnementen.** Uw SaaS-abonnementen kunnen hun eigen stoeltoewijzing hebben (in te stellen in de abonnementeneditor — zie [Teamstoelen in een abonnement](agency-accounts.md#step-3--set-up-pricing-tiers)), die automatisch wordt toegepast wanneer een klant een abonnement afsluit. Een limiet die u via dit eindpunt instelt, telt als een **handmatige** toekenning: bij het kopen van een abonnement wordt deze vervangen door de eigen stoeltoewijzing van het abonnement (die aankoop is een expliciete keuze voor een abonnement), maar onbeheerde maandelijkse **verlengingen overschrijven nooit een handmatige limiet** — een eenmalige uitzondering die u aan een klant verleent, blijft dus behouden gedurende de factureringscyclus. Het wissen van de handmatige limiet met `null` geeft het veld bij de volgende verlenging weer terug aan het abonnement.

**Beperk wat een klant meeneemt tussen verlengingen.** Twee extra `usage_limits` sleutels staan naast `roll_over_to_next_month`. Beide worden ook geaccepteerd door `POST /v1/subaccounts` bij het aanmaken, en `null` wist beide.

| Sleutel | Wat het doet |
|---|---|
| `rollover_cap_months` | Aantal maanden aan tegoed dat de klant mag behouden. Een getal van 0 tot 120, breuken toegestaan (`0.5` = een halve maand). Bij elke verlenging wordt het ongebruikte saldo ingekort tot maximaal dit aantal keer het tegoed dat die verlenging toekent, voordat de nieuwe tegoeden worden toegevoegd; `0` neemt niets mee. |
| `rollover_expiry_days` | Een geheel aantal dagen, 1 tot 3650. Tegoeden die zo lang ongebruikt blijven, vervallen bij de eerste verlenging nadat ze die leeftijd bereiken. Uitgaven worden altijd eerst van de oudste tegoeden afgetrokken, dus een klant die elke maand zijn tegoed opmaakt, verliest nooit iets. |

Indien niet ingesteld, vallen beide terug op het abonnement van de klant; een waarde die hier wordt verzonden, gaat voor op die van het abonnement. Alleen terugkerende tegoeden (het maandelijkse tegoed en abonnementstegoeden) zijn hieraan onderhevig: opwaarderingen, automatische opwaarderingen en eenmalige toevoegingen worden nooit beperkt of laten vervallen. Elke inkorting wordt naar de tegoedgeschiedenis van de klant geschreven als een **Rollover Cap Credit Adjustment** of een **Expired Credits Credit Adjustment** en telt nooit als verbruik. De equivalenten op abonnementsniveau zijn `rollover_cap_months` en `rollover_expiry_days` op een prijscategorie — zie [De velden op een categorie](#the-fields-on-a-tier) en [Beperken wat wordt meegenomen](sub-accounts.md#capping-what-rolls-over).

***

## AI-prijzen en -beleid per klant instellen

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

Acht extra schakelaars per klant, naast `/limits`, `/features` en `/menu-visibility` hierboven. Elk gebruikt de uid van het sub-account in de URL (geen `sub_account_id` body/query-parameter — het doel is al benoemd in het pad) en heeft hetzelfde bereik: jouw agentschap-sleutel, en het sub-account moet bij jouw agentschap horen.

**Welke AI-modellen een klant kan gebruiken**

```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 }` meldt de klant aan voor (of af van) het Max AI-niveau — onze infrastructuur tegen de catalogusprijs van het platform. Dit inschakelen voor een BYOK-klant verandert hun AI-kosten van "gratis op mijn eigen sleutel" naar "in rekening gebracht op mijn tegoedpool", dus het is een bewuste beslissing per klant in plaats van een agency-brede standaard.

Om te beperken VAN WELKE niveaus de campagnes en Agents van een klant überhaupt mogen kiezen (in plaats van alleen Max te blokkeren), gebruik je `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` is een array afkomstig uit `standard`, `economy`, `max`, `mini` — het VERVANGT de toegestane lijst van de klant. Stuur `null` (of `[]`) om de beperking te wissen en hen elk niveau te laten kiezen. Dit is belangrijk omdat een sub-account dat zijn eigen AI-niveau kiest, uitgeeft uit **jouw** tegoedpool, dus het is de hefboom voor het vastleggen van welke modellen een reseller-klant op jouw rekening mag gebruiken.

**Een antwoordbericht terwijl de klant geen tegoed meer heeft**

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

Wanneer het saldo van de klant (of jouw pool) leeg is, kan de AI niet antwoorden en hoort de contactpersoon niets. Met `enabled: true` krijgt elke contactpersoon die tijdens de uitval schrijft één keer `message` (maximaal 500 tekens, verzonden zoals het is op elk kanaal), en de AI beantwoordt die gesprekken echt zodra er weer tegoed is. `enabled: false` bewaart de opgeslagen tekst voor later; `enabled: false` zonder `message` verwijdert de instelling. Dezelfde schakelaar als **Antwoordbericht bij geen tegoed** in de bewerkingsmodal van het sub-account — zie [Een antwoordbericht terwijl een klant geen tegoed meer heeft](sub-accounts.md#a-holding-reply-while-a-client-is-out-of-credits).

**Wat een klant betaalt per AI-actie en de WhatsApp-toeslag**

Twee manieren om je tarief voor klanten in te stellen, van eenvoudigst naar meest gedetailleerd:

```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` is de prijs in credits die het EIGEN saldo van het sub-account verbruikt per AI-actie van het Max-model — jouw toeslag voor de klant bovenop wat jouw pool daadwerkelijk betaalt. `null` wist de overschrijving terug naar de catalogusprijs van het platform. Het tarief moet ten minste gelijk zijn aan wat een Max-actie jouw eigen pool kost (zodat je een klant nooit onder je eigen kostprijs kunt prijzen) en maximaal 10 credits; een verzoek buiten dat venster wordt afgewezen met de berekende ondergrens in het foutbericht.

Gebruik `action-pricing` voor prijzen per actietype in plaats van één vast Max-tarief:

```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` is een SAMENVOEGING met de bestaande kaart van de klant — een sleutel die je niet noemt, blijft zoals hij was, en `null` zet die sleutel terug naar de standaardwaarde. De herkende sleutels:

| Sleutel | Prijzen |
|---|---|
| `AI_MESSAGE` | Een AI-antwoord |
| `AI_TOOL_USE` | Een AI-tool-aanroep |
| `EVALUATION_CALL` | Een chat-evaluatieronde |
| `INTERRUPTION_HANDLING` | Een onderbreking tijdens het antwoorden afhandelen |
| `CONTACT_TAG` | Een door AI toegewezen contactlabel |
| `CHAT_SUMMARY` | Een chatsamenvatting |
| `wa_carrier_multiplier` | Een toeslagvermenigvuldiger toegepast op elke niet-AI WhatsApp-vergoeding die de klant betaalt: maandelijkse nummerhuur, bezorgkosten voor beheerde banen en Meta/Twilio-template-doorgiftekosten. |

Tarieven per actie moeten een getal groter dan 0 en tot 10 zijn; `wa_carrier_multiplier` moet ten minste `1` zijn (geen korting onder de kostprijs) en tot 10. Het verzenden van een niet-herkende sleutel, of een waarde buiten het bereik, wijst de HELE aanvraag af en benoemt elke foutieve sleutel, zodat een typefout nooit stilletjes een prijs kan opslaan die in werkelijkheid niet wordt toegepast.

Als je lid bent van de [Champions Circle](https://skool.com/dm-champions), geeft `insider-rate` je Max/Lead Finder-tarief met 20% korting door aan één klant in plaats van dit op het hele bureau toe te passen:

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

Het inschakelen vereist dat je eigen bureau-account daadwerkelijk een Circle-lidmaatschap heeft; het uitschakelen vereist dit nooit, dus een verlopen lid kan altijd een klant terugzetten.

**Vergrendel secties van het draaiboek van een klant**

```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` is een array getrokken uit `instructions`, `goal`, `rules`, `personality`, `conclude_unless` — het VERVANGT de vergrendelde lijst van de klant. Een vergrendelde sectie wordt aan de serverzijde geweigerd als de SUB-ACCOUNT zelf probeert deze te wijzigen (direct of via API-sleutel), terwijl jij (via `sub_account_id`) en de eigen dashboard-beheerdersweergave van de klant nog steeds alles kunnen bewerken. Stuur `null` (of `[]`) om alles te ontgrendelen. Handig voor 'done-for-you'-klanten waarbij jij het draaiboek bezit en wordt beoordeeld op het resultaat.

**Stel namens een klant diens meldingsvoorkeuren in**

```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` vervangt de volledige set meldingsvoorkeuren van de klant (geen samenvoeging per sleutel — stuur elke categorie die je wilt behouden, passend bij hoe de eigen instellingenpagina van de sub-account opslaat). Elke categorie onder `settings` accepteert `enabled` (boolean) en maximaal drie `channels` uit `email`, `in_app`, `webhook`. Stuur `null` om terug te keren naar de standaardinstellingen van het platform.

Alle zeven eindpunten reageren met `{ "success": true, "data": { "subAccountUid": "...", ...the field(s) you set... } }` en worden in het auditlogboek bijgehouden met de waarde voor/na. Veelvoorkomende fouten: `403` als je account geen Agency/Dev is of de sub-account niet van jou is om te beheren, `400` als het geen bureau-sub-account is of een waarde buiten het bereik valt.

***

## Pauzeer een klant die zijn abonnement heeft opgeschort

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

Wanneer een klant zijn abonnement bij jou opschort, pauzeer dan hun account in plaats van het te verwijderen: alles wat ze versturen stopt onmiddellijk — uitgaande berichten, uitzendingen, AI-antwoorden op elk kanaal — en wanneer ze inloggen, zien ze een schermvullende **Account gepauzeerd**-blokkering (met jouw optionele bericht) in plaats van de app. Niets wordt verwijderd of losgekoppeld: agents, campagnes, gekoppelde kanalen, contacten en chatgeschiedenis blijven precies zoals ze zijn, dus het opheffen van de pauze brengt de klant precies terug waar ze gebleven waren — geen installatie die opnieuw moet worden gedaan.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `message` | Nee | Wordt getoond aan de klant op hun vergrendelscherm. Laat leeg voor de standaardtekst. |
| `reason` | Nee | Interne notitie voor het bureau, opgeslagen bij de pauze en in het auditlogboek — wordt nooit aan de klant getoond. |

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

**Antwoord**

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

Wanneer de klant terugkomt, heft `POST /v1/subaccounts/SUB_ACCOUNT_UID/unpause` (geen body) de blokkering op — het versturen van berichten en AI-antwoorden wordt direct hervat.

Goed om te weten:

- **Dit is dezelfde status als de 'Hard blocked'-schakelaar in het dashboard** ([Een sub-account blokkeren / pauzeren](sub-accounts.md#blocking-pausing-a-sub-account)) — een klant die via de API is gepauzeerd, wordt als geblokkeerd weergegeven in het dashboard en vice versa, en het opheffen van de pauze verwijdert een blokkering die van beide kanten is geplaatst. De huidige status is afleesbaar uit het `agency_block`-veld op `GET /v1/subaccounts` (`level` van `"none"`, `"soft_blocked"` of `"hard_blocked"`).
- **Beide aanroepen zijn idempotent.** Het pauzeren van een reeds gepauzeerde klant ververst alleen het bericht, de reden en de tijdstempel; het opheffen van de pauze bij een actieve klant verandert niets.
- **De klant krijgt geen automatische e-mail** — veel bureaus werken met white-labeling, dus het informeren van de klant laten we aan jou over.
- **Je eigen DM Champ-facturering blijft onaangetast.** Het pauzeren van een klant heeft alleen invloed op jouw relatie met hen.
- **AI-assistenten kunnen dit ook**: de [MCP-server](../integrations/connect-ai-clients.md) stelt deze eindpunten beschikbaar als de `pause_subaccount` en `unpause_subaccount` tools.

***

## Direct credits toekennen of aftrekken

`POST /v1/subaccounts/credits`

Voegt een exact bedrag aan tegoed toe aan of verwijdert dit van het saldo van een sub-account — het API-equivalent van de handmatige tegoedcorrectie in het dashboard. Dit is een eenmalige saldowijziging, anders dan de terugkerende `monthly_credits`, `roll_over_to_next_month`, `rollover_cap_months` en `rollover_expiry_days` instellingen op [`PUT /v1/subaccounts/{subAccountUid}/limits`](#set-an-exact-team-member-limit-for-a-client).

Dit is het enige eindpunt op deze pagina dat de sub-account identificeert op **e-mailadres** in plaats van `sub_account_id`.

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `email` | Ja | Het e-mailadres van de sub-account, zoals dat onder jouw bureau bestaat. |
| `amount` | Ja | Een aantal credits dat niet nul is. Positief voegt toe, negatief trekt af. |
| `description` | Nee | Wordt getoond bij de aanpassing in de creditgeschiedenis van de klant. Standaard is een algemene "Aangepast door bureau via API" regel. |

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

**Antwoord**

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

Een negatieve `amount` die het saldo onder nul zou brengen, wordt geweigerd met `400`, waarbij je wordt geïnformeerd over het beschikbare saldo en wat je probeerde af te trekken. Als de klant zijn eigen Stripe-facturering heeft (doorverkoopmodus), telt een toegevoegd bedrag ook als tegoed dat ze hebben gekocht, dus het overleeft hun volgende maandelijkse reset op dezelfde manier als een echte opwaardering; bij een standaard toegewezen klant wordt het behandeld als onderdeel van hun terugkerende tegoed. Hoe dan ook zijn het eenmalige toevoegingen, dus een limiet voor meenemen of een vervaldatum die op het account (of het abonnement) is ingesteld, kort deze nooit in — alleen het terugkerende tegoed en abonnementstegoeden zijn hieraan onderhevig.

***

## Gesprekken van een sub-account lezen

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

Hiermee kunt u een monitoring- of ondersteuningsweergave van de gesprekken van een klant opbouwen zonder in te loggen op hun account. Vermeld eerst hun contacten met een voorbeeld van het laatste bericht en lees vervolgens de volledige berichtgeschiedenis van één contact.

**Contacten vermelden**

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

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `pageSize` | Nee | Contacten per pagina. Standaard 25, maximaal 50. |
| `lastActivityAt` | Nee | Paginering-cursor — geef de `lastActivityAt` van de vorige pagina door om door te gaan. |
| `searchQuery` | Nee | Filteren op contactnaam of telefoonnummer. |

**Antwoord**

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

Contacten worden gesorteerd op meest recente activiteit eerst. Blijf pagineren met `lastActivityAt` zolang `hasMore` gelijk is aan `true`.

**Berichten van één contact lezen**

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

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `pageSize` | Nee | Berichten per pagina. Standaard 30, maximaal 100. |
| `beforeTimestamp` | Nee | Paginering-cursor — haal berichten op die ouder zijn dan deze ISO-tijdstempel. |

**Antwoord**

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

Berichten worden geretourneerd met de nieuwste eerst; blader achteruit door de geschiedenis met `beforeTimestamp`.

***

## Kredietgebruik en campagnestatus voor uw hele portfolio lezen

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

Twee overzichten in dashboardstijl voor elk subaccount dat u beheert, voor het opbouwen van uw eigen bureau-rapportage in plaats van dat u per klant moet doorklikken.

**Kredietgebruik**

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

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `from` / `to` | Ja | ISO-datumbereik. |
| `subAccountId` | Nee | Weglaten voor een overzicht voor het hele bureau, één rij per subaccount. Voeg toe om over te schakelen naar de detailmodus: het overzicht van dat subaccount plus de onbewerkte, gepagineerde gebruiksrecords. |
| `limitCount` | Nee | Alleen detailmodus. Standaard 500, maximaal 2000. |
| `startAfterTimestamp` | Nee | Alleen detailmodus — paginering-cursor. |

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

Geef `subAccountId` door en hetzelfde antwoord bevat ook `records`: individuele kosten met `amount`, `reason`, `campaignName`, `contactName` en `timestamp`. Een klant die uitgeeft via zijn eigen BYOK-sleutel in plaats van via uw tegoeden, heeft geen kosten/token-cijfers (`costsRedacted: true`) — dat is telemetrie over platformkosten, niet iets om te tonen aan een wederverkoper.

**Campagnestatus**

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

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `pageSize` | Nee | Sub-accounts per pagina. Standaard 10, maximaal 50. |
| `lastDocumentId` | Nee | Paginering-cursor. |
| `searchQuery` | Nee | Filteren op sub-accountnaam of e-mailadres. |

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

`hasIssues` / `issueDetails` markeert sub-accounts die de moeite waard zijn om te bekijken — bijvoorbeeld een gepauzeerde campagne of een campagne waaraan geen kanaal is gekoppeld. Gebruik dit om een gezondheidsdashboard voor het hele klantenbestand op te bouwen in plaats van elke klant te openen om een stagnerende campagne op te merken.

> Zie `GET /analytics/agency-rollup` in de Analytics API-handleiding voor tijdreeksberichten en kredietactiviteit voor elke klant (een reeks die klaar is voor grafieken in plaats van een momentopname).

***

## Een klantaccount verzenden dat al is ingesteld

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

Een [snapshot](snapshots.md) is een herbruikbaar sjabloon: een of meer AI-agents plus hun kennisbank, tools en media, vastgelegd vanuit je eigen account. Twee eindpunten voegen dit toe aan je provisioning-flow.

**Automatisch — elke nieuwe klant krijgt het standaard mee.** Markeer een snapshot één keer als je standaard en elk account dat je vanaf dat moment aanmaakt, wordt geleverd met dit sjabloon geïnstalleerd. Dit geldt voor accounts die zijn aangemaakt via `POST /v1/subaccounts`, accounts die je in het dashboard aanmaakt en accounts die automatisch worden aangemaakt wanneer een klant betaalt via je betaallink.

Zoek eerst het id van de snapshot:

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

Stel het vervolgens in als de standaard:

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

Dat is de volledige integratie. Stuur `{"snapshot_id": null}` om het weer uit te schakelen. Je kunt hetzelfde doen vanuit het dashboard door op de ster op de pagina **Snapshots** te klikken.

Om uit te lezen wat er momenteel is gemarkeerd (bijvoorbeeld voordat een provisioning-script beslist of er een moet worden ingesteld), retourneert `GET /v1/snapshots/default` `{ "success": true, "data": { "default_snapshot_id": "SNAPSHOT_ID" } }` — `null` wanneer er niets is gemarkeerd. `GET /v1/snapshots` (gebruikt om de bovenstaande id te vinden) retourneert dezelfde `default_snapshot_id` naast de volledige `snapshots`-array, dus de meeste integraties hebben slechts die ene aanroep nodig. De velden van het volledige snapshot-object staan in de [Snapshots](snapshots.md)-handleiding.

**Op aanvraag — installeren in één account.** Handig voor het onboarden van een bestaande klant, of om een klant later een tweede sjabloon te geven.

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

Laat `sub_account_id` weg en het wordt in plaats daarvan in je eigen agency-account geïnstalleerd. Net als bij de `/subaccounts`-eindpunten, benoemen deze het doelaccount in het pad of de body in plaats van via de omgevingsparameter `sub_account_id`.

Goed om te weten voordat je hiermee aan de slag gaat:

- **Geïnstalleerde agents starten gepauzeerd.** Verbind eerst de kanalen van de klant en activeer daarna de agent. Dit geldt voor zowel het automatische als het on-demand pad.
- **Provisioning mislukt nooit door een snapshot.** Als de installatie niet kan worden voltooid, wordt het klantaccount nog steeds aangemaakt en is het bruikbaar — het komt alleen leeg aan en je kunt de snapshot achteraf toepassen.
- **Kanalen, agenda's en OAuth-verbindingen worden nooit gekopieerd.** Elk account verbindt zijn eigen kanalen. Tools die een eenvoudige API-sleutel gebruiken, blijven direct werken.
- **Twee keer toepassen creëert een tweede kopie.** Er wordt niets overschreven.

***

## Bouw de template zelf via de API

`POST /v1/snapshots` · agents, aangepaste functies en media via de API

Het bovenstaande gedeelte distribueert een snapshot die iemand in het dashboard heeft gebouwd. Het authoring-gedeelte is ook beschikbaar, zodat de hele cyclus — de master-setup één keer samenstellen, vastleggen en aan elke klant overhandigen — vanuit code kan worden uitgevoerd.

De onderdelen, in de volgorde waarin een provisioning-script ze gebruikt:

1. **Maak uw aangepaste functies.** `POST /v1/custom-functions` maakt er een aan; `GET /v1/custom-functions` geeft een overzicht van wat u heeft, en `GET`, `PUT` en `DELETE` op `/v1/custom-functions/{customFunctionId}` lezen, updaten en verwijderen er een. `POST /v1/custom-functions/test` voert een dry-run uit van een definitie voordat u deze opslaat.
2. **Maak en vorm de agent.** `POST /v1/agents` maakt deze aan, `PUT /v1/agents/{agentId}` update deze, en `PATCH /v1/agents/{agentId}/active` met `{ "active": false }` houdt deze gepauzeerd terwijl u werkt (dezelfde aanroep met `true` zet deze live). `GET /v1/agents` geeft een overzicht van de agents.
3. **Geef de agent zijn vaardigheden.** `POST /v1/agents/{agentId}/custom-functions` met `{ "custom_function_id": "..." }` koppelt een functie aan de agent; de bijbehorende `DELETE /v1/agents/{agentId}/custom-functions/{customFunctionId}` ontkoppelt deze.
4. **Vul de mediabibliotheek.** `POST /v1/agents/{agentId}/media-library` uploadt een item (JSON met `base64Data`, `mimeType`, `title`, `description`); `GET` geeft een overzicht van de items van de agent, en `PATCH`/`DELETE` op `/{itemId}` updaten of verwijderen er een.
5. **Leg het vast als een snapshot.**

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

Vanaf daar geldt het vorige gedeelte: markeer het als de standaard zodat elke nieuwe klant ermee wordt aangemaakt, of pas het op aanvraag toe. Beheerfuncties zijn ook beschikbaar: `PATCH /v1/snapshots/{snapshotId}` met `{ "name": "..." }` hernoemt er een, `DELETE /v1/snapshots/{snapshotId}` verwijdert er een (en haalt de markering als standaard weg indien van toepassing), en `GET /v1/snapshots/apply-targets` geeft een overzicht van elk account waarin u kunt installeren.

De endpoints voor agents, aangepaste functies en media accepteren allemaal `sub_account_id`, dus dezelfde aanroepen kunnen ook een agent direct binnen het account van één klant onderhouden. Snapshot-aanroepen werken altijd op uw agency-account — de template blijft bij u. Volledige request- en response-schema's voor al deze onderdelen staan in de [API-referentie](../api/reference.md).

***

## Beheer uw prijscategorieën via de API

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

De abonnementen die u verkoopt in **SaaS-modus → Prijscategorieën** kunnen vanuit code worden gelezen en gewijzigd, zodat uw eigen beheerpaneel of provisioning-script een abonnement kan toevoegen, een prijs kan aanpassen of een betaallink kan verstrekken zonder dat iemand het dashboard hoeft te openen. Authenticeer met uw bureau-API-sleutel zoals bij elke andere aanroep op deze pagina; deze endpoints zijn op bureau-niveau, dus ze vereisen geen `sub_account_id`. Elke schrijfactie voert dezelfde validatie en dezelfde Stripe-product-en-prijs-synchronisatie uit als het opslaan in het dashboard, dus een abonnement dat hier wordt aangemaakt is niet te onderscheiden van een abonnement dat u handmatig hebt ingesteld.

### Uw categorieën weergeven

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

**Antwoord**

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

Elke categorie wordt geretourneerd met zijn **`tierIndex`** — zijn positie in uw lijst met abonnementen, wat de manier is waarop de andere drie aanroepen ernaar verwijzen — en een kant-en-klare **`checkout_url`**, dezelfde link die het tabblad **Betalingen** u geeft, die al wijst naar het [white label-domein](white-labeling.md) waarop dat abonnement wordt verkocht.

### Een categorie toevoegen

De body is één categorie-object; het wordt toegevoegd aan het einde van uw lijst.

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

Het antwoord bevat de aangemaakte categorie, inclusief de `tierIndex` waarop deze is terechtgekomen en de `checkout_url` ervan.

### Een categorie bewerken

Stuur alleen de velden die u wilt wijzigen; al het andere in het abonnement blijft zoals het was.

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

Een veld dat de API niet herkent, wordt geweigerd in plaats van genegeerd, en de foutmelding benoemt het — zodat een typefout nooit stilletjes een instelling kan schrijven die er live uitziet maar niets doet. Het wijzigen van de prijs, de credits, de valuta of de factureringsfrequentie creëert een nieuwe prijs in uw Stripe; klanten die al geabonneerd zijn, blijven op het abonnement waarvoor ze zich hebben aangemeld.

### Een categorie verwijderen

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

Dezelfde regel als in het dashboard: een abonnement met actieve abonnees kan niet worden verwijderd. Het verzoek wordt geweigerd, met de melding hoeveel abonnees er op dat abonnement zitten — annuleer of migreer ze eerst. Een succesvolle verwijdering reageert met uw resterende categorieën, die al opnieuw zijn genummerd.

### De velden van een categorie

| Veld | Wat het is |
|---|---|
| `label` / `description` | De naam van het abonnement en de optionele regel die op je afrekenpagina wordt getoond. |
| `credits` | Tegoeden die de klant **per maand** krijgt bij een maandelijks of jaarlijks abonnement, en **per factuurperiode** bij een wekelijks abonnement. |
| `price_cents` | Prijs per factuurinterval, in de kleinste munteenheid (`2900` = $29.00). Bij een jaarlijks abonnement is dit de prijs voor het hele jaar. |
| `currency` | ISO-code in kleine letters — `usd`, `eur`, `gbp`, enzovoort. |
| `billing_interval` / `billing_interval_count` | `month` (de standaard), `year`, of `week` met een aantal van 1–52 voor "elke N weken". |
| `trial_days` | Lengte van de gratis proefperiode, 0 tot 90. `0` (of het weglaten ervan) betekent geen proefperiode. |
| `trial_credits` | Tegoeden waarmee de klant de proefperiode start. Standaard is dit het `credits` van het abonnement. |
| `trial_card_required` | `false` laat de klant de proefperiode starten zonder een kaart in te voeren. Standaard is `true`. |
| `trial_hard_expiry` | `true` geeft ongebruikte proeftegoed terug aan jouw pool en vergrendelt het account van de klant wanneer een proefperiode eindigt zonder upgrade. Standaard is `false` — zie [Hard verlopen na proefperiode](agency-accounts.md#step-3--set-up-pricing-tiers). |
| `rollover_cap_months` | Maanden aan tegoed die klanten met dit abonnement mogen meenemen tussen verlengingen — een getal van 0 tot 120, breuken toegestaan. `0` neemt niets mee; `null` (de standaard) betekent geen limiet. Zie [Beperken wat wordt meegenomen](sub-accounts.md#capping-what-rolls-over). |
| `rollover_expiry_days` | Dagen waarna ongebruikte tegoeden vervallen bij de volgende verlenging — een geheel getal van 1 tot 3650. `null` (de standaard) betekent dat ze nooit vervallen. |
| `features` / `feature_settings` | Wat klanten met dit abonnement krijgen — dezelfde functie-ID's als [Kies welke kanaaltypes een klant kan verbinden](#choose-which-channel-types-a-client-can-connect). |
| `team_seats_limit` | Teamzetels die het abonnement toekent: een exact getal, `0` voor geen, `-1` voor onbeperkt. |
| `white_label_config` | Op welk van jouw [white label domeinen](white-labeling.md#up-to-three-white-labels) het abonnement wordt verkocht. |

De proefperiode-velden zijn alleen relevant voor een abonnement met een proefperiode: sla een niveau op met `trial_days: 0` en ze worden verwijderd. De Stripe-product- en prijs-ID's van het abonnement worden voor u beheerd en kunnen niet handmatig worden ingesteld.

Drie dingen om goed te krijgen:

- **Tier-indexen zijn posities, geen permanente id's.** Het verwijderen van een abonnement schuift elk volgend abonnement één plek op. Haal de lijst daarom opnieuw op na elke wijziging — en kopieer de checkout-links die je hebt gepubliceerd opnieuw, precies zoals je zou doen na het verwijderen van een abonnement in het dashboard.
- **SaaS-modus moet eerst worden ingesteld.** Deze endpoints vereisen een agency-account met white labeling en een reeds opgeslagen Stripe-sleutel; zonder deze is er geen Stripe-account waarop het product en de prijs van het abonnement kunnen worden geplaatst.
- **Twintig abonnementen is het maximum**, hetzelfde als in het dashboard. Het `max_tiers`-veld in het lijstantwoord geeft je de huidige limiet aan.

Volledige schema's voor aanvragen en antwoorden staan in de [API-referentie](../api/reference.md), onder **Agency**.

***

## Stel uw prijs per credit in via de API

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

De prijs die klanten betalen voor ad-hoc opwaarderingen (**SaaS-modus → Prijs per credit**) kan ook vanuit de code worden gelezen en gewijzigd. Dit is gebouwd voor situaties waarin de prijs automatisch moet worden aangepast: een bureau dat credits verkoopt in de ene valuta maar factureert in een andere, kan een geplande taak de prijs laten herzien naarmate de wisselkoers verandert, in plaats van dat iemand deze elke week handmatig moet aanpassen.

### De huidige prijs lezen

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

**Antwoord**

```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` is de laagste prijs die het platform toestaat in die valuta, zodat een taak een nieuwe prijs kan controleren voordat deze wordt verzonden. Alle drie de waarden zijn `null` totdat er een prijs is ingesteld.

### Wijzigen

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

Verzend alleen wat u wilt wijzigen. `price_per_credit_cents` is de prijs in de kleinste valuta-eenheid (`130` = R$1,30); `currency` is een ISO-code in kleine letters; `note` is een optionele regel van maximaal 200 tekens die direct onder de prijs per credit op de factuurpagina van de klant wordt getoond — handig voor een referentieprijs in een andere valuta, zoals *"USD 0,25 per credit tegen onze referentiekoers"*. Stuur `"note": ""` om deze te verwijderen. Het antwoord heeft dezelfde vorm als het lezen hierboven, zodat een taak kan vergelijken en het schrijven kan overslaan wanneer er niets is veranderd.

Dezelfde regels gelden als in het dashboard: de prijs mag niet onder het platformminimum voor die valuta komen en het account moet white-labeling hebben. In tegenstelling tot de eindpunten voor prijscategorieën is er geen Stripe-sleutel vereist om deze waarde te lezen of te wijzigen.

### Geef een taak een sleutel die niets anders kan

Het invoeren van uw volledige agentschapsleutel in een planner geeft meer toegang dan nodig is voor een prijswijziging. Maak in plaats daarvan een **scoped key** aan die beperkt is tot het gedeelte **Agency Credit Price**: die sleutel kan alleen de prijs per credit lezen en wijzigen en niets anders — deze heeft geen toegang tot subaccounts, abonnementen, credits of uw Stripe-koppeling.

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

Het antwoord bevat de nieuwe sleutel in `api_key` **één keer** — deze wordt daarna nooit meer getoond, dus sla deze direct op. Stel `"read_only": true` in voor een sleutel die alleen de prijs hoeft te lezen, en voeg `"expires_at"` (een ISO-datum) toe als u wilt dat deze automatisch verloopt. Alleen de sleutel van de accounteigenaar kan scoped keys aanmaken; bekijk of trek ze in met `GET /v1/api-keys` en `DELETE /v1/api-keys/{id}`.

***

## Laat een sub-account uw prijzen lezen

`GET /v1/subaccounts/agency-pricing`

Elk ander eindpunt op deze pagina wordt aangeroepen met **uw agency-sleutel**, waarbij optioneel een klant wordt getarget via `sub_account_id`. Dit is het tegenovergestelde: het wordt aangeroepen met de **eigen API-sleutel van het sub-account**, zonder `sub_account_id`, zodat de eigen opwaardeerpagina van een klant (of een integratie die u voor hen bouwt) kan weergeven wat u hen in rekening brengt zonder ooit uw agency-account te zien.

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

**Antwoord**

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

Dit weerspiegelt precies wat [`GET /v1/agency/pricing-tiers`](#list-your-tiers) en [`GET /v1/agency/credit-price`](#read-the-current-price) voor u als agency retourneren, minus alles wat de klant niet hoeft te zien (Stripe-ids, `max_tiers`, enzovoort). Het werkt alleen voor een account dat daadwerkelijk een sub-account is met een gekoppeld agency — het aanroepen hiervan vanuit uw eigen agency-account resulteert in een toestemmingsfout.

***

## Om rekening mee te houden

- **Gebruik uw agency-sleutel.** Authenticeer elke aanroep met de API-sleutel van uw agency-account — niet die van het sub-account. De `sub_account_id`-parameter is wat de actie omleidt.
- **Credits komen van het sub-account.** Aankopen en terugkerende kosten worden in mindering gebracht op het kredietsaldo van het betreffende sub-account, niet op dat van u.
- **Een `404` betekent "niet uw sub-account."** Controleer het id dubbel en zorg dat het account er een is die u beheert.
- **De parameter is overal waar deze wordt geaccepteerd optioneel.** Laat deze weg en hetzelfde eindpunt werkt op uw agency-account, zodat u één integratie voor beide kunt hergebruiken.

***

## Gerelateerd

- [API-toegang](../integrations/api-access.md) — authenticatie, basis-URL, fouten, snelheidslimieten.
- [Sub-accounts](sub-accounts.md) — bekijk en beheer de accounts die u kunt targeten.
- [Automatisch opwaarderen van sub-accounts](sub-account-auto-recharge.md) — verleen tegoeden aan een sub-account via webhook + API.
- [Campagnes API](../api/campaigns.md) — campagnes maken, bijwerken en kopiëren, inclusief de volledige veldreferentie.
- [Kanaalverbinding API](../api/channels.md) — verbind de kanalen van een klant en routeer ze naar een campagne.
- Analytics API-handleiding (in de API-sectie) — de agency sub-account rollup en elk ander rapportage-eindpunt.
- [Snapshots](snapshots.md) — wat een snapshot vastlegt en hoe u er een bouwt in het dashboard.
