OTL ShoesAPI
Exemplos em
Menu da documentação

Listar produtos

O catálogo, com preço de atacado, preço sugerido, foto e estoque por numeração.

GET/v1/produtos

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âmetroTipoPadrãoDescrição
limitenúmero50Itens por página, de 1 a 100.
cursortexto—O proximoCursor da resposta anterior. Veja Paginação.
atualizadoDesdedata—Só produtos alterados a partir desta data (com fuso). Inclui os desativados. Mudança de estoque conta como alteração.
buscatexto—Só dígitos: procura no SKU. Texto: cada palavra tem de aparecer no título, em qualquer ordem.
categoriatexto—Id de uma categoria. Veja Categorias.
estilotexto—Id de um estilo.
tagtexto—Id de uma tag.
numeracaotexto—Só produtos com estoque nesta numeração. Ex.: 38.
comEstoquebooleano—true: com estoque em alguma numeração. false: sem estoque em nenhuma.
ordemtextorecentesrecentes, 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
  }
}
CampoTipoDescrição
dados[].skutextoIdentificador do produto. É o que você deve guardar.
dados[].titulotextoNome do produto.
dados[].imagemtexto ou nullURL da foto, em WebP.
dados[].precoAtacadodinheiroQuanto o parceiro paga à OTL por par. Informação reservada — não exiba ao cliente final.
dados[].precoSugeridodinheiroSugestão de preço de venda ao cliente final. É só uma referência: o parceiro define o preço dele.
dados[].precoAtualdinheiro ou nullO 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[].numeracaotextoA numeração. Pode ser uma faixa, como 27/28.
dados[].numeracoes[].skuVariacaotexto ou nullSKU da variação, pronto: SKU do produto + sufixo da numeração. null para numeração fora da tabela.
dados[].numeracoes[].estoquenúmero ou nullPares disponíveis para pedido. null = desconhecido, não zero.
dados[].categoriaslistaCada item com id e nome. Idem estilos e tags.
dados[].ativobooleanofalse = saiu do catálogo. Só aparece assim com atualizadoDesde.
dados[].atualizadoEmdataÚltima alteração — de cadastro, preço ou estoque.
paginacao.proximoCursortexto ou nullMande em cursor para a próxima página.
paginacao.temMaisbooleanoSe 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, com ativo: false — é assim que o seu sistema fica sabendo que precisa tirar o produto do ar.
  • estoque: null não é zero. Alguns produtos ainda não têm estoque por numeração no sistema. Trate como “consultar”.
  • Numeração com estoque: 0 continua 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 mesmo precoAtacado e valores diferentes em precoAtual. É 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 em atualizadoDesde.
  • 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 pelo precoAtual é 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

Ver também