Introdução
A API pública permite que ERPs autorizados enviem dados cadastrais e comerciais, consultem pedidos importados de marketplaces e confirmem seu processamento. Todos os endpoints listados nesta página foram identificados como disponíveis no contrato atual.
/api/v1. Mudanças futuras serão refletidas nesta página.Fluxo ERP → HUB → Marketplaces
- O ERP autentica cada requisição com as credenciais autorizadas.
- Dados cadastrais de produto são enviados para
/api/products. - Preço e estoque são enviados separadamente para
/api/products/price-stock. - O TotalSeller HUB controla o processamento entre a origem e os marketplaces.
- Pedidos disponíveis são consultados pelo ERP e confirmados após a importação.
O HUB é a camada de controle do fluxo. O integrador deve manter idempotência, tratar warnings e registrar as respostas relevantes para suporte operacional.
Base URL e formato
As rotas públicas usam o prefixo /api. A URL do ambiente autorizado é fornecida no processo de integração.
https://YOUR_BASE_URL/api/products
Envie Accept: application/json. Requisições com body JSON também devem enviar Content-Type: application/json. Filtros de GET são enviados por query string; POST, PUT e DELETE recebem JSON conforme cada contrato.
Autenticação
Todos os endpoints, exceto a renovação de credencial, exigem estes headers:
| Header | Obrigatório | Descrição |
|---|---|---|
Seller-Id | Sim | Identificador da credencial da API. |
Access-Token | Sim | Token de acesso ativo e não expirado. |
Accept: application/json
Content-Type: application/json
Seller-Id: YOUR_SELLER_ID
Access-Token: YOUR_ACCESS_TOKEN
Credenciais ausentes, inválidas, revogadas, inativas ou expiradas retornam HTTP 401.
{
"status": "error",
"code": 401,
"message": "Credenciais inválidas",
"data": null,
"errors": {
"credentials": "invalid"
}
}
Padrão de resposta JSON
A maioria dos endpoints responde com um envelope comum. O HTTP status acompanha o campo code.
{
"status": "success",
"code": 200,
"message": "Request processed successfully.",
"data": {},
"errors": [],
"warnings": []
}
Listagens paginadas também podem retornar:
{
"pagination": {
"current_page": 1,
"per_page": 10,
"total": 100,
"total_pages": 10
}
}
warnings sinaliza processamento parcial, dado ignorado ou uma situação que exige atenção. Uma resposta success com warnings não representa necessariamente sucesso integral.
PUT /api/refresh_token usa success: true|false, sem o envelope padrão.Boas práticas de integração
- Use HTTPS e armazene credenciais em um cofre de segredos ou variável protegida do servidor.
- Defina timeouts e trate separadamente falhas de rede, HTTP e validação.
- Leia
warningsmesmo quandostatusforsuccess. - Implemente retries com backoff apenas em operações seguras e após avaliar a idempotência.
- Use
custom_codecomo vínculo estável do SKU controlado pelo ERP. - Não envie preço ou estoque no endpoint cadastral de produtos.
- Não registre tokens nem respostas que contenham dados pessoais além do necessário.
- Valide em ambiente autorizado antes de ativar rotinas em produção.
Produtos
Campos cadastrais
| Campo | Tipo | POST | Observação |
|---|---|---|---|
custom_code | string | Obrigatório | SKU controlado pelo ERP; define o upsert no POST. |
parent_code | string | Obrigatório | Agrupa variações de um produto. |
ean | string | Opcional | Código EAN. |
title | string | Obrigatório | Título do SKU/produto. |
description | string | Opcional | Descrição do produto. |
category_code | string | Obrigatório | Categoria personalizada existente e final. |
brand_code | string | Obrigatório | Marca personalizada existente. |
cost_price | decimal | Opcional | Custo interno; não é preço de venda. |
weight, height, width, length | number | Opcional | Peso e dimensões. |
is_active | boolean/0/1 | Opcional | Produto é criado ativo por padrão. |
images | string[] | Opcional | Hashes retornados pelo upload de imagens. |
category, brand | object | Opcional | Objetos auxiliares para upsert; os códigos continuam obrigatórios. |
price, base_price, campos de estoque, channels e price_tables são rejeitados com HTTP 422. Envie dados comerciais somente para /api/products/price-stock.Buscar produtos
Filtros opcionais: custom_code, parent_code e integration_id. c003_id, C003_Id e account_id são aliases legados de integration_id. channel_id não seleciona uma integração com segurança; nesse caso o preço base é usado e a resposta inclui warning. Este endpoint não possui paginação.
curl -X GET 'https://YOUR_BASE_URL/api/products?custom_code=SKU-001&integration_id=12' \
-H 'Accept: application/json' \
-H 'Seller-Id: YOUR_SELLER_ID' \
-H 'Access-Token: YOUR_ACCESS_TOKEN'
Na resposta, base_price é sempre o preço base. price usa a tabela ativa vinculada à integração quando aplicável; sem tabela válida, usa o preço base; tabela ativa sem preço para o produto retorna 0. O bloco channels é somente leitura.
Criar ou atualizar por custom_code
O POST é um upsert: cria quando custom_code não existe e atualiza quando já existe.
{
"parent_code": "PRODUCT-001",
"custom_code": "SKU-001",
"ean": "7890000000001",
"title": "Example Product",
"description": "Example description",
"category_code": "CATEGORY-001",
"brand_code": "BRAND-001",
"cost_price": 60,
"weight": 0.5,
"height": 10,
"width": 20,
"length": 30,
"is_active": 1,
"images": ["IMAGE_HASH_1"]
}
Atualizar por ID
product_id deve ser inteiro positivo. O payload é parcial: somente campos enviados são alterados.
{
"title": "Updated Example Product",
"cost_price": 65
}
Preço e estoque
Atualiza preço base, estoque de origem e tabelas de preço sem criar produtos. products e price_tables são opcionais individualmente, mas pelo menos um bloco não vazio deve ser enviado.
{
"products": [
{
"custom_code": "SKU-001",
"base_price": 110.94,
"source_stock": 10
}
],
"price_tables": [
{
"table_code": "RETAIL",
"table_name": "Retail price",
"items": [
{
"custom_code": "SKU-001",
"price": 110.94
}
]
}
]
}
Campos
| Campo | Regra |
|---|---|
products[].custom_code | Obrigatório; deve identificar produto existente. |
products[].base_price | Decimal ≥ 0; preço base de venda. |
products[].source_stock | Decimal ≥ 0; recalcula estoque disponível. |
price_tables[].table_code | Obrigatório; string de até 80 caracteres. |
price_tables[].table_name | Obrigatório; string de até 120 caracteres. |
price_tables[].items | Lista não vazia. |
items[].custom_code | Obrigatório; produto existente. |
items[].price | Obrigatório; decimal ≥ 0. |
Cada item de products precisa conter base_price e/ou source_stock. Aliases como price, available_quantity, stock e quantity não são aceitos nesse bloco.
Regras comerciais
- Preço base é o fallback quando não há tabela válida vinculada.
- Tabela ativa sem preço para o produto resolve o preço como
0. - Atualizações são parciais; itens de tabela não enviados permanecem intactos.
- Produto inexistente gera warning e não é criado.
- O preço enviado ao marketplace pode receber ajustes configurados e proteção de markup mínimo.
- Estoque disponível é
max(source_stock - reserved_stock, 0).
GET /api/products retorna a resolução base/tabela. Ajustes adicionais e markup mínimo pertencem ao fluxo de envio ao marketplace e não aparecem nesse campo.Imagens
Importa uma ou várias imagens por URL. Cada item pode ser uma string ou um objeto {"url":"..."}.
{
"images": [
"https://cdn.example.com/product-1.jpg",
{ "url": "https://cdn.example.com/product-2.png" }
]
}
Regras confirmadas: apenas HTTP/HTTPS; hosts locais, privados ou reservados são bloqueados; credenciais embutidas na URL são bloqueadas; redirects não são seguidos; a resposta deve ser HTTP 200 com Content-Type: image/*; máximo de 10 MiB; largura mínima de 650 px. O retorno inclui hashes que podem ser usados em products[].images.
O processamento pode ser parcial e retornar warnings. Se nenhuma imagem puder ser processada, a API retorna HTTP 422.
Categorias personalizadas
A listagem aceita page (padrão 1) e limit (padrão 10). O POST aceita objeto ou lista e faz upsert por custom_code.
[
{
"custom_code": "CATEGORY-PARENT",
"name": "Parent Category",
"parent_code": ""
},
{
"custom_code": "CATEGORY-CHILD",
"name": "Child Category",
"parent_code": "CATEGORY-PARENT"
}
]
custom_code e name são obrigatórios. parent_code é opcional, mas o pai precisa existir antes do filho. Para excluir, envie {"custom_code":"CATEGORY-CHILD"}. A exclusão é bloqueada se a categoria não existir, tiver filhos ou estiver associada a produtos.
Categorias de canal
A raiz exige channel_id inteiro positivo. A busca de filhos também exige parent_code; não use o ID interno como pai.
[
{
"channel_id": 1,
"category_id": "CHANNEL-CATEGORY-001",
"custom_categories": [
"CATEGORY-CHILD",
"CATEGORY-OTHER"
]
}
]
category_id, apesar do nome, recebe o código da categoria no marketplace. custom_categories recebe os códigos das categorias personalizadas.Marcas personalizadas
A listagem é paginada. O POST aceita objeto ou lista e faz upsert por custom_code.
[
{
"custom_code": "BRAND-001",
"name": "Example Brand"
}
]
Para excluir, envie {"custom_code":"BRAND-001"}. A exclusão é bloqueada se a marca não existir ou estiver associada a produtos.
Marcas de canal
A listagem exige channel_id inteiro positivo. O contrato da associação espera uma lista:
[
{
"channel_id": 1,
"brand_id": 25,
"custom_brands": ["BRAND-001"]
}
]
brand_id é o ID retornado por GET /api/channel_brands. Duplicidades e referências inexistentes podem ser ignoradas com warnings.
Pedidos
Os endpoints permitem consultar pedidos já importados de marketplaces e confirmar o resultado da importação no ERP. Pedidos são limitados à integração resolvida para a credencial. Se a credencial não puder ser resolvida com segurança para uma única integração ativa, a API retorna HTTP 403 sem expor pedidos.
Listar pedidos disponíveis
Por padrão, retorna apenas pedidos com status PENDING_ERP. Quando não há registros, retorna HTTP 200 com data.orders: [].
| Query | Regra |
|---|---|
page | Padrão 1. |
limit | Padrão 50; máximo 100. |
include_all | Quando verdadeiro, libera filtros de outros status. |
sync_status | Aplicado somente com include_all. |
hub_status, marketplace_status | Filtros de status. |
marketplace_order_number, erp_order_number | Filtros por número. |
date_from, date_to | Data do marketplace; aceita data ou datetime. |
last_interaction_from, last_interaction_to | Janela da última interação. |
sku | Busca por SKU do seller ou código do ERP. |
curl -X GET 'https://YOUR_BASE_URL/api/orders?limit=50&sku=SKU-001' \
-H 'Accept: application/json' \
-H 'Seller-Id: YOUR_SELLER_ID' \
-H 'Access-Token: YOUR_ACCESS_TOKEN'
Detalhe do pedido
order_id é o identificador retornado na listagem. A resposta pode incluir dados do pedido, marketplace, integração, empresa, comprador, status, valores, datas, itens, pacotes de envio e estado de sincronização. Um pedido fora do escopo da integração retorna 404.
Confirmar importação no ERP
Sucesso exige erp_order_number:
{
"result": "success",
"erp_order_number": "ERP-123456",
"message": "Order imported successfully."
}
Erro exige message; erp_response é opcional e aceita string, objeto ou lista:
{
"result": "error",
"message": "Could not create the order in the ERP.",
"erp_response": {
"code": "CUSTOMER_VALIDATION_FAILED"
}
}
O sucesso é idempotente quando repetido com o mesmo número ERP. Se o pedido já estiver confirmado com outro número, retorna HTTP 409. Pedidos ainda aguardando marketplace também retornam 409. Status aceitos: PENDING_ERP, ERROR_ERP e CONFIRMED_BY_ERP apenas para idempotência com o mesmo número.
Renovação de credencial
Não envia body e não usa Access-Token. Envie:
Seller-Id: YOUR_SELLER_ID
Refresh-Token: YOUR_REFRESH_TOKEN
A renovação substitui os tokens anteriores. No comportamento padrão atual, access tokens valem 7 dias e refresh tokens 90 dias; esses prazos podem variar por configuração.
{
"success": true,
"seller_id": "YOUR_SELLER_ID",
"access_token": "NEW_ACCESS_TOKEN",
"refresh_token": "NEW_REFRESH_TOKEN",
"access_token_expires_at": "2026-07-07 12:00:00",
"refresh_token_expires_at": "2026-09-28 12:00:00",
"expires_in": 604800
}
Após mais de 10 tentativas em 5 minutos para a combinação de IP e Seller-Id, a API retorna HTTP 429. Tokens em texto puro são entregues somente na resposta de renovação; guarde-os com segurança.
Erros comuns
| HTTP | Causa comum | Ação |
|---|---|---|
| 401 | Credencial ausente, inválida, revogada ou expirada. | Confirme headers e renove a credencial quando aplicável. |
| 403 | Credencial sem escopo seguro para a operação. | Contate o suporte para revisar a autorização. |
| 404 | Recurso não encontrado ou fora do escopo. | Valide o identificador e o contexto da integração. |
| 409 | Conflito de estado ou idempotência. | Consulte o estado atual antes de repetir. |
| 422 | Payload inválido ou campos no endpoint incorreto. | Leia errors, corrija os campos e reenvie. |
| 429 | Limite de tentativas de renovação atingido. | Aguarde a janela e use backoff. |
| 500 | Falha interna ou um comportamento conhecido do contrato atual. | Registre horário e contexto sem tokens e acione o suporte. |
{
"status": "error",
"code": 422,
"message": "Campos obrigatórios não informados.",
"data": null,
"errors": {
"custom_code": [
"Campo obrigatório não informado: custom_code"
]
}
}
Comportamentos conhecidos
Estes pontos estão confirmados no contrato atual e podem ser normalizados em versões futuras:
- Listagens vazias de categorias e marcas personalizadas podem retornar 404 em vez de 200 com lista vazia.
- O refresh token não usa o envelope padrão.
- Atualização de produto inexistente pode retornar 500 em vez de 404 ou 422.
- Exclusão de marca/categoria inexistente ou vinculada pode retornar 500.
- Associação de categoria usa código de marketplace em
category_id; associação de marca usa ID embrand_id. - Não existe contrato público confirmado de estoque independente por canal.
- A listagem de produtos não é paginada.
- O preço da consulta de produtos não inclui ajustes adicionais nem proteção de markup mínimo.
API Console
Use o console ao lado (ou abaixo, em telas menores) para montar comandos cURL e, quando o ambiente permitir CORS, enviar uma requisição diretamente do navegador.