# Enviar catálogo de produtos

> Sincroniza o cadastro dos produtos de um usuário final, sem estoque, para a IA opinar sobre um produto antes da primeira venda. A operação é idempotente e aceita lotes de até 500 produtos.

Página: https://docs.diligensai.com.br/referencia/produtos

`PUT https://api.diligensai.com.br/api/v1/products`

Autorização: `X-API-Key: <sua API key>`. Chamada do seu backend, com a API key da conta. Nunca do navegador. Veja [Autenticação](https://docs.diligensai.com.br/guias/autenticacao.md).

Esta rota envia o cadastro dos produtos da loja de um usuário final. **O estoque não entra**, apenas o cadastro. Com o catálogo, a IA conhece o produto antes da primeira venda, e o usuário pode fazer perguntas como "o que acha da garrafa térmica que acabei de cadastrar?", "o preço da pochete está bom?", "quais produtos não venderam nada este mês?" ou "como estão os produtos que lancei?".

A IA responde com base no cadastro (preço, preço "de", custo, categoria, descrição e data do cadastro) e, quando o produto já foi vendido, também com base nos pedidos.

**Requisição**

cURL:

```bash
curl -X PUT "https://api.diligensai.com.br/api/v1/products" \
  -H "X-API-Key: $DILIGENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "loja-123",
    "products": [{
      "id": "P-12",
      "sku": "GAR-TER-500",
      "name": "Garrafa térmica",
      "status": "active",
      "category": "Acessórios",
      "price_cents": 12990,
      "cost_cents": 5000,
      "created_at": "2026-09-28T10:00:00-03:00",
      "updated_at": "2026-09-28T10:00:00-03:00"
    }]
  }'
```

Node.js:

```javascript
const response = await fetch('https://api.diligensai.com.br/api/v1/products', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.DILIGENS_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    external_id: 'loja-123',
    products: [{
      id: 'P-12',
      sku: 'GAR-TER-500',
      name: 'Garrafa térmica',
      status: 'active',
      category: 'Acessórios',
      price_cents: 12990,
      cost_cents: 5000,
      created_at: '2026-09-28T10:00:00-03:00',
      updated_at: '2026-09-28T10:00:00-03:00',
    }],
  }),
});
const { batch_id, received } = await response.json();
```

Python:

```python
import os
import requests

response = requests.put(
    "https://api.diligensai.com.br/api/v1/products",
    headers={"X-API-Key": os.environ["DILIGENS_API_KEY"]},
    json={
        "external_id": "loja-123",
        "products": [{
            "id": "P-12",
            "sku": "GAR-TER-500",
            "name": "Garrafa térmica",
            "status": "active",
            "category": "Acessórios",
            "price_cents": 12990,
            "cost_cents": 5000,
            "created_at": "2026-09-28T10:00:00-03:00",
            "updated_at": "2026-09-28T10:00:00-03:00",
        }],
    },
    timeout=30,
)
response.raise_for_status()
```

PHP:

```php
<?php
$body = [
    'external_id' => 'loja-123',
    'products' => [[
        'id' => 'P-12',
        'sku' => 'GAR-TER-500',
        'name' => 'Garrafa térmica',
        'status' => 'active',
        'category' => 'Acessórios',
        'price_cents' => 12990,
        'cost_cents' => 5000,
        'created_at' => '2026-09-28T10:00:00-03:00',
        'updated_at' => '2026-09-28T10:00:00-03:00',
    ]],
];

$ch = curl_init('https://api.diligensai.com.br/api/v1/products');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: ' . getenv('DILIGENS_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($body),
]);
$result = json_decode(curl_exec($ch), true);
```

**Resposta**

202:

```json
{ "batch_id": "0199a3c2-7f1e-7b6a-9d1c-2f4e8a6b1c3d", "received": 2 }
```

400:

```json
{
  "status": 400,
  "code": "COMMON_002",
  "message": "Dados inválidos",
  "details": [{ "field": "products[3].price_cents", "message": "Deve ser no mínimo 0" }]
}
```

## Sincronização e webhook

O mesmo endpoint atende aos dois momentos da integração, assim como no [PUT /orders](https://docs.diligensai.com.br/referencia/pedidos.md):

- **Sincronização do catálogo:** Pagine todos os produtos do usuário e envie lotes de até 500, respeitando o [limite de chamadas](https://docs.diligensai.com.br/guias/limites.md).
- **Webhook:** A cada produto criado ou alterado, envie um lote com 1 produto. Quando um produto for excluído no seu sistema, envie-o com `status: "archived"`. Não existe rota de exclusão.

A rota responde com o status **202** assim que o lote é validado, e a gravação acontece em segundo plano, como no [PUT /orders](https://docs.diligensai.com.br/referencia/pedidos.md#gravacao-em-segundo-plano). Não reenvie um lote que recebeu 202.

## Vínculo com os pedidos

O `id` do produto enviado aqui é o mesmo `items[].product_id` dos itens do [PUT /orders](https://docs.diligensai.com.br/referencia/pedidos.md). É esse id que liga as vendas ao produto do catálogo: sem ele nos itens, a IA não sabe quais produtos do catálogo foram vendidos. As duas sincronizações são independentes, e não importa qual chega primeiro.

Cada `id` corresponde ao que você vende com ele. Se o seu sistema tem variações (tamanho, cor) com id próprio, envie cada variação como um produto, com o `variant_name` e o `parent_id` (o produto pai).

> **O preço do catálogo é o de tabela**
>
> O valor efetivamente vendido continua sendo o dos itens do pedido (`unit_price_cents` e `total_cents`): a receita, o preço médio e a margem são sempre calculados a partir dos pedidos. O preço do cadastro serve apenas como informação para a IA.

## Regras

- Um lote tem de 1 a 500 produtos, sempre do mesmo usuário final (`external_id`), e o body tem no máximo 5 MB. Se o usuário final ainda não existir, ele é criado.
- **Valores em centavos**, sempre inteiros. **Datas** em RFC 3339 com fuso. Datas mais de 1 hora no futuro são recusadas com o status 400 `COMMON_002`.
- **Envie sempre o produto completo**, com o estado atual. O cadastro é substituído por inteiro a cada envio, e um campo que não for enviado fica vazio.
- **`updated_at` é obrigatório** e deve ser a data da última alteração do produto no seu sistema, e não o horário do envio. A Diligens só grava o produto se essa data for **mais recente** que a do registro existente; portanto, reenviar não duplica dados. Para corrigir um produto, reenvie-o com um `updated_at` mais recente.
- **Envie sempre o `created_at`**, a data do cadastro no seu sistema. É com ela que a IA sabe que um produto é novo e ainda não teve tempo de vender. Sem ela, o produto não aparece entre os lançamentos.
- **Sem `status`**, o produto entra como `active`.
- **O lote é tudo ou nada:** Se a gravação falhar, nenhum produto do lote é gravado, e a Diligens tenta gravar o lote inteiro de novo. Reenviar também é seguro.

## Corpo da requisição

- `external_id` (`string`, obrigatório)

  O identificador do usuário final dono do catálogo, com até 255 caracteres.

- `products` (`array de objetos`, obrigatório)

  De 1 a 500 produtos.

  **Campos de cada produto:**

  - `id` (`string`, obrigatório)

    O id do produto no seu sistema, com até 255 caracteres. Ele deve ser único por usuário final e é o mesmo `product_id` dos itens dos pedidos.

  - `name` (`string`, obrigatório)

    O nome do produto, com até 500 caracteres.

  - `updated_at` (`string (RFC 3339)`, obrigatório)

    A data da última alteração no seu sistema, e não o horário do envio.

  - `status` (`enum`, padrão: `active`)

    `active`, `inactive`, `draft` ou `archived`.

  - `product_type` (`enum`)

    `physical`, `digital`, `service`, `subscription` ou `other`.

  - `parent_id` (`string`)

    O produto pai das variações, com até 255 caracteres.

  - `sku` (`string`)

    Até 100 caracteres. A IA identifica o produto pelo nome ou pelo `sku`.

  - `gtin` (`string`)

    O EAN (código de barras), apenas com dígitos, até 14.

  - `variant_name` (`string`)

    O nome da variação, com até 255 caracteres, por exemplo, `"M / Azul"`.

  - `description` (`string`)

    A descrição do produto, com até 5.000 caracteres.

  - `category, brand` (`string`)

    Cada campo aceita até 255 caracteres.

  - `unit` (`string`)

    A unidade de venda, com até 20 caracteres, por exemplo, `un` ou `kg`.

  - `price_cents` (`integer`)

    O preço de venda no cadastro, em centavos, ≥ 0.

  - `compare_at_price_cents` (`integer`)

    O preço "de" de uma promoção, em centavos, ≥ 0.

  - `cost_cents` (`integer`)

    O custo unitário, em centavos, ≥ 0.

  - `url` (`string`)

    A URL http(s) da página do produto, com até 2.000 caracteres.

  - `tags` (`array de strings`)

    Até 30 tags, com até 100 caracteres cada.

  - `created_at` (`string (RFC 3339)`)

    A data do cadastro no seu sistema. É com ela que a IA identifica os lançamentos.

Veja a seguir um exemplo com todos os campos de um produto:

```json
{
  "external_id": "loja-123",
  "products": [
    {
      "id": "P-12",
      "parent_id": "P-PAI-12",
      "sku": "GAR-TER-500",
      "gtin": "7891234567895",
      "name": "Garrafa térmica",
      "variant_name": "500 ml / Preta",
      "description": "Aço inox com parede dupla: gelada por 24h e quente por 12h.",
      "status": "active",
      "product_type": "physical",
      "category": "Acessórios",
      "brand": "Trama",
      "unit": "un",
      "price_cents": 12990,
      "compare_at_price_cents": 15990,
      "cost_cents": 5000,
      "url": "https://loja.exemplo.com/garrafa-termica",
      "tags": ["inox", "lançamento"],
      "created_at": "2026-09-28T10:00:00-03:00",
      "updated_at": "2026-09-28T10:00:00-03:00"
    }
  ]
}
```

## Resposta

- `batch_id` (`string`)

  O identificador do lote. Guarde no seu log: com ele, o suporte localiza o lote se você precisar.

- `received` (`integer`)

  A quantidade de produtos recebidos no lote. Os produtos já registrados com um `updated_at` igual ou mais recente são ignorados na gravação, sem erro.

## Erros

| Status | `code` | Quando |
|---|---|---|
| 400 | `COMMON_001` | JSON ou data malformados |
| 400 | `COMMON_002` | Campo inválido, com o caminho em `details[].field`, por exemplo, `products[3].price_cents` |
| 413 | `COMMON_004` | Body acima de 5 MB (reduza o tamanho do lote) |
| 429 | `INGEST_001` | A cota diária de itens da conta acabou, e o lote não entrou (veja [Cota diária](https://docs.diligensai.com.br/guias/limites.md#cota-diaria)) |
| 500 | `COMMON_003` | Erro interno. Reenvie o lote |
| 503 | `COMMON_006` | Serviço indisponível por um momento, e o lote não foi aceito. Aguarde alguns segundos e reenvie |

Um campo inválido rejeita o lote inteiro. Os erros gerais (401, 402, 403, 429) estão descritos em [Erros](https://docs.diligensai.com.br/guias/erros.md).
