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¶
sellerIdjá 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):
Visão geral do fluxo¶
Fluxo recomendado (90% dos casos):
- Produtos — loop de
POST /integration/products/v2/createupdateenviandomainCategory(string com>),brand,specificationsevariations[].variantSpecificationno mesmo payload. Backend resolve tudo.
Fluxo alternativo (catálogo massivo, 10k+ produtos):
- Categorias — enviar em bulk, pais antes dos filhos (uma vez).
- Atributos — enviar em bulk (uma vez).
- Marcas — enviar em loop ou bulk (uma vez).
- Produtos — upsert via
POST /integration/products/v2/createupdate(passamainCategorycomo string; backend reutiliza a categoria já criada sem duplicar). - Mapear SKUs entre o ERP e o
skuIdinterno 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:
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 porurl+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:
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:
- Faça
GET /integration/products/{externalId}para checar o estado atual. - Se existir, use
PUT(ouPATCH) ao invés dePOST. - 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¶
mainSkuem branco.variations[].skuem branco.- Mais de uma variação
main=true. - Categoria como
hubeeIdao 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/createupdateaceita 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 → skuIdpersistida 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¶
- Estoque e preço — sync de quantidade e preço por SKU.
- Receber pedidos — fila de pedidos por status.
- Faturamento e DANFE — emissão de notas via invoicer.
- Etiquetas de envio — geração de tags e PLP.