Catálogo da vitrine
Como a API pública de navegação lista produtos, categorias, marcas e filtros, como ela mostra preço e estoque e o que muda entre visitante e cliente logado.
O catálogo da vitrine é a parte da API que o comprador usa para navegar: listar produtos, abrir a página de um produto, percorrer categorias e marcas e refinar com filtros. É a mesma API que a loja web da Venddor e os apps móveis consomem, e você pode usá-la para montar a sua própria vitrine (um site headless, um app, um quiosque) ou para sincronizar o catálogo público com outro sistema.
Todas as rotas desta seção são GET (exceto onde indicado), ficam sob /api/v1/storefront/... e não exigem login na configuração padrão. A loja é identificada pelo domínio da requisição ou pelo cabeçalho X-Tenant-ID (veja Lojas e tenants).
Como funciona
Uma vitrine típica faz estas chamadas, nesta ordem:
- Montar o menu
GET /categories/navdevolve as categorias em formato leve, já respeitando o idioma e a regra de "ocultar categorias vazias" da loja. Use/categories/treequando precisar da árvore aninhada. - Listar os produtos de uma categoria
GET /products?category_id=...compage,per_page, ordenação e filtros de preço, marca e estoque. A resposta traz o cartão de cada produto: nome, imagens, preço, estoque, vendedor e o caminho canônico da URL. - Montar a barra de filtros
GET /categories/{id}/available-filtersdevolve os filtros da categoria com a contagem de produtos de cada valor, já levando em conta o que o comprador marcou. Reenvie as escolhas comofeatures[id]=valorna listagem. - Abrir um produto
GET /products/{id}aceita UUID ou slug e devolve o produto completo: SEO, breadcrumb, escadas de preço, vídeos. O restante da página (opções, variações, características, anexos) vem das rotas de Página de produto. - Atualizar preços depois do login
Páginas em cache mostram o preço de visitante. Depois do login,
GET /products/prices?ids=...troca em lote pelos preços reais do cliente, sem refazer as listas.
Status de produto
A vitrine só enxerga A. O campo status existe na resposta e é sempre A.
status | Significado | Aparece na vitrine |
|---|---|---|
A | Ativo e aprovado | Sim |
D | Desativado | Não (404) |
H | Oculto | Não (404) |
R | Em moderação (aguardando aprovação) | Não (404) |
X | Rejeitado | Não (404) |
Status de categoria
status | Significado | Aparece na vitrine |
|---|---|---|
A | Ativa | Sim |
D | Desativada | Não |
H | Oculta | Não |
Uma categoria desativada ou oculta esconde também toda a sua subárvore: GET /categories/{id} de uma filha devolve 404 enquanto um ancestral estiver inativo.
Conceitos-chave
Preço, desconto e preço de visitante
price é o preço unitário atual em centavos. list_price (pode ser null) é o preço "de" que a loja risca quando há promoção cadastrada no produto. catalog_discount é o desconto por unidade, em centavos, de uma promoção de catálogo ativa: o valor que o carrinho de fato cobra é price - catalog_discount. O carrinho e a vitrine usam a mesma conta.
Para cliente logado, price já vem com a tabela do grupo de usuário dele (por exemplo, tabela regional ou atacado) quando existe preço por grupo para a quantidade 1. Visitante vê o preço base. Se a loja tiver desligado "visitante vê preço", os preços saem zerados e price_redacted vem true: mostre "Entre para ver o preço".
price_tiers é a escada de preço por quantidade (preços universais). Detalhes de cada degrau ficam na rota de preço por quantidade, fora desta seção.
Estoque e disponibilidade
| Campo | Tipo | Significado |
|---|---|---|
track_inventory | boolean | false = estoque ilimitado, ignore quantity. |
quantity | número | Saldo na unidade de venda. Pode ser fracionado (0.5 kg). amount é um alias do mesmo valor. |
out_of_stock_action | string | O que fazer com saldo zero: N esconde o botão de compra (padrão), B aceita venda sem estoque (encomenda), S mostra o aviso "avise-me quando chegar". |
min_quantity, max_quantity, quantity_step | número | Regras de quantidade na compra. 0 em min/max = sem limite. |
sale_unit | string | Unidade de venda: un, kg, cx, l, par ou personalizada. |
zero_price_action | string | O que fazer com preço zero: P usa o preço, R pede orçamento (sem compra), A permite comprar de graça. |
Duas configurações da loja mudam o que a lista mostra: se "mostrar produtos sem estoque" estiver desligado, a lista só traz produtos com saldo (ou sem controle de estoque); se "mostrar produtos de preço zero" estiver desligado, produtos com preço zero somem e o GET /products/{id} deles dá 404. Em listagens de busca ou de categoria, produtos indisponíveis vão para o fim, sem mudar a ordem escolhida dentro de cada grupo.
Vendedores (marketplace)
Em lojas com vários vendedores (sellers, ou lojistas), cada produto pertence a um vendedor: vendor_id, vendor_name, vendor_slug (e os aliases company_id, company_name). Produtos da própria loja não têm vendedor. Com o módulo de produtos mestre ligado, vários vendedores podem ofertar o mesmo produto: a lista colapsa as ofertas num único cartão, o da oferta mais barata visível, e o link do cartão já aponta para essa oferta. Se a loja filtra por região de entrega, só aparecem vendedores que entregam ao CEP do comprador (veja "Região de entrega" abaixo).
SEO
A página de produto traz seo.meta_title, seo.meta_description e seo.canonical_url já resolvidos (com os textos padrão da loja quando o produto não tem os próprios), breadcrumb e categories (a cadeia da raiz até a categoria do produto). Cada produto da lista traz canonical_path, a URL canônica em segmentos (["escritorio", "zz-doc-midia-teste", "p"] vira /escritorio/zz-doc-midia-teste/p) e slugs com os slugs por idioma, usados para hreflang. Categorias trazem seo_title, seo_description e meta_keywords quando preenchidos. Slugs antigos de produto redirecionam: veja Consultar produto.
Idioma
Envie ?lang=pt-BR (ou en, es) ou o cabeçalho Accept-Language. O nome, a descrição e os textos de produtos, categorias e marcas voltam no idioma pedido; quando não há tradução, vale o texto base. Sem nenhum dos dois, o texto base.
Cache e cabeçalhos
| Situação | Cache-Control |
|---|---|
| Visitante, rota de catálogo | public, max-age=300, stale-while-revalidate=1800 |
Menu de categorias (/categories/nav) | public, max-age=60, stale-while-revalidate=300 |
| Preço e escada de preço de um produto | no-store |
Qualquer rota com Authorization | private, no-store |
| Loja com filtro de região de entrega ativo | private, no-store |
| Erros | no-store, no-cache, must-revalidate |
As respostas também trazem ETag e Vary: X-Tenant-ID. Como o cache público dura minutos, uma mudança de preço feita pelo lojista pode levar até 5 minutos para aparecer a visitantes; para preços atuais use /products/prices ou o carrinho.
Região de entrega
Em lojas que ativaram o filtro por região de entrega, o catálogo esconde vendedores que não entregam ao comprador. A região vem, nesta ordem, de deliver_to_state, deliver_to_city, deliver_to_district e deliver_to_cep (ou buyer_state... e cep) na query, dos cabeçalhos X-Buyer-State, X-Buyer-City, X-Buyer-District, X-Buyer-CEP, ou do endereço padrão do cliente logado. Sem região, nada é filtrado. Com filtro ativo a resposta é private, no-store.
Visitante, cliente e chave de API
| Visitante | Cliente logado (JWT) | Chave de API | |
|---|---|---|---|
| Acesso | Livre, a menos que a loja exija login (401) | Sempre liberado | Precisa do escopo storefront:read (403 missing_scope) |
| Produtos restritos a grupos | Só os públicos | Também os do grupo do cliente | Conforme o dono da chave |
| Preço | Base (ou zerado se a loja esconde) | Preço do grupo do cliente | Conforme o dono da chave |
| Cache | Público | private, no-store | Conforme o caso |
Para obter o token do cliente, veja Autenticação.
Endpoints
| Método e caminho | Para que serve | Auth | Página |
|---|---|---|---|
GET /api/v1/storefront/products | Listar produtos com filtros, ordenação e paginação | Opcional | Listar produtos |
GET /api/v1/storefront/products/{id} | Produto completo por UUID ou slug | Opcional | Consultar produto |
GET /api/v1/storefront/products/by-barcode | Achar produto pelo código de barras | Opcional | Consultas rápidas |
GET /api/v1/storefront/products/prices | Preços reais de vários produtos de uma vez | Opcional | Consultas rápidas |
GET /api/v1/storefront/categories | Todas as categorias ativas, em lista plana | Opcional | Categorias |
GET /api/v1/storefront/categories/visible | Mesma lista, sem categorias vazias se a loja pedir | Opcional | Categorias |
GET /api/v1/storefront/categories/nav | Menu: mesma lista em formato leve | Opcional | Categorias |
GET /api/v1/storefront/categories/tree | Árvore aninhada de categorias | Opcional | Categorias |
GET /api/v1/storefront/categories/{id} | Uma categoria | Opcional | Categorias |
GET /api/v1/storefront/categories/{id}/filters | Filtros da categoria com contagem | Opcional | Filtros de categoria |
GET /api/v1/storefront/categories/{id}/available-filters | Filtros com contagem que reflete as escolhas atuais | Opcional | Filtros de categoria |
GET /api/v1/storefront/filters | Filtros globais ou de uma categoria | Opcional | Filtros de categoria |
GET /api/v1/storefront/features/{id} | Uma característica e seus valores | Opcional | Filtros de categoria |
GET /api/v1/storefront/brands | Marcas com produtos ativos | Opcional | Marcas |
GET /api/v1/storefront/brands/{slug} | Uma marca | Opcional | Marcas |
GET /api/v1/storefront/brands/{slug}/products | Produtos de uma marca | Opcional | Marcas |
GET /api/v1/storefront/feeds/{id} | Feed de produtos por id (módulo de feeds) | Não | Feeds de produtos |
GET /api/v1/feeds/{token} | Feed de produtos pelo link secreto | Token na URL | Feeds de produtos |
Erros comuns
- Esperar
404em página vazia. Pedir uma página além do fim devolve200com"products": null. Pare quandopage >= total_pages. - Pedir
per_pagemaior que 100. Não é limitado a 100: volta ao padrão de 20. Use no máximo 100. - Usar
features_hashcom ids em UUID. O formato separa valores por hífen e os UUIDs também têm hífen, então o filtro não casa. Usefeatures[featureId]=variantId. - Tratar
pricecomo o valor cobrado. Subtraiacatalog_discount. E lembre que visitante e cliente podem ver preços diferentes. - Mostrar preço de uma resposta em cache a um cliente logado. Troque os preços com
/products/pricesdepois do login. - Chamar
GET /categories/{id}com slug. Categorias só aceitam UUID; o slug dá 404. Resolva o slug pela lista de categorias. - Mandar
idscom mais de 200 itens. Os excedentes são ignorados em silêncio, em/productse em/products/prices.