OTL ShoesAPI
Exemplos em
Menu da documentação

Sincronizar catálogo e estoque

Uma carga inicial, depois só o que mudou. É a receita de quase toda integração.

A ideia

  1. Carga inicial: uma vez, percorra o catálogo inteiro e grave no seu sistema.
  2. Sincronização: a cada 5 a 15 minutos, peça só o que mudou desde a última vez.

Isso mantém o seu sistema atualizado com poucas chamadas por execução, muito abaixo do limite.

1. Carga inicial

Percorra as páginas até temMais ser false.

curl "https://api.otlshoes.com.br/v1/produtos?limite=100" \
  -H "Authorization: Bearer otl_prod_SEU_TOKEN"
curl "https://api-sandbox.otlshoes.com.br/v1/produtos?limite=100" \
  -H "Authorization: Bearer otl_sbx_SEU_TOKEN"
const resposta = await fetch('https://api.otlshoes.com.br/v1/produtos?limite=100', {
  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=100', {
  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=100');
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=100');
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": 100
    },
    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": 100
    },
    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.

const BASE = process.env.OTL_API_URL;      // https://api.otlshoes.com.br/v1
const TOKEN = process.env.OTL_API_TOKEN;

async function chamar(caminho) {
  const resposta = await fetch(BASE + caminho, { headers: { Authorization: `Bearer ${TOKEN}` } });

  if (resposta.status === 429) {
    // Passou do limite: espera o que a API mandou e tenta de novo.
    const segundos = Number(resposta.headers.get('Retry-After') ?? 5);
    await new Promise(r => setTimeout(r, segundos * 1000));
    return chamar(caminho);
  }
  if (!resposta.ok) {
    const { erro } = await resposta.json();
    throw new Error(`${erro.codigo}: ${erro.mensagem} (${erro.requestId})`);
  }
  return resposta.json();
}

async function percorrer(filtros, aoReceber) {
  let cursor = null;
  do {
    const params = new URLSearchParams({ limite: '100', ...filtros });
    if (cursor) params.set('cursor', cursor);

    const pagina = await chamar(`/produtos?${params}`);
    await aoReceber(pagina.dados);
    cursor = pagina.paginacao.proximoCursor;
  } while (cursor);
}

// Marque o início ANTES de começar: é o ponto de partida da primeira sincronização.
const inicio = new Date().toISOString();
await percorrer({}, produtos => salvarNoMeuSistema(produtos));
await guardarUltimaSincronizacao(inicio);
Por que marcar o horário antes

Se um produto mudar enquanto a carga roda, a marcação feita no início garante que a primeira sincronização o traga de novo. Marcando no fim, essa mudança se perderia.

2. Sincronização

Peça o que mudou desde a última execução. A data precisa do fuso — toISOString() já devolve com Z, que serve.

curl "https://api.otlshoes.com.br/v1/produtos?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100" \
  -H "Authorization: Bearer otl_prod_SEU_TOKEN"
curl "https://api-sandbox.otlshoes.com.br/v1/produtos?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100" \
  -H "Authorization: Bearer otl_sbx_SEU_TOKEN"
const resposta = await fetch('https://api.otlshoes.com.br/v1/produtos?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100', {
  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?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100', {
  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?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100');
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?atualizadoDesde=2026-09-29T17%3A00%3A00Z&limite=100');
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={
        "atualizadoDesde": "2026-09-29T17:00:00Z",
        "limite": 100
    },
    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={
        "atualizadoDesde": "2026-09-29T17:00:00Z",
        "limite": 100
    },
    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.

const desde = await lerUltimaSincronizacao();
const inicio = new Date().toISOString();

await percorrer({ atualizadoDesde: desde }, async produtos => {
  for (const produto of produtos) {
    if (!produto.ativo) {
      await tirarDoAr(produto.sku);          // saiu do catálogo da OTL
    } else {
      await atualizarNoMeuSistema(produto);   // preço, estoque, foto, título…
    }
  }
});

await guardarUltimaSincronizacao(inicio);

O que conta como mudança: cadastro, preço, foto, categorias e estoque. Um par vendido já faz o produto aparecer na próxima sincronização.

3. O que fazer com cada produto

Para cada numeração, três situações:

estoque O que significa O que fazer
Número maior que zero Há pares Vender normalmente
0 Sem pares agora Pausar a numeração no seu sistema
null Desconhecido Não tratar como zero. Consulte antes de vender

Use skuVariacao como SKU de cada variação no seu sistema. Se vier null, aquela numeração não tem SKU de variação — não monte um por conta própria.

4. Confirmar antes de vender

O número que você sincronizou pode ter mudado. Antes de aceitar uma venda, consulte o produto:

curl "https://api.otlshoes.com.br/v1/produtos/320/estoque" \
  -H "Authorization: Bearer otl_prod_SEU_TOKEN"
curl "https://api-sandbox.otlshoes.com.br/v1/produtos/320/estoque" \
  -H "Authorization: Bearer otl_sbx_SEU_TOKEN"
const resposta = await fetch('https://api.otlshoes.com.br/v1/produtos/320/estoque', {
  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/320/estoque', {
  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/320/estoque');
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/320/estoque');
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/320/estoque",
    headers={
        "Authorization": "Bearer otl_prod_SEU_TOKEN",
    },
    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/320/estoque",
    headers={
        "Authorization": "Bearer otl_sbx_SEU_TOKEN",
    },
    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.

Erros comuns

  • Rodar a carga inicial a cada execução. Gasta o limite e não traz nada que a sincronização não traga.
  • Mudar os filtros no meio da paginação. O cursor vale para a consulta em que foi gerado.
  • Guardar a data do fim da execução como ponto de partida da próxima, em vez da do início.
  • Tratar estoque: null como zero, tirando do ar um produto que tem pares.
  • Ignorar ativo: false, mantendo no ar um produto que saiu do catálogo.