> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Produtos

> Crie e atualize produtos com suporte para filiais, variações e modificadores.

Você pode criar ou atualizar produtos em massa pelo SKU. Se um produto com o SKU especificado já existir, ele é atualizado; caso contrário, é criado. Os produtos podem ser atribuídos a uma [filial](/pt/guias/integracoes/e-commerce/api-sucursales/criar), ter múltiplas variações e grupos de modificadores.

<ParamField path="app_id" type="string" required>
  O ID da sua loja no Jelou Shop.
</ParamField>

<ParamField body="resources" type="object[]" required>
  Lista de produtos a criar ou atualizar (máx. 500 por requisição).
</ParamField>

## Campos do produto

Cada objeto dentro de `resources` aceita os seguintes campos:

<ParamField body="sku" type="string" required>
  Identificador único do produto (máx. 255 caracteres).
</ParamField>

<ParamField body="name" type="string" required>
  Nome do produto (máx. 255 caracteres).
</ParamField>

<ParamField body="price" type="number" required>
  Preço do produto (mín. 0).
</ParamField>

<ParamField body="description" type="string">
  Descrição do produto.
</ParamField>

<ParamField body="has_tax" type="boolean" default="true">
  Indica se o preço inclui impostos.
</ParamField>

<ParamField body="tax" type="number">
  Taxa de imposto individual do produto (entre `0` e `100`). Exemplo: `15` para 15%, `12` para 12%. Aplica-se apenas quando `enable_per_product_tax` está ativo na [configuração da loja](/pt/guias/integracoes/e-commerce/api-configuracion/tienda). Se não for enviado ou for `0`, a taxa global da loja é usada.
</ParamField>

<ParamField body="status" type="boolean" default="true">
  Status do produto (ativo/inativo).
</ParamField>

<ParamField body="stock_type" type="string" default="unlimited">
  Tipo de inventário: `limited` ou `unlimited`.
</ParamField>

<ParamField body="stock" type="number">
  Quantidade disponível. Aplica-se apenas quando `stock_type` é `limited`.
</ParamField>

<ParamField body="product_url" type="string">
  URL do produto na sua loja (máx. 2048 caracteres).
</ParamField>

<ParamField body="discount_type" type="string">
  Tipo de desconto: `value` (valor fixo) ou `percentage`.
</ParamField>

<ParamField body="discount" type="number">
  Valor do desconto (mín. 0).
</ParamField>

<ParamField body="categories" type="string[]">
  Lista de nomes de categorias. São criadas automaticamente se não existirem.
</ParamField>

<ParamField body="images" type="string[]">
  Lista de URLs públicas de imagens do produto.
</ParamField>

<ParamField body="note_enabled" type="boolean" default="false">
  Habilita um campo de nota na página de detalhe do produto, permitindo que o cliente adicione um comentário ao adicionar o produto ao carrinho (ex.: "Sem cebola", "Embrulhar para presente").
</ParamField>

<ParamField body="note_label" type="string">
  Texto placeholder exibido no campo de nota (máx. 255 caracteres). Se não especificado, um texto genérico padrão é usado.
</ParamField>

<ParamField body="branch" type="string">
  Código da filial à qual o produto é atribuído. A filial deve já existir.
</ParamField>

<Warning>
  A filial deve ser criada antes de ser atribuída a um produto. Use o endpoint [Criar filial](/pt/guias/integracoes/e-commerce/api-sucursales/criar) para registrá-la primeiro.
</Warning>

<ParamField body="features" type="object[]">
  Lista de características (ficha técnica) do produto.

  <Expandable title="Campos de cada característica">
    <ParamField body="label" type="string" required>
      Nome da característica (máx. 255 caracteres). Ex.: `Material`, `Peso`.
    </ParamField>

    <ParamField body="value" type="string" required>
      Valor da característica (máx. 200 caracteres). Ex.: `Algodão`, `500g`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="replace_variations" type="boolean" default="false">
  Controla como as variações são sincronizadas. Com `false` (padrão), as variações são atualizadas ou criadas por SKU e as existentes que não estiverem no payload são **mantidas**. Com `true`, as variações que não estiverem no payload são **removidas**.
</ParamField>

<ParamField body="variations" type="object[]">
  Lista de variações do produto.

  <Expandable title="Campos da variação">
    <ParamField body="sku" type="string" required>
      SKU único da variação (máx. 255 caracteres). Se uma variação com este SKU já existir, ela é atualizada.
    </ParamField>

    <ParamField body="price" type="number" required>
      Preço da variação (mín. 0).
    </ParamField>

    <ParamField body="stock_type" type="string" default="unlimited">
      Tipo de inventário da variação: `limited` ou `unlimited`. Permite gerenciar o estoque de cada variação de forma independente do produto pai.
    </ParamField>

    <ParamField body="stock" type="number">
      Quantidade disponível da variação. Aplica-se apenas quando `stock_type` é `limited`.
    </ParamField>

    <ParamField body="properties" type="object">
      Propriedades adicionais em formato JSON.
    </ParamField>

    <ParamField body="images" type="string[]">
      Lista de URLs de imagens da variação.
    </ParamField>

    <ParamField body="attributes" type="object[]">
      Lista de atributos da variação.

      <Expandable title="Campos do atributo">
        <ParamField body="name" type="string" required>
          Nome do atributo — por exemplo: `Tamanho`, `Cor` (máx. 255 caracteres).
        </ParamField>

        <ParamField body="value" type="string" required>
          Valor do atributo — por exemplo: `M`, `Azul` (máx. 255 caracteres).
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="modifier_groups" type="object[]">
  Grupos de modificadores do produto (adicionais). Comum em delivery de comida (ex.: "Escolha sua Bebida", "Escolha seus Molhos"). Se o campo estiver ausente, os modificadores existentes são preservados. Se for um array vazio `[]`, todos os modificadores são excluídos. Se tiver dados, os modificadores são completamente substituídos.

  <Expandable title="Campos do grupo de modificadores">
    <ParamField body="code" type="string" required>
      Código único do grupo (máx. 255 caracteres).
    </ParamField>

    <ParamField body="name" type="string" required>
      Nome visível do grupo (máx. 255 caracteres).
    </ParamField>

    <ParamField body="min_quantity" type="integer" default="0">
      Número mínimo de opções que o cliente deve selecionar.
    </ParamField>

    <ParamField body="max_quantity" type="integer" default="1">
      Número máximo de opções que o cliente pode selecionar (mín. 1).
    </ParamField>

    <ParamField body="max_per_option" type="integer">
      Número máximo de vezes que a mesma opção pode ser escolhida (mín. 1).
    </ParamField>

    <ParamField body="is_required" type="boolean" default="false">
      Se o grupo é obrigatório para adicionar ao carrinho.
    </ParamField>

    <ParamField body="position" type="integer">
      Posição de ordenação do grupo (atribuída automaticamente se omitida).
    </ParamField>

    <ParamField body="options" type="object[]" required>
      Lista de opções do grupo (mínimo 1 opção).

      <Expandable title="Campos da opção">
        <ParamField body="code" type="string" required>
          Código único da opção (máx. 255 caracteres).
        </ParamField>

        <ParamField body="name" type="string" required>
          Nome visível da opção (máx. 255 caracteres).
        </ParamField>

        <ParamField body="price" type="number" default="0">
          Preço adicional pela opção (mín. 0).
        </ParamField>

        <ParamField body="image_url" type="string">
          URL da imagem da opção (deve ser uma URL pública).
        </ParamField>

        <ParamField body="position" type="integer">
          Posição de ordenação (atribuída automaticamente se omitida).
        </ParamField>

        <ParamField body="status" type="boolean" default="true">
          Se a opção está disponível.
        </ParamField>

        <ParamField body="linked_product_sku" type="string">
          SKU de um produto vinculado. Resolvido para `linked_product_id` (UUID). Se o SKU não for encontrado, armazenado como `null`.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<Info>
  Os modificadores são armazenados no produto e retornados automaticamente em todos os endpoints que retornam produtos.
</Info>

## Exemplo completo

Este exemplo cria três produtos: uma camiseta com variações de tamanho, uma calça simples e uma pizza com grupos de modificadores.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://gateway.jelou.ai/ecommerce/v2/apps/{app_id}/batch/products/upsert_by_sku" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "resources": [
        {
          "sku": "TSHIRT-BLU",
          "name": "Camiseta Básica Azul",
          "description": "Camiseta 100% algodão premium, corte regular",
          "price": 29.99,
          "has_tax": true,
          "tax": 15,
          "status": true,
          "stock_type": "limited",
          "stock": 150,
          "product_url": "https://minha-loja.com/camiseta-basica-azul",
          "discount_type": "percentage",
          "discount": 10,
          "branch": "BRANCH-DOWNTOWN",
          "categories": ["Roupas", "Camisetas"],
          "images": [
            "https://minha-loja.com/images/camiseta-azul-frente.jpg",
            "https://minha-loja.com/images/camiseta-azul-costas.jpg"
          ],
          "variations": [
            {
              "sku": "TSHIRT-BLU-S",
              "price": 29.99,
              "stock_type": "limited",
              "stock": 50,
              "attributes": [
                { "name": "Tamanho", "value": "S" },
                { "name": "Cor", "value": "Azul" }
              ]
            },
            {
              "sku": "TSHIRT-BLU-M",
              "price": 29.99,
              "stock_type": "limited",
              "stock": 60,
              "attributes": [
                { "name": "Tamanho", "value": "M" },
                { "name": "Cor", "value": "Azul" }
              ]
            },
            {
              "sku": "TSHIRT-BLU-L",
              "price": 31.99,
              "stock_type": "limited",
              "stock": 40,
              "attributes": [
                { "name": "Tamanho", "value": "L" },
                { "name": "Cor", "value": "Azul" }
              ],
              "images": [
                "https://minha-loja.com/images/camiseta-azul-L.jpg"
              ]
            }
          ]
        },
        {
          "sku": "PANTS-BLK-M",
          "name": "Calça Preta",
          "price": 49.99,
          "branch": "BRANCH-NORTH",
          "categories": ["Roupas", "Calças"],
          "note_enabled": true,
          "note_label": "Alguma instrução especial?"
        },
        {
          "sku": "PIZZA-FAMILY",
          "name": "Pizza Pepperoni Família",
          "description": "Pizza pepperoni tamanho família com massa artesanal",
          "price": 18.99,
          "stock_type": "unlimited",
          "branch": "BRANCH-NORTH",
          "categories": ["Pizzas", "Promoções"],
          "images": ["https://exemplo.com/pizza-pepperoni.jpg"],
          "modifier_groups": [
            {
              "code": "drinks",
              "name": "Escolha sua Bebida",
              "min_quantity": 1,
              "max_quantity": 1,
              "is_required": true,
              "options": [
                { "code": "coca-1l", "name": "Coca Cola 1L", "price": 0 },
                { "code": "sprite-1l", "name": "Sprite 1L", "price": 0 },
                { "code": "fanta-1l", "name": "Fanta 1L", "price": 0.50 }
              ]
            },
            {
              "code": "extras",
              "name": "Ingredientes Extras",
              "min_quantity": 0,
              "max_quantity": 5,
              "max_per_option": 2,
              "is_required": false,
              "options": [
                { "code": "extra-cheese", "name": "Queijo Extra", "price": 1.50 },
                { "code": "ham", "name": "Presunto", "price": 2.00 },
                { "code": "olives", "name": "Azeitonas", "price": 1.00 }
              ]
            }
          ]
        }
      ]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "message": "Batch upsert process initiated successfully",
    "count": 3,
    "jobs": 3
  }
  ```

  ```json 422 Validation Error theme={null}
  {
    "message": "The resources field is required.",
    "errors": {
      "resources": ["The resources field is required."]
    }
  }
  ```
</ResponseExample>

<Note>
  Substitua `{app_id}` pelo ID da sua loja e `YOUR_API_KEY` pela sua [chave de API](/pt/guias/configuracao/chaves-api).
</Note>

## Comportamento

<AccordionGroup>
  <Accordion title="Processamento assíncrono (padrão)">
    O endpoint retorna `202 Accepted` imediatamente. Os produtos são processados em segundo plano.
  </Accordion>

  <Accordion title="Processamento síncrono (X-Sync)">
    Envie o header `X-Sync: true` para processar de forma síncrona e receber o resultado por produto na mesma resposta (`200 OK`). Nesse modo o máximo é de **50 produtos** por requisição.

    ```json 200 OK theme={null}
    {
      "message": "Batch upsert processed synchronously",
      "count": 2,
      "succeeded": 1,
      "failed_count": 1,
      "failed": [
        { "sku": "CAM-BLU", "status": "failed", "error": "..." }
      ],
      "results": [
        { "sku": "PAN-NEG-M", "status": "success" },
        { "sku": "CAM-BLU", "status": "failed", "error": "..." }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Filial inexistente">
    Se o código da filial não corresponder a nenhuma filial da loja, o produto é criado sem atribuição de filial. Nenhum erro é produzido.
  </Accordion>

  <Accordion title="Categorias automáticas">
    Se uma categoria não existir, ela é criada automaticamente dentro da loja e da filial correspondente.
  </Accordion>

  <Accordion title="Imagens assíncronas">
    As imagens são baixadas e processadas em segundo plano após a criação do produto.
  </Accordion>

  <Accordion title="Variações por SKU">
    As variações são identificadas pelo SKU. Se uma variação com esse SKU já existir, ela é atualizada em vez de criar uma nova. Por padrão, as variações existentes que não estiverem no payload são mantidas; envie `replace_variations: true` para removê-las.
  </Accordion>

  <Accordion title="Modificadores">
    Se `modifier_groups` estiver ausente no payload, os modificadores existentes são preservados. Se for um array vazio `[]`, todos os modificadores são excluídos. Se tiver dados, os modificadores são completamente substituídos.
  </Accordion>
</AccordionGroup>

## Erros de validação

Se os dados não atenderem às regras de validação, a API responde com `422` e detalha os campos com erros.

```json theme={null}
{
  "message": "The resources field is required.",
  "errors": {
    "resources": ["The resources field is required."]
  }
}
```

<Accordion title="Mensagens de erro comuns">
  | Campo                                          | Mensagem                                                              |
  | ---------------------------------------------- | --------------------------------------------------------------------- |
  | `resources`                                    | At least one product is required.                                     |
  | `resources`                                    | Cannot process more than 500 products at once.                        |
  | `resources.*.sku`                              | Each product must have a SKU.                                         |
  | `resources.*.name`                             | Each product must have a name.                                        |
  | `resources.*.price`                            | Each product must have a price. / Price cannot be negative.           |
  | `resources.*.stock_type`                       | Stock type must be either limited or unlimited.                       |
  | `resources.*.images.*`                         | Each image must be a valid URL.                                       |
  | `resources.*.variations.*.stock_type`          | Variation stock type must be either limited or unlimited.             |
  | `resources.*.variations.*.stock`               | Variation stock must be an integer. / Cannot be negative.             |
  | `resources.*.modifier_groups.*.code`           | Each modifier group must have a code.                                 |
  | `resources.*.modifier_groups.*.name`           | Each modifier group must have a name.                                 |
  | `resources.*.modifier_groups.*.options`        | Each modifier group must have at least one option.                    |
  | `resources.*.modifier_groups.*.options.*.code` | Each modifier option must have a code.                                |
  | `resources.*.modifier_groups.*.options.*.name` | Each modifier option must have a name.                                |
  | `resources.*.tax`                              | Tax rate must be a number. / cannot be negative. / cannot exceed 100. |
  | `resources.*.replace_variations`               | Replace variations flag must be true or false.                        |
  | `resources.*.features.*.label`                 | Each feature must have a label. / cannot exceed 255 characters.       |
  | `resources.*.features.*.value`                 | Each feature must have a value. / cannot exceed 200 characters.       |
</Accordion>

<Note>
  As mensagens da coluna **Mensagem** são retornadas pela API em inglês (não são localizadas), portanto aparecem aqui exatamente como a API responde.
</Note>

## Limites

* Máximo de **500 produtos** por requisição no modo assíncrono (padrão), ou **50** no modo síncrono (`X-Sync: true`).
* Máximo de **10.000 produtos** por janela de **60 segundos** por loja (rate limit). Se excedido, a API responde com `429 Too Many Requests`.
* Todos os produtos são validados antes de serem processados.
