# Clients

> Lister les clients commerciaux, lire les remises, les prix convenus et les acheteurs d'un client, et créer ou mettre à jour des clients.

Un client est un compte de commerce : une entreprise à laquelle vous vendez, avec un code de compte, une remise de compte optionnelle, et les personnes (acheteurs) qui peuvent se connecter pour passer des commandes pour elle.

## L’objet client

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

| Champ            | Type           | Description                                                                                                                                                                               |
| ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | chaîne         | Identifiant du client.                                                                                                                                                                    |
| `accountCode`    | chaîne         | Votre code de compte. Unique dans l’espace de travail, en ignorant les majuscules. Il peut être modifié dans l’application, donc conservez le `id` pour quoi que ce soit de longue durée. |
| `name`           | chaîne         | Nom commercial.                                                                                                                                                                           |
| `legalName`      | chaîne ou null | Nom enregistré, lorsqu’il est différent.                                                                                                                                                  |
| `email`          | chaîne ou null | Email principal, en minuscules.                                                                                                                                                           |
| `discountBp`     | entier ou null | Le pourcentage propre au client sur l’ensemble de la liste, en points de base. `null` pour aucun.                                                                                         |
| `adjustment`     | chaîne         | `discount` (retiré de la liste) ou `markup` (ajouté). Les clients existants sont `discount`.                                                                                              |
| `active`         | booléen        | Les clients inactifs ne peuvent pas se connecter ou passer de commande.                                                                                                                   |
| `newsOptOut`     | booléen        | Le client a désactivé les emails d’offres et d’actualités. Les emails de changement de prix sont toujours envoyés.                                                                        |
| `catalogueScope` | chaîne         | `all` (voit l’ensemble du catalogue) ou `approved` (voit et commande uniquement sa liste approuvée). Défini sur la page client ; en lecture seule dans l’API.                             |
| `createdAt`      | chaîne         | Lorsque le client a été ajouté.                                                                                                                                                           |

Partout où l’API prend un client, vous pouvez utiliser le `id` ou le `accountCode`.

## Liste des clients

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

Tous les clients, triés par nom.

| Nom      | Type   | Requis | Description                 |
| -------- | ------ | ------ | --------------------------- |
| `limit`  | entier | non    | 1 à 100. Défaut 100.        |
| `offset` | entier | non    | Lignes à ignorer. Défaut 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
}
```

## Obtenir un client

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

Le client plus tout ce qui affecte leurs prix, et qui peut se connecter pour eux.

| Nom  | Dans   | Type   | Requis | Description                              |
| ---- | ------ | ------ | ------ | ---------------------------------------- |
| `id` | chemin | chaîne | oui    | Identifiant du client ou code de compte. |

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

| Champ                | Description                                                                                                                                                                                                                                                                            |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `priceList`          | Nom de la liste de prix à partir de laquelle ce client achète. `priceListId` est l’identifiant de cette liste. Un produit sans prix sur la liste est un prix sur demande.                                                                                                              |
| `discountRules`      | Pourcentages de produit, catégorie et marque. `scope` est `product`, `category` ou `brand` ; `adjustment` est `discount` ou `markup` ; `target` est son nom. Les règles de produit donnent également `sku`, `list` et `unit` (ce qu’ils paient).                                       |
| `agreedPrices`       | Prix convenus (fixes) valides maintenant, par SKU, en unités mineures, avec le prix `list` actuel et le prix `unit` que le client paie. `note` est la remarque ; `noteVisible` indique si le client le voit. `internalNote` est réservé au personnel et n’est jamais montré au client. |
| `buyers`             | Personnes qui peuvent se connecter et passer des commandes pour ce client.                                                                                                                                                                                                             |
| `pendingInvitations` | Invitations envoyées mais non encore acceptées, et non expirées.                                                                                                                                                                                                                       |

Voir [comment les prix sont calculés](/docs/api/prices#how-prices-are-worked-out) pour savoir comment ils s’additionnent.

## Créer ou mettre à jour des clients

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

Envoyez jusqu’à **100 clients** par appel. Chaque ligne est associée à un client existant de la manière suivante :

1. Si `accountCode` est défini, par code de compte. Aucune correspondance signifie un nouveau client avec ce code.
2. Sinon, si `email` est défini, par courriel. Aucune correspondance signifie un nouveau client.
3. Sinon par nom, en ignorant la casse.

Les nouveaux clients sans code de compte en reçoivent un généré à partir de leur nom.

### Corps

| Champ                     | Type                   | Obligatoire | Description                                                                                                                                                                                        |
| ------------------------- | ---------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customers`               | tableau                | oui         | 1 à 100 clients. Les codes de compte doivent être uniques dans l'appel.                                                                                                                            |
| `customers[].accountCode` | chaîne ou null         | oui         | Jusqu'à 50 caractères. `null` pour correspondre par courriel ou nom.                                                                                                                               |
| `customers[].name`        | chaîne                 | oui         | Nom commercial, jusqu'à 160 caractères.                                                                                                                                                            |
| `customers[].email`       | chaîne ou null         | oui         | Courriel de contact principal. `null` le vide.                                                                                                                                                     |
| `customers[].discountBp`  | entier ou null         | oui         | Pourcentage de la liste complète, de 0 à 10 000, pour une remise ou une surcharge. `null` le vide.                                                                                                 |
| `customers[].adjustment`  | `discount` ou `markup` | non         | Le sens dans lequel `discountBp` déplace le prix. Omettez-le pour laisser la direction actuelle. Les nouveaux clients par défaut sont `discount`.                                                  |
| `customers[].priceList`   | chaîne ou null         | non         | Nom d'une liste de prix existante. Omettez-le pour laisser la liste inchangée. `null` place le client sur Standard. Un nom inconnu est rejeté.                                                     |
| `customers[].region`      | chaîne ou null         | non         | Nom d'une région existante. Omettez-le pour laisser la région inchangée. `null` le vide. Un nom inconnu est rejeté.                                                                                |
| `customers[].rep`         | chaîne ou null         | non         | Courriel ou nom d'utilisateur d'un membre du personnel affecté à ce client. Omettez-le pour laisser le représentant inchangé. `null` le vide. Un courriel ou nom d'utilisateur inconnu est rejeté. |

`accountCode`, `name`, `email` et `discountBp` sont requis, et écrits comme envoyés : `null` vide `email` et `discountBp`. Ainsi, un champ manquant dans votre code ne peut pas laisser accidentellement un pourcentage ancien en place. `adjustment` et `priceList` sont optionnels : laissez `adjustment` de côté pour garder une surcharge ou une remise telle quelle.

La feuille de calcul des clients a le même choix. À côté de Discount %, elle peut avoir une colonne d'Adjustment (`discount` ou `markup`), une colonne de liste de prix et une colonne de représentant commercial. La cellule du représentant est le courriel de cette personne, ou son nom d'utilisateur lorsqu'il se connecte sans courriel. Une cellule vide laisse ce champ tel quel. Un nom de liste inconnu ou un représentant inconnu est une erreur sur cette ligne.

Mettre à jour un client ne fait pas envoyer de courriel à quelqu'un ou inviter des acheteurs. Invitez les acheteurs depuis la page du client dans l'application. Le nom enregistré, le statut actif, les règles de remise et les prix convenus sont également modifiés dans l'application.

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

Le changement apparaît dans le journal d'activité sous le nom de la clé.
