# Clientes

> Listar clientes comerciais, ler descontos, preços acordados e compradores de um cliente, e criar ou atualizar clientes.

Um cliente é uma conta comercial: uma empresa com quem você vende, com um código de conta, um desconto opcional para a conta e as pessoas (compradores) que podem fazer login para fazer pedidos por ela.

## Objeto do cliente

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

| Campo            | Tipo            | Descrição                                                                                                                                          |
| ---------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | string          | ID do cliente.                                                                                                                                     |
| `accountCode`    | string          | Seu código de conta. Único no workspace, ignorando maiúsculas. Pode ser alterado no app, então mantenha o `id` para qualquer coisa de longo prazo. |
| `name`           | string          | Nome comercial.                                                                                                                                    |
| `legalName`      | string ou nulo  | Nome registrado, quando diferente.                                                                                                                 |
| `email`          | string ou nulo  | E-mail principal, em minúsculas.                                                                                                                   |
| `discountBp`     | inteiro ou nulo | A porcentagem do cliente sobre a lista inteira, em pontos básicos. `null` para nenhuma.                                                            |
| `adjustment`     | string          | `discount` (removido da lista) ou `markup` (adicionado). Clientes existentes são `discount`.                                                       |
| `active`         | booleano        | Clientes inativos não podem fazer login ou fazer pedidos.                                                                                          |
| `newsOptOut`     | booleano        | O cliente desativou ofertas e e-mails de notícias. E-mails de alteração de preço ainda são enviados.                                               |
| `catalogueScope` | string          | `all` (vê toda a catálogo) ou `approved` (vê e faz pedidos apenas da sua lista aprovada). Definido na página do cliente; somente leitura na API.   |
| `createdAt`      | string          | Quando o cliente foi adicionado.                                                                                                                   |

Em qualquer lugar em que a API aceite um cliente, você pode usar o `id` ou o `accountCode`.

## Listar clientes

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

Todos os clientes, ordenados pelo nome.

| Nome     | Tipo    | Obrigatório | Descrição                 |
| -------- | ------- | ----------- | ------------------------- |
| `limit`  | inteiro | não         | 1 a 100. Padrão 100.      |
| `offset` | inteiro | não         | Linhas a pular. Padrão 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
}
```

## Obter um cliente

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

O cliente mais tudo o que afeta seus preços, e quem pode fazer login por ele.

| Nome | Em      | Tipo   | Obrigatório | Descrição                         |
| ---- | ------- | ------ | ----------- | --------------------------------- |
| `id` | caminho | string | sim         | ID do cliente ou código de conta. |

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

| Campo                | Descrição                                                                                                                                                                                                                                                            |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `priceList`          | Nome da lista de preços com a qual este cliente compra. `priceListId` é o ID dessa lista. Um produto sem preço na lista é preço sob consulta.                                                                                                                        |
| `discountRules`      | Porcentagens de produto, categoria e marca. `scope` é `product`, `category` ou `brand`; `adjustment` é `discount` ou `markup`; `target` é seu nome. As regras de produto também fornecem `sku`, `list` e `unit` (o que eles pagam).                                  |
| `agreedPrices`       | Preços acordados (fixos) válidos agora, por SKU, em unidades menores, com o preço atual `list` e o preço `unit` que eles pagam. `note` é o comentário; `noteVisible` diz se esse cliente o vê. `internalNote` é exclusivo para equipe e nunca é mostrado ao cliente. |
| `buyers`             | Pessoas que podem fazer login e fazer pedidos por este cliente.                                                                                                                                                                                                      |
| `pendingInvitations` | Convites enviados, mas não aceitos, e não expirados.                                                                                                                                                                                                                 |

Veja [como os preços são calculados](/docs/api/prices#how-prices-are-worked-out) para como esses se combinam.

## Criar ou atualizar clientes

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

Envie até **100 clientes** por chamada. Cada linha é correspondida a um cliente existente da seguinte forma:

1. Se `accountCode` estiver definido, por código de conta. Nenhum correspondente significa um novo cliente com esse código.
2. Caso contrário, se `email` estiver definido, por e-mail. Nenhum correspondente significa um novo cliente.
3. Caso contrário, por nome, ignorando maiúsculas.

Novos clientes sem código de conta recebem um criado a partir do seu nome.

### Corpo

| Campo                     | Tipo                   | Obrigatório | Descrição                                                                                                                                                                                  |
| ------------------------- | ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `customers`               | array                  | sim         | 1 a 100 clientes. Os códigos de conta devem ser únicos dentro da chamada.                                                                                                                  |
| `customers[].accountCode` | string ou nulo         | sim         | Até 50 caracteres. `null` para corresponder por e-mail ou nome.                                                                                                                            |
| `customers[].name`        | string                 | sim         | Nome comercial, até 160 caracteres.                                                                                                                                                        |
| `customers[].email`       | string ou nulo         | sim         | E-mail do contato principal. `null` apaga-o.                                                                                                                                               |
| `customers[].discountBp`  | inteiro ou nulo        | sim         | Porcentagem da lista completa, de 0 a 10.000, para desconto ou acréscimo. `null` apaga-o.                                                                                                  |
| `customers[].adjustment`  | `discount` ou `markup` | não         | Para qual direção `discountBp` move o preço. Omita para manter a direção atual. Novos clientes padrão para `discount`.                                                                     |
| `customers[].priceList`   | string ou nulo         | não         | Nome de uma lista de preços existente. Omita para manter a lista inalterada. `null` coloca o cliente no Padrão. Um nome desconhecido é rejeitado.                                          |
| `customers[].region`      | string ou nulo         | não         | Nome de uma região existente. Omita para manter a região inalterada. `null` apaga-a. Um nome desconhecido é rejeitado.                                                                     |
| `customers[].rep`         | string ou nulo         | não         | E-mail ou nome de usuário de um funcionário atribuído a este cliente. Omita para manter o representante inalterado. `null` apaga-o. Um e-mail ou nome de usuário desconhecido é rejeitado. |

`accountCode`, `name`, `email` e `discountBp` são obrigatórios e escritos como enviados: `null` apaga `email` e `discountBp`. Dessa forma, um campo ausente no seu código não pode deixar silenciosamente um percentual antigo em vigor. `adjustment` e `priceList` são opcionais: deixe `adjustment` de fora para manter um acréscimo ou desconto como está.

A planilha de clientes tem a mesma escolha. Além de Desconto %, ela pode ter uma coluna de Ajuste (`discount` ou `markup`), uma coluna de Lista de Preços e uma coluna de Representante de Vendas. A célula do representante é o e-mail dessa pessoa, ou seu nome de usuário quando elas se conectam sem e-mail. Uma célula em branco deixa esse campo como está. Um nome de lista desconhecido ou representante é um erro nessa linha.

Atualizar um cliente não envia e-mails nem convida compradores. Convide compradores a partir da página do cliente no app. O nome registrado, status ativo, regras de desconto e preços acordados também são alterados no app.

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

A alteração aparece no log de atividades como o nome da chave.
