# Vitry API - Documentação Completa > API REST para integração com lojas virtuais Vitry. Gerencie produtos, pedidos, clientes, categorias, marcas, atributos e webhooks de forma programática. ## Informações Gerais - Base URL: https://sualoja.com.br/api/v1 - Formato: JSON (enviar `Content-Type: application/json` e `Accept: application/json`) - Autenticação: API Key via header `X-API-Key` ou `Authorization: Bearer {chave}` - Rate limit: 60 requisições/minuto por chave (retorna 429 ao exceder) - Paginação: `page` (padrão: 1) e `per_page` (padrão: 25, máx: 100) - Ordenação: `sort` (campo) e `direction` (`asc` ou `desc`) ## Autenticação Todas as requisições exigem uma API Key. Gere chaves no painel admin: Configurações > Chaves de API. Envie a chave de uma destas formas: - Header `X-API-Key: SUA_CHAVE` (recomendado) - Header `Authorization: Bearer SUA_CHAVE` Erros de autenticação: - 401: API key não informada → `{"message": "API key não informada."}` - 401: API key inválida → `{"message": "API key inválida ou expirada."}` Gerenciamento de chaves: gerar, revogar, reativar, excluir. A chave completa é exibida apenas uma vez na criação. ## Formato de Resposta Item único: ```json {"data": {"id": 1, "name": "Produto Exemplo"}} ``` Listagem paginada: ```json { "data": [...], "links": {"first": "?page=1", "last": "?page=5", "prev": null, "next": "?page=2"}, "meta": {"current_page": 1, "from": 1, "last_page": 5, "per_page": 25, "to": 25, "total": 120} } ``` Exclusão: ```json {"message": "Produto removido."} ``` Erro de validação (422): ```json {"message": "Dados inválidos.", "errors": {"name": ["O campo nome é obrigatório."], "price": ["O campo preço deve ser um número."]}} ``` Erro de integridade (422): ```json {"message": "Não é possível excluir: 15 produto(s) vinculado(s)."} ``` ## Códigos de Status HTTP - 200 OK: Requisição processada com sucesso - 201 Created: Recurso criado com sucesso (POST) - 401 Unauthorized: API key ausente, inválida ou expirada - 404 Not Found: Recurso não encontrado - 422 Unprocessable Entity: Erro de validação ou restrição de integridade - 429 Too Many Requests: Rate limit excedido (60 req/min) - 500 Server Error: Erro interno do servidor ## Produtos Dois tipos: `simple` (SKU único, sem variações) e `variable` (múltiplas variantes com atributos). ### Listar produtos GET /api/v1/products Query parameters: - search (string): Busca por nome, SKU ou código de barras - brand_id (integer): Filtrar por marca - category_id (integer): Filtrar por categoria - active (boolean): Filtrar por status ativo - featured (boolean): Filtrar por produtos em destaque - type (string): `simple` ou `variable` - sku (string): Busca exata por SKU - price_min (number): Preço mínimo - price_max (number): Preço máximo - include (string): Incluir relações: `variants` - sort (string): Campo de ordenação (padrão: created_at) - direction (string): `asc` ou `desc` (padrão: desc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 1, "type": "variable", "name": "Camiseta Preta", "slug": "camiseta-preta", "sku": "CAM-001", "barcode": "7891234567890", "description": "Camiseta 100% algodão", "price": 79.90, "sale_price": 59.90, "promotion_price": null, "current_price": 59.90, "is_on_sale": true, "stock": 150, "weight": 0.3, "dimensions": {"length": 30, "width": 25, "height": 3}, "active": true, "featured": false, "is_new": true, "brand": {"id": 1, "name": "Nike"}, "categories": [{"id": 2, "name": "Camisetas"}], "images": { "cover": "https://sualoja.com.br/storage/.../cover.webp", "gallery": ["https://sualoja.com.br/storage/.../foto1.webp"] }, "variants": [{ "id": 10, "sku": "CAM-001-P", "price": null, "sale_price": null, "stock": 50, "attributes": {"Tamanho": "P", "Cor": "Preto"} }], "seo": {"title": "...", "description": "...", "keywords": "..."}, "rating_avg": 4.5, "rating_count": 23, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-03-10T14:22:00Z" }] } ``` ### Buscar produto GET /api/v1/products/{id} Retorna o produto com todas as relações (variantes, categorias, marca, imagens). ### Criar produto POST /api/v1/products Campos: - name (string, obrigatório): Nome do produto - type (string, obrigatório): `simple` ou `variable` - price (number, obrigatório): Preço (mínimo: 0) - sale_price (number): Preço promocional - sku (string): SKU (deve ser único) - barcode (string): Código de barras - description (string): Descrição do produto - stock (integer): Quantidade em estoque - weight (number): Peso em kg - length (number): Comprimento em cm - width (number): Largura em cm - height (number): Altura em cm - brand_id (integer): ID da marca - category_ids (array): Array de IDs de categorias - active (boolean): Ativo na loja (padrão: true) - featured (boolean): Produto em destaque - is_new (boolean): Lançamento - seo_title (string): Título para SEO - seo_description (string): Meta description para SEO - seo_keywords (string): Palavras-chave para SEO Retorna 201 Created com o produto criado. ### Atualizar produto PUT /api/v1/products/{id} Aceita os mesmos campos do POST, todos opcionais. Apenas os campos enviados serão atualizados. ### Excluir produto DELETE /api/v1/products/{id} Retorna: `{"message": "Produto removido."}` ## Variantes Variantes só existem em produtos do tipo `variable`. Cada variante representa uma combinação de atributos (ex: Tamanho P + Cor Azul). ### Listar variantes de um produto GET /api/v1/products/{product_id}/variants Exemplo de resposta: ```json { "data": [{ "id": 10, "product_id": 1, "variant_group_id": null, "sku": "CAM-001-P-VERM", "price": 89.90, "sale_price": null, "stock": 50, "weight": 0.3, "dimensions": {"length": 30, "width": 25, "height": 3}, "active": true, "order": 0, "attribute_values": [ {"id": 1, "attribute": "Cor", "value": "Vermelho"}, {"id": 5, "attribute": "Tamanho", "value": "P"} ], "images": {"cover": "...", "gallery": []}, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-03-10T14:22:00Z" }] } ``` ### Criar variante POST /api/v1/products/{product_id}/variants Campos: - attribute_value_ids (array, obrigatório): IDs dos valores de atributo (ex: [1, 5]) - sku (string): SKU da variante (deve ser único) - price (number): Preço (se diferente do produto pai) - sale_price (number): Preço promocional - stock (integer): Estoque da variante - weight (number): Peso em kg - length (number): Comprimento em cm - width (number): Largura em cm - height (number): Altura em cm - active (boolean): Ativa (padrão: true) - order (integer): Ordem de exibição Retorna 201 Created. ### Atualizar variante PUT /api/v1/products/{product_id}/variants/{variant_id} Aceita os mesmos campos do POST, todos opcionais. ### Excluir variante DELETE /api/v1/products/{product_id}/variants/{variant_id} Retorna: `{"message": "Variante removida."}` ## Categorias Categorias suportam hierarquia (pai > filhos). ### Listar categorias GET /api/v1/categories Query parameters: - search (string): Busca por nome - parent_id (integer): Filtrar por categoria pai - tree (boolean): `true` para retornar hierarquia completa (raízes com filhos aninhados) - sort (string): Campo de ordenação (padrão: order) - direction (string): `asc` ou `desc` (padrão: asc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 1, "parent_id": null, "name": "Roupas", "slug": "roupas", "description": "Roupas masculinas e femininas", "order": 1, "show_on_home": true, "icon": "https://sualoja.com.br/storage/.../icon.webp", "children_count": 3, "products_count": 45, "children": [{"id": 2, "parent_id": 1, "name": "Camisetas", "slug": "camisetas", "children_count": 0, "products_count": 20}], "seo": {"title": "...", "description": "...", "keywords": "..."}, "created_at": "2026-01-10T08:00:00Z", "updated_at": "2026-03-05T12:00:00Z" }] } ``` ### Buscar categoria GET /api/v1/categories/{id} Retorna a categoria com subcategorias. ### Criar categoria POST /api/v1/categories Campos: - name (string, obrigatório): Nome da categoria - parent_id (integer): ID da categoria pai (para subcategorias) - description (string): Descrição - order (integer): Ordem de exibição - show_on_home (boolean): Exibir na home - seo_title (string): Título para SEO - seo_description (string): Meta description - seo_keywords (string): Palavras-chave ### Atualizar categoria PUT /api/v1/categories/{id} Aceita os mesmos campos do POST, todos opcionais. ### Excluir categoria DELETE /api/v1/categories/{id} Restrição: Não é possível excluir categoria com produtos vinculados (retorna 422). ## Marcas ### Listar marcas GET /api/v1/brands Query parameters: - search (string): Busca por nome - active (boolean): Filtrar por status ativo - sort (string): Campo de ordenação (padrão: name) - direction (string): `asc` ou `desc` (padrão: asc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 1, "name": "Nike", "slug": "nike", "description": "Marca esportiva internacional", "website": "https://nike.com", "active": true, "logo": "https://sualoja.com.br/storage/.../nike-logo.webp", "products_count": 120, "created_at": "2026-01-10T08:00:00Z", "updated_at": "2026-03-05T12:00:00Z" }] } ``` ### Buscar marca GET /api/v1/brands/{id} ### Criar marca POST /api/v1/brands Campos: - name (string, obrigatório): Nome da marca - description (string): Descrição - website (string): URL do site oficial - active (boolean): Ativa (padrão: true) - seo_title (string): Título para SEO - seo_description (string): Meta description - seo_keywords (string): Palavras-chave ### Atualizar marca PUT /api/v1/brands/{id} Aceita os mesmos campos do POST, todos opcionais. ### Excluir marca DELETE /api/v1/brands/{id} Restrição: Não é possível excluir marca com produtos vinculados (retorna 422). ## Atributos Atributos de variação (ex: Cor, Tamanho) e seus valores. Usados para criar variantes de produtos. ### Listar atributos GET /api/v1/attributes Query parameters: - search (string): Busca por nome - sort (string): Campo de ordenação (padrão: order) - direction (string): `asc` ou `desc` (padrão: asc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 1, "name": "Cor", "display_name": null, "slug": "cor", "order": 0, "values": [ {"id": 1, "attribute_id": 1, "value": "Vermelho", "slug": "vermelho", "display_value": null, "order": 0, "icon": null}, {"id": 2, "attribute_id": 1, "value": "Azul", "slug": "azul", "display_value": "#0000FF", "order": 1, "icon": null} ], "created_at": "2026-01-10T08:00:00Z", "updated_at": "2026-01-10T08:00:00Z" }] } ``` ### Buscar atributo GET /api/v1/attributes/{id} Retorna o atributo com todos os seus valores. ### Criar atributo POST /api/v1/attributes Campos: - name (string, obrigatório): Nome do atributo (ex: "Cor", "Tamanho") - order (integer): Ordem de exibição ### Atualizar atributo PUT /api/v1/attributes/{id} ### Excluir atributo DELETE /api/v1/attributes/{id} Restrição: Não é possível excluir atributo com valores vinculados a variantes (retorna 422). ### Valores de Atributo #### Listar valores GET /api/v1/attributes/{attribute_id}/values #### Criar valor POST /api/v1/attributes/{attribute_id}/values Campos: - value (string, obrigatório): Valor (ex: "Vermelho", "P", "Algodão") - display_value (string): Valor de exibição (ex: código de cor "#FF0000") - order (integer): Ordem de exibição #### Atualizar valor PUT /api/v1/attributes/{attribute_id}/values/{value_id} #### Excluir valor DELETE /api/v1/attributes/{attribute_id}/values/{value_id} Restrição: Não é possível excluir valor vinculado a variantes (retorna 422). ## Pedidos ### Listar pedidos GET /api/v1/orders Query parameters: - status (string): Filtrar por status do pedido - payment_status (string): Filtrar por status de pagamento - customer_id (integer): Filtrar por cliente - order_number (string): Filtrar por número do pedido - date_from (date): Pedidos a partir desta data (YYYY-MM-DD) - date_to (date): Pedidos até esta data (YYYY-MM-DD) - sort (string): Campo de ordenação (padrão: created_at) - direction (string): `asc` ou `desc` (padrão: desc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 42, "order_number": "2026-00042", "customer": {"id": 5, "name": "João Silva", "email": "joao@email.com"}, "guest_name": null, "guest_email": null, "status": "processing", "payment_status": "paid", "payment_method": "pix", "items": [{ "id": 100, "product_id": 1, "variant_id": 10, "product_name": "Camiseta Preta", "variant_label": "P / Preto", "sku": "CAM-001-P", "quantity": 2, "unit_price": 59.90, "total": 119.80 }], "shipping": { "method": "PAC", "cost": 14.90, "days": 8, "tracking_code": "BR123456789BR", "tracking_url": "https://rastreamento.correios.com.br/...", "address": { "name": "João Silva", "phone": "(11) 98765-4321", "zip": "01001-000", "street": "Rua A", "number": "100", "complement": null, "district": "Centro", "city": "São Paulo", "state": "SP" } }, "subtotal": 119.80, "discount": 10.00, "pix_discount": 0, "shipping_cost": 14.90, "total": 124.70, "coupon_code": "DESCONTO10", "notes": null, "paid_at": "2026-03-15T10:30:00Z", "shipped_at": null, "delivered_at": null, "cancelled_at": null, "created_at": "2026-03-15T10:25:00Z", "updated_at": "2026-03-15T10:30:00Z" }] } ``` ### Buscar pedido GET /api/v1/orders/{id} Retorna o pedido completo com cliente, itens e eventos. ### Criar pedido POST /api/v1/orders Cria um pedido diretamente, sem passar pelo carrinho. Ideal para integrações com ERPs. Envie `customer_id` para vincular a um cliente cadastrado, ou envie `guest_name` e `guest_email` para um pedido de visitante. Campos: - customer_id (integer): ID do cliente (obrigatório se não enviar guest_*) - guest_name (string): Nome do visitante - guest_email (string): E-mail do visitante - guest_phone (string): Telefone do visitante - payment_method (string, obrigatório): `pix`, `credit_card` ou `boleto` - items (array, obrigatório): Lista de itens (mínimo 1) - items[].product_id (integer, obrigatório): ID do produto - items[].variant_id (integer): ID da variante - items[].quantity (integer, obrigatório): Quantidade (mínimo: 1) - items[].unit_price (number, obrigatório): Preço unitário - shipping_name (string, obrigatório): Nome do destinatário - shipping_zip (string, obrigatório): CEP - shipping_street (string, obrigatório): Rua - shipping_number (string, obrigatório): Número - shipping_complement (string): Complemento - shipping_district (string, obrigatório): Bairro - shipping_city (string, obrigatório): Cidade - shipping_state (string, obrigatório): Estado (UF) - shipping_phone (string): Telefone para entrega - shipping_method (string, obrigatório): Método de envio (ex: "PAC", "SEDEX") - shipping_cost (number, obrigatório): Custo do frete - shipping_days (integer): Prazo em dias - subtotal (number, obrigatório): Subtotal dos itens - discount (number): Desconto aplicado - total (number, obrigatório): Valor total do pedido - notes (string): Observações do pedido Retorna 201 Created. Pedido criado com status: pending e payment_status: pending. Evento order.created é disparado automaticamente via webhook. ### Atualizar pedido PUT /api/v1/orders/{id} Atualiza status, rastreamento e observações. Não permite alterar itens. Campos: - status (string): Novo status do pedido - payment_status (string): Novo status de pagamento - tracking_code (string): Código de rastreamento - tracking_url (string): URL de rastreamento - notes (string): Observações - shipped_at (datetime): Data de envio - delivered_at (datetime): Data de entrega Timestamps automáticos: - Ao alterar status para `shipped`, o campo `shipped_at` é preenchido automaticamente (se não enviado). - Ao alterar status para `delivered`, o campo `delivered_at` é preenchido automaticamente (se não enviado). ### Status do pedido - pending: Aguardando pagamento - processing: Pagamento confirmado, preparando - paid: Pago - shipped: Enviado - delivered: Entregue - cancelled: Cancelado - refunded: Reembolsado ### Status de pagamento - pending: Aguardando - paid: Pago - failed: Falhou - refunded: Reembolsado ### Métodos de pagamento - pix: PIX - credit_card: Cartão de crédito - boleto: Boleto bancário ## Clientes Suporta pessoa física (PF) e pessoa jurídica (PJ). ### Listar clientes GET /api/v1/customers Query parameters: - search (string): Busca por nome, e-mail ou razão social - status (string): `active` ou `inactive` - type (string): `pf` (pessoa física) ou `pj` (pessoa jurídica) - email (string): Busca exata por e-mail - cpf (string): Filtrar por CPF - cnpj (string): Filtrar por CNPJ - sort (string): Campo de ordenação (padrão: created_at) - direction (string): `asc` ou `desc` (padrão: desc) - per_page (integer): Itens por página (máx: 100, padrão: 25) Exemplo de resposta: ```json { "data": [{ "id": 5, "type": "pf", "name": "João Silva", "email": "joao@email.com", "cpf": "123.456.789-00", "cnpj": null, "company_name": null, "phone": "(11) 3456-7890", "mobile": "(11) 98765-4321", "status": "active", "birth_date": "1990-05-15", "addresses": [{ "id": 1, "label": "Casa", "cep": "01001-000", "street": "Rua A", "number": "100", "complement": "Apto 42", "district": "Centro", "city": "São Paulo", "state": "SP", "is_default": true }], "orders_count": 8, "created_at": "2026-01-20T14:00:00Z", "updated_at": "2026-03-10T09:30:00Z" }] } ``` ### Buscar cliente GET /api/v1/customers/{id} Retorna o cliente com endereços e contagem de pedidos. ### Criar cliente POST /api/v1/customers Campos: - name (string, obrigatório): Nome completo ou razão social - email (string, obrigatório): E-mail (deve ser único) - type (string, obrigatório): `pf` (pessoa física) ou `pj` (pessoa jurídica) - password (string, obrigatório): Senha (mínimo 6 caracteres) - cpf (string): CPF (para PF) - cnpj (string): CNPJ (para PJ) - company_name (string): Razão social (para PJ) - phone (string): Telefone fixo - mobile (string): Celular - status (string): `active` ou `inactive` (padrão: active) - birth_date (date): Data de nascimento (formato: YYYY-MM-DD) ### Atualizar cliente PUT /api/v1/customers/{id} Aceita os mesmos campos do POST, todos opcionais. O campo `password` é opcional na atualização. ### Desativar cliente DELETE /api/v1/customers/{id} Soft delete: o status é alterado para `inactive`, preservando o histórico de pedidos. Retorna: `{"message": "Cliente desativado."}` ### Tipos de cliente - pf (Pessoa Física): campos específicos `cpf`, `birth_date` - pj (Pessoa Jurídica): campos específicos `cnpj`, `company_name` ## Webhooks Receba notificações em tempo real quando eventos acontecem na loja via HTTP POST. ### Eventos disponíveis - order.created: Pedido criado - order.paid: Pagamento confirmado - order.cancelled: Pedido cancelado - order.shipped: Pedido enviado - product.created: Produto criado - product.updated: Produto atualizado - product.deleted: Produto excluído - customer.created: Cliente cadastrado ### Listar webhooks GET /api/v1/webhooks ### Registrar webhook POST /api/v1/webhooks Campos: - url (string, obrigatório): URL que receberá os POST (HTTPS recomendado) - events (array, obrigatório): Lista de eventos para ouvir A resposta inclui um `secret` que é exibido apenas uma vez. Use-o para verificar a assinatura das notificações. ### Remover webhook DELETE /api/v1/webhooks/{id} ### Formato das notificações Headers enviados: - Content-Type: application/json - User-Agent: Vitry-Webhook/1.0 - X-Vitry-Event: Nome do evento (ex: order.created) - X-Vitry-Delivery: UUID único desta entrega - X-Vitry-Signature: Assinatura HMAC-SHA256 do body Body: ```json { "event": "order.created", "data": {"id": 42, "order_number": "2026-00042", "customer": {"id": 5, "name": "João Silva"}, "status": "pending", "total": 124.70}, "timestamp": "2026-03-15T10:25:00Z" } ``` O campo `data` contém o recurso completo no mesmo formato da API REST. ### Verificação de assinatura Calcule o HMAC-SHA256 do body usando o secret do webhook e compare com o header X-Vitry-Signature: PHP: ```php $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_VITRY_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $payload, $webhookSecret); if (!hash_equals($expected, $signature)) { http_response_code(401); exit; } ``` JavaScript: ```javascript const expected = crypto.createHmac('sha256', webhookSecret).update(payload).digest('hex'); if (signature !== expected) return res.status(401).send('Invalid'); ``` ### Retentativas Se a URL retornar status diferente de 2xx: - 1a tentativa: imediata - 2a tentativa: 10 segundos - 3a tentativa: 60 segundos - Timeout: 10 segundos por requisição - Após 5 falhas consecutivas: webhook desativado automaticamente ### Boas práticas - Responda rápido: retorne 200 OK imediatamente e processe de forma assíncrona - Verifique a assinatura: sempre valide o header X-Vitry-Signature - Use HTTPS - Idempotência: use o header X-Vitry-Delivery para evitar processar duplicatas - Logs: armazene os payloads recebidos para debugging