Pular para conteúdo

Sincronizar catálogo de produtos

Esta jornada descreve como um ERP integrador deve sincronizar um catálogo grande (dezenas de milhares de produtos) com o Gubee seguindo a ordem correta de dependências entre domínios.

Recomendado: use POST /integration/products/v2/createupdate para tudo

O endpoint /v2/createupdate cria automaticamente categoria, marca e atributos que não existirem ainda, na mesma chamada em que cria o produto. Em 95% dos casos de integração, você não precisa criar categorias, marcas ou atributos separadamente — basta enviar tudo no payload do produto.

POST /integration/products/v2/createupdate
{
  "externalId": "X",
  "name": "...",
  "mainCategory": "Esportes > Calçados > Tênis",   # criado em cascata
  "brand": "MarcaDemo",                             # criado se não existe
  "specifications": [{ "name": "Gênero", "values": ["Masculino"] }],
  "variations": [
    {
      "sku": "SKU-X",
      "variantSpecification": [{ "name": "Cor", "values": ["Preto"] }],
      "prices": [...], "stocks": [...], "images": [...]
    }
  ]
}

Use os passos abaixo (bulk de categoria/atributo/marca) apenas quando:

  • Você quer reutilizar categorias em 10k+ produtos sem reenviar a string a cada chamada (economia de processamento)
  • Você precisa criar categorias com metadados adicionais (externalId próprio, attributes obrigatórios por categoria) antes de subir produtos
  • Você está migrando um catálogo legado comHierquia rígida que precisa ser validada primeiro

Para a maioria dos ERPs, o fluxo é simplesmente: loop de POST /v2/createupdate para cada produto. Fim.

Pré-requisitos

  • sellerId já cadastrado no Gubee (extraído automaticamente do JWT).
  • Token JWT válido com a claim sellerId. Veja Autenticação.
  • Os domínios abaixo têm dependência estrita de ordem (quando você escolhe criar separadamente):
categorias ──┐
marcas     ──┼──> produtos ──> variações (sku) ──> estoque/preço
atributos  ──┘

Visão geral do fluxo

Fluxo recomendado (90% dos casos):

  1. Produtos — loop de POST /integration/products/v2/createupdate enviando mainCategory (string com >), brand, specifications e variations[].variantSpecification no mesmo payload. Backend resolve tudo.

Fluxo alternativo (catálogo massivo, 10k+ produtos):

  1. Categorias — enviar em bulk, pais antes dos filhos (uma vez).
  2. Atributos — enviar em bulk (uma vez).
  3. Marcas — enviar em loop ou bulk (uma vez).
  4. Produtos — upsert via POST /integration/products/v2/createupdate (passa mainCategory como string; backend reutiliza a categoria já criada sem duplicar).
  5. Mapear SKUs entre o ERP e o skuId interno do Gubee.

A separação entre externalId (chave do ERP) e skuId/hubeeId (chaves internas) é central no modelo — leia Identificadores: externalId, skuId, hubeeId antes de codificar.

Passo 1 — Criar categorias em bulk

POST /integration/categories/bulk aceita uma lista de CategoryApiDTO e cria em uma única transação. A ordem dentro do array importa: categorias pai devem vir antes das filhas, porque o campo parent referencia o name (chave natural) da categoria pai já integrada.

Request

curl -X POST 'https://api.gubee.com.br/integration/categories/bulk' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '[
    {
      "id": "cat-erp-1",
      "name": "Eletrônicos",
      "description": "Categoria raiz de eletrônicos",
      "active": true,
      "enabledAutoIntegration": true
    },
    {
      "id": "cat-erp-2",
      "parent": "Eletrônicos",
      "name": "Smartphones",
      "active": true
    },
    {
      "id": "cat-erp-3",
      "parent": "Eletrônicos > Smartphones",
      "name": "Acessórios para Smartphones",
      "active": true
    }
  ]'

Response 200

[
  { "id": "cat-erp-1", "hubeeId": "60f8a5c2e3b2a87654321000" },
  { "id": "cat-erp-2", "hubeeId": "60f8a5c2e3b2a87654321001" },
  { "id": "cat-erp-3", "hubeeId": "60f8a5c2e3b2a87654321002" }
]

Campos da CategoryApiDTO

Campo Tipo Obrigatório Descrição
id string não externalId no ERP. Recomendado p/ idempotência.
name string sim Chave natural única por seller.
parent string não Nome da categoria pai. Para níveis profundos use "A > B > C".
description string não
active boolean não (false)
enabledAutoIntegration boolean não (true) Replicar automaticamente para marketplaces.

Atualização em bulk

PUT /integration/categories/bulk com o mesmo formato, localizando por externalId (id). Se a categoria não existir, retorna 404. Para atualizar uma única, use PUT /integration/categories/{externalId}.

Passo 2 — Criar atributos em bulk

POST /integration/attributes/bulk segue o mesmo padrão. Atributos podem ser de produto (aplicados ao produto todo) ou variantes (definem combinações de variação — ex.: "Cor", "Tamanho").

Request

curl -X POST 'https://api.gubee.com.br/integration/attributes/bulk' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '[
    {
      "id": "attr-cor",
      "name": "Cor",
      "label": "Cor",
      "attrType": "TEXT",
      "variant": true,
      "required": true,
      "options": ["Preto", "Branco", "Azul", "Vermelho"]
    },
    {
      "id": "attr-tamanho",
      "name": "Tamanho",
      "attrType": "TEXT",
      "variant": true,
      "options": ["P", "M", "G", "GG"]
    },
    {
      "id": "attr-material",
      "name": "Material",
      "attrType": "TEXT",
      "variant": false,
      "options": ["Plástico", "Alumínio", "Vidro"]
    }
  ]'

Campos da AttributeApiDTO

Campo Tipo Obrigatório Descrição
id string não externalId no ERP.
name string sim Chave natural única.
label string não Rótulo exibido.
attrType TEXT não (TEXT) Apenas TEXT é tratado atualmente.
variant boolean não (false) Se true, define variações do produto.
required boolean não (false) Obriga preenchimento no produto.
options List<String> não Lista de valores permitidos para o atributo.

Passo 3 — Criar marcas

Marcas não têm endpoint bulk. Crie uma a uma (paralelize, se necessário):

curl -X POST 'https://api.gubee.com.br/integration/brands' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "brand-samsung",
    "name": "Samsung",
    "description": "Samsung Electronics"
  }'

Para atualizar: PUT /integration/brands/byExternalId/{externalId}. Para remover uma marca obsoleta que está bloqueando upsert por nome: DELETE /integration/brands/byExternalId/{externalId} (caso raro — Magento recria marcas e o registro antigo conflita).

Passo 4 — Upsert de produtos via V2 (recomendado)

POST /integration/products/v2/createupdate é o endpoint canônico para sincronização em massa. Ele é idempotente por externalId: se já existe, atualiza; se não, cria.

A vantagem principal sobre o V1 (POST /integration/products) é que o campo mainCategory aceita a string completa "level1 > level2 > level3" — o backend resolve o hubeeId internamente, sem precisar de lookup prévio.

Request completo

curl -X POST 'https://api.gubee.com.br/integration/products/v2/createupdate' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "externalId": "prod-erp-1001",
    "mainSku": "PHONE-1001-BLK",
    "name": "Samsung Galaxy S24 256GB Preto",
    "mainCategory": "Eletrônicos > Smartphones",
    "brand": "Samsung",
    "status": "ACTIVE",
    "type": "SIMPLE",
    "origin": "NATIONAL",
    "ncm": "8517.12.31",
    "specifications": [
      { "name": "Material", "value": "Alumínio" }
    ],
    "variations": [
      {
        "sku": "PHONE-1001-BLK",
        "main": true,
        "ean": "7891234567890",
        "cost": 3200.00,
        "warrantyTime": 12,
        "handlingTime": 1,
        "dimension": {
          "weight": { "value": 0.167, "unit": "KILOGRAM" },
          "length": 7.0, "width": 15.0, "height": 0.6
        },
        "images": [
          { "url": "https://cdn.erp.com/img/s24-front.jpg", "main": true },
          { "url": "https://cdn.erp.com/img/s24-back.jpg" }
        ],
        "stocks": [
          { "qty": 50, "warehouseId": "DEFAULT", "crossDockingTime": { "value": 1, "unit": "DAY" } }
        ],
        "prices": [
          { "value": 4999.90, "type": "DEFAULT" }
        ]
      },
      {
        "sku": "PHONE-1001-WHT",
        "main": false,
        "ean": "7891234567891",
        "cost": 3200.00,
        "prices": [{ "value": 4999.90, "type": "DEFAULT" }],
        "stocks": [{ "qty": 25, "warehouseId": "DEFAULT" }]
      }
    ]
  }'

Response 200

O endpoint retorna o productId (externalId) em text/plain:

prod-erp-1001

Campos principais da CreateUpdateProductApiDTO

Campo Tipo Obrigatório Notas
externalId string recomendado Chave do ERP. Idempotência baseada aqui.
mainSku string recomendado SKU da variação principal.
name string sim
mainCategory string sim "Cat1" ou "Cat1 > Cat2 > Cat3".
brand string sim Nome exato da marca já integrada.
status ACTIVE/INACTIVE não (ACTIVE)
type SIMPLE/KIT não (SIMPLE)
origin NATIONAL/IMPORTED não (NATIONAL)
ncm string não
specifications Set<Attribute> não Atributos de produto (não variantes).
variations List<Variation> sim Pelo menos 1. Variação main=true será a principal.

Variação (CreateUpdateVariationApiDTO)

Campo Tipo Obrigatório Notas
skuId string não no create Gerado automaticamente no create; obrigatório no update.
sku string sim SKU natural do ERP.
main boolean não (false) Exatamente uma variação deve ser main=true.
ean string não Código EAN/GTIN.
cost BigDecimal não Custo para cálculo de rentabilidade.
dimension Dimension não Peso + medidas para cálculo de frete.
warrantyTime Long não Meses.
handlingTime Long não Dias para despacho.
images Set<Image> não Veja Gestão de imagens.
stocks Set<Stock> não Atalho para criaar estoque junto.
prices Set<Price> não Atalho para criar preço junto.
videos Set<Video> não Merge cirúrgico (somente main/order editáveis).

Importante sobre videos: o campo no payload de produto serve apenas para metadados de apresentação (main, order). Não é possível criar vídeo via PUT de produto — use o fluxo dedicado em Upload de vídeos.

Passo 5 — Atualizações incrementais

Atualização total (PUT)

Use PUT /integration/products/{externalId} quando precisar reescrever o produto inteiro (variações, dimensões, atributos, imagens, etc.). Substitui o estado anterior:

curl -X PUT 'https://api.gubee.com.br/integration/products/prod-erp-1001' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{ ...produto completo... }'

Retorna 202 Accepted (sem body). Atualização é assíncrona via evento assíncrono na plataforma de streaming. Também disponível por mainSkuId: PUT /integration/products/byMainSkuId/{mainSkuId}.

Atualização parcial (PATCH) — recomendada para correções pontuais

PATCH /integration/products/variations/sku/{sku} altera apenas os campos informados no body. Nulos são ignorados. Semântica de images:

  • null → preserva atual
  • [] → remove todas
  • [...] → substitui atomicamente (URLs/existentes são preservadas via merge server-side por url+uuid)
curl -X PATCH 'https://api.gubee.com.br/integration/products/variations/sku/PHONE-1001-BLK' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cost": 2999.00,
    "ean": "7891234567999",
    "warrantyTime": 18,
    "handlingTime": 2,
    "dimension": { "weight": { "value": 0.170, "unit": "KILOGRAM" } },
    "name": "Galaxy S24 256GB Preto Edição 2025"
  }'

Response:

{ "modified": true }

PATCH /integration/products/{externalId} atualiza campos escalares do produto (name, nbm, origin, originCountry, disableIntegration).

Endpoints granulares de imagem

Para operações cirúrgicas sem reescrever a lista:

Operação Endpoint
Adicionar imagem POST /integration/products/{externalId}/variations/{skuId}/images
Remover imagem DELETE /integration/products/{externalId}/variations/{skuId}/images/{imageUuid}
Definir como principal PUT /integration/products/{externalId}/variations/{skuId}/images/{imageUuid}/main
Reordenar PUT /integration/products/{externalId}/variations/{skuId}/images/order
Patch metadados PATCH /integration/products/{externalId}/variations/{skuId}/images/{imageUuid}

Passo 6 — Mapear SKUs do ERP para skuId interno

A função mais usada em integração contínua. O ERP trabalha com o SKU natural; os endpoints de leitura internos (estoque, preço, pedidos) frequentemente retornam o skuId interno do Gubee.

POST /integration/products/skuIds/bySkus resolve um lote de SKUs em skuIds:

curl -X POST 'https://api.gubee.com.br/integration/products/skuIds/bySkus' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '["PHONE-1001-BLK", "PHONE-1001-WHT", "PHONE-1002-BLK"]'

Response

[
  {
    "id": "60f8a5c2e3b2a87654321100",
    "skuId": "sku-uuid-1001-blk",
    "sku": "PHONE-1001-BLK",
    "ean": "7891234567890"
  },
  {
    "id": "60f8a5c2e3b2a87654321101",
    "skuId": "sku-uuid-1001-wht",
    "sku": "PHONE-1001-WHT",
    "ean": "7891234567891"
  }
]

SKUs não encontrados são omitidos do array de resposta (não geram erro). Compare o tamanho do array retornado com o enviado para detectar SKUs pendentes.

Também disponível: - GET /integration/products/skuIds/bySkus?skus=A,B,C (limitado pela URL). - GET /integration/products/skus/bySkuIds?skuIds=X,Y — caminho inverso. - POST /integration/products/skus/bySkuIds — versão POST do anterior.

Variação retornada: VariationSkuApiMapDTO

Campo Tipo Descrição
id string ID interno da variação.
skuId string skuId usado em endpoints internos.
sku string SKU natural do ERP.
ean string EAN, se cadastrado.

Tratamento de erros

409 Conflict — externalId duplicado

Ocorre quando duas requests concorrentes tentam criar o mesmo externalId, ou quando há inconsistência no banco (extremamente raro). Solução:

  1. Faça GET /integration/products/{externalId} para checar o estado atual.
  2. Se existir, use PUT (ou PATCH) ao invés de POST.
  3. Se não existir mas ainda assim receber 409, aguarde 5s e refaça o GET.

404 — Relação não integrada

Some of relation [attribute, category, brand] not integrated before. Causa: você está referenciando no payload um nome/ID que ainda não foi integrado. Verifique se categoria/marca/atributo já existem (via GET /byName ou GET /{externalId} dos respectivos endpoints) antes de reenviar o produto.

400 — Validação

  • mainSku em branco.
  • variations[].sku em branco.
  • Mais de uma variação main=true.
  • Categoria como hubeeId ao invés do nome completo (no V2, use sempre o caminho "A > B > C").

Performance — sincronizando 10k+ produtos

Paralelização

O limite prático por seller é ~20 requisições concorrentes no endpoint /v2/createupdate. Acima disso o backend começa a aplicar backpressure.

Estratégia recomendada:

              ┌── worker 1 (POST produtos 1..500)
              ├── worker 2 (POST produtos 501..1000)
fila ERP ────┼── worker 3 (POST produtos 1001..1500)
              ├── ...
              └── worker N (POST produtos 9501..10000)

Cada worker consome um batch local de 100-500 itens em sequência. A paralelização real acontece entre workers, não dentro de um único worker.

Tamanho de batch

  • Categorias/atributos em bulk: até 200 por chamada.
  • Produtos: o endpoint /v2/createupdate aceita um produto por chamada. Use pool de conexões HTTP persistente (Keep-Alive) e pipelining.

Idempotência

O campo externalId é a âncora de idempotência. Pode-se reexecutar a sincronização integral do catálogo a qualquer momento — produtos existentes serão atualizados em vez de duplicados.

Carga inicial vs incremental

  • Carga inicial: rode à noite ou em janela de baixo tráfego. 10k produtos com 3 variações em média = ~30k chamadas. A ~5/s por worker com 10 workers = ~10 min.
  • Incremental: monitore mudanças no ERP (timestamp de última alteração) e sincronize apenas o delta.

Read-after-write

O Gubee usa CQRS com eventual consistência entre comando e leitura. Após um POST /v2/createupdate retornar 200, o produto pode levar de 1 a 5 segundos para aparecer em GET /integration/products/{externalId}. Veja Eventual consistency e CQRS.

Checklist final

  • Categorias integradas (pais antes dos filhos).
  • Atributos integrados (variantes e não-variantes).
  • Marcas integradas.
  • Produtos upsertados via /v2/createupdate.
  • Tabela de mapeamento sku → skuId persistida no ERP (via /skuIds/bySkus).
  • Estoque e preço sincronizados — veja Estoque e preço.
  • Worker de pedidos em pé — veja Receber pedidos.

Próximos passos