# Klanten

> Lijst handelsklanten, lees één klant's kortingen, overeengekomen prijzen en kopers, en maak of werk klanten bij.

Een klant is een handelsrekening: een bedrijf waar je naar verkoopt, met een accountcode, een optionele accountkorting en de personen (kopers) die kunnen inloggen om voor het bedrijf te bestellen.

## Het klantobject

```json
{
  "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
  "accountCode": "OAK01",
  "name": "Oakfield Joinery",
  "legalName": "Oakfield Joinery Ltd",
  "email": "accounts@oakfieldjoinery.co.uk",
  "discountBp": 500,
  "active": true,
  "newsOptOut": false,
  "catalogueScope": "all",
  "createdAt": "2026-07-02T10:12:44.000Z"
}
```

| Veld             | Type            | Beschrijving                                                                                                                                             |
| ---------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | string          | Klant-ID.                                                                                                                                                |
| `accountCode`    | string          | Je accountcode. Uniek in de workspace, onafhankelijk van hoofdletters. Het kan worden gewijzigd in de app, dus houd het `id` voor iets dat langdurig is. |
| `name`           | string          | Handelsnaam.                                                                                                                                             |
| `legalName`      | string of null  | Geregistreerde naam, wanneer anders.                                                                                                                     |
| `email`          | string of null  | Hoofdcontact e-mail, in kleine letters.                                                                                                                  |
| `discountBp`     | integer of null | De korting van de klant op de hele lijst, in basispunten. `null` voor geen korting.                                                                      |
| `adjustment`     | string          | `discount` (afgenomen van de lijst) of `markup` (toegevoegd). Bestaande klanten zijn `discount`.                                                         |
| `active`         | boolean         | Inactieve klanten kunnen niet inloggen of bestellen.                                                                                                     |
| `newsOptOut`     | boolean         | De klant heeft de offertes en nieuwsbrieven uitgeschakeld. Prijsveranderingsemails worden nog steeds verstuurd.                                          |
| `catalogueScope` | string          | `all` (ziet de hele catalogus) of `approved` (ziet en bestelt alleen zijn goedgekeurde lijst). Instelbaar op de klantpagina; alleen-lezen in de API.     |
| `createdAt`      | string          | Wanneer de klant is toegevoegd.                                                                                                                          |

Overal waar de API een klant nodig heeft, kun je het `id` of het `accountCode` gebruiken.

## Klantenlijst

**GET** `/api/v1/customers`

Elke klant, gesorteerd op naam.

| Naam     | Type    | Verplicht | Beschrijving                                |
| -------- | ------- | --------- | ------------------------------------------- |
| `limit`  | integer | nee       | 1 tot 100. Standaard 100.                   |
| `offset` | integer | nee       | Aantal rijen om over te slaan. Standaard 0. |

```bash
curl "https://tradecatalog.app/api/v1/customers?limit=100" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/customers?limit=100', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: customers, nextOffset } = await res.json()
```

```json
{
  "data": [
    {
      "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "accountCode": "OAK01",
      "name": "Oakfield Joinery",
      "legalName": "Oakfield Joinery Ltd",
      "email": "accounts@oakfieldjoinery.co.uk",
      "discountBp": 500,
      "active": true,
      "newsOptOut": false,
      "catalogueScope": "all",
      "createdAt": "2026-07-02T10:12:44.000Z"
    }
  ],
  "nextOffset": null
}
```

## Een klant ophalen

**GET** `/api/v1/customers/{id}`

De klant plus alles wat hun prijzen beïnvloedt, en wie er voor hen in kan loggen.

| Naam | In  | Type   | Verplicht | Beschrijving             |
| ---- | --- | ------ | --------- | ------------------------ |
| `id` | pad | string | ja        | Klant-ID of accountcode. |

```bash
curl https://tradecatalog.app/api/v1/customers/OAK01 \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/customers/OAK01', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: customer } = await res.json()
```

```json
{
  "data": {
    "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
    "accountCode": "OAK01",
    "name": "Oakfield Joinery",
    "legalName": "Oakfield Joinery Ltd",
    "email": "accounts@oakfieldjoinery.co.uk",
    "discountBp": 500,
    "priceListId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "priceList": "Standard",
    "active": true,
    "newsOptOut": false,
    "createdAt": "2026-07-02T10:12:44.000Z",
    "discountRules": [
      {
        "id": "d0e1f2a3-4b5c-4d6e-9f7a-8b9c0d1e2f3a",
        "scope": "product",
        "discountBp": 2000,
        "productId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
        "sku": "HX-8510",
        "target": "Hex bolt M10 x 50 zinc",
        "list": 2280,
        "unit": 1824
      },
      {
        "id": "e1f2a3b4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
        "scope": "brand",
        "discountBp": 1500,
        "productId": null,
        "sku": null,
        "target": "Northgate",
        "list": null,
        "unit": null
      },
      {
        "id": "f2a3b4c5-6d7e-4f80-9a1b-2c3d4e5f6a7b",
        "scope": "category",
        "discountBp": 1250,
        "productId": null,
        "sku": null,
        "target": "Hinges",
        "list": null,
        "unit": null
      }
    ],
    "agreedPrices": [
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "productId": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
        "price": 1450,
        "note": "Contract price to March 2027",
        "noteVisible": true,
        "internalNote": "Agreed with their buyer",
        "sku": "HX-8512",
        "name": "Hex bolt M12 x 50 zinc",
        "list": 1980,
        "unit": 1450
      }
    ],
    "buyers": [{ "name": "Sam Patel", "email": "sam@oakfieldjoinery.co.uk" }],
    "pendingInvitations": [{ "email": "orders@oakfieldjoinery.co.uk" }]
  },
  "currency": "GBP"
}
```

| Veld                 | Beschrijving                                                                                                                                                                                                                                                                      |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `priceList`          | Naam van de prijslijst waar deze klant van koopt. `priceListId` is het ID van die lijst. Een product zonder prijs op de lijst is op verzoek.                                                                                                                                      |
| `discountRules`      | Product-, categorie- en merkpercentages. `scope` is `product`, `category` of `brand`; `adjustment` is `discount` of `markup`; `target` is zijn naam. Productregels geven ook `sku`, `list` en `unit` (wat ze betalen).                                                            |
| `agreedPrices`       | Overeengekomen (vaste) prijzen geldig nu, per SKU, in mindere eenheden, met de huidige `list` prijs en de `unit` prijs die ze betalen. `note` is de opmerking; `noteVisible` zegt of die klant het ziet. `internalNote` is personeelsgebruik en wordt nooit aan de klant getoond. |
| `buyers`             | Personen die kunnen inloggen en bestellen voor deze klant.                                                                                                                                                                                                                        |
| `pendingInvitations` | Aanbiedingen die zijn verstuurd maar nog niet zijn geaccepteerd, en niet verlopen.                                                                                                                                                                                                |

Zie [hoe prijzen worden berekend](/docs/api/prices#how-prices-are-worked-out) voor hoe deze combineren.

## Klanten aanmaken of bijwerken

**PUT** `/api/v1/customers`

Stuur maximaal **100 klanten** per oproep. Elke rij wordt gekoppeld aan een bestaande klant op deze manier:

1. Als `accountCode` is ingesteld, op accountcode. Geen overeenkomst betekent een nieuwe klant met die code.
2. Anders, als `email` is ingesteld, op e-mail. Geen overeenkomst betekent een nieuwe klant.
3. Anders op naam, ongeacht hoofdletters.

Nieuwe klanten zonder accountcode krijgen er een gemaakt van hun naam.

### Lichaam

| Veld                      | Type                   | Verplicht      | Beschrijving                                                                                                                                                                                                          |
| ------------------------- | ---------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customers`               | array                  | ja             | 1 tot 100 klanten. Accountcodes moeten uniek zijn binnen de oproep.                                                                                                                                                   |
| `customers[].accountCode` | string of null         | ja             | Maximaal 50 tekens. `null` om te matchen op e-mail of naam.                                                                                                                                                           |
| `customers[].name`        | string                 | ja             | Handelsnaam, maximaal 160 tekens.                                                                                                                                                                                     |
| `customers[].email`       | string of null         | ja             | Hoofdcontact e-mail. `null` verwijderd het.                                                                                                                                                                           |
| `customers[].discountBp`  | integer of null        | ja             | Gehele lijst percentage, 0 tot 10.000, voor een korting of een mark-up. `null` verwijderd het.                                                                                                                        |
| `customers[].adjustment`  | `discount` of `markup` | niet verplicht | Welke kant `discountBp` beweegt de prijs. Laat het weg om de huidige richting te behouden. Nieuwe klanten standaard `discount`.                                                                                       |
| `customers[].priceList`   | string of null         | niet verplicht | Naam van een bestaande prijslijst. Laat het weg om de lijst onveranderd te laten. `null` zet de klant op Standard. Een onbekende naam wordt afgewezen.                                                                |
| `customers[].region`      | string of null         | niet verplicht | Naam van een bestaande regio. Laat het weg om de regio onveranderd te laten. `null` verwijderd het. Een onbekende naam wordt afgewezen.                                                                               |
| `customers[].rep`         | string of null         | niet verplicht | E-mail of gebruikersnaam van een medewerker die aan deze klant is toegewezen. Laat het weg om de verkoopafdeling onveranderd te laten. `null` verwijderd het. Een onbekende e-mail of gebruikersnaam wordt afgewezen. |

`accountCode`, `name`, `email` en `discountBp` zijn verplicht en worden zoals verstuurd: `null` verwijderd `email` en `discountBp`. Zo kan een ontbrekend veld in je code niet ongemerkt een oude percentage laten staan. `adjustment` en `priceList` zijn optioneel: laat `adjustment` weg om een mark-up of korting zoals het is te houden.

Het klantenspreadsheet heeft dezelfde keuze. Naast Korting % kan het een Aanpassing kolom hebben (`discount` of `markup`), een Prijslijst kolom en een Verkoopafdeling kolom. De verkoopafdeling cel is het e-mailadres van die persoon, of hun gebruikersnaam wanneer ze zich aanmelden zonder e-mail. Een lege cel laat dat veld zoals het is. Een onbekende lijstnaam of verkoopafdeling is een fout op die rij.

Het bijwerken van een klant e-mailt niemand of nodigt kopers niet uit. Nodig kopers uit vanaf de klantpagina in de app. Geregistreerde naam, actieve status, kortingregels en overeengekomen prijzen worden ook in de app gewijzigd.

```bash
curl -X PUT https://tradecatalog.app/api/v1/customers \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customers": [
      {
        "accountCode": "OAK01",
        "name": "Oakfield Joinery",
        "email": "accounts@oakfieldjoinery.co.uk",
        "discountBp": 500
      },
      {
        "accountCode": "BRK07",
        "name": "Brookside Builders",
        "email": null,
        "discountBp": null
      }
    ]
  }'
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/customers', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ customers }),
})
const { data } = await res.json()
console.log(`${data.created} new, ${data.updated} updated`)
```

```json
{
  "data": { "created": 1, "updated": 1 }
}
```

De wijziging verschijnt in de activiteitenlog als de naam van de sleutel.
