Listar produtos
O catálogo, com preço de atacado, preço sugerido, foto e estoque por numeração.
Quando usar
- Na carga inicial: pagine até o fim para trazer o catálogo inteiro.
- Na sincronização: com
atualizadoDesde, para trazer só o que mudou.
Para conferir o estoque de um produto na hora da venda, use Estoque do produto, que é mais leve.
Parâmetros
Todos opcionais, na query string.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
limite | número | 50 | Itens por página, de 1 a 100. |
cursor | texto | — | O proximoCursor da resposta anterior. Veja Paginação. |
atualizadoDesde | data | — | Só produtos alterados a partir desta data (com fuso). Inclui os desativados. Mudança de estoque conta como alteração. |
busca | texto | — | Só dígitos: procura no SKU. Texto: cada palavra tem de aparecer no título, em qualquer ordem. |
categoria | texto | — | Id de uma categoria. Veja Categorias. |
estilo | texto | — | Id de um estilo. |
tag | texto | — | Id de uma tag. |
numeracao | texto | — | Só produtos com estoque nesta numeração. Ex.: 38. |
comEstoque | booleano | — | true: com estoque em alguma numeração. false: sem estoque em nenhuma. |
ordem | texto | recentes | recentes, atualizacao, preco_asc, preco_desc ou titulo. Com atualizadoDesde, o padrão passa a ser atualizacao. |
Exemplo
curl "https://api.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true" \
-H "Authorization: Bearer otl_prod_SEU_TOKEN"curl "https://api-sandbox.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true" \
-H "Authorization: Bearer otl_sbx_SEU_TOKEN"const resposta = await fetch('https://api.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true', {
headers: {
Authorization: 'Bearer otl_prod_SEU_TOKEN',
},
});
if (!resposta.ok) {
const { erro } = await resposta.json();
throw new Error(`${erro.codigo}: ${erro.mensagem} (${erro.requestId})`);
}
const dados = await resposta.json();const resposta = await fetch('https://api-sandbox.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true', {
headers: {
Authorization: 'Bearer otl_sbx_SEU_TOKEN',
},
});
if (!resposta.ok) {
const { erro } = await resposta.json();
throw new Error(`${erro.codigo}: ${erro.mensagem} (${erro.requestId})`);
}
const dados = await resposta.json();<?php
$ch = curl_init('https://api.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer otl_prod_SEU_TOKEN',
]);
$corpo = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($corpo['erro']['codigo'] . ': ' . $corpo['erro']['mensagem']);
}<?php
$ch = curl_init('https://api-sandbox.otlshoes.com.br/v1/produtos?limite=2&comEstoque=true');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer otl_sbx_SEU_TOKEN',
]);
$corpo = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($corpo['erro']['codigo'] . ': ' . $corpo['erro']['mensagem']);
}import requests
resposta = requests.get(
"https://api.otlshoes.com.br/v1/produtos",
headers={
"Authorization": "Bearer otl_prod_SEU_TOKEN",
},
params={
"limite": 2,
"comEstoque": True
},
timeout=30,
)
if not resposta.ok:
erro = resposta.json()["erro"]
raise RuntimeError(f'{erro["codigo"]}: {erro["mensagem"]} ({erro["requestId"]})')
dados = resposta.json()import requests
resposta = requests.get(
"https://api-sandbox.otlshoes.com.br/v1/produtos",
headers={
"Authorization": "Bearer otl_sbx_SEU_TOKEN",
},
params={
"limite": 2,
"comEstoque": True
},
timeout=30,
)
if not resposta.ok:
erro = resposta.json()["erro"]
raise RuntimeError(f'{erro["codigo"]}: {erro["mensagem"]} ({erro["requestId"]})')
dados = resposta.json()Testar agora no ambiente de testes
A chamada é feita de verdade, do seu navegador para a API de testes (https://api-sandbox.otlshoes.com.br/v1). Use um token otl_sbx_…: ele fica só nesta aba, e some ao fechá-la. Token de produção não é aceito aqui.
Resposta
{
"dados": [
{
"sku": "320",
"titulo": "Tênis Adidas Forum Low Branco",
"imagem": "https://cdn.otlshoes.com.br/otl-catalog/drop/320.webp",
"precoAtacado": "139.90",
"precoSugerido": "259.90",
"precoAtual": "209.90",
"numeracoes": [
{
"numeracao": "38",
"skuVariacao": "320U",
"estoque": 4
},
{
"numeracao": "39",
"skuVariacao": "320V",
"estoque": 0
},
{
"numeracao": "40",
"skuVariacao": "320W",
"estoque": 7
}
],
"categorias": [
{
"id": "cmf1a2b3c0001",
"nome": "Casual"
}
],
"estilos": [
{
"id": "cmf1a2b3c0002",
"nome": "Masculino"
}
],
"tags": [
{
"id": "cmf1a2b3c0003",
"nome": "Lançamento"
}
],
"ativo": true,
"atualizadoEm": "2026-09-29T10:12:00-03:00"
},
{
"sku": "631",
"titulo": "Tênis Meia LED Homem Aranha",
"imagem": "https://cdn.otlshoes.com.br/otl-catalog/drop/631.webp",
"precoAtacado": "89.90",
"precoSugerido": "169.90",
"precoAtual": "139.90",
"numeracoes": [
{
"numeracao": "27/28",
"skuVariacao": "631J",
"estoque": 3
},
{
"numeracao": "29/30",
"skuVariacao": "631L",
"estoque": null
}
],
"categorias": [
{
"id": "cmf1a2b3c0009",
"nome": "Infantil"
}
],
"estilos": [],
"tags": [],
"ativo": true,
"atualizadoEm": "2026-09-28T16:40:00-03:00"
}
],
"paginacao": {
"proximoCursor": "eyJvIjoicmVjZW50ZXMiLCJ2IjoiMjAyNi0wOS0yOFQxOTo0MDowMC4wMDBaIiwiaWQiOiJjbWYxIn0",
"temMais": true
}
}
| Campo | Tipo | Descrição |
|---|---|---|
dados[].sku | texto | Identificador do produto. É o que você deve guardar. |
dados[].titulo | texto | Nome do produto. |
dados[].imagem | texto ou null | URL da foto, em WebP. |
dados[].precoAtacado | dinheiro | Quanto o parceiro paga à OTL por par. Informação reservada — não exiba ao cliente final. |
dados[].precoSugerido | dinheiro | Sugestão de preço de venda ao cliente final. É só uma referência: o parceiro define o preço dele. |
dados[].precoAtual | dinheiro ou null | O preço de venda deste parceiro: o atacado mais o acréscimo que ele definiu na loja do HUB (em percentual ou em reais, com o arredondamento que ele escolheu). null quando ele não tem loja no HUB. |
dados[].numeracoes[].numeracao | texto | A numeração. Pode ser uma faixa, como 27/28. |
dados[].numeracoes[].skuVariacao | texto ou null | SKU da variação, pronto: SKU do produto + sufixo da numeração. null para numeração fora da tabela. |
dados[].numeracoes[].estoque | número ou null | Pares disponíveis para pedido. null = desconhecido, não zero. |
dados[].categorias | lista | Cada item com id e nome. Idem estilos e tags. |
dados[].ativo | booleano | false = saiu do catálogo. Só aparece assim com atualizadoDesde. |
dados[].atualizadoEm | data | Última alteração — de cadastro, preço ou estoque. |
paginacao.proximoCursor | texto ou null | Mande em cursor para a próxima página. |
paginacao.temMais | booleano | Se há mais páginas. |
Regras que não se leem no JSON
- A listagem normal traz só produtos ativos. Com
atualizadoDesde, os desativados também vêm, comativo: false— é assim que o seu sistema fica sabendo que precisa tirar o produto do ar. estoque: nullnão é zero. Alguns produtos ainda não têm estoque por numeração no sistema. Trate como “consultar”.- Numeração com
estoque: 0continua na lista. Ela existe, só não tem par agora. - O estoque é o do momento da resposta. Confirme antes de fechar uma venda.
precoAtualé de cada parceiro. Dois parceiros veem o mesmoprecoAtacadoe valores diferentes emprecoAtual. É o mesmo preço que aparece na vitrine dele no HUB — e muda quando ele altera o acréscimo da loja, sem que o produto apareça ematualizadoDesde.- A ordenação por preço (
preco_asc,preco_desc) é pelo atacado. Como o acréscimo da loja é o mesmo para todos os produtos, a ordem peloprecoAtualé a mesma.
Sincronizando? Use a ordem padrão
Com atualizadoDesde, a ordem padrão (atualizacao, do mais antigo para o mais novo) garante que
um produto alterado durante a sua varredura vá para o fim e seja visto de novo, em vez de ser
pulado.
Erros possíveis
| HTTP | codigo |
Quando |
|---|---|---|
| 400 | PARAMETRO_INVALIDO |
limite fora de 1–100, ordem desconhecida, comEstoque diferente de true/false, data sem fuso, parâmetro repetido. detalhes[].campo diz qual |
| 400 | CURSOR_INVALIDO |
Cursor alterado, ou usado com outra ordem |
| 403 | ESCOPO_INSUFICIENTE |
A integração não tem produtos:ler |
| 429 | LIMITE_EXCEDIDO |
Passou do limite por minuto |
