openapi: 3.1.0
info:
  title: UniSupri Marketplace API
  description: |-
    API REST do marketplace **UniSupri** para vendedores e integradores.
    Utilize esta documentação para integrar seu ERP, sistema de gestão ou automação com a plataforma.
    
    ## Autenticação
    
    Todos os endpoints exigem um **Token de Integração** no header `Authorization`.
    Crie seu token no painel do seller em **Configurações → Tokens de Integração**.
    
    ```
    Authorization: Bearer sk_seu_token_aqui
    ```
    
    > O token tem o prefixo `sk_` e não expira, a menos que você defina uma data de expiração ou o revogue manualmente.
    
    ## Formato de Resposta
    
    Todas as respostas seguem um envelope padronizado:
    
    ```json
    {
      "success": true,
      "code": 200,
      "message_code": "SUCCESS",
      "description": "The request was successful.",
      "data": {},
      "meta": {}
    }
    ```
    
    Em caso de erro, o campo `success` será `false`, `message_code` conterá o código do erro e `errors` trará os detalhes:
    
    ```json
    {
      "success": false,
      "code": 422,
      "message_code": "VALIDATION_ERROR",
      "description": "Os dados enviados são inválidos.",
      "data": null,
      "errors": { "name": ["O campo name é obrigatório."] }
    }
    ```
    
    ## Paginação
    
    Endpoints que retornam listas utilizam paginação via query params `page` e `per_page`. A resposta inclui metadados no campo `meta`:
    
    ```json
    {
      "meta": {
        "current_page": 1,
        "last_page": 5,
        "per_page": 20,
        "total": 98
      }
    }
    ```
  version: 1.0.0
  contact:
    name: UniSupri
servers:
  - url: https://api.sandbox.samdevel.com.br
    description: Sandbox (ambiente de homologação)
tags:
  - name: Produtos
    description: |-
      Gerencie o catálogo de produtos, incluindo especificações, dimensões e disponibilidade.
      
      **Campos principais:**
      - `sku`: código único do produto na loja
      - `price_type`: tipo de precificação. Use `default` (unitário) ou `wholesale` (atacado com faixas)
      - `base_unit`: unidade de medida (un, kg, pc, cx, pct, lt, mt, m2, m3)
      - `dimensions`: peso e dimensões para cálculo de frete (height, width, length em cm; weight em kg)
      - `is_available_for_sale`: controla se o produto aparece como disponível no catálogo
      - `is_industrializable`: indica se pode ser fabricado sob demanda (produção)
  - name: Dados Auxiliares
    description: |-
      Endpoints públicos para obter valores válidos de enums.
      
      Use antes de cadastrar produtos para obter os valores aceitos nos campos `base_unit` e `price_type`. Estas rotas **não exigem autenticação**.
  - name: Galeria de Imagens
    description: |-
      Gerencie até 6 fotos por produto, com thumbnail e reordenação.
      
      Você não vai encontrar uma rota que receba o arquivo da imagem direto aqui: o upload é centralizado em `POST /api/media/upload`, que devolve um `image_id`. A galeria trabalha sempre com esse id — assim a mesma imagem pode ser reaproveitada e o envio de arquivos fica num único lugar.
      
      **Fluxo de uso:**
      1. Faça upload da imagem via `POST /api/media/upload`
      2. Vincule à galeria via `POST /api/products/{id}/gallery` usando o `image_id` retornado
      3. A primeira imagem é definida automaticamente como thumbnail
      4. Reordene com `PUT /api/products/{id}/gallery/reorder`
  - name: Preços de Produtos
    description: |-
      Configure estratégias de precificação para seus produtos.
      
      **Tipos de preço:**
      - `default`: preço único para venda unitária (apenas 1 faixa com min_quantity=1)
      - `wholesale`: tabela de atacado com até 5 faixas de preço progressivas por quantidade mínima
      
      **Regras gerais:**
      - Máximo de **5 faixas** de preço por produto
      - Produtos `default` aceitam apenas 1 preço com `min_quantity = 1`
      - O preço base (a faixa com `min_quantity = 1`) é o piso da tabela e não pode ser removido: é ele que define o valor de venda unitária e serve de base para as demais faixas. Para zerar a tabela de atacado, remova as faixas maiores e mantenha apenas essa.
      - Para voltar o `price_type` de `wholesale` para `default`, deixe só o preço base: remova antes as faixas extras, senão a troca é recusada
      
      **Medidas de embalagem (atacado):**
      Faixas de atacado (`min_quantity >= 2`) descrevem a embalagem fechada vendida — caixa/fardo — usada para calcular o frete da venda fracionada. Os campos `package_weight`, `package_height`, `package_width` e `package_length` são **tudo-ou-nada**: ou os quatro vêm preenchidos, ou nenhum (preenchimento parcial retorna 422).
      
      A **primeira faixa de atacado configurada precisa trazer as medidas completas** — é a embalagem de referência do produto. Faixas seguintes podem ser criadas sem medidas, e nesse caso herdam a referência da faixa já medida; o produto nunca pode ficar sem ao menos uma faixa medida (a remoção da última faixa medida com outras faixas dependentes é recusada).
      
      **Atacado automático (`auto_wholesale_pricing` no produto):**
      Quando ligado, o preço de cada faixa `>= 2` deixa de ser informado manualmente e passa a ser **derivado do preço base unitário menos o `discount_percent`** da faixa. Nesse modo:
      - é obrigatório existir o preço base (`min_quantity = 1`) antes de criar faixas de atacado;
      - os descontos precisam ser **progressivos**: faixa de quantidade maior exige `discount_percent` maior (preço sempre decrescente);
      - alterar o preço base recalcula automaticamente todas as faixas.
  - name: Estoque de Produtos
    description: |-
      Gerencie o inventário dos produtos nos locais de estoque.
      
      **Conceitos:**
      - `quantity`: estoque físico total
      - `reserved_quantity`: unidades reservadas em carrinhos/pedidos
      - `available_quantity`: disponível para venda (quantity - reserved_quantity)
      
      **Movimentação** (flags no body do POST):
      - sem flag: define o valor absoluto do estoque
      - `increment: true`: adiciona ao estoque atual
      - `decrement: true`: subtrai do estoque atual
      
      > Locais do tipo **Fulfillment** têm o estoque sincronizado pelo WMS (o sistema do centro de distribuição), então esta rota não aceita alterações neles — o número viria do WMS e seria sobrescrito de qualquer forma. Ajuste o estoque apenas nos seus próprios locais (`seller_location`); os de Fulfillment se atualizam sozinhos conforme as movimentações no armazém.
  - name: Locais de Estoque
    description: |-
      Gerencie depósitos, centros de distribuição e lojas físicas que armazenam seus produtos.
      
      **Tipos de local:**
      - `seller_location`: depósito ou loja própria do vendedor
      - `fulfillment`: centro de distribuição gerenciado pela plataforma (somente leitura)
      
      **Status:**
      - `available`: local operacional, disponível para venda e cálculo de frete
      - `unavailable`: local desativado, produtos não aparecem como disponíveis
      
      Cada local pode ter múltiplos endereços vinculados (origem de envio, endereço fiscal, etc.).
  - name: Enderecos de Estoque
    description: |-
      Gerencie endereços físicos vinculados aos locais de estoque.
      
      Cada endereço define um ponto de origem para cálculo de frete e emissão de notas fiscais. Um local pode ter vários endereços com tipos diferentes (envio, fiscal, etc.).
  - name: Categorias
    description: |-
      Busca e navegação de categorias para publicação de anúncios.
      
      A categoria define quais atributos são obrigatórios ou opcionais no anúncio. Use a busca para encontrar a categoria mais específica para seu produto.
  - name: Publicacao
    description: |-
      Criação e publicação de anúncios no marketplace.
      
      Inclui simulação (validação prévia), criação do rascunho **com descrição e ficha técnica em um único payload**, vinculação de SKUs às variações do catálogo, checklist de revisão e ativação para venda.
      
      **Fluxo resumido:**
      
      1. `POST /api/items/simulate`: valida sem persistir.
      2. `POST /api/items/create`: cria como `draft` (já com `description` e `technical_sheets` se desejar).
      3. `GET /api/items/{id}/review-checklist`: verifica o que ainda falta.
      4. `PUT /api/items/{id}/publish`: envia para revisão (`pending_review`).
      
      > Para o fluxo passo a passo completo, consulte o **Guia de Anúncios**.
  - name: Gestão de Anúncios
    description: |-
      Consulta e edição dos anúncios já criados.
      
      Listagem paginada com filtros e ordenação, detalhe completo, descrição (com preview do conteúdo processado), score de qualidade com dicas, atributos, variações e fichas técnicas.
      
      As rotas de edição valem para anúncios do tipo `default`. Anúncios de catálogo são mantidos pela plataforma — para eles o vendedor gerencia apenas as próprias ofertas (veja **Catálogo e Ofertas**).
  - name: Catálogo e Ofertas
    description: |-
      Descoberta de anúncios de catálogo e gestão das suas ofertas.
      
      No catálogo, vários vendedores competem dentro do mesmo anúncio: você encontra o anúncio, escolhe a opção (variação) desejada e vincula um SKU seu. Isso cria uma **oferta**, que passa a disputar a buybox com as ofertas dos demais vendedores.
      
      Aqui também estão a listagem das suas ofertas, a atualização de status (ativar/pausar) e a remoção do vínculo.
  - name: Media
    description: |-
      Upload, listagem e remoção de imagens.
      
      **Formatos aceitos:** JPEG, PNG, WebP (máx. 2MB)
      
      **Fluxo de uso:**
      1. Envie a imagem via `POST /api/media/upload` (multipart/form-data)
      2. Use o `id` retornado para vincular a galeria de produtos ou à página da loja
      
      **Regras de retenção:**
      - Imagens não associadas a nenhum recurso são excluídas por uma rotina de limpeza da plataforma (janela de **~72h**)
      - Enquanto uma imagem estiver vinculada a um produto ou à loja, a exclusão é bloqueada (erro 409) — assim você não derruba sem querer uma imagem que está no ar. Desvincule do recurso primeiro e só então exclua.
  - name: Autenticação
    description: |-
      Obtenha um token de acesso a partir da credencial (username + senha) criada no Portal do Vendedor.
      
      O token vale por **30 dias** e cada chamada rotaciona — invalida o token anterior. Endpoint público, protegido por rate limit por IP+username.
      
      Fluxo completo, erros, troca de senha e boas práticas: ver guia de [Autenticação](/guia/autenticacao).
  - name: Sessão
    description: Confirma o token e retorna os dados da loja autenticada, a `company` associada e os escopos. Útil pra validar que a integração está apontando pra loja certa antes de fazer qualquer outra chamada.
  - name: Página da Loja
    description: |-
      Gerencie a identidade visual e informações da página pública da loja.
      
      **Recursos visuais:**
      - **Logo**: imagem de destaque (JPG, PNG, WebP, máx. 5MB)
      - **Background**: imagem de fundo da página
      
      Logo e background são enviados direto nos endpoints da própria página — você não precisa passar pelo fluxo de upload da API de Media nem colar um `id` aqui; mande o arquivo de uma vez.
  - name: Avaliação
    description: |-
      Acompanhe a nota da loja e o histórico de avaliações dos compradores.
      
      A nota é calculada automaticamente com base nas avaliações recebidas nos pedidos entregues.
  - name: Pedidos
    description: |-
      Encontrar pedidos, ler dashboards e **avançar o ciclo de vida**.
      
      Todo pedido tem um `workflow_type` (`standard`, `production`, `in_store_pickup`, `pre_order`, `backorder`, `collective`) que define quais transições de status são aceitas. **Existe uma única rota que muda estado:** [`POST /orders/{id}/status`](/reference/pedidos#tag/pedidos/POST/api/v1/sellers/orders/{id}/status). Cancelamento, conclusão de produção, retirada confirmada — tudo passa por aí. O que muda é o valor de `status` enviado.
      
      Você não precisa decorar o que pode chamar: [`GET /orders/{id}/possible-statuses`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/possible-statuses) já devolve, para o estado atual do pedido, as transições válidas com `target_status` e `payload_schema` prontos. Renderize um botão por `next_action`, monte o `data` a partir do schema, mande na rota única.
      
      → Mapa completo do ciclo de vida em **[Pedidos — Visão geral](/guia/pedidos)**.
  - name: Fiscal
    description: |-
      Emissão e consulta de **NF-e** (modelo 55) e **Declaração de Conteúdo**.
      
      A plataforma não emite NF-e — ela é gerada no seu ERP, autorizada pela SEFAZ, e você registra a chave (e o XML) aqui para que o **DANFE** seja produzido em background. Para Declaração de Conteúdo é o inverso: a plataforma gera o PDF a partir dos dados do pedido, basta disparar.
      
      **Pré-condição:** o pedido precisa estar em `preparing` em diante (não em `paid`). Para Declaração também é preciso que a loja tenha a feature `fiscal_declaration` ativa — confira em [`GET /orders/{id}/fiscal`](/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal) (`declaration_available`).
      
      Geração de PDF é **assíncrona**: dispara → polling em `/fiscal` até `has_pdf=true` → baixa via `/fiscal/{invoiceId}/pdf` (URL S3 válida por 15 min).
      
      → Passo a passo em **[Pedidos — Fiscal](/guia/pedidos-fiscal)**.
  - name: Etiquetas
    description: |-
      Geração e download de etiquetas (PDFs) dos shipments do pedido.
      
      Um pedido pode ter mais de um shipment (pacotes diferentes); cada shipment pode ter uma ou mais etiquetas (formatos diferentes). A geração é **assíncrona** — `POST /shipment-labels/generate` enfileira um job por shipment, e você faz polling em `GET /orders/{id}/shipments` até `labels_ready === labels_total`.
      
      Baixe cada etiqueta via `GET /logistics/shipments/{id}/labels/{labelId}/download` — devolve URL S3 assinada com 15 min de validade. Chamar `/generate` de novo **regenera** todas as etiquetas (não é incremental).
      
      → Modelo mental e cenários em **[Pedidos — Logística](/guia/pedidos-logistica)**.
  - name: Enums de Pedido
    description: |-
      **Tabelas de referência** dos tipos e valores do ecossistema de pedidos. Em vez de hardcodar listas de status, canais, métodos de pagamento ou entrega, consulte estas rotas — elas refletem exatamente os valores que a API aceita e devolve, já com `label` (pt-BR) e, quando aplicável, `color` para UI.
      
      A rota consolidada [`GET /orders/enums`](/reference/pedidos#tag/enums-de-pedido/GET/api/v1/sellers/orders/enums) traz **todos** os enums do pedido num só request. As rotas públicas de status (`/api/global/enums/order/status`) e de logística (`/api/logistics/enums/*`) completam o conjunto. Os valores só mudam em deploy.
      
      → Para as transições de status válidas a partir do estado atual de um pedido, use [`GET /orders/{id}/possible-statuses`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/possible-statuses).
  - name: Devoluções
    description: |-
      Acompanhamento e tratamento de devoluções (RMA) da loja.
      
      A solicitação nasce no cliente. A listagem mostra **todas as devoluções da loja**, desde a abertura — transparência total para a operação acompanhar. Quando a plataforma precisa da decisão do seller, atribui `status=forwarded_to_seller` ("encaminhar" transfere a tomada de decisão, não a visibilidade). Filtre essa fila com `?status=forwarded_to_seller`.
      
      Decisão é binária: [`/approve`](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/approve) ou [`/reject`](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/reject) (com `reason` obrigatório que é exibido ao cliente). Após aprovar, gere a coleta reversa em modo `carrier` (transportador parceiro) ou `manual` (seller organiza por fora). Quando o produto chegar fisicamente à loja, confirme em [`/mark-received`](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/mark-received). Em dúvida sobre o que fazer em cada estado, consulte [`/possible-actions`](/reference/devolucoes#tag/devolucoes/GET/api/v1/sellers/orders/returns/{id}/possible-actions) — a API devolve as ações válidas para o status atual.
      
      Depois que o produto volta (`received`), a plataforma decide o desfecho: cria a solicitação de estorno (`status=refunded` e `resolution=refunded`, fechando em `closed` quando o financeiro conclui) ou encerra sem estorno (`closed` com `resolution=resolved_externally` e a justificativa em `resolution_notes`). Um mesmo pedido pode ter **devoluções parciais sucessivas** — cada uma com seu próprio desfecho — desde que a anterior tenha encerrado e haja saldo devolvível nos itens.
      
      → Estados, SLA e cenários em **[Devoluções — Visão geral](/guia/devolucoes)**; desfecho e reembolso em **[Devoluções — Resolução](/guia/devolucoes-resolucao)**.
  - name: Central de Atendimento
    description: |-
      Atendimento entre **loja** e **comprador** ancorado em um pedido, com suporte a intervenção da plataforma.
      
      Cada conversa tem um `type` que define o ciclo de vida:
      - `pre_delivery` — aberta automaticamente quando o pedido é pago; encerra ao confirmar entrega.
      - `post_delivery` — chamado pós-venda (troca, garantia, dúvida), aberto manualmente pelo comprador sobre um assunto do catálogo.
      
      **Participantes** (`participants[].type`):
      - `customer` — comprador
      - `store` — loja/seller (esta API)
      - `platform` — atendimento da plataforma (exposto assim para o seller; internamente persistido como `operator`)
      
      **Status** (`status`): `open`, `closed`. Quando encerrada, `closed_reason` indica o motivo (`order_delivered`, `customer_closed`, `admin_closed`, `auto_inactive`).
      
      **Identificadores**: todas as conversas, mensagens e anexos são expostos por **ULID** (`{conversation}`, `{attachment}`). IDs auto-increment internos nunca aparecem na API.
      
      **Anexos**: o envio é feito em duas etapas — primeiro um `attachments/intent` reserva o slot e devolve uma URL pré-assinada para upload direto (`PUT`); em seguida a mensagem é criada com o `attachment_ulid`. Para visualizar o arquivo, o cliente solicita uma URL temporária via `attachments/{attachment}/access`.
  - name: Perguntas e Respostas
    description: |-
      Gerencie as **Perguntas e Respostas (Q&A)** das publicações da loja autenticada.
      
      Este recurso é o canal direto entre o comprador e o seller no detalhe do produto. Perguntas só ficam visíveis publicamente depois de passar pela moderação (status `APPROVED`). O seller pode então respondê-las pelo portal.
      
      **Regras importantes:**
      - O seller só enxerga perguntas de publicações **vinculadas à sua loja** (via `publication.stores` ou `publication.storeDefault`).
      - A resposta é criada por padrão com status `PENDING` (entra na fila de moderação). Lojas com a *feature* `trusted_answers` têm a resposta publicada diretamente como `APPROVED`, sem moderação.
      - O contador de não respondidas em `/unanswered-count` considera **apenas** Questions já `APPROVED` que não possuem nenhuma Answer `APPROVED`. É a métrica usada nos widgets de dashboard do portal do seller.
      - Todos os identificadores públicos são **ULIDs** (26 caracteres, base32 Crockford).
      
      **Status do ciclo de moderação** (`InteractionStatus`):
      - `PENDING` — aguardando moderação
      - `APPROVED` — visível publicamente
      - `REJECTED` — recusado pela moderação
  - name: Ocorrências
    description: |-
      Gestão de **ocorrências** (tickets internos) que afetam a loja autenticada.
      
      Uma ocorrência representa um incidente, pendência ou caso operacional que precisa de atenção — uma devolução em análise, um problema de logística, uma divergência de estoque, etc. Cada ocorrência pertence a uma **matriz** (`matrix`), está em uma **etapa** (`step`) do fluxo dessa matriz e pode ter relações com entidades do marketplace (pedido, loja, item).
      
      Conceitos:
      
      - **Matrix** — categoria/fluxo da ocorrência. As 7 matrizes seedadas são: `orders` (Pedidos), `logistics` (Logística), `payments` (Pagamentos), `rma` (Devoluções), `stock` (Estoque), `stores` (Lojas), `general` (Geral). Para sellers, a mais comum é `rma`.
      - **Step** — etapa dentro de uma matriz. As 6 etapas padrão são: `new`, `analyzing`, `waiting`, `resolving`, `resolved`, `cancelled`. Cada step tem `position` (ordem no kanban), `is_initial` e `is_final`.
      - **Severity** — `low`, `medium`, `high`, `critical` (impacta ordenação e cor de destaque).
      - **Status** — `open`, `resolved`, `cancelled`, `auto_closed`. A listagem oculta `auto_closed` por padrão.
      - **Origin** — `manual` (criada por operador), `system` (gerada por evento) ou `scheduled` (job recorrente).
      
      O seller só enxerga ocorrências com `visible_to_store=true` E nas quais sua loja participa via `OccurrenceRelation(entity_type="store")`. Qualquer outra retorna **403**.
      
      IDs de ocorrência são **ULID** (26 chars Crockford). IDs de matrix/step são inteiros.
  - name: Comentários
    description: |-
      **Comentários** trocados entre a loja e o time interno dentro de uma ocorrência.
      
      - Apenas comentários com `visibility=shared` são expostos ao seller.
      - Comentários do seller são sempre criados com `author_type="store"` e `visibility="shared"`.
      - Comentários com `is_system=true` são eventos automáticos do fluxo (mudança de etapa, fechamento, atribuição) — exiba-os com estilo diferente.
      - O ID do comentário é **ULID** (26 chars), assim como o ID da ocorrência.
  - name: Enums de Ocorrências
    description: "**Tabelas de referência** dos enums de ocorrências (origem, severidade, status). Em vez de hardcodar essas listas, consulte [`GET /occurrences/enums`](/reference/ocorrencias#tag/enums-de-ocorrências/GET/api/seller/occurrences/enums) — cada item traz `value` e `label` (pt-BR); `severities` inclui `color` para destaque. Os valores só mudam em deploy."
paths:
  "/api/global/enums/product/units":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Unidades de Medida
      description: Retorna os valores válidos para o campo `base_unit` dos produtos. Rota pública, não requer autenticação.
      responses:
        "200":
          description: Lista de unidades de medida
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        value:
                          type: string
                        label:
                          type: string
                        description:
                          type: string
              example:
                success: true
                message_code: SUCCESS
                data:
                  - value: un
                    label: Unidade
                    description: Itens individuais (padrão).
                  - value: pc
                    label: Peça
                    description: Peças individuais.
                  - value: cx
                    label: Caixa
                    description: Caixas contendo múltiplos itens.
                  - value: pct
                    label: Pacote
                    description: Pacotes contendo múltiplos itens.
                  - value: kg
                    label: Quilograma
                    description: Produtos vendidos por peso em quilogramas.
                  - value: lt
                    label: Litro
                    description: Produtos líquidos vendidos por litro.
                  - value: mt
                    label: Metro
                    description: Produtos lineares vendidos por metro (cabos, tecidos, perfis).
                  - value: m2
                    label: Metro Quadrado
                    description: Produtos de área vendidos por metro quadrado (pisos, vidros, painéis).
                  - value: m3
                    label: Metro Cúbico
                    description: Produtos volumétricos vendidos por metro cúbico (madeiras, granéis).
  "/api/global/enums/product/price-types":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Tipos de Preço
      description: Retorna os valores válidos para o campo `price_type` dos produtos. Rota pública, não requer autenticação.
      responses:
        "200":
          description: Lista de tipos de preço
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - value: default
                    label: Preço Padrão
                    description: Preço padrão para compras unitárias.
                  - value: wholesale
                    label: Preço de Atacado
                    description: Preço especial para compras em grandes quantidades.
  "/api/products":
    get:
      tags:
        - Produtos
      summary: Listar Produtos
      description: Retorna uma lista paginada de produtos do seller autenticado.
      parameters:
        - name: filter[search]
          in: query
          description: Busca geral (procura em nome e SKU)
          required: false
          schema:
            type: string
            example: camiseta
        - name: filter[sku]
          in: query
          description: Filtro específico por SKU
          required: false
          schema:
            type: string
            example: SKU-001
        - name: filter[name]
          in: query
          description: Filtro específico por Nome
          required: false
          schema:
            type: string
            example: Produto Exemplo
        - name: filter[price_type]
          in: query
          description: Filtro por tipo de preço
          required: false
          schema:
            type: string
            enum:
              - default
              - wholesale
            example: wholesale
        - name: filter[base_unit]
          in: query
          description: Filtro por unidade de medida
          required: false
          schema:
            type: string
            enum:
              - un
              - kg
              - pc
              - cx
              - pct
              - lt
              - mt
              - m2
              - m3
            example: un
        - name: filter[status]
          in: query
          description: |-
            Recorte por situação do produto:
            - `ativos`: disponíveis para venda
            - `vendendo`: ativos com pelo menos um anúncio ativo
            - `disponiveis`: ativos ainda sem nenhum anúncio (prontos para anunciar)
            - `sem-estoque`: ativos sem saldo de estoque
            - `inativos`: indisponíveis para venda
            - `industrializaveis`: fabricados sob demanda
          required: false
          schema:
            type: string
            enum:
              - ativos
              - vendendo
              - disponiveis
              - sem-estoque
              - inativos
              - industrializaveis
            example: ativos
        - name: sort
          in: query
          description: "Campo de ordenação. Prefixe com `-` para descendente. Valores permitidos: `name`, `sku`, `price_type`, `base_unit`, `created_at`, `updated_at`."
          required: false
          schema:
            type: string
            example: "-created_at"
        - name: include_dimensions_formatted
          in: query
          description: Incluir formatação amigável das dimensões na resposta
          required: false
          schema:
            type: boolean
            default: false
        - name: page
          in: query
          description: Número da página
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Itens por página
          schema:
            type: integer
            default: 15
      responses:
        "200":
          description: Lista de produtos recuperada com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: The request was successful.
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 2
                        records: 2
                  data:
                    - id: 01H8X7...A
                      name: Produto Exemplo A
                      sku: SKU-001
                      price_type: default
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
                      base_unit: un
                      dimensions:
                        width: 10
                        height: 10
                        length: 10
                        weight: 0.5
                      is_available_for_sale: true
                      is_industrializable: false
                      auto_wholesale_pricing: false
                      allow_sale_without_stock: false
                      thumbnail:
                        id: 01JMZXZ72881MD2P37N2K97YJJ
                        resources:
                          - name: thumbnail
                            size: 135x135
                            url: https://cdn.example.com/data/thumbnail_img.webp
                          - name: md
                            size: 400x400
                            url: https://cdn.example.com/data/md_img.webp
                      price: 50
                    - id: 01H8X7...B
                      name: Produto Exemplo B
                      sku: SKU-002
                      price_type: wholesale
                      quantity: 200
                      reserved_quantity: 0
                      available_quantity: 200
                      base_unit: kg
                      dimensions:
                        width: 20
                        height: 15
                        length: 30
                        weight: 1.2
                      is_available_for_sale: true
                      is_industrializable: false
                      auto_wholesale_pricing: false
                      allow_sale_without_stock: false
                      thumbnail: null
                      min_price: 40
                      max_price: 45
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
    post:
      tags:
        - Produtos
      summary: Criar Produto
      description: |-
        Cadastra um novo produto para o seller. O campo `sku` deve ser único por loja.
        
        Lojas que operam em **modo espelho** (subloja que herda o catálogo da matriz) não criam SKUs próprios — a tentativa retorna `422` com o código `SUBSTORE_CANNOT_CREATE_SKU`.
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProductInput"
            example:
              name: Novo Produto Exemplo
              sku: PROD-NEW-001
              base_unit: un
              price_type: default
              dimensions:
                height: 15.5
                width: 10
                length: 20
                weight: 0.75
              is_available_for_sale: true
              is_industrializable: false
              allow_sale_without_stock: false
      responses:
        "201":
          description: Produto criado com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 201
                message_code: RESOURCE_CREATED
                description: Recurso criado com sucesso.
                data:
                  id: 01H8X...
                  name: Novo Produto Exemplo
                  sku: PROD-NEW-001
                  base_unit: un
                  dimensions:
                    width: 10
                    height: 15.5
                    length: 20
                    weight: 0.75
                  is_available_for_sale: true
                  is_industrializable: false
                  allow_sale_without_stock: false
                  price_type: default
                  auto_wholesale_pricing: false
                  prices: []
                  quantity: 0
                  reserved_quantity: 0
                  available_quantity: 0
                  thumbnail: null
                  images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/products/{id}":
    get:
      tags:
        - Produtos
      summary: Detalhes do Produto
      description: Retorna detalhes de um produto específico, incluindo preços e estoque por local.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      responses:
        "200":
          description: Detalhes do produto
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: The request was successful.
                data:
                  product:
                    id: 01H8X...
                    name: Produto Exemplo A
                    sku: SKU-001
                    price_type: default
                    quantity: 100
                    reserved_quantity: 5
                    available_quantity: 95
                    base_unit: un
                    dimensions:
                      width: 10
                      height: 10
                      length: 10
                      weight: 0.5
                    is_available_for_sale: true
                    is_industrializable: false
                    auto_wholesale_pricing: false
                    allow_sale_without_stock: false
                    thumbnail:
                      id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                    price: 50
                  prices:
                    - value: 50
                      min_quantity: 1
                      discount_percent: null
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                  stock:
                    - location_stock:
                        location_id: 01H81AV32307PVBSV4RXF15LOC
                        store_id: 01H81AV32307PVBSV4RXF15ST1
                        name: Depósito Central
                        status: available
                        location_type: seller_location
                        allows_pickup: true
                        allows_production: false
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Produtos
      summary: Atualizar Produto
      description: |-
        Atualiza os dados de um produto existente. Os campos `name` e `dimensions` (completo) são obrigatórios em toda atualização; os demais são opcionais e mantêm o valor atual quando omitidos. O `sku` não pode ser alterado — se enviado, é simplesmente ignorado.
        
        **Atenção ao trocar `price_type`:** voltar de `wholesale` para `default` só é aceito quando o produto tem uma única faixa de preço com `min_quantity = 1`. A trava existe para não deixar faixas de atacado órfãs num produto que passou a ser unitário — se ainda houver faixas extras, remova-as primeiro (veja a API de Preços) e então faça a troca.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProductUpdateInput"
            examples:
              simple_update:
                summary: Atualização Simples (Nome e Tipo de Preço)
                value:
                  name: Produto Exemplo A - Atualizado
                  price_type: wholesale
                  dimensions:
                    width: 10
                    height: 10
                    length: 10
                    weight: 0.5
              full_update:
                summary: Atualização Completa (Todos os campos)
                value:
                  name: Produto Exemplo A - Revisado
                  base_unit: cx
                  price_type: default
                  dimensions:
                    width: 12.5
                    height: 10
                    length: 25
                    weight: 1.5
                  is_available_for_sale: true
                  is_industrializable: false
                  auto_wholesale_pricing: false
                  allow_sale_without_stock: false
      responses:
        "200":
          description: Produto atualizado com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: RESOURCE_UPDATED
                description: Recurso atualizado com sucesso.
                data:
                  id: 01H8X...
                  name: Produto Exemplo A - Atualizado
                  sku: SKU-001
                  base_unit: un
                  dimensions:
                    width: 10
                    height: 10
                    length: 10
                    weight: 0.5
                  is_available_for_sale: true
                  is_industrializable: false
                  allow_sale_without_stock: false
                  price_type: wholesale
                  auto_wholesale_pricing: false
                  prices:
                    - value: 50
                      min_quantity: 1
                      discount_percent: null
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                    - value: 40
                      min_quantity: 10
                      discount_percent: 20
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                  quantity: 100
                  reserved_quantity: 5
                  available_quantity: 95
                  thumbnail: null
                  images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      name:
                        - O campo name é obrigatório.
                      "dimensions.height":
                        - O campo dimensions.height é obrigatório.
                    meta: []
                price_type_change:
                  summary: Não é possível alterar price_type
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Não é possível alterar o tipo de preço para "default" pois existem múltiplos preços cadastrados.
                    data: []
                    errors:
                      - Não é possível alterar o tipo de preço para "default" pois existem múltiplos preços cadastrados.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery":
    get:
      tags:
        - Galeria de Imagens
      summary: Listar Imagens do Produto
      description: Retorna a galeria de imagens do produto, incluindo o ID da thumbnail atual e o limite máximo de 6 imagens.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      responses:
        "200":
          description: Galeria do produto
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: sm
                          size: 200x200
                          url: https://cdn.example.com/data/sm_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                        - name: lg
                          size: 800x800
                          url: https://cdn.example.com/data/lg_img.webp
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 1
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Galeria de Imagens
      summary: Adicionar Imagem ao Produto
      description: Vincula uma imagem (já carregada via `POST /api/media/upload`) à galeria do produto. Máximo de 6 imagens. A primeira imagem é automaticamente definida como thumbnail.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - image_id
              properties:
                image_id:
                  type: string
                  description: ID da imagem obtido via POST /api/media/upload
            example:
              image_id: 01JMZXZ72881MD2P37N2K97YJJ
      responses:
        "201":
          description: Imagem adicionada com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 201
                message_code: RESOURCE_CREATED
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: sm
                          size: 200x200
                          url: https://cdn.example.com/data/sm_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                        - name: lg
                          size: 800x800
                          url: https://cdn.example.com/data/lg_img.webp
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 1
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Campo image_id inválido ou ausente
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image_id:
                        - O campo image_id é obrigatório.
                    meta: []
                limit_reached:
                  summary: Limite de 6 imagens atingido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Limite máximo de 6 fotos por produto atingido.
                    data: []
                    errors:
                      - Limite máximo de 6 fotos por produto atingido.
                    meta: []
                duplicate:
                  summary: Imagem já está na galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Esta imagem já está na galeria do produto.
                    data: []
                    errors:
                      - Esta imagem já está na galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/{image_id}":
    delete:
      tags:
        - Galeria de Imagens
      summary: Remover Imagem do Produto
      description: Remove uma imagem da galeria. Se a imagem removida for a thumbnail, a primeira imagem restante será promovida automaticamente.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
        - name: image_id
          in: path
          required: true
          description: ID da imagem a ser removida
          schema:
            type: string
      responses:
        "200":
          description: Imagem removida com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images: []
                  thumbnail_id: null
                  total: 0
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          description: Produto ou imagem não encontrada
          content:
            "application/json":
              examples:
                product:
                  summary: Produto não encontrado
                  value:
                    success: false
                    code: 404
                    message_code: NOT_FOUND
                    description: O recurso solicitado não foi encontrado.
                    data: []
                    errors:
                      - Produto não encontrado.
                    meta: []
                image:
                  summary: Imagem não encontrada na galeria
                  value:
                    success: false
                    code: 404
                    message_code: NOT_FOUND
                    description: O recurso solicitado não foi encontrado.
                    data: []
                    errors:
                      - Imagem não encontrada na galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/thumbnail":
    put:
      tags:
        - Galeria de Imagens
      summary: Definir Thumbnail
      description: Define qual imagem da galeria será usada como thumbnail (destaque). A imagem deve já estar na galeria do produto.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - image_id
              properties:
                image_id:
                  type: string
                  description: ID da imagem que será a thumbnail
            example:
              image_id: 01JMZXZ72881MD2P37N2K97YJJ
      responses:
        "200":
          description: Thumbnail definida com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  thumbnail:
                    id: 01JMZXZ72881MD2P37N2K97YJJ
                    resources:
                      - name: thumbnail
                        size: 135x135
                        url: https://cdn.example.com/data/thumbnail_img.webp
                      - name: md
                        size: 400x400
                        url: https://cdn.example.com/data/md_img.webp
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Campo image_id ausente
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image_id:
                        - O campo image_id é obrigatório.
                    meta: []
                not_in_gallery:
                  summary: Imagem não está na galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: A imagem deve estar na galeria do produto para ser definida como thumbnail.
                    data: []
                    errors:
                      - A imagem deve estar na galeria do produto para ser definida como thumbnail.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/reorder":
    put:
      tags:
        - Galeria de Imagens
      summary: Reordenar Imagens
      description: "Define a nova ordem das imagens da galeria. Todos os IDs enviados devem pertencer à galeria do produto. Atenção: imagens da galeria que ficarem fora da lista são removidas — envie sempre a lista completa. Se a thumbnail atual não estiver na lista, a primeira imagem da nova ordem será promovida."
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - images
              properties:
                images:
                  type: array
                  minItems: 1
                  items:
                    type: string
                  description: Array de IDs de imagens na nova ordem desejada (imagens omitidas são removidas da galeria)
            example:
              images:
                - 01JMZXZ72881MD2P37N2K97YJJ
                - 01JN0A1B2C3D4E5F6G7H8I9J0K
                - 01JN1X2Y3Z4A5B6C7D8E9F0G1H
      responses:
        "200":
          description: Imagens reordenadas com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                    - id: 01JN0A1B2C3D4E5F6G7H8I9J0K
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                    - id: 01JN1X2Y3Z4A5B6C7D8E9F0G1H
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 3
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Array de imagens inválido
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      images:
                        - O campo images é obrigatório.
                    meta: []
                not_in_gallery:
                  summary: Imagem não pertence à galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Imagem não pertence à galeria do produto.
                    data: []
                    errors:
                      - Imagem 01JN0A1... não pertence à galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/prices":
    get:
      tags:
        - Preços de Produtos
      summary: Listar Preços do Produto
      description: |-
        Retorna a lista de faixas de preço de um produto, ordenadas por `min_quantity`.
        
        - **Produto `default` (preço padrão):** retorna uma única faixa (`min_quantity = 1`) com as medidas de embalagem nulas — as dimensões físicas ficam no próprio produto.
        - **Produto `wholesale` (atacado):** retorna a faixa base (`min_quantity = 1`, com as medidas da unidade projetadas a partir do produto) seguida das faixas de atacado (`min_quantity >= 2`, com as medidas da embalagem fechada).
        - `discount_percent` só vem preenchido quando o atacado é automático (`auto_wholesale_pricing`): nesse modo o preço de cada faixa é derivado do preço base menos o desconto (a faixa base vem com `0`). No atacado manual ele é `null`.
        - Produto sem preços cadastrados: `data` vem vazio e `meta.message` informa "Não há preços cadastrados".
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
      responses:
        "200":
          description: Lista de preços recuperada com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Price"
              examples:
                default_price:
                  summary: Produto com preço padrão (preço único, sem faixas de atacado)
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 29.9
                        min_quantity: 1
                        discount_percent: null
                        package_weight: null
                        package_height: null
                        package_width: null
                        package_length: null
                wholesale_manual:
                  summary: "Atacado manual: preço de cada faixa definido à mão (discount_percent null)"
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 50
                        min_quantity: 1
                        discount_percent: null
                        package_weight: 1.2
                        package_height: 10
                        package_width: 12
                        package_length: 15
                      - value: 45
                        min_quantity: 10
                        discount_percent: null
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                      - value: 40
                        min_quantity: 50
                        discount_percent: null
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                wholesale_auto:
                  summary: "Atacado automático: discount_percent define o preço de cada faixa (auto_wholesale_pricing)"
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 50
                        min_quantity: 1
                        discount_percent: 0
                        package_weight: 1.2
                        package_height: 10
                        package_width: 12
                        package_length: 15
                      - value: 45
                        min_quantity: 10
                        discount_percent: 10
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                      - value: 40
                        min_quantity: 50
                        discount_percent: 20
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Preços de Produtos
      summary: Adicionar Preço
      description: |-
        Adiciona uma nova faixa de preço para o produto (ex: preço de atacado para compras acima de X unidades).
        
        Regras de negócio:
        - Máximo de 5 faixas de preço por produto (400 BAD_REQUEST se exceder).
        - Não pode haver duplicidade de min_quantity (409 DUPLICATED).
        - Produtos do tipo `default` não aceitam min_quantity >= 2 (422 UNPROCESSABLE_ENTITY).
        - Em produtos `wholesale`, faixas `min_quantity >= 2` exigem as medidas completas da embalagem (peso + altura + largura + comprimento). A primeira faixa de atacado precisa trazer as medidas; faixas seguintes podem omiti-las herdando a referência (422 se faltar uma faixa medida ou se o preenchimento for parcial).
        - Em produtos `wholesale` com `auto_wholesale_pricing` ligado: o preço base (`min_quantity = 1`) precisa existir antes das faixas de atacado (422), e os `discount_percent` devem ser progressivos (422).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/PriceInput"
            examples:
              base_price:
                summary: Preço base (unitário, min_quantity = 1)
                value:
                  price: 50
                  min_quantity: 1
              wholesale_first_tier:
                summary: Primeira faixa de atacado (com medidas obrigatórias)
                value:
                  price: 45
                  min_quantity: 10
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
              wholesale_next_tier:
                summary: Faixa de atacado seguinte (medidas herdadas)
                value:
                  price: 40
                  min_quantity: 50
              wholesale_auto_tier:
                summary: Faixa de atacado automática (preço derivado do desconto)
                value:
                  min_quantity: 50
                  discount_percent: 20
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
      responses:
        "201":
          description: Preço criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Price"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  value: 45
                  min_quantity: 10
                  discount_percent: null
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
        "400":
          description: Limite máximo de preços atingido
          content:
            "application/json":
              example:
                success: false
                code: 400
                message_code: BAD_REQUEST
                description: Preço máximo atingido.
                data: []
                errors:
                  - Preço máximo atingido.
                meta: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      price:
                        - O campo Preço deve ser um número.
                      min_quantity:
                        - O campo Quantidade mínima deve ser um número.
                    meta: []
                price_quantity_invalid:
                  summary: Produto DEFAULT não aceita min_quantity >= 2
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Quantidade de preço inválida.
                    data: []
                    errors:
                      - Quantidade de preço inválida.
                    meta: []
                package_dimensions_required:
                  summary: Faixa de atacado sem medidas completas da embalagem
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    data: []
                    errors:
                      - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    meta: []
                wholesale_base_price_missing:
                  summary: Atacado automático sem preço base definido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    data: []
                    errors:
                      - Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    meta: []
                wholesale_discount_not_progressive:
                  summary: Descontos do atacado automático não progressivos
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    data: []
                    errors:
                      - "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/prices/{min_quantity}":
    put:
      tags:
        - Preços de Produtos
      summary: Atualizar Preço
      description: |-
        Atualiza uma faixa de preço existente, identificada pela quantidade mínima na URL (não é possível mudar a própria `min_quantity` — para isso, remova e recrie).
        
        Além do `price`, em produtos `wholesale` o corpo aceita `discount_percent` e as medidas da embalagem (`package_weight`, `package_height`, `package_width`, `package_length`). O merge é parcial para as medidas e o `discount_percent`: campo ausente no corpo mantém o valor já salvo. **Atenção ao `price`:** ele não participa do merge — na precificação manual (e na faixa base), omitir o `price` zera o valor da faixa. Sempre reenvie o preço atual quando for alterar só as medidas. As mesmas regras de medidas completas, atacado automático e desconto progressivo do POST se aplicam.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
        - name: min_quantity
          in: path
          required: true
          schema:
            type: integer
          description: "Quantidade mínima que identifica a faixa de preço (ex: 1, 10)"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                price:
                  type: number
                  nullable: true
                  description: Novo valor monetário. Ignorado nas faixas `min_quantity >= 2` quando o produto está em atacado automático (o preço é derivado do desconto).
                discount_percent:
                  type: number
                  nullable: true
                  minimum: 0
                  exclusiveMaximum: 100
                  description: Novo percentual de desconto sobre o preço base (atacado automático). Precisa manter a progressividade entre as faixas.
                package_weight:
                  type: number
                  nullable: true
                  minimum: 0.001
                  description: Peso da embalagem fechada, em kg.
                package_height:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Altura da embalagem fechada, em cm.
                package_width:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Largura da embalagem fechada, em cm.
                package_length:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Comprimento da embalagem fechada, em cm.
            examples:
              update_price:
                summary: Atualizar só o valor
                value:
                  price: 48.5
              update_wholesale_measures:
                summary: Atualizar medidas da embalagem de uma faixa de atacado (reenvie o price no modo manual, senão ele zera)
                value:
                  price: 45
                  package_weight: 13
                  package_height: 32
                  package_width: 42
                  package_length: 52
              update_auto_discount:
                summary: Atualizar desconto (atacado automático)
                value:
                  discount_percent: 18
      responses:
        "200":
          description: Preço atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Price"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  value: 48.5
                  min_quantity: 1
                  discount_percent: null
                  package_weight: 1.2
                  package_height: 10
                  package_width: 12
                  package_length: 15
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio do atacado
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      discount_percent:
                        - O campo Percentual de desconto deve ser menor que 100.
                    meta: []
                package_dimensions_required:
                  summary: Faixa de atacado sem medidas completas da embalagem
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    data: []
                    errors:
                      - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    meta: []
                wholesale_base_price_missing:
                  summary: Atacado automático sem preço base definido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    data: []
                    errors:
                      - Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    meta: []
                wholesale_discount_not_progressive:
                  summary: Descontos do atacado automático não progressivos
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    data: []
                    errors:
                      - "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    meta: []
      security:
        - bearer: []
    delete:
      tags:
        - Preços de Produtos
      summary: Remover Preço
      description: |-
        Remove uma faixa de preço específica do produto.
        
        Regras de negócio:
        - Não é possível remover o preço base (min_quantity < 2). A API retorna 400 BAD_REQUEST.
        - Em produtos `wholesale`, não é possível remover a última faixa que carrega as medidas da embalagem enquanto houver outras faixas de atacado dependendo dela (o produto precisa manter ao menos uma faixa medida). A API retorna 422 UNPROCESSABLE_ENTITY.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
        - name: min_quantity
          in: path
          required: true
          schema:
            type: integer
          description: Quantidade mínima da faixa de preço a ser removida
      responses:
        "204":
          description: Preço removido com sucesso (sem corpo de resposta)
        "400":
          description: Preço base não pode ser removido
          content:
            "application/json":
              example:
                success: false
                code: 400
                message_code: BAD_REQUEST
                description: Preço mínimo não pode ser removido.
                data: []
                errors:
                  - Preço mínimo não pode ser removido.
                meta: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Não é possível remover a última faixa medida com faixas dependentes
          content:
            "application/json":
              example:
                success: false
                code: 422
                message_code: UNPROCESSABLE_ENTITY
                description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                data: []
                errors:
                  - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                meta: []
      security:
        - bearer: []
  "/api/products/{product_id}/stock":
    get:
      tags:
        - Estoque de Produtos
      summary: Consultar Estoque do Produto
      description: Retorna o total de estoque do produto, além da distribuição detalhada por localização e a lista de locais disponíveis.
      operationId: getProductStock
      parameters:
        - name: product_id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
            example: 01H81AV32307PVBSV4RXF15EK9
      responses:
        "200":
          description: Dados de estoque recuperados com sucesso.
          content:
            "application/json":
              schema:
                allOf:
                  - "$ref": "#/components/schemas/SuccessResponse"
                  - type: object
                    properties:
                      data:
                        "$ref": "#/components/schemas/ProductStockOverview"
              example:
                success: true
                message_code: SUCCESS
                data:
                  total: 150
                  stocks:
                    - id: 321
                      product_sku:
                        id: 01H81AV32307PVBSV4RXF15EK9
                        name: Caneta Esferográfica Azul
                        sku: CAN-AZ-01
                        base_unit: un
                        dimensions: null
                        is_available_for_sale: true
                        is_industrializable: false
                        allow_sale_without_stock: false
                        price_type: fixed
                        auto_wholesale_pricing: false
                        prices:
                          - value: 2.5
                            min_quantity: 1
                            discount_percent: 0
                            package_weight: null
                            package_height: null
                            package_width: null
                            package_length: null
                        quantity: 150
                        reserved_quantity: 5
                        available_quantity: 145
                        thumbnail: null
                        images: []
                      location_stock:
                        location_id: 01H81AV32307PVBSV4RXF15LOC
                        store_id: 42
                        name: Matriz - São Paulo
                        status: available
                        location_type: seller_location
                        allows_pickup: false
                        allows_production: false
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
                  locations:
                    - location_id: 01H81AV32307PVBSV4RXF15LOC
                      store_id: 42
                      name: Matriz - São Paulo
                      status: available
                      location_type: seller_location
                      allows_pickup: false
                      allows_production: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Estoque de Produtos
      summary: Atualizar Estoque do Produto
      description: Atualiza a quantidade de estoque de um produto em uma localização específica. Permite definir um valor absoluto ou incrementar/decrementar.
      operationId: updateProductStock
      parameters:
        - name: product_id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
            example: 01H81AV32307PVBSV4RXF15EK9
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/StockUpdateInput"
            examples:
              set_absolute:
                summary: Definir quantidade absoluta
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 50
              increment:
                summary: Incrementar estoque
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 10
                  increment: true
              decrement:
                summary: Decrementar estoque
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 5
                  decrement: true
      responses:
        "200":
          description: Estoque atualizado com sucesso.
          content:
            "application/json":
              schema:
                allOf:
                  - "$ref": "#/components/schemas/SuccessResponse"
                  - type: object
                    properties:
                      data:
                        "$ref": "#/components/schemas/ProductStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  id: 321
                  product_id: 01H81AV32307PVBSV4RXF15EK9
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 50
                  reserved_quantity: 0
                  available_quantity: 50
        "400":
          description: "Requisição inválida (ex: tentar incrementar e decrementar ao mesmo tempo)."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              example:
                success: false
                message_code: BAD_REQUEST
                data: Não é permitido incrementar e decrementar estoque ao mesmo tempo.
                message: Não é permitido incrementar e decrementar estoque ao mesmo tempo.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: "Erro de validação ou operação não permitida (ex: Fulfillment)."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                validation_error:
                  summary: Erro de Validação
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      quantity:
                        - O campo quantity deve ser pelo menos 1.
                      location_id:
                        - O campo location_id é obrigatório.
                    meta: []
                fulfillment_error:
                  summary: Erro de Fulfillment
                  value:
                    success: false
                    message_code: UNPROCESSABLE_ENTITY
                    data:
                      message: "Operação não permitida: estoques em localizações do tipo fulfillment são sincronizados via WMS e não podem ser alterados por esta rota."
      security:
        - bearer: []
  "/api/locations-stock/enums":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Enums
      description: Retorna os valores e rotulos para os enums utilizados nos locais de estoque. A lista `address_types` traz todos os tipos de endereco da plataforma; para enderecos de locais de estoque, os aceitos no cadastro sao `logistics`, `local_real` e `local_devolucao`.
      responses:
        "200":
          description: Sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: object
                    properties:
                      location_types:
                        type: array
                        items:
                          type: object
                      location_statuses:
                        type: array
                        items:
                          type: object
                      address_types:
                        type: array
                        items:
                          type: object
              example:
                success: true
                message_code: SUCCESS
                data:
                  location_types:
                    - value: seller_location
                      label: Local do Vendedor
                      description: Localização pertencente ao vendedor.
                    - value: fulfillment
                      label: Centro de Distribuição
                      description: Localização gerenciada pela plataforma.
                  location_statuses:
                    - value: available
                      label: Disponível
                      description: Localização disponível para uso.
                    - value: unavailable
                      label: Indisponível
                      description: Localização indisponível para uso.
                  address_types:
                    - value: main
                      label: Principal
                    - value: other
                      label: Outro
                    - value: residential
                      label: Residencial
                    - value: delivery
                      label: Entrega
                    - value: billing
                      label: Cobrança
                    - value: corporate
                      label: Corporativo
                    - value: commercial
                      label: Comercial
                    - value: financial
                      label: Financeiro
                    - value: logistics
                      label: Logística
                    - value: local_real
                      label: Local Real
                    - value: local_devolucao
                      label: Local de Devolução
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/locations-stock":
    get:
      tags:
        - Locais de Estoque
      summary: Listar Locais de Estoque
      description: Retorna todos os locais de estoque disponiveis para a loja do seller.
      responses:
        "200":
          description: Lista de locais recuperada com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - location_id: 01K9T...
                    store_id: 42
                    name: Deposito Central
                    status: available
                    location_type: seller_location
                    allows_pickup: true
                    allows_production: false
                    branch:
                      uid: 01K9B...
                      name: Filial Campinas
                      document: "12345678000199"
                  - location_id: 01K9U...
                    store_id: 42
                    name: Loja Fisica SP
                    status: unavailable
                    location_type: seller_location
                    allows_pickup: false
                    allows_production: false
                    branch: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
    post:
      tags:
        - Locais de Estoque
      summary: Criar Local de Estoque
      description: Cria um novo local de estoque para o seller. Existe um limite de locais por loja — ao atingi-lo, a API responde 403 orientando a contatar o suporte.
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/LocationStockInput"
            example:
              name: Novo Deposito Zona Norte
              status: available
              location_type: seller_location
              allows_pickup: false
              allows_production: false
              company_branch_uid: 01K9B...
      responses:
        "201":
          description: Local criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  location_id: 01KA...
                  store_id: 42
                  name: Novo Deposito Zona Norte
                  status: available
                  location_type: seller_location
                  allows_pickup: false
                  allows_production: false
                  branch:
                    uid: 01K9B...
                    name: Filial Campinas
                    document: "12345678000199"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}":
    get:
      tags:
        - Locais de Estoque
      summary: Detalhes do Local
      description: Retorna detalhes de um local de estoque especifico.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      responses:
        "200":
          description: Detalhes do local
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: SUCCESS
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central
                  status: available
                  location_type: seller_location
                  allows_pickup: true
                  allows_production: false
                  branch: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Locais de Estoque
      summary: Atualizar Local de Estoque
      description: Atualiza dados do local (nome, flags). Vale só para locais do tipo `seller_location` — os `fulfillment` são geridos pela plataforma, então chegam aqui como somente leitura e tentar editá-los é recusado.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                  description: Nome do local
                allows_pickup:
                  type: boolean
                  description: Permite retirada no local
                allows_production:
                  type: boolean
                  description: Permite producao sob demanda
                company_branch_uid:
                  type: string
                  nullable: true
                  description: ID da filial a vincular ao local. Precisa ser uma filial ativa da empresa. Envie null para desvincular a filial atual.
            example:
              name: Deposito Central - Expansao
              allows_pickup: true
              allows_production: false
              company_branch_uid: 01K9B...
      responses:
        "200":
          description: Local atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central - Expansao
                  status: available
                  location_type: seller_location
                  allows_pickup: true
                  allows_production: false
                  branch:
                    uid: 01K9B...
                    name: Filial Campinas
                    document: "12345678000199"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/status":
    put:
      tags:
        - Locais de Estoque
      summary: Alterar Status do Local
      description: "Atualiza o status de um local de estoque (ex: available, unavailable)."
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - status
              properties:
                status:
                  type: string
                  enum:
                    - available
                    - unavailable
                  description: Novo status do local
            example:
              status: unavailable
      responses:
        "200":
          description: Status atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central
                  status: unavailable
                  location_type: seller_location
                  allows_pickup: false
                  allows_production: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/addresses":
    get:
      tags:
        - Enderecos de Estoque
      summary: Listar Enderecos
      description: Lista os enderecos vinculados a um local de estoque especifico.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque pai
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 15
        - name: type
          in: query
          schema:
            type: string
          description: "Filtrar por tipo de endereco (ex: logistics, local_real, local_devolucao)"
      responses:
        "200":
          description: Lista de enderecos
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: object
                    description: "Envelope paginado: os itens ficam em data.data e os metadados em data.meta."
                    properties:
                      meta:
                        "$ref": "#/components/schemas/PaginationMeta"
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 1
                        records: 1
                  data:
                    - id: 01K9A...
                      uid: 01K9A...
                      street: Avenida Paulista
                      number: "1000"
                      complement: null
                      reference: null
                      can_deliver_to_neighbor: false
                      delivery_instructions: null
                      district: Bela Vista
                      city: Sao Paulo
                      state: SP
                      country: BR
                      zip_code: 01310-100
                      latitude: -23.561414
                      longitude: -46.655881
                      place_id: null
                      address_components: null
                      full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                      is_complete: true
                      type: logistics
                      type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Enderecos de Estoque
      summary: Adicionar Endereco
      description: Vincula um novo endereco a um local de estoque.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque pai
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddressInput"
            example:
              zip_code: 01310-100
              street: Avenida Paulista
              number: "1000"
              district: Bela Vista
              city: Sao Paulo
              state: SP
              type: logistics
      responses:
        "201":
          description: Endereco criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1000"
                  complement: null
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/addresses/{addressId}":
    get:
      tags:
        - Enderecos de Estoque
      summary: Detalhes do Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalhes do endereco
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1000"
                  complement: null
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Enderecos de Estoque
      summary: Atualizar Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddressUpdateInput"
            example:
              number: "1001"
              complement: Sala 5
      responses:
        "200":
          description: Endereco atualizado
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1001"
                  complement: Sala 5
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1001 - Sala 5 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
    delete:
      tags:
        - Enderecos de Estoque
      summary: Remover Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Endereco desvinculado/removido com sucesso. Sem corpo de resposta.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
  "/api/categories/departaments":
    get:
      tags:
        - Categorias
      summary: Listar Departamentos
      description: Retorna os departamentos ativos da plataforma, ordenados por nome. Use para filtrar a busca de categorias.
      security:
        - bearer: []
      responses:
        "200":
          description: Lista de departamentos
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - id: 1
                    name: Alimentos e Bebidas
                    icon_svg: null
                  - id: 2
                    name: Tecnologia
                    icon_svg: <svg>...</svg>
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/categories/browse":
    get:
      tags:
        - Categorias
      summary: Navegar Categorias
      description: Navegacao hierarquica de categorias. Sem parametros retorna as categorias raiz. Use `parent_id` para descer na arvore. Quando `has_children` for `false`, a categoria é folha e pode receber anúncios.
      parameters:
        - name: departament_id
          in: query
          description: Filtrar por departamento
          required: false
          schema:
            type: integer
        - name: parent_id
          in: query
          description: ID da categoria pai (omitir para raiz)
          required: false
          schema:
            type: integer
      security:
        - bearer: []
      responses:
        "200":
          description: Lista de categorias no nivel solicitado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - id: 93
                    name: Computadores e Notebooks
                    has_children: true
                    departament:
                      id: 2
                      name: Tecnologia
                    parent: null
                  - id: 95
                    name: Celulares e Smartphones
                    has_children: false
                    departament:
                      id: 2
                      name: Tecnologia
                    parent: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/categories/search":
    get:
      tags:
        - Categorias
      summary: Buscar Categorias
      description: Busca categorias por nome. Retorna apenas categorias de último nível (folhas da árvore), que são as únicas que podem ser usadas para criar anúncios.
      parameters:
        - name: search
          in: query
          description: Termo de busca (obrigatorio)
          required: true
          schema:
            type: string
            example: notebook
        - name: departament_id
          in: query
          description: Filtrar por departamento
          required: false
          schema:
            type: integer
            example: 2
      security:
        - bearer: []
      responses:
        "200":
          description: Lista de categorias encontradas
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CategoriesResponse"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - id: 94
                    name: Notebooks
                    description: Laptops e portateis
                    departament:
                      id: 2
                      name: Tecnologia
                      icon_svg: null
                    parent:
                      id: 93
                      parent_id: null
                      name: Computadores e Notebooks
                      hierarchy:
                        - id: 93
                          name: Computadores e Notebooks
                    is_visible: true
                    priority: 1
                    domain: computers
                    level: 1
                    hierarchy:
                      - id: 93
                        name: Computadores e Notebooks
                      - id: 94
                        name: Notebooks
                    settings: []
                    catalog_overrides: null
                    total_children: 0
                    children: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/categories/{category_id}/attributes":
    get:
      tags:
        - Publicacao
      summary: Atributos da Categoria
      description: |-
        Retorna os atributos disponíveis para a categoria. Inclui características (marca, material) e variações (cor, tamanho).
        
        Use `only_features=1` ou `only_variations=1` para filtrar — os dois juntos não são aceitos (retorna 422).
        
        Com `matrix=true`, os atributos vêm agrupados por seção (`data` é uma lista de grupos `{ key, label, items }`). Sem `matrix` (ou `matrix=false`), `data` é a lista plana de atributos.
      parameters:
        - name: category_id
          in: path
          required: true
          description: ID da categoria selecionada
          schema:
            type: integer
        - name: only_variations
          in: query
          required: false
          description: Retornar apenas atributos de variações
          schema:
            type: string
        - name: only_features
          in: query
          required: false
          description: Retornar apenas características do produto
          schema:
            type: string
            example: "1"
        - name: matrix
          in: query
          required: false
          description: "Quando `true`, agrupa os atributos por matriz de organização (Identificação, Aparência, Técnico, ...). Default: `false` (lista plana)."
          schema:
            type: boolean
            example: true
      security:
        - bearer: []
      responses:
        "200":
          description: Lista de atributos da categoria
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CategoryAttributesResponse"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - key: appearance
                    label: Aparência e Design
                    items:
                      - id: 210
                        attribute_id: 3
                        category_id: 121
                        is_required: false
                        is_feature: false
                        is_variant: true
                        is_combinable: true
                        is_filter: true
                        attribute_data:
                          id: 3
                          name: Cor
                          type: color
                          value_type: select
                          is_feature: false
                          is_variant: true
                          is_calculable: false
                          domain: global
                          ui:
                            public_name: Cor
                            description: Cor predominante do produto
                            matrix: appearance
                            select_type: select
                          value_definition:
                            fields:
                              value:
                                type: string
                                required: true
                                label: Nome da cor
                                placeholder: "Ex.: Vermelho Ferrari"
                              main_color:
                                type: string
                                required: true
                                label: Cor principal
                                placeholder: "Ex.: Vermelho"
                              hex:
                                type: string
                                required: true
                                pattern: ^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$
                                label: Código HEX
                                placeholder: "Ex.: #FF0000"
                            options:
                              - id: 15
                                attribute_id: 3
                                value: Verde
                                component:
                                  value: Verde
                                  main_color: Verde
                                  hex: "#2E7D32"
                                  brightness: dark
                                position: 1
                                is_default: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/categories/{category_id}/combine-attributes":
    post:
      tags:
        - Publicacao
      summary: Gerar Combinações de Variações
      description: |-
        Gera todas as combinações possíveis a partir dos atributos de variação selecionados. Exemplo: Voltagem (110V, 220V) + Cor (Azul, Verde) = 4 combinações.
        
        Apenas atributos marcados como `is_combinable` na categoria podem ser usados. Cada valor pode ser um texto, o `id` de uma opção pré-cadastrada ou um objeto — por exemplo `{ "value": "Vinho", "hex": "#722F37", "custom": true }` para cor customizada, ou `{ "width": 50, "height": 70, "unit": "cm" }` para dimensões.
      parameters:
        - name: category_id
          in: path
          required: true
          description: ID da categoria selecionada
          schema:
            type: integer
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - attributes
              properties:
                attributes:
                  type: object
                  additionalProperties:
                    type: array
                    items:
                      type:
                        - string
                        - integer
                        - object
                  description: Mapa de attribute_id -> array de valores. Cada valor pode ser texto, option_id ou objeto (cor customizada, valor com unidade, dimensões)
                primary_attribute_id:
                  type: integer
                  nullable: true
                  description: "ID do atributo principal (opcional, usado para organizar fotos por variação; default: primeiro atributo informado)"
            example:
              attributes:
                "1":
                  - 110 V
                  - 220 V
                "3":
                  - Azul
                  - Verde
              primary_attribute_id: 1
      responses:
        "200":
          description: Combinacoes geradas com sucesso
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AttributeCombinationsResponse"
              example:
                success: true
                message_code: SUCCESS
                data:
                  combinations:
                    - attributes:
                        - attribute_id: 1
                          attribute_name: Voltagem
                          value: 110 V
                        - attribute_id: 3
                          attribute_name: Cor
                          value: Azul
                          hex: "#1565C0"
                          main_color: Azul
                          brightness: dark
                      attributes_details:
                        Voltagem: 110 V
                        Cor: Azul
                      description: "Voltagem: 110 V, Cor: Azul"
                    - attributes:
                        - attribute_id: 1
                          attribute_name: Voltagem
                          value: 110 V
                        - attribute_id: 3
                          attribute_name: Cor
                          value: Verde
                          hex: "#2E7D32"
                          main_color: Verde
                          brightness: dark
                      attributes_details:
                        Voltagem: 110 V
                        Cor: Verde
                      description: "Voltagem: 110 V, Cor: Verde"
                    - attributes:
                        - attribute_id: 1
                          attribute_name: Voltagem
                          value: 220 V
                        - attribute_id: 3
                          attribute_name: Cor
                          value: Azul
                          hex: "#1565C0"
                          main_color: Azul
                          brightness: dark
                      attributes_details:
                        Voltagem: 220 V
                        Cor: Azul
                      description: "Voltagem: 220 V, Cor: Azul"
                    - attributes:
                        - attribute_id: 1
                          attribute_name: Voltagem
                          value: 220 V
                        - attribute_id: 3
                          attribute_name: Cor
                          value: Verde
                          hex: "#2E7D32"
                          main_color: Verde
                          brightness: dark
                      attributes_details:
                        Voltagem: 220 V
                        Cor: Verde
                      description: "Voltagem: 220 V, Cor: Verde"
                  primary_attribute:
                    id: 1
                    name: Voltagem
                    has_options: true
                    default_unit: null
                    values:
                      - value: 110 V
                        label: 110 V
                      - value: 220 V
                        label: 220 V
        "400":
          description: Pedido inválido (sem atributos, atributo não combinável, valor não permitido ou categoria inexistente)
          content:
            "application/json":
              example:
                success: false
                message_code: BAD_REQUEST
                message: Atributo ID 5 não é combinável ou não pertence à categoria
                data: Atributo ID 5 não é combinável ou não pertence à categoria
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/items/simulate":
    post:
      tags:
        - Publicacao
      summary: Simular Anúncio
      description: |-
        Valida os dados do anúncio sem persistir. O body é idêntico ao de `POST /api/items/create` e aceita todos os campos do mesmo schema `CreateAdvertisementRequest`, incluindo `description` e `technical_sheets`.
        
        **Validações executadas:**
        - Atributos obrigatórios da categoria preenchidos
        - GTIN (EAN) válido, quando informado
        - Estrutura e tipos dos campos (incluindo `description.layout`, `description.raw_content` e cada item de `technical_sheets`)
        - `seller_sku` (ou `variations[].seller_sku`) existente na loja, com preço e estoque ativos
        - Imagens referenciadas existem na plataforma
        
        Use antes de criar para identificar erros de payload com antecedência. Se retornar **200**, os dados estão prontos para chamar `POST /api/items/create`.
        
        Na resposta, `item_id` é apenas ilustrativo (sequencial zero) — o ID real só é gerado na criação. Erros de regra de negócio (ex.: SKU sem estoque ativo) retornam **422** com `data.reason` e `data.details`.
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CreateAdvertisementRequest"
            example:
              title: Caixa de Som JBL Go!
              category_id: 121
              gtin:
                type: 13
                value: "6925281995583"
              attributes:
                - id: 1
                  value_id: 3
                  value: HyperX
                - id: 397
                  value: 12
                  unit: ml
              variations:
                - seller_sku: FK-JBL-2000-AZUL
                  attributes:
                    - attribute_id: 397
                      attribute_name: Quantidade
                      value: "1"
                      unit: ml
                  images:
                    - id: "{{image_id}}"
              images:
                - id: "{{image_id}}"
              description:
                layout: markdown
                raw_content: |-
                  ## Caixa de Som JBL Go!
                  
                  Som potente, bateria de longa duração e resistência à água.
              technical_sheets:
                - type: technical_specification
                  title: Especificações Técnicas
                  items:
                    - item_key: Potência
                      item_value: 4,2W RMS
                      display_order: 0
                    - item_key: Bateria
                      item_value: 5 horas
                      display_order: 1
      responses:
        "200":
          description: Anuncio validado com sucesso (objeto projetado, nada foi persistido)
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/SimulatedAdvertisementResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/create":
    post:
      tags:
        - Publicacao
      summary: Criar Anúncio
      description: |-
        Cria o anúncio com status `draft` (rascunho).
        
        É obrigatório informar **`seller_sku`** (anúncio sem variações) **ou** **`variations`** (cada uma com seu `seller_sku`). Os SKUs precisam existir previamente na loja, com preço e estoque configurados.
        
        O payload aceita opcionalmente os campos **`description`** (texto longo do anúncio) e **`technical_sheets`** (fichas técnicas). Assim o integrador pode criar o anúncio já completo, sem precisar chamar `PUT /api/items/{id}/description` e `POST /api/items/{id}/technical-sheets` em separado.
        
        Se estes campos forem omitidos, eles podem ser preenchidos depois usando os endpoints específicos. Após o rascunho estar pronto, ative o anúncio com [PUT /api/items/{publication_id}/publish](/reference#tag/publicacao/PUT/api/items/{publication_id}/publish).
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CreateAdvertisementRequest"
            example:
              title: Caixa de Som JBL Go!
              category_id: 121
              gtin:
                type: 13
                value: "6925281995583"
              attributes:
                - id: 1
                  value_id: 3
                  value: HyperX
                - id: 397
                  value: 12
                  unit: ml
              variations:
                - seller_sku: FK-JBL-2000-AZUL
                  attributes:
                    - attribute_id: 397
                      attribute_name: Quantidade
                      value: "1"
                      unit: ml
                  images:
                    - id: "{{image_id}}"
              images:
                - id: "{{image_id}}"
              description:
                layout: markdown
                raw_content: |-
                  ## Caixa de Som JBL Go!
                  
                  Som potente, bateria de longa duração e resistência à água.
              technical_sheets:
                - type: technical_specification
                  title: Especificações Técnicas
                  items:
                    - item_key: Potência
                      item_value: 4,2W RMS
                      display_order: 0
                    - item_key: Bateria
                      item_value: 5 horas
                      display_order: 1
      responses:
        "201":
          description: Anuncio criado com sucesso como rascunho
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  item_id: SAM-0000000000007
                  title: Caixa de Som JBL Go!
                  short_description: null
                  slug: caixa-de-som-jbl-go
                  type:
                    value: default
                    label: Padrão
                  status:
                    value: draft
                    label: Rascunho
                  seller_sku: null
                  price_range: null
                  score: null
                  campaigns: []
                  pricing_context: []
                  frontend_sync_status: pending
                  last_frontend_sync_at: null
                  submitted_for_review_at: null
                  reviewed_at: null
                  rejection_reason: null
                  gtin:
                    type: "13"
                    value: "6925281995583"
                  store:
                    store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                    store_name: Minha Loja
                    store_code: MINHALOJA
                    store_status: active
                    store_url: minha-loja
                    store_type: store_seller
                    commission_table_id: 1
                    max_locations: 1
                    features: []
                    company: {}
                    shares_products: false
                    shares_stock_locations: false
                    shares_logistics: false
                    is_primary: false
                    primary_store: null
                  department:
                    id: 2
                    name: Tecnologia
                    icon_svg: null
                  category:
                    id: 121
                    parent_id: 94
                    name: Caixas de Som
                    hierarchy:
                      - id: 94
                        name: Notebooks
                      - id: 121
                        name: Caixas de Som
                  thumbnail: null
                  images:
                    - id: 9c2f6a1e-4b7d-4f3a-9e2b-1d5c8a7f6e3b
                      resources:
                        - name: sm
                          url: https://cdn.exemplo.com.br/images/9c2f6a1e-sm.webp
                        - name: lg
                          url: https://cdn.exemplo.com.br/images/9c2f6a1e-lg.webp
                  attributes:
                    - attribute_id: 1
                      attribute_name: Marca
                      attribute_value: HyperX
                      value_unit: null
                      value_id: 3
                      component: null
                  variations:
                    - offer_id: 01K8QC1NPP47BZ3Q1PLTACQ3OB
                      description: "Quantidade: 1 ml"
                      attributes:
                        - attribute_id: 397
                          attribute_name: Quantidade
                          value: "1"
                          unit: ml
                      status: pending
                      type: offer
                      images: []
                      seller_sku: FK-JBL-2000-AZUL
                      offer:
                        price_type: default
                        price_from: 299.9
                        prices: []
                        quantity: 50
                        price: 299.9
                        original_price: null
                        has_discount: false
                        discount_percent: 0
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/review-checklist":
    get:
      tags:
        - Publicacao
      summary: Checklist de Revisão
      description: |-
        Retorna o estado do anúncio em rascunho com a lista de verificações necessárias para publicação (imagem, título, descrições, atributos obrigatórios da categoria e SKU vinculado). Use para descobrir o que ainda falta antes de chamar `PUT /publish`.
        
        O retorno traz `checks` (cada verificação com `ok`) e `issues` (pendências, com `severity`). Itens com `severity=error` bloqueiam a publicação; `warning` (ex.: descrição curta) é apenas recomendação. O campo `ready=true` indica que o anúncio passa nos requisitos e pode ser publicado.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` retornado em `POST /api/items/create`"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Checklist obtido
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  ready: false
                  issues:
                    - code: missing_short_description
                      severity: warning
                      message: Recomendamos preencher a descrição curta para melhor exibição em listagens.
                      field: short_description
                    - code: missing_required_attribute
                      severity: error
                      message: "Atributo obrigatório não preenchido: Marca"
                      field: attributes
                  checks:
                    - code: image
                      label: Pelo menos uma imagem
                      ok: true
                    - code: title
                      label: Título preenchido
                      ok: true
                    - code: short_description
                      label: Descrição curta preenchida
                      ok: false
                    - code: description
                      label: Descrição detalhada preenchida
                      ok: true
                    - code: required_attributes
                      label: Atributos obrigatórios da categoria
                      ok: false
                    - code: sku
                      label: SKU vinculado
                      ok: true
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/items/{publication_id}/publish":
    put:
      tags:
        - Publicacao
      summary: Publicar Anúncio
      description: |-
        Envia o anúncio em rascunho (`draft`) para revisão da plataforma. O status muda para `pending_review` e o anúncio fica disponível para venda assim que aprovado.
        
        **Pré-requisitos:**
        
        - O anúncio precisa estar em `draft` e ser do tipo padrão (`default`).
        - Precisa passar no checklist. Consulte [GET /review-checklist](/reference#tag/publicacao/GET/api/items/{publication_id}/review-checklist) antes para evitar 422.
        
        Se o checklist falhar, a resposta vem com código 422 e o campo `checklist` (no topo do envelope) traz as verificações e pendências.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` retornado em `POST /api/items/create`"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Anúncio enviado para revisão
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
              example:
                success: true
                message_code: SUCCESS
                data:
                  item_id: SAM-0000000000007
                  title: Caixa de Som JBL Go!
                  short_description: Som potente e portátil
                  slug: caixa-de-som-jbl-go
                  type:
                    value: default
                    label: Padrão
                  status:
                    value: pending_review
                    label: Revisão
                  seller_sku: null
                  price_range: null
                  score: null
                  campaigns: []
                  pricing_context: []
                  frontend_sync_status: pending
                  last_frontend_sync_at: null
                  submitted_for_review_at: "2026-06-10 14:32:11"
                  reviewed_at: null
                  rejection_reason: null
                  gtin:
                    type: "13"
                    value: "6925281995583"
                  department:
                    id: 2
                    name: Tecnologia
                    icon_svg: null
                  category:
                    id: 121
                    parent_id: 94
                    name: Caixas de Som
                    hierarchy: []
                  thumbnail: null
                  images: []
                  attributes: []
                  variations: []
        "400":
          description: Anúncio não está em rascunho ou tipo não suportado
          content:
            "application/json":
              example:
                success: false
                message_code: BAD_REQUEST
                data:
                  - Apenas anúncios em rascunho podem ser publicados.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Checklist de revisão não passou
          content:
            "application/json":
              example:
                success: false
                message_code: BAD_REQUEST
                data:
                  - O anúncio ainda não está pronto para revisão.
                checklist:
                  ready: false
                  issues:
                    - code: missing_required_attribute
                      severity: error
                      message: "Atributo obrigatório não preenchido: Marca"
                      field: attributes
                  checks:
                    - code: required_attributes
                      label: Atributos obrigatórios da categoria
                      ok: false
  "/api/items/catalog/options/{optionId}/attach-sku":
    post:
      tags:
        - Catálogo e Ofertas
      summary: Vincular SKU à Variação
      description: |-
        Vincula um produto (SKU) a uma variação de um anúncio de **catálogo** ativo, criando uma oferta da loja. O SKU deve existir na loja e ter preço e estoque configurados. Pode-se usar SKUs diferentes por variação ou o mesmo para todas.
        
        Após o vínculo, o recálculo da buybox é agendado em background — por isso `is_buybox_winner` pode vir `false` logo após a criação.
      parameters:
        - name: optionId
          in: path
          required: true
          description: ID da opção/variação do catálogo (26 caracteres)
          schema:
            type: string
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AttachSKURequest"
            example:
              seller_sku: FK-JBL-2000-VERDE
              status: active
      responses:
        "200":
          description: SKU vinculado com sucesso
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AttachedSKUResponse"
              example:
                success: true
                message_code: SUCCESS
                data:
                  offer_id: 01K8QC1NPP47BZ3Q1PLTACQ3OB
                  seller_sku: FK-JBL-2000-VERDE
                  status: active
                  is_buybox_winner: false
                  last_buybox_calculation: null
                  price_data:
                    price: 299.9
                  stock_data:
                    id: 12345
                    product_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    location_id: 01K8PB0KMM36AY2P0NKSZBP2NC
                    quantity: 50
                    reserved_quantity: 5
                    available_quantity: 45
                  publication:
                    item_id: SAM-0000000000003
                    title: Caixa de Som JBL Go!
                    status: active
                    type: catalog
                    thumbnail_id: 9c2f6a1e-4b7d-4f3a-9e2b-1d5c8a7f6e3b
                  option:
                    uid: 01K8QC0XYZ47BZ3Q1PLTACQ3OA
                    description: "Voltagem: 110 V, Cor: Verde"
                    attributes:
                      - attribute_id: 1
                        attribute_name: Voltagem
                        value: 110 V
                        unit: null
                        value_id: 1
                      - attribute_id: 3
                        attribute_name: Cor
                        value: Verde
                        unit: null
                        value_id: 15
                    type: option
                    status: active
                    images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items":
    get:
      tags:
        - Gestão de Anúncios
      summary: Listar Anúncios
      description: |-
        Lista paginada dos anúncios da loja autenticada, com busca, filtros e ordenação.
        
        A paginação vem aninhada em **`data.meta.pagination`** (padrão `PaginatedResource`), com os itens em `data.data`.
        
        Quando `search` é informado, a ordenação passa a ser por relevância e o parâmetro `sort` é ignorado.
      security:
        - bearer: []
      parameters:
        - name: search
          in: query
          required: false
          description: Busca textual (título, identificadores etc.). Também aceito como `filter[search]`.
          schema:
            type: string
            example: furadeira
        - name: sort
          in: query
          required: false
          description: "Campo de ordenação. Prefixe com `-` para ordem decrescente. Aceita: `item_id`, `title`, `status`, `type`, `created_at`, `updated_at`, `score`, `price`. Default: `-updated_at`. Ignorado quando há `search`."
          schema:
            type: string
            example: "-updated_at"
        - name: filter[item_id]
          in: query
          required: false
          description: Filtra por `item_id` exato.
          schema:
            type: string
            example: SAM-0000000000007
        - name: filter[type]
          in: query
          required: false
          description: Tipo do anúncio.
          schema:
            type: string
            enum:
              - default
              - catalog
        - name: filter[status]
          in: query
          required: false
          description: Status do anúncio.
          schema:
            type: string
            enum:
              - active
              - draft
              - pending_review
              - inactive
              - paused
        - name: filter[category_id]
          in: query
          required: false
          description: ID da categoria. Inclui automaticamente as subcategorias descendentes.
          schema:
            type: integer
            example: 152
        - name: filter[departament_id]
          in: query
          required: false
          description: ID do departamento.
          schema:
            type: integer
            example: 3
        - name: filter[score_tier]
          in: query
          required: false
          description: "Faixa de score: `low` (<40), `medium` (40–69) ou `high` (≥70). Anúncios sem score contam como `low`."
          schema:
            type: string
            enum:
              - low
              - medium
              - high
        - name: filter[price_min]
          in: query
          required: false
          description: Preço mínimo.
          schema:
            type: number
            example: 50
        - name: filter[price_max]
          in: query
          required: false
          description: Preço máximo.
          schema:
            type: number
            example: 300
        - name: filter[widget]
          in: query
          required: false
          description: "Recortes prontos de dashboard. Para anúncios `default`: `campanha` (em campanha ativa), `inativos`, `sem_estoque`. Combinado com `filter[type]=catalog`: `vencendo` (ofertas que vencem a buybox), `competindo` (variações com mais de uma oferta ativa), `sem_estoque`."
          schema:
            type: string
            example: sem_estoque
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `15`, máximo `100`.
          schema:
            type: integer
            default: 15
            example: 15
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
      responses:
        "200":
          description: Anúncios listados
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 2
                        records: 2
                  data:
                    - id: 42
                      item_id: SAM-0000000000007
                      title: Furadeira de Impacto 650W 110V
                      type: default
                      status: active
                      thumbnail:
                        id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                        resource:
                          name: thumbnail
                          url: https://cdn.exemplo.com/images/furadeira-thumb.webp
                      department:
                        id: 3
                        name: Ferramentas
                      category:
                        id: 152
                        name: Furadeiras
                      price_range:
                        - 189.9
                        - 219.9
                      store:
                        id: 12
                        store_name: Loja das Ferramentas
                      score:
                        total: 78
                        tier: high
                      frontend_sync_status: synced
                      last_frontend_sync_at: "2026-06-09 14:32:11"
                    - id: 57
                      item_id: SAM-0000000000012
                      title: Parafusadeira sem Fio 12V com Maleta
                      type: default
                      status: paused
                      thumbnail: null
                      department:
                        id: 3
                        name: Ferramentas
                      category:
                        id: 158
                        name: Parafusadeiras
                      price_range: null
                      store:
                        id: 12
                        store_name: Loja das Ferramentas
                      score:
                        total: 35
                        tier: low
                      frontend_sync_status: pending
                      last_frontend_sync_at: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/items/my-categories-tree":
    get:
      tags:
        - Gestão de Anúncios
      summary: Árvore de Categorias da Loja
      description: |-
        Retorna a árvore departamento → categoria → subcategoria considerando apenas onde a loja autenticada tem pelo menos um anúncio. Útil para montar filtros de navegação no portal.
        
        Cada categoria traz `count` (anúncios diretamente nela) e `total_count` (incluindo as descendentes).
      security:
        - bearer: []
      parameters:
        - name: type
          in: query
          required: false
          description: Restringe a contagem a um tipo de anúncio.
          schema:
            type: string
            enum:
              - default
              - catalog
      responses:
        "200":
          description: Árvore montada
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  departaments:
                    - id: 3
                      name: Ferramentas
                      count: 8
                      categories:
                        - id: 150
                          name: Ferramentas Elétricas
                          parent_id: null
                          count: 2
                          total_count: 8
                          children:
                            - id: 152
                              name: Furadeiras
                              parent_id: 150
                              count: 5
                              total_count: 5
                              children: []
                            - id: 158
                              name: Parafusadeiras
                              parent_id: 150
                              count: 1
                              total_count: 1
                              children: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/items/{publication_id}":
    get:
      tags:
        - Gestão de Anúncios
      summary: Detalhe do Anúncio
      description: "Retorna o anúncio completo: dados gerais, imagens, atributos, variações com preço/estoque, score, campanhas e estado de sincronização com a vitrine. É o mesmo shape devolvido por `POST /api/items/create`."
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Anúncio encontrado
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Dados Gerais
      description: |-
        Atualiza título, descrição curta, miniatura e galeria de imagens de um anúncio do tipo `default`. Anúncios de catálogo não podem ser editados pelo vendedor (retorna `403`).
        
        As imagens devem ter sido enviadas previamente pela API de mídia — aqui você só referencia os IDs. Campos omitidos permanecem como estão (exceto `title`, que é sempre obrigatório).
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                title:
                  type: string
                  maxLength: 100
                  description: Título do anúncio
                short_description:
                  type:
                    - string
                    - "null"
                  maxLength: 260
                  description: Descrição curta exibida em listagens
                thumbnail_id:
                  type:
                    - string
                    - "null"
                  description: ID de uma imagem já enviada, usada como miniatura
                images:
                  type:
                    - array
                    - "null"
                  description: Galeria de imagens do anúncio (substitui a atual quando enviada)
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        description: ID da imagem
                    required:
                      - id
              required:
                - title
            example:
              title: Furadeira de Impacto 650W 110V com Maleta
              short_description: Furadeira de impacto 650W com mandril de 1/2" e maleta para transporte.
              thumbnail_id: 01JD2R8KQX5VTWMA3B7YCE9FGH
              images:
                - id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                - id: 01JD2R8KQX5VTWMA3B7YCE9FGJ
      responses:
        "200":
          description: "Anúncio atualizado (envelope com `message_code: RESOURCE_UPDATED` e o anúncio completo em `data`)"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/description":
    get:
      tags:
        - Gestão de Anúncios
      summary: Consultar Descrição
      description: Retorna a descrição detalhada do anúncio no formato bruto (`raw_content`), junto com o `layout` em que foi gravada. Para ver o conteúdo já processado para exibição, use `GET .../description/preview`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Descrição encontrada
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  layout: markdown
                  raw_content: |-
                    ## Furadeira de Impacto 650W
                    
                    Ideal para alvenaria, madeira e metal.
                    
                    - Mandril de 1/2"
                    - Empunhadura auxiliar
                    - Maleta inclusa
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Descrição
      description: |-
        Substitui a descrição detalhada de um anúncio do tipo `default`. Envie o conteúdo bruto e o `layout` correspondente — o processamento para exibição acontece na leitura.
        
        Anúncios de catálogo têm descrição mantida pela plataforma e retornam `403`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                layout:
                  type: string
                  enum:
                    - text
                    - html
                    - markdown
                  description: Formato do `raw_content`
                raw_content:
                  type: string
                  description: Conteúdo bruto da descrição
              required:
                - layout
                - raw_content
            example:
              layout: markdown
              raw_content: |-
                ## Furadeira de Impacto 650W
                
                Ideal para alvenaria, madeira e metal.
      responses:
        "200":
          description: Descrição atualizada
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  layout: markdown
                  raw_content: |-
                    ## Furadeira de Impacto 650W
                    
                    Ideal para alvenaria, madeira e metal.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/description/preview":
    get:
      tags:
        - Gestão de Anúncios
      summary: Pré-visualizar Descrição
      description: "Retorna a descrição com o conteúdo já processado de acordo com o `layout` (ex.: Markdown convertido para HTML), em `processed_content`. Use para mostrar ao vendedor exatamente o que o comprador verá."
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Preview gerado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  layout: markdown
                  raw_content: |-
                    ## Furadeira de Impacto 650W
                    
                    Ideal para alvenaria, madeira e metal.
                  processed_content: |-
                    <h2>Furadeira de Impacto 650W</h2>
                    <p>Ideal para alvenaria, madeira e metal.</p>
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/items/{publication_id}/score":
    get:
      tags:
        - Gestão de Anúncios
      summary: Score do Anúncio
      description: |-
        Retorna o score de qualidade do anúncio (0–100) com o detalhamento por pilar: **qualidade** (fotos, título, descrição — até 70 pts), **reputação** (avaliações e perguntas respondidas — até 30 pts) e **vendas** (até 30 pts).
        
        Cada item do `breakdown` traz os pontos obtidos, o máximo possível, um `status` (`excellent`, `info`, `warning`) e uma dica (`tip`) de como melhorar.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Score calculado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  total_score: 78
                  last_calculated_at: "2026-06-09T14:30:00-03:00"
                  breakdown:
                    quality:
                      score: 50
                      max: 70
                      details:
                        - item: fotos
                          points: 20
                          max: 20
                          status: excellent
                          tip: Você tem o número ideal de fotos.
                        - item: titulo
                          points: 15
                          max: 15
                          status: excellent
                          tip: Seu título está com tamanho ideal.
                        - item: descricao
                          points: 5
                          max: 15
                          status: warning
                          tip: Escreva uma descrição mais completa (mais de 500 caracteres).
                    reputation:
                      score: 26
                      max: 30
                      details:
                        - item: stars
                          points: 16
                          max: 20
                          status: excellent
                          tip: Sua média é 4.2 estrelas.
                        - item: questions
                          points: 10
                          max: 10
                          status: excellent
                          tip: Todas as perguntas foram respondidas.
                    sales:
                      score: 2
                      max: 30
                      details:
                        - item: orders
                          points: 2
                          max: 30
                          status: info
                          tip: Item com 3 vendas confirmadas.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/items/{publication_id}/attributes":
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Atributos
      description: |-
        Substitui os atributos base de um anúncio do tipo `default`. Os atributos enviados são validados contra a categoria atual do anúncio — obrigatórios da categoria precisam estar presentes e valores de atributos com opções pré-definidas precisam existir.
        
        Atributos de dimensão exigem `width`/`height` (e `depth` em 3D) com `unit`; atributos do tipo lista exigem `items`. As variações existentes não são alteradas.
        
        Anúncios de catálogo retornam `403`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                attributes:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    properties:
                      id:
                        type: integer
                        description: ID do atributo (aceita também o alias `attribute_id`)
                      value:
                        description: Valor do atributo. Para atributos com opções pré-definidas, informe `value` ou `value_id`. Aceita o alias `attribute_value`.
                      value_id:
                        type:
                          - integer
                          - "null"
                        description: ID da opção pré-definida do atributo
                      unit:
                        type:
                          - string
                          - "null"
                        description: Unidade de medida (obrigatória em atributos numéricos com unidade). Aceita o alias `value_unit`.
                      width:
                        type:
                          - number
                          - "null"
                        description: Largura — atributos de dimensão (2D/3D)
                      height:
                        type:
                          - number
                          - "null"
                        description: Altura — atributos de dimensão (2D/3D)
                      depth:
                        type:
                          - number
                          - "null"
                        description: Profundidade — atributos de dimensão 3D
                      items:
                        type:
                          - array
                          - "null"
                        description: Itens — atributos do tipo lista
                        items:
                          type: object
                    required:
                      - id
              required:
                - attributes
            example:
              attributes:
                - id: 2
                  value_id: 25
                - id: 14
                  value: "650"
                  unit: W
                - id: 21
                  width: 28
                  height: 9
                  depth: 26
                  unit: cm
      responses:
        "200":
          description: "Atributos atualizados (envelope com `message_code: RESOURCE_UPDATED` e o anúncio completo em `data`)"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/variations":
    get:
      tags:
        - Gestão de Anúncios
      summary: Listar Variações
      description: Lista as variações do anúncio com atributos, imagens e a oferta de cada uma (preço, estoque, desconto). Em anúncios `default` o identificador vem como `offer_id`; em catálogo, como `option_id`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      responses:
        "200":
          description: Variações listadas
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PublicationVariationOut"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - offer_id: 01JD4A0B1C2D3E4F5G6H7J8K9M
                    description: "Voltagem: 110 V"
                    attributes:
                      - attribute_id: 7
                        attribute_name: Voltagem
                        value: 110 V
                        value_id: 71
                    status: active
                    type: offer
                    images:
                      - id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                        resources:
                          - name: sm
                            url: https://cdn.exemplo.com/images/furadeira-sm.webp
                          - name: lg
                            url: https://cdn.exemplo.com/images/furadeira-lg.webp
                    seller_sku: FUR-650-110
                    offer:
                      price_type: default
                      price_from: 219.9
                      prices: []
                      quantity: 35
                      price: 189.9
                      original_price: 219.9
                      has_discount: true
                      discount_percent: 14
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    post:
      tags:
        - Gestão de Anúncios
      summary: Adicionar Variação
      description: |-
        Adiciona uma variação a um anúncio do tipo `default`, vinculando um SKU da loja. O `seller_sku` precisa pertencer à loja autenticada (ou estar associado a ela, no modo de produtos compartilhados) — preço e estoque da variação derivam dele.
        
        Os atributos da variação seguem as mesmas regras de validação dos atributos do anúncio (opções pré-definidas, unidades, dimensões e listas).
        
        Anúncios de catálogo retornam `403`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                description:
                  type: string
                  description: "Descrição legível da combinação (ex.: Voltagem: 220 V)"
                seller_sku:
                  type: string
                  description: SKU da loja vinculado à variação
                images:
                  type:
                    - array
                    - "null"
                  description: Imagens específicas da variação
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                        description: ID da imagem
                    required:
                      - id
                status:
                  type:
                    - string
                    - "null"
                  enum:
                    - pending
                    - active
                    - hidden
                  description: Status inicial da variação
                attributes:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    properties:
                      attribute_id:
                        type: integer
                        description: ID do atributo
                      attribute_name:
                        type:
                          - string
                          - "null"
                      value:
                        description: Valor do atributo. Para atributos com opções pré-definidas, informe `value` ou `value_id`.
                      value_id:
                        type:
                          - integer
                          - "null"
                        description: ID da opção pré-definida do atributo
                      unit:
                        type:
                          - string
                          - "null"
                        description: Unidade de medida (obrigatória em atributos numéricos com unidade)
                      width:
                        type:
                          - number
                          - "null"
                        description: Largura — atributos de dimensão (2D/3D)
                      height:
                        type:
                          - number
                          - "null"
                        description: Altura — atributos de dimensão (2D/3D)
                      depth:
                        type:
                          - number
                          - "null"
                        description: Profundidade — atributos de dimensão 3D
                      items:
                        type:
                          - array
                          - "null"
                        description: Itens — atributos do tipo lista
                        items:
                          type: object
                    required:
                      - attribute_id
              required:
                - description
                - seller_sku
                - attributes
            example:
              description: "Voltagem: 220 V"
              seller_sku: FUR-650-220
              status: active
              images:
                - id: 01JD2R8KQX5VTWMA3B7YCE9FGJ
              attributes:
                - attribute_id: 7
                  attribute_name: Voltagem
                  value: 220 V
                  value_id: 72
      responses:
        "201":
          description: "Variação criada (envelope com `message_code: RESOURCE_CREATED` e a variação em `data`)"
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/PublicationVariationOut"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Variações em Lote
      description: |-
        Substitui o conjunto de variações de um anúncio do tipo `default`. Envie a lista completa — variações ausentes deixam de existir.
        
        Em anúncios `default` cada variação precisa de `seller_sku` (a ausência retorna `422`). No `status`, `paused` é aceito como sinônimo de `hidden`.
        
        Anúncios de catálogo retornam `403`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                variations:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    properties:
                      seller_sku:
                        type:
                          - string
                          - "null"
                        description: SKU da loja (obrigatório em anúncios `default`)
                      description:
                        type:
                          - string
                          - "null"
                        description: Descrição legível da combinação
                      images:
                        type:
                          - array
                          - "null"
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                      status:
                        type:
                          - string
                          - "null"
                        enum:
                          - pending
                          - active
                          - hidden
                          - paused
                        description: "`paused` é normalizado para `hidden`"
                      attributes:
                        type:
                          - array
                          - "null"
                        items:
                          type: object
                          properties:
                            attribute_id:
                              type: integer
                              description: ID do atributo
                            attribute_name:
                              type:
                                - string
                                - "null"
                            value:
                              description: Valor do atributo. Para atributos com opções pré-definidas, informe `value` ou `value_id`.
                            value_id:
                              type:
                                - integer
                                - "null"
                              description: ID da opção pré-definida do atributo
                            unit:
                              type:
                                - string
                                - "null"
                              description: Unidade de medida (obrigatória em atributos numéricos com unidade)
                            width:
                              type:
                                - number
                                - "null"
                              description: Largura — atributos de dimensão (2D/3D)
                            height:
                              type:
                                - number
                                - "null"
                              description: Altura — atributos de dimensão (2D/3D)
                            depth:
                              type:
                                - number
                                - "null"
                              description: Profundidade — atributos de dimensão 3D
                            items:
                              type:
                                - array
                                - "null"
                              description: Itens — atributos do tipo lista
                              items:
                                type: object
                          required:
                            - attribute_id
              required:
                - variations
            example:
              variations:
                - seller_sku: FUR-650-110
                  description: "Voltagem: 110 V"
                  status: active
                  attributes:
                    - attribute_id: 7
                      attribute_name: Voltagem
                      value: 110 V
                      value_id: 71
                - seller_sku: FUR-650-220
                  description: "Voltagem: 220 V"
                  status: hidden
                  attributes:
                    - attribute_id: 7
                      attribute_name: Voltagem
                      value: 220 V
                      value_id: 72
      responses:
        "200":
          description: "Variações atualizadas (envelope com `message_code: RESOURCE_UPDATED` e o anúncio completo em `data`)"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/CreatedAdvertisementResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/variations/{uid}":
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Variação
      description: |-
        Atualiza uma variação específica de um anúncio do tipo `default`. Todos os campos são opcionais — o que não for enviado é mantido.
        
        Anúncios de catálogo retornam `403`; variação inexistente retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: uid
          in: path
          required: true
          description: ID da variação (campo `offer_id`/`option_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD4A0B1C2D3E4F5G6H7J8K9M
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                description:
                  type:
                    - string
                    - "null"
                seller_sku:
                  type:
                    - string
                    - "null"
                  description: Novo SKU da loja para a variação
                images:
                  type:
                    - array
                    - "null"
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                    required:
                      - id
                status:
                  type:
                    - string
                    - "null"
                  enum:
                    - pending
                    - active
                    - hidden
                attributes:
                  type:
                    - array
                    - "null"
                  items:
                    type: object
                    properties:
                      attribute_id:
                        type: integer
                        description: ID do atributo
                      attribute_name:
                        type:
                          - string
                          - "null"
                      value:
                        description: Valor do atributo. Para atributos com opções pré-definidas, informe `value` ou `value_id`.
                      value_id:
                        type:
                          - integer
                          - "null"
                        description: ID da opção pré-definida do atributo
                      unit:
                        type:
                          - string
                          - "null"
                        description: Unidade de medida (obrigatória em atributos numéricos com unidade)
                      width:
                        type:
                          - number
                          - "null"
                        description: Largura — atributos de dimensão (2D/3D)
                      height:
                        type:
                          - number
                          - "null"
                        description: Altura — atributos de dimensão (2D/3D)
                      depth:
                        type:
                          - number
                          - "null"
                        description: Profundidade — atributos de dimensão 3D
                      items:
                        type:
                          - array
                          - "null"
                        description: Itens — atributos do tipo lista
                        items:
                          type: object
                    required:
                      - attribute_id
            example:
              status: hidden
      responses:
        "200":
          description: "Variação atualizada (envelope com `message_code: RESOURCE_UPDATED` e a variação em `data`)"
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/PublicationVariationOut"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
    delete:
      tags:
        - Gestão de Anúncios
      summary: Remover Variação
      description: |-
        Remove uma variação do anúncio. Retorna `204` sem corpo — no cliente, cheque o status da resposta antes de tentar fazer parse de JSON.
        
        Anúncios de catálogo retornam `403`; variação inexistente retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: uid
          in: path
          required: true
          description: ID da variação (campo `offer_id`/`option_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD4A0B1C2D3E4F5G6H7J8K9M
      responses:
        "204":
          description: Variação removida (sem corpo)
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/items/{publication_id}/technical-sheets":
    get:
      tags:
        - Gestão de Anúncios
      summary: Listar Fichas Técnicas
      description: Lista as fichas técnicas do anúncio, opcionalmente filtrando por tipo. Anúncio inexistente ou que não pertence à loja retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: type
          in: query
          required: false
          description: Filtra por tipo de ficha.
          schema:
            type: string
            enum:
              - technical_specification
              - characteristics
              - materials
              - installation
              - maintenance
              - safety
              - environmental
              - custom
      responses:
        "200":
          description: Fichas listadas
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/TechnicalSheet"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - sheet_id: 01JD3F8GZQK2V5W7XA9BC4DE6H
                    publication_id: SAM-0000000000007
                    type:
                      value: technical_specification
                      label: Ficha Técnica
                      description: Especificações técnicas detalhadas do produto
                    title: Especificações gerais
                    items:
                      - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2V4
                        item_key: Potência
                        item_value: 650 W
                        display_order: 0
                      - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2W5
                        item_key: Voltagem
                        item_value: 110 V
                        display_order: 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    post:
      tags:
        - Gestão de Anúncios
      summary: Criar Ficha Técnica
      description: |-
        Cria uma ficha técnica para o anúncio. A combinação de `type` + `title` é única por anúncio — repetir retorna `422`.
        
        Os `items` são opcionais na criação; você pode incluí-los depois via atualização ou importação de HTML.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - technical_specification
                    - characteristics
                    - materials
                    - installation
                    - maintenance
                    - safety
                    - environmental
                    - custom
                  description: Tipo da ficha técnica
                title:
                  type: string
                  maxLength: 255
                items:
                  type: array
                  items:
                    type: object
                    properties:
                      item_key:
                        type: string
                        maxLength: 255
                      item_value:
                        type: string
                        maxLength: 1000
                      display_order:
                        type: integer
                        minimum: 0
                    required:
                      - item_key
                      - item_value
              required:
                - type
                - title
            example:
              type: technical_specification
              title: Especificações gerais
              items:
                - item_key: Potência
                  item_value: 650 W
                  display_order: 0
                - item_key: Voltagem
                  item_value: 110 V
                  display_order: 1
      responses:
        "201":
          description: "Ficha criada (envelope com `message_code: RESOURCE_CREATED`)"
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/TechnicalSheet"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  sheet_id: 01JD3F8GZQK2V5W7XA9BC4DE6H
                  publication_id: SAM-0000000000007
                  type:
                    value: technical_specification
                    label: Ficha Técnica
                    description: Especificações técnicas detalhadas do produto
                  title: Especificações gerais
                  items:
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2V4
                      item_key: Potência
                      item_value: 650 W
                      display_order: 0
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2W5
                      item_key: Voltagem
                      item_value: 110 V
                      display_order: 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/technical-sheets/{ulid}":
    get:
      tags:
        - Gestão de Anúncios
      summary: Consultar Ficha Técnica
      description: Retorna uma ficha técnica específica com todos os itens. Ficha inexistente ou de anúncio que não pertence à loja retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: ulid
          in: path
          required: true
          description: ID da ficha técnica (campo `sheet_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD3F8GZQK2V5W7XA9BC4DE6H
      responses:
        "200":
          description: Ficha encontrada
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/TechnicalSheet"
              example:
                success: true
                message_code: SUCCESS
                data:
                  sheet_id: 01JD3F8GZQK2V5W7XA9BC4DE6H
                  publication_id: SAM-0000000000007
                  type:
                    value: technical_specification
                    label: Ficha Técnica
                    description: Especificações técnicas detalhadas do produto
                  title: Especificações gerais
                  items:
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2V4
                      item_key: Potência
                      item_value: 650 W
                      display_order: 0
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2W5
                      item_key: Voltagem
                      item_value: 110 V
                      display_order: 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    put:
      tags:
        - Gestão de Anúncios
      summary: Atualizar Ficha Técnica
      description: |-
        Atualiza tipo, título e/ou itens de uma ficha técnica. Todos os campos são opcionais. Ao enviar `items`, a lista substitui a atual por completo.
        
        Se a mudança de `type`/`title` colidir com outra ficha do mesmo anúncio, retorna `422`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: ulid
          in: path
          required: true
          description: ID da ficha técnica (campo `sheet_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD3F8GZQK2V5W7XA9BC4DE6H
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - technical_specification
                    - characteristics
                    - materials
                    - installation
                    - maintenance
                    - safety
                    - environmental
                    - custom
                title:
                  type: string
                  maxLength: 255
                items:
                  type: array
                  items:
                    type: object
                    properties:
                      item_key:
                        type: string
                        maxLength: 255
                      item_value:
                        type: string
                        maxLength: 1000
                      display_order:
                        type: integer
                        minimum: 0
                    required:
                      - item_key
                      - item_value
            example:
              title: Especificações técnicas
              items:
                - item_key: Potência
                  item_value: 650 W
                  display_order: 0
                - item_key: Rotação
                  item_value: 0–2.800 rpm
                  display_order: 1
      responses:
        "200":
          description: Ficha atualizada
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/TechnicalSheet"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
    delete:
      tags:
        - Gestão de Anúncios
      summary: Excluir Ficha Técnica
      description: Exclui uma ficha técnica e seus itens. Retorna `204` sem corpo — no cliente, cheque o status antes de tentar fazer parse de JSON.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: ulid
          in: path
          required: true
          description: ID da ficha técnica (campo `sheet_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD3F8GZQK2V5W7XA9BC4DE6H
      responses:
        "204":
          description: Ficha excluída (sem corpo)
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/items/{publication_id}/technical-sheets/import":
    post:
      tags:
        - Gestão de Anúncios
      summary: Importar Ficha Técnica de HTML
      description: |-
        Cria uma ficha técnica nova extraindo os pares chave/valor de um trecho de HTML (ex.: tabela de especificações copiada do site do fabricante). Útil para não digitar item por item.
        
        A combinação `type` + `title` segue única por anúncio (`422` se repetir).
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - technical_specification
                    - characteristics
                    - materials
                    - installation
                    - maintenance
                    - safety
                    - environmental
                    - custom
                title:
                  type: string
                  maxLength: 255
                html_content:
                  type: string
                  description: "HTML de onde os itens serão extraídos (ex.: `<table>` de especificações)"
              required:
                - type
                - title
                - html_content
            example:
              type: technical_specification
              title: Especificações do fabricante
              html_content: <table><tr><td>Potência</td><td>650 W</td></tr><tr><td>Voltagem</td><td>110 V</td></tr></table>
      responses:
        "201":
          description: "Ficha criada a partir do HTML (envelope com `message_code: RESOURCE_CREATED`)"
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/TechnicalSheet"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  sheet_id: 01JD3F8GZQK2V5W7XA9BC4DE6H
                  publication_id: SAM-0000000000007
                  type:
                    value: technical_specification
                    label: Ficha Técnica
                    description: Especificações técnicas detalhadas do produto
                  title: Especificações gerais
                  items:
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2V4
                      item_key: Potência
                      item_value: 650 W
                      display_order: 0
                    - item_id: 01JD3F8H1T8MN2P4Q6R8S0T2W5
                      item_key: Voltagem
                      item_value: 110 V
                      display_order: 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/{publication_id}/technical-sheets/{ulid}/import":
    post:
      tags:
        - Gestão de Anúncios
      summary: Importar Itens para Ficha Existente
      description: |-
        Extrai itens de um trecho de HTML e os **acrescenta** a uma ficha técnica existente. Chaves repetidas são ignoradas (a primeira ocorrência vence) e a ordem de exibição é renumerada ao final.
        
        Se nenhum item puder ser extraído do HTML, retorna `422`. Ficha que não pertence ao anúncio informado retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: publication_id
          in: path
          required: true
          description: "`item_id` do anúncio"
          schema:
            type: string
            example: SAM-0000000000007
        - name: ulid
          in: path
          required: true
          description: ID da ficha técnica (campo `sheet_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD3F8GZQK2V5W7XA9BC4DE6H
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                html_content:
                  type: string
                  description: HTML de onde os itens serão extraídos
              required:
                - html_content
            example:
              html_content: <table><tr><td>Rotação</td><td>0–2.800 rpm</td></tr></table>
      responses:
        "200":
          description: Itens importados e mesclados
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/TechnicalSheet"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/sku/{sku}":
    get:
      tags:
        - Gestão de Anúncios
      summary: Anúncios por SKU
      description: |-
        Lista todos os anúncios da loja que usam um SKU específico — seja no corpo do anúncio, nas variações ou nas suas ofertas de catálogo. Um mesmo SKU pode aparecer em anúncios `default` e `catalog` ao mesmo tempo.
        
        Útil antes de alterar ou desativar um SKU, para saber o que será impactado. SKU sem anúncios retorna lista vazia.
      security:
        - bearer: []
      parameters:
        - name: sku
          in: path
          required: true
          description: SKU da loja
          schema:
            type: string
            example: FUR-650-110
      responses:
        "200":
          description: Anúncios encontrados
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - id: 01JD1PVBQ2K4M6N8P0R2T4V6X8
                    item_id: SAM-0000000000007
                    title: Furadeira de Impacto 650W 110V
                    short_description: Furadeira de impacto 650W com mandril de 1/2" e maleta.
                    type: default
                    status: active
                    slug: furadeira-de-impacto-650w-110v
                    store:
                      store_id: 01JD0STQ2K4M6N8P0R2T4V6X8Y
                      store_name: Loja das Ferramentas
                      store_code: LDF
                    department:
                      id: 3
                      name: Ferramentas
                    category:
                      id: 152
                      name: Furadeiras
                    thumbnail:
                      id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                      resource:
                        name: thumbnail
                        url: https://cdn.exemplo.com/images/furadeira-thumb.webp
                    price_range:
                      - 189.9
                      - 219.9
                    score: 78
                    campaigns: []
                    frontend_sync_status: synced
                    last_frontend_sync_at: "2026-06-09 14:32:11"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/items/catalog/publications":
    get:
      tags:
        - Catálogo e Ofertas
      summary: Explorar Anúncios de Catálogo
      description: |-
        Lista paginada dos anúncios de catálogo ativos, disponíveis para a loja vincular SKUs. Cada anúncio vem com suas opções (variações) e indica, por opção, se a sua loja já tem oferta (`has_offer`) e quantas (`offer_count`).
        
        Use `has_relation` para ver só os anúncios onde você já oferta (`yes`) ou onde ainda não oferta (`no`). A paginação vem aninhada em `data.meta.pagination`.
      security:
        - bearer: []
      parameters:
        - name: search
          in: query
          required: false
          description: Busca textual no título do anúncio.
          schema:
            type: string
            example: caneta
        - name: has_relation
          in: query
          required: false
          description: "`yes`: apenas anúncios onde a loja já tem oferta; `no`: apenas onde ainda não tem. Omitido: todos."
          schema:
            type: string
            enum:
              - "yes"
              - "no"
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `15`.
          schema:
            type: integer
            default: 15
            example: 15
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
      responses:
        "200":
          description: Anúncios de catálogo listados
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 1
                        records: 1
                  data:
                    - item_id: SAM-0000000000021
                      title: Caneta Esferográfica Azul 1.0mm
                      status: active
                      type: catalog
                      thumbnail_id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                      thumbnail:
                        url: https://cdn.exemplo.com/images/caneta-sm.webp
                      department:
                        id: 5
                        name: Papelaria
                      category:
                        id: 233
                        name: Canetas
                      has_relation: true
                      total_offers: 1
                      has_variations: true
                      options_count: 2
                      options:
                        - option_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          description: "Quantidade: 50 un"
                          attributes:
                            - attribute_id: 19
                              attribute_name: Quantidade
                              value: "50"
                              unit: un
                          has_offer: true
                          offer_count: 1
                        - option_id: 01JD4A0B1C2D3E4F5G6H7J8K9P
                          description: "Quantidade: 100 un"
                          attributes:
                            - attribute_id: 19
                              attribute_name: Quantidade
                              value: "100"
                              unit: un
                          has_offer: false
                          offer_count: 0
                      frontend_sync_status: synced
                      last_frontend_sync_at: "2026-06-09 10:21:44"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/catalog/{itemId}/options":
    get:
      tags:
        - Catálogo e Ofertas
      summary: Opções de um Anúncio de Catálogo
      description: |-
        Lista as variações (opções) de um anúncio de catálogo ativo, com atributos, imagens e os dados da oferta vencedora da buybox em `offer`. O `option_id` retornado é o que você usa em `POST /api/items/catalog/options/{optionId}/attach-sku` para vincular o seu SKU.
        
        Anúncio inexistente ou não ativo retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: itemId
          in: path
          required: true
          description: "`item_id` do anúncio de catálogo"
          schema:
            type: string
            example: SAM-0000000000021
      responses:
        "200":
          description: Opções listadas
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PublicationVariationOut"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - option_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                    description: "Quantidade: 50 un"
                    attributes:
                      - attribute_id: 19
                        attribute_name: Quantidade
                        value: "50"
                        unit: un
                    status: active
                    type: option
                    images: []
                    offer:
                      price_type: default
                      price_from: 1245
                      prices: []
                      quantity: 2
                      price: 1245
                      original_price: null
                      has_discount: false
                      discount_percent: 0
                      buybox:
                        winner_store:
                          store_name: Papelaria Central
                          store_code: PPC
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/items/catalog/offers/{offerId}":
    put:
      tags:
        - Catálogo e Ofertas
      summary: Atualizar Oferta
      description: |-
        Atualiza o status de uma oferta sua vinculada a uma opção de catálogo. A atualização é restrita ao `status` — preço e estoque seguem o SKU vinculado.
        
        Após a mudança, a buybox da variação é recalculada automaticamente. Oferta inexistente ou de outra loja retorna `404`.
      security:
        - bearer: []
      parameters:
        - name: offerId
          in: path
          required: true
          description: ID da oferta (campo `offer_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD5B2C3D4E5F6G7H8J9K0M1N
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum:
                    - active
                    - paused
                    - inactive
                  description: Novo status da oferta
              required:
                - status
            example:
              status: paused
      responses:
        "200":
          description: Oferta atualizada
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/AttachedSKUData"
              example:
                success: true
                message_code: SUCCESS
                data:
                  offer_id: 01JD5B2C3D4E5F6G7H8J9K0M1N
                  seller_sku: CAN-AZ-50
                  status: paused
                  is_buybox_winner: false
                  last_buybox_calculation: "2026-06-09T13:05:22-03:00"
                  price_data:
                    price: 24.9
                  stock_data:
                    quantity: 120
                    available_quantity: 118
                  publication:
                    item_id: SAM-0000000000021
                    title: Caneta Esferográfica Azul 1.0mm
                    status: active
                    type: catalog
                    thumbnail_id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                  option:
                    uid: 01JD4A0B1C2D3E4F5G6H7J8K9N
                    description: "Quantidade: 50 un"
                    attributes:
                      - attribute_id: 19
                        attribute_name: Quantidade
                        value: "50"
                        unit: un
                    type: option
                    status: active
                    images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
    delete:
      tags:
        - Catálogo e Ofertas
      summary: Remover Oferta
      description: |-
        Remove o vínculo do seu SKU com a opção de catálogo (exclui a oferta) e dispara o recálculo da buybox da variação.
        
        **Atenção:** diferente das demais rotas DELETE da API, esta retorna `200` com uma mensagem em `data` — não `204`. Oferta inexistente ou de outra loja retorna `422`.
      security:
        - bearer: []
      parameters:
        - name: offerId
          in: path
          required: true
          description: ID da oferta (campo `offer_id`, 26 caracteres)
          schema:
            type: string
            example: 01JD5B2C3D4E5F6G7H8J9K0M1N
      responses:
        "200":
          description: Oferta removida
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  message: Oferta removida com sucesso
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/items/catalog/my-offers":
    get:
      tags:
        - Catálogo e Ofertas
      summary: Minhas Ofertas
      description: |-
        Lista paginada dos anúncios de catálogo onde a sua loja tem ofertas, agrupando por anúncio → opção → ofertas (`buybox_offers`). Cada oferta indica se está vencendo a buybox (`is_buybox_winner`), o preço unitário e o estoque já convertido para a unidade de venda da opção (`quantity_multiplier` — ex.: opção "50 un" divide o estoque do SKU por 50).
        
        A paginação vem aninhada em `data.meta.pagination`. O campo `options` é um espelho de `variations` (mesmo conteúdo).
      security:
        - bearer: []
      parameters:
        - name: search
          in: query
          required: false
          description: Busca textual no título do anúncio.
          schema:
            type: string
            example: caneta
        - name: seller_sku
          in: query
          required: false
          description: Filtra pelas ofertas de um SKU específico.
          schema:
            type: string
            example: CAN-AZ-50
        - name: offer_status
          in: query
          required: false
          description: Status da oferta.
          schema:
            type: string
            enum:
              - active
              - paused
              - inactive
              - pending
        - name: is_buybox_winner
          in: query
          required: false
          description: "`true`: apenas anúncios com ofertas suas vencendo a buybox."
          schema:
            type: boolean
            example: true
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `15`.
          schema:
            type: integer
            default: 15
            example: 15
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
      responses:
        "200":
          description: Ofertas listadas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 1
                        records: 1
                  data:
                    - item_id: SAM-0000000000021
                      title: Caneta Esferográfica Azul 1.0mm
                      status: active
                      type: catalog
                      thumbnail_id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                      images:
                        - id: 01JD2R8KQX5VTWMA3B7YCE9FGH
                          resources:
                            - name: sm
                              url: https://cdn.exemplo.com/images/caneta-sm.webp
                      has_variations: true
                      variations:
                        - id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          uid: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          option_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          description: "Quantidade: 50 un"
                          attributes:
                            - attribute_id: 19
                              attribute_name: Quantidade
                              value: "50"
                              unit: un
                              is_calculable: true
                          images: []
                          type: option
                          buybox_offers:
                            - uid: 01JD5B2C3D4E5F6G7H8J9K0M1N
                              id: 01JD5B2C3D4E5F6G7H8J9K0M1N
                              seller_sku: CAN-AZ-50
                              status: active
                              is_buybox_winner: true
                              last_buybox_calculation: "2026-06-09T13:05:22-03:00"
                              publication_variation_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                              price_data:
                                price: 24.9
                              stock_data:
                                quantity: 2
                                available_quantity: 2
                              unit_price: 24.9
                              price_from: 1245
                              quantity: 2
                              quantity_multiplier: 50
                      frontend_sync_status: synced
                      last_frontend_sync_at: "2026-06-09 10:21:44"
                      options:
                        - id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          uid: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          option_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                          description: "Quantidade: 50 un"
                          attributes:
                            - attribute_id: 19
                              attribute_name: Quantidade
                              value: "50"
                              unit: un
                              is_calculable: true
                          images: []
                          type: option
                          buybox_offers:
                            - uid: 01JD5B2C3D4E5F6G7H8J9K0M1N
                              id: 01JD5B2C3D4E5F6G7H8J9K0M1N
                              seller_sku: CAN-AZ-50
                              status: active
                              is_buybox_winner: true
                              last_buybox_calculation: "2026-06-09T13:05:22-03:00"
                              publication_variation_id: 01JD4A0B1C2D3E4F5G6H7J8K9N
                              price_data:
                                price: 24.9
                              stock_data:
                                quantity: 2
                                available_quantity: 2
                              unit_price: 24.9
                              price_from: 1245
                              quantity: 2
                              quantity_multiplier: 50
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/media":
    get:
      summary: Listar Imagens
      description: Retorna uma lista paginada de imagens do seller autenticado. Suporta filtros por `reference_id`, `reference_model` e `in_use`, além de ordenação por `created_at` e `updated_at`.
      tags:
        - Media
      security:
        - bearer: []
      parameters:
        - name: page
          in: query
          description: Número da página atual.
          required: false
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: "Quantidade de itens por página (min: 1, max: 100)."
          required: false
          schema:
            type: integer
            default: 15
            minimum: 1
            maximum: 100
        - name: filter[in_use]
          in: query
          description: Filtrar por imagens que estão em uso ou não.
          required: false
          schema:
            type: boolean
            example: true
        - name: filter[reference_id]
          in: query
          description: Filtrar pelo ID do recurso referenciado.
          required: false
          schema:
            type: string
        - name: filter[reference_model]
          in: query
          description: Filtra por tipo de recurso vinculado. Use sempre o nome curto (`product`, `publication`, `store`) — esses aliases são a forma estável e pública; nomes de classe internos não são aceitos e retornam 422.
          required: false
          schema:
            type: string
            enum:
              - product
              - publication
              - store
        - name: sort
          in: query
          description: "Campo de ordenação. Prefixe com `-` para descendente. Valores permitidos: `created_at`, `updated_at`."
          required: false
          schema:
            type: string
            example: "-created_at"
      responses:
        "200":
          description: Sucesso. Retorna a coleção paginada de imagens.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MediaCollectionResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/media/upload":
    post:
      summary: Upload de Imagem
      description: "Realiza o upload de uma nova imagem. Formatos aceitos: JPEG, PNG e WebP. Tamanho máximo: 2 MB. A imagem é convertida para WebP e redimensionada automaticamente nas variações `thumbnail`, `sm`, `md`, `lg` e `xl`."
      tags:
        - Media
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "multipart/form-data":
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  format: binary
                  description: Arquivo de imagem (jpeg, png, webp). Máximo 2 MB.
      responses:
        "201":
          description: Imagem enviada e processada com sucesso.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MediaCreatedResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          description: Erro de validação no upload
          content:
            "application/json":
              examples:
                image_required:
                  summary: Imagem não enviada
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image:
                        - A imagem é obrigatória.
                    meta: []
                invalid_mime:
                  summary: Tipo de arquivo inválido
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image:
                        - "A imagem deve ser dos tipos: jpeg, png ou webp."
                    meta: []
                file_too_large:
                  summary: Arquivo excede 2 MB
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image:
                        - A imagem deve ter no máximo 2MB.
                    meta: []
  "/api/media/clone-from-skus":
    post:
      summary: Clonar fotos a partir de produtos (SKUs)
      description: |-
        Copia as fotos da galeria dos produtos informados e devolve **novas mídias independentes**, prontas para usar em um anúncio.
        
        No nosso modelo, uma mídia pertence a uma única entidade — a foto da galeria do produto não pode ser vinculada diretamente a um anúncio. Esta rota resolve isso: os arquivos são copiados no storage e cada cópia vira um registro novo, com `source_image_id` apontando para a imagem de origem.
        
        Cada alvo é um par `key` + `sku`. A `key` é um rótulo livre seu (por exemplo, o valor do atributo de variação — `"Azul"` — ou `"general"` para a galeria do anúncio) e serve só para você reencontrar as cópias na resposta. O mesmo SKU em duas keys diferentes gera **cópias distintas** — é o esperado, já que cada variação do anúncio precisa das suas próprias mídias.
        
        Não se preocupe com sobras: cópia que não for usada em nenhum anúncio é removida automaticamente pela limpeza diária de mídia (após ~72 horas).
        
        SKU que não pertence (ou não está associado) à sua loja não gera erro — ele apenas aparece em `ignored_skus` e a key correspondente volta vazia.
      tags:
        - Media
      security:
        - bearer: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - targets
              properties:
                targets:
                  type: array
                  minItems: 1
                  maxItems: 30
                  description: Lista de alvos de clonagem. Máximo de 30 por requisição.
                  items:
                    type: object
                    required:
                      - key
                      - sku
                    properties:
                      key:
                        type: string
                        maxLength: 100
                        description: "Rótulo livre para agrupar as cópias na resposta (ex.: valor da variação ou \"general\")."
                        example: Azul
                      sku:
                        type: string
                        maxLength: 100
                        description: SKU do produto cuja galeria será clonada.
                        example: CAMISETA-AZUL-M
            example:
              targets:
                - key: Azul
                  sku: CAMISETA-AZUL-M
                - key: Vermelho
                  sku: CAMISETA-VERM-M
      responses:
        "200":
          description: Cópias criadas. As keys do `results` são as mesmas enviadas nos targets — keys de SKUs ignorados ou sem fotos voltam com lista vazia. As fotos vêm na ordem de exibição do produto (thumbnail primeiro).
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MediaCloneFromSkusResponse"
              example:
                success: true
                message_code: OK
                data:
                  results:
                    Azul:
                      - id: 01JNB2T4G93QRT5V6W7X8Y9Z0A
                        source_image_id: 01JMZXZ72881MD2P37N2K97YJJ
                        original_source: https://cdn.example.com/products/2026/06/11/original_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
                        urls:
                          - name: sm
                            size: 200x200
                            url: https://cdn.example.com/products/2026/06/11/sm_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
                    Vermelho: []
                  ignored_skus:
                    - CAMISETA-VERM-M
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          description: Erro de validação nos targets.
          content:
            "application/json":
              examples:
                targets_required:
                  summary: Nenhum alvo informado
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      targets:
                        - Informe ao menos um alvo (key + sku).
                    meta: []
                too_many_targets:
                  summary: Mais de 30 alvos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      targets:
                        - Máximo de 30 alvos por requisição.
                    meta: []
  "/api/media/{file_hash}":
    get:
      summary: Detalhes da Imagem
      description: Retorna detalhes de uma imagem específica pelo hash/ID.
      tags:
        - Media
      security:
        - bearer: []
      parameters:
        - name: file_hash
          in: path
          description: ID da imagem.
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sucesso. Retorna os dados da imagem.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MediaItemResponse"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    delete:
      summary: Excluir Imagem
      description: Remove uma imagem permanentemente. Imagens que estão em uso (`in_use = true`) não podem ser excluídas (retorna 409).
      tags:
        - Media
      security:
        - bearer: []
      parameters:
        - name: file_hash
          in: path
          description: ID da imagem.
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Imagem excluída com sucesso. Sem conteúdo no corpo da resposta.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
  "/api/integration/auth":
    post:
      tags:
        - Autenticação
      summary: Obter token de acesso
      description: |-
        Recebe o **username** e a **senha** de uma credencial criada no Portal do Vendedor e devolve um **token de acesso** (`sk_live_*`) válido por 30 dias.
        
        **Rotação:** cada chamada bem-sucedida invalida o token anterior — só existe 1 token ativo por credencial. Crie credenciais separadas para sistemas que precisam de tokens independentes.
        
        **Endpoint público:** não exige `Authorization` — a própria credencial autentica.
        
        **Rate limit:** 5 tentativas por minuto por `(IP, username)`. Excedeu → 429.
        
        **Erros 401:** mensagem genérica de propósito (não revela se a falha foi por username inexistente, senha errada ou credencial inativa) para evitar enumeração.
      security: []
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - username
                - password
              properties:
                username:
                  type: string
                  maxLength: 255
                  description: "Username da credencial, no formato `slug-loja@cnpj-curto` (ex.: `loja-xpto@12345678`). Gerado automaticamente na criação da credencial."
                password:
                  type: string
                  maxLength: 255
                  description: Senha da credencial. Mostrada uma única vez na criação (e na troca de senha) — guarde em cofre de senhas.
            example:
              username: loja-xpto@12345678
              password: Xk9p2Lm7nQ4rT8vW3yZ1
      responses:
        "200":
          description: Token emitido. O token anterior, se houver, deixa de funcionar.
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  token: sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
                  expires_at: "2026-06-19T12:34:56-03:00"
        "401":
          description: Credenciais inválidas. Username não encontrado, senha errada **ou** credencial inativa — a mensagem é a mesma para não vazar oracle.
          content:
            "application/json":
              example:
                success: false
                message_code: UNAUTHORIZED
                data:
                  message: Credenciais inválidas.
        "422":
          description: Validação falhou (username ou password ausente/vazio).
          content:
            "application/json":
              example:
                success: false
                code: 422
                message_code: VALIDATION_ERROR
                description: Foram encontrados erros de validação na requisição.
                data: []
                errors:
                  username:
                    - O campo username é obrigatório.
                  password:
                    - O campo password é obrigatório.
                meta: []
        "429":
          description: "Muitas tentativas — limite de 5 por minuto por (IP, username). Aguarde o `Retry-After` antes de tentar novamente. Atenção: hoje o corpo vem com `message_code` genérico — confie no status HTTP 429 e no header `Retry-After`, não no corpo."
          content:
            "application/json":
              example:
                success: false
                code: 500
                message_code: SERVER_ERROR
                description: Não foi possivel processar a sua solicitação, revise os dados e tente novamente.
                data: []
                errors:
                  - Muitas tentativas. Por favor, tente novamente mais tarde.
                meta: []
  "/api/stores/auth/session":
    get:
      tags:
        - Sessão
      summary: Sessão Ativa
      description: "Confirma o token e retorna os dados da loja autenticada. Para tokens de integração, a resposta traz `auth.auth_type: \"integration_token\"`, `auth.is_owner: false` e `scopes` com a lista de habilidades concedidas à credencial — use esse retorno pra validar que a integração está apontando pra loja certa e com as permissões esperadas. A loja vem com seus dados e a `company` associada."
      responses:
        "200":
          description: Sessão ativa
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  store:
                    store_id: 01KS3PRGYQHHDCRYNRXGSZHP41
                    store_name: EcomShop
                    store_code: ecomshop
                    store_status: active
                    store_url: ecomshop
                    store_type: default
                    commission_table_id: null
                    max_locations: 5
                    features: []
                    company:
                      company_id: 01KS3PRGYGAAQDJ3APCXNDXRPS
                      name: Empresa de João
                      social_name: João Silva LTDA
                      document:
                        name: CNPJ
                        type: cnpj
                        number: "94105981000185"
                        description: Cadastro Nacional da Pessoa Jurídica
                      state_registration: null
                      tax_regime: simples
                      has_primary_store: true
                      primary_store_name: null
                      allow_partner_branches: false
                    shares_products: false
                    shares_stock_locations: false
                    shares_logistics: false
                    is_primary: true
                    primary_store: null
                  scopes:
                    - store_products_read
                    - store_orders_read
                    - store_rating_read
                  auth:
                    is_owner: false
                    auth_type: integration_token
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/stores/page":
    get:
      tags:
        - Página da Loja
      summary: Ver Página da Loja
      description: Retorna todos os dados da página da loja, incluindo identidade visual.
      responses:
        "200":
          description: Dados da página
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja dedicada a produtos de qualidade
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Produtos premium para o atacado
                    full_description: Descrição completa da loja...
                    logo_image: https://cdn.example.com/logo.png
                    background_image: https://cdn.example.com/bg.jpg
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
    put:
      tags:
        - Página da Loja
      summary: Atualizar Dados da Página
      description: Atualiza textos descritivos da página da loja.
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                description:
                  type: string
                  maxLength: 500
                  nullable: true
                foundation_date:
                  type: string
                  format: date
                  nullable: true
                short_description:
                  type: string
                  maxLength: 255
                  nullable: true
                full_description:
                  type: string
                  nullable: true
            example:
              description: Loja referência em atacado de tecnologia
              short_description: Tecnologia no atacado
              foundation_date: "2015-05-20"
      responses:
        "200":
          description: Página atualizada com sucesso
          content:
            "application/json":
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja referência em atacado de tecnologia
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Tecnologia no atacado
                    full_description: Descrição completa da loja...
                    logo_image: https://cdn.example.com/logo.png
                    background_image: https://cdn.example.com/bg.jpg
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/stores/page/logo":
    post:
      tags:
        - Página da Loja
      summary: Upload de Logo
      description: "Envia a imagem de logo da loja. Formatos: JPG, PNG, WEBP, GIF. Máximo: 5MB."
      requestBody:
        required: true
        content:
          "multipart/form-data":
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  format: binary
                  description: "Arquivo de imagem (max 5MB, formatos: jpeg, png, jpg, gif, webp)"
      responses:
        "201":
          description: Logo enviado com sucesso
          content:
            "application/json":
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja dedicada a produtos de qualidade
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Produtos premium para o atacado
                    full_description: Descrição completa da loja...
                    logo_image: https://cdn.example.com/logo.webp
                    background_image: https://cdn.example.com/bg.jpg
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
    delete:
      tags:
        - Página da Loja
      summary: Remover Logo
      description: Remove a imagem de logo da loja.
      responses:
        "200":
          description: Logo removido
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja dedicada a produtos de qualidade
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Produtos premium para o atacado
                    full_description: Descrição completa da loja...
                    logo_image: null
                    background_image: https://cdn.example.com/bg.jpg
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/stores/page/background":
    post:
      tags:
        - Página da Loja
      summary: Upload de Background
      description: Envia a imagem de fundo da página da loja.
      requestBody:
        required: true
        content:
          "multipart/form-data":
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  format: binary
                  description: "Arquivo de imagem (max 5MB, formatos: jpeg, png, jpg, gif, webp)"
      responses:
        "201":
          description: Background enviado com sucesso
          content:
            "application/json":
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja dedicada a produtos de qualidade
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Produtos premium para o atacado
                    full_description: Descrição completa da loja...
                    logo_image: https://cdn.example.com/logo.png
                    background_image: https://cdn.example.com/bg.webp
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
    delete:
      tags:
        - Página da Loja
      summary: Remover Background
      description: Remove a imagem de fundo da loja.
      responses:
        "200":
          description: Background removido
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  store_name: Minha Loja Premium
                  store_code: MINHALOJA
                  store_status: active
                  store_url: minha-loja-premium
                  store_type: default
                  commission_table_id: null
                  max_locations: 5
                  shares_stock_locations: false
                  shares_logistics: false
                  shares_products: false
                  features: []
                  company:
                    company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                    name: Empresa Comércio Ltda
                    social_name: Empresa de Comércio Eletrônico LTDA
                    document:
                      name: CNPJ
                      type: cnpj
                      number: 12.345.678/0001-90
                      description: Cadastro Nacional da Pessoa Jurídica
                    state_registration:
                      name: IE
                      type: ie
                      number: 123.456.789.012
                      description: Inscrição Estadual
                    tax_regime: simples
                    has_primary_store: true
                    primary_store_name: Minha Loja Premium
                    allow_partner_branches: false
                  attributes:
                    description: Loja dedicada a produtos de qualidade
                    foundation_date: "2015-05-20T00:00:00-03:00"
                    short_description: Produtos premium para o atacado
                    full_description: Descrição completa da loja...
                    logo_image: https://cdn.example.com/logo.png
                    background_image: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/stores/rating":
    get:
      tags:
        - Avaliação
      summary: Resumo de Avaliação
      description: Retorna a nota atual da loja em cada critério e a nota geral (0-10).
      responses:
        "200":
          description: Resumo da avaliação
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  store:
                    store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                    store_name: Minha Loja Premium
                    store_code: MINHALOJA
                    store_status: active
                    store_url: minha-loja-premium
                    store_type: default
                    commission_table_id: null
                    max_locations: 5
                    features: []
                    company:
                      company_id: 01K8PB0KMM36AY2P0NKSZBP2NB
                      name: Empresa Comércio Ltda
                      social_name: Empresa de Comércio Eletrônico LTDA
                      document:
                        name: CNPJ
                        type: cnpj
                        number: 12.345.678/0001-90
                        description: Cadastro Nacional da Pessoa Jurídica
                      state_registration: null
                      tax_regime: simples
                      has_primary_store: true
                      primary_store_name: Minha Loja Premium
                      allow_partner_branches: false
                    shares_products: false
                    shares_stock_locations: false
                    shares_logistics: false
                    is_primary: true
                    primary_store: null
                  rating:
                    subjects:
                      shipping: 8.5
                      support: 9.2
                      selling: 7.8
                      shipping_time: 8
                      cancel_counts: 9.5
                    overall: 8.6
                    period_start: "2026-01-13T00:00:00.000000Z"
                    period_end: "2026-04-13T23:59:59.000000Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/stores/rating/history":
    get:
      tags:
        - Avaliação
      summary: Histórico de Avaliação
      description: Retorna o histórico paginado de variações na nota da loja.
      parameters:
        - name: per_page
          in: query
          description: Quantidade de itens por página.
          schema:
            type: integer
            default: 20
        - name: filter[subject]
          in: query
          description: Filtrar por critério (`shipping`, `support`, `selling`, `shipping_time`, `cancel_counts`).
          schema:
            type: string
            enum:
              - shipping
              - support
              - selling
              - shipping_time
              - cancel_counts
        - name: filter[direction]
          in: query
          description: Filtrar por direção da variação (`up`, `down`).
          schema:
            type: string
            enum:
              - up
              - down
        - name: filter[date_between]
          in: query
          description: "Intervalo de datas no formato `from,to` (ex.: `2026-01-01,2026-03-31`). Alternativa aos parâmetros `from`/`to`."
          schema:
            type: string
        - name: from
          in: query
          description: Data inicial (compara contra `occurred_at`).
          schema:
            type: string
            format: date
        - name: to
          in: query
          description: Data final (compara contra `occurred_at`).
          schema:
            type: string
            format: date
        - name: sort
          in: query
          description: "Campo de ordenação. Prefixe com `-` para descendente. Valores permitidos: `occurred_at`, `subject`, `direction`, `value`. Padrão: `-occurred_at`."
          schema:
            type: string
            example: "-occurred_at"
      responses:
        "200":
          description: Histórico paginado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 20
                      last_page: 8
                      has_prev_page: false
                      has_next_page: true
                      records:
                        from: 1
                        to: 20
                        records: 150
                  data:
                    - subject: shipping
                      direction: up
                      value: 1.5
                      meta: {}
                      occurred_at: "2026-04-10T14:30:00.000000Z"
                    - subject: support
                      direction: down
                      value: 0.8
                      meta: {}
                      occurred_at: "2026-04-09T10:15:00.000000Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/v1/sellers/orders":
    get:
      tags:
        - Pedidos
      summary: Listar Pedidos
      description: |-
        Lista paginada de pedidos da loja autenticada. **É a rota que o painel chama no dia a dia para montar filas de trabalho** — pedidos pagos aguardando preparação, enviados aguardando entrega, etc.
        
        Combine `status` (aceita CSV: `paid,preparing`) com `date_from`/`date_to` para recortes operacionais. Para visão agregada por dia ou KPIs do dia, prefira `/dashboard` ou `/summary`. Para o payload completo de um pedido específico, use `/orders/{id}`.
        
        Paginação via `limit` (default 20); o `meta` da resposta carrega `current_page`, `last_page` e `total`.
        
        **Privacidade do comprador:** os dados de identificação do cliente só são liberados após a confirmação do pagamento. Enquanto `customer_data_released` for `false` (pedidos não-pagos), o campo `customer_name` é omitido. A flag passa a `true` a partir de `paid` e nos status operacionais seguintes. Pelo mesmo motivo, o parâmetro `search` por CPF/CNPJ ou nome do comprador só encontra pedidos já pagos — busca por `order_number` funciona em qualquer status.
      security:
        - bearer: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: limit
          in: query
          required: false
          description: Tamanho da página (default 20)
          schema:
            type: integer
            example: 20
        - name: status
          in: query
          required: false
          description: "Filtra por status do pedido (ex: `paid`, `preparing`, `shipped`). Aceita múltiplos valores separados por vírgula."
          schema:
            type: string
        - name: search
          in: query
          required: false
          description: Busca por número do pedido ou nome do cliente
          schema:
            type: string
        - name: channel
          in: query
          required: false
          description: Canal de origem. Aceita múltiplos valores separados por vírgula.
          schema:
            type: string
            enum:
              - marketplace
              - website
              - direct
              - store_seller
              - physical_store
              - mobile_app
              - whatsapp
              - pos
              - api
        - name: type
          in: query
          required: false
          description: Tipo do pedido. Aceita múltiplos valores separados por vírgula.
          schema:
            type: string
            enum:
              - standard
              - production
              - pre_order
              - backorder
              - physical_store
        - name: workflow
          in: query
          required: false
          description: Filtra por `workflow_type`. Aceita múltiplos valores separados por vírgula.
          schema:
            type: string
            enum:
              - standard
              - production
              - in_store_pickup
              - pre_order
              - backorder
              - collective
        - name: delivery_method
          in: query
          required: false
          description: Filtra por método de entrega. Aceita múltiplos valores separados por vírgula.
          schema:
            type: string
            enum:
              - shipping
              - pickup
        - name: customer_id
          in: query
          required: false
          description: Pedidos de um cliente específico (ULID do cliente)
          schema:
            type: string
        - name: physical_store_id
          in: query
          required: false
          description: Pedidos vinculados a uma loja física
          schema:
            type: integer
        - name: agent_group_uid
          in: query
          required: false
          description: Filtra por grupo de agentes
          schema:
            type: string
        - name: item_id
          in: query
          required: false
          description: Pedidos contendo um item específico
          schema:
            type: integer
        - name: date_from
          in: query
          required: false
          description: Data inicial (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: date_to
          in: query
          required: false
          description: Data final (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: sort
          in: query
          required: false
          description: "Ordenação. Default `-created_at`. Permitido: `created_at`, `updated_at`, `total_order`, `status`. Prefixo `-` para descendente."
          schema:
            type: string
            example: "-created_at"
      responses:
        "200":
          description: Pedidos listados
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - order_number: ORD-000123
                    checkout_group_id: 01K8PB1QABCDEFGHIJK
                    store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                    store_name: Minha Loja Premium
                    store:
                      id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      name: Minha Loja Premium
                      website_domain: minhaloja.com.br
                    customer_id: 01K8PB2RCUST00001
                    customer_data_released: true
                    customer:
                      id: 01K8PB2RCUST00001
                      name: João Silva
                      email: joao@example.com
                    agent_group_uid: null
                    physical_store_id: null
                    type: standard
                    type_label: Padrão
                    channel: marketplace
                    channel_label: Marketplace
                    workflow_type: standard
                    workflow_type_label: Padrão
                    status: paid
                    status_label: Pago
                    status_color: green
                    totals:
                      products: 220
                      shipping: 29.9
                      discounts: -10
                      order: 239.9
                    items: []
                    items_count: 3
                    lines_count: 2
                    items_preview:
                      - title: Tênis Esportivo Azul
                        quantity: 2
                        thumbnail_url: https://cdn.example.com/produtos/tenis-azul-sm.webp
                    notes: null
                    metadata: null
                    delivery_method: shipping
                    delivery_method_label: Envio
                    picked_up_at: null
                    is_production_order: false
                    payment_method: pix
                    payment_method_label: PIX
                    payment_status: paid
                    payment_status_label: Pago
                    payment_expires_at: null
                    created_at: "2026-04-26T14:32:11Z"
                    updated_at: "2026-04-26T14:35:42Z"
                meta:
                  current_page: 1
                  last_page: 4
                  per_page: 20
                  total: 78
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/v1/sellers/orders/dashboard":
    get:
      tags:
        - Pedidos
      summary: Dashboard (período configurável)
      description: |-
        Estatísticas consolidadas dos últimos `days` dias (default 30, máx 365). Devolve quatro blocos:
        
        - `chart` — série temporal com pedidos vendidos, pendentes, cancelados e faturamento (`revenue`) por bucket (até 30 pontos no período).
        - `totals` — totais do período por categoria, incluindo `revenue` (GMV das vendas).
        - `totals_previous` — mesmos totais para o período anterior de igual duração (use para variação %).
        - `recent_orders` — últimos pedidos criados (limitado por `recent_limit`, default 10).
        
        Use para a tela "Visão geral" do seller. Para KPIs fixos do dia atual sem janela configurável, prefira `/summary`.
      security:
        - bearer: []
      parameters:
        - name: days
          in: query
          required: false
          description: Tamanho do período em dias (1-365). Default `30`.
          schema:
            type: integer
            minimum: 1
            maximum: 365
            default: 30
        - name: recent_limit
          in: query
          required: false
          description: Quantos pedidos recentes incluir na lista (1-50). Default `10`.
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
        - name: channel
          in: query
          required: false
          description: "Restringe o dashboard a um ou mais canais (separados por vírgula, ex: `website,store_seller`). Sem o parâmetro, considera todos os canais da loja."
          schema:
            type: string
            example: website,store_seller
      responses:
        "200":
          description: Dashboard carregado
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Dashboard carregado
                data:
                  chart:
                    - day: 27 abr
                      sold: 4
                      pending: 1
                      cancelled: 0
                      revenue: 1240.5
                    - day: 28 abr
                      sold: 6
                      pending: 2
                      cancelled: 1
                      revenue: 1893.4
                  recent_orders:
                    - id: ORD-000123
                      order_number: ORD-000123
                      customer_data_released: true
                      customer_name: João Silva
                      status: paid
                      status_label: Pago
                      status_color: green
                      workflow_type: standard
                      workflow_label: Padrão
                      total_order: 239.9
                      created_at: "2026-04-26T14:32:11Z"
                  totals:
                    sold: 142
                    pending: 18
                    awaiting_dispatch: 23
                    cancelled: 7
                    total: 190
                    revenue: 45230.8
                  totals_previous:
                    sold: 118
                    pending: 22
                    awaiting_dispatch: 19
                    cancelled: 9
                    total: 168
                    revenue: 38912.15
                  period:
                    days: 30
                    from: "2026-03-27T00:00:00Z"
                    to: "2026-04-26T23:59:59Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/v1/sellers/orders/summary":
    get:
      tags:
        - Pedidos
      summary: Resumo do dia (KPIs fixos)
      description: |-
        KPIs do **dia de hoje** para o cabeçalho do painel: pedidos criados, receita, ticket médio, delta vs. ontem em %, contadores de pedidos em cada status operacional (`preparing`, `ready_to_ship`, `shipped`) e entregas concluídas hoje. Também desmembra pedidos abertos por canal.
        
        Não aceita filtros — sempre o panorama global da loja autenticada. Para janela configurável e gráfico de evolução, use `/dashboard`.
      security:
        - bearer: []
      responses:
        "200":
          description: Resumo obtido
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  today_count: 12
                  today_delta_pct: 33.3
                  today_revenue: 4587.5
                  today_avg_ticket: 382.29
                  awaiting_action: 4
                  preparing: 3
                  ready_to_ship: 2
                  shipped: 5
                  delivered_today: 7
                  by_channel:
                    site: 6
                    loja: 2
                    whatsapp: 3
                    direto: 1
                    vendedor_loja: 2
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/api/v1/sellers/orders/{id}":
    get:
      tags:
        - Pedidos
      summary: Detalhe do Pedido
      description: |-
        Payload completo de um pedido: itens com produto/publicação, cliente, snapshot de endereço, totais, splits financeiros resumidos, documentos fiscais (`invoices[]`), anotações internas, metadata de envio (carrier escolhido, opção de frete).
        
        É o que a tela de detalhe do pedido consome. Para apenas dados financeiros aprofundados (audit de comissão, benefícios aplicados), use `/financial`. Para apenas tracking, use `/tracking`.
        
        **Privacidade do comprador:** o objeto `customer`, o `delivery_address` e os snapshots de PII no `metadata` (`document_snapshot`, `phone_snapshot`, `address_snapshot`, ...) só são retornados após a confirmação do pagamento. Em pedidos não-pagos ou em análise de risco, `customer_data_released` vem `false` e esses campos são omitidos — o pedido pode ainda não se concretizar. A flag passa a `true` a partir de `paid`.
        
        **Link de pagamento (pedidos B2B):** em pedidos dos canais `direct` e `store_seller`, a resposta inclui `payment_url` (link público de pagamento) e `payment_link_target` (`platform` quando o link abre no frontend da plataforma; `store` quando abre no domínio da loja virtual). Ambos vêm `null` quando o pedido não está mais em `waiting_payment` ou o token expirou; nos demais canais os campos são omitidos. Enquanto o token é válido, `payment_token` também acompanha a resposta.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: Número do pedido (`order_number`)
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Pedido obtido
          content:
            "application/json":
              examples:
                com_cnpj:
                  summary: Transportadora com CNPJ (sem endereço fiscal)
                  value:
                    success: true
                    code: 200
                    message_code: SUCCESS
                    description: Requisicao realizada com sucesso.
                    data:
                      order_number: ORD-000123
                      checkout_group_id: 01K8PB1QABCDEFGHIJK
                      store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      store:
                        id: 01K8PB0KMM36AY2P0NKSZBP2NA
                        name: Minha Loja Premium
                        logo_url: https://cdn.example.com/logo.webp
                        website_domain: minhaloja.com.br
                      customer_id: 01K8PB2RCUST00001
                      customer_data_released: true
                      customer:
                        id: 01K8PB2RCUST00001
                        name: João Silva
                        email: joao@example.com
                      sales_agent_uid: null
                      agent_group_uid: null
                      physical_store_id: null
                      type: standard
                      type_label: Padrão
                      channel: marketplace
                      channel_label: Marketplace
                      workflow_type: standard
                      workflow_type_label: Padrão
                      status: paid
                      status_label: Pago
                      status_color: green
                      totals:
                        products: 220
                        shipping: 29.9
                        discounts: -10
                        order: 239.9
                      items:
                        - id: 9981
                          item_id: ITM-0551
                          variation_id: null
                          product_id: 121
                          buybox_offer_uid: null
                          ad_type: single
                          quantity: 2
                          prices:
                            base_unit: 119.9
                            unit: 110
                            total_discount: 19.8
                            subtotal: 220
                          fees:
                            marketplace: 31.9
                          net_value: 188.1
                          publication:
                            id: ITM-0551
                            title: Tênis Esportivo Azul
                            thumbnail_url: https://cdn.example.com/produtos/tenis-azul-sm.webp
                          product:
                            id: 121
                            name: Tênis Esportivo Azul
                            sku: TEN-AZ-001
                            dimensions:
                              weight: 0.8
                              height: 12
                              width: 28
                              length: 35
                            thumbnail_url: https://cdn.example.com/produtos/tenis-azul-sm.webp
                          shipment_id: 4421
                          stock_origin: null
                          created_at: "2026-04-26T14:32:11Z"
                      applied_benefits:
                        - id: 871
                          order_item_id: 9981
                          benefit_type: discount
                          amount: -10
                          source_type: coupon
                          source_id: 23
                          beneficiary: customer
                          absorbed_by: marketplace
                          description: Cupom BEMVINDO10
                          snapshot:
                            code: BEMVINDO10
                            rule: first_purchase
                          created_at: "2026-04-26T14:32:11Z"
                      items_count: 2
                      lines_count: 1
                      items_preview:
                        - title: Tênis Esportivo Azul
                          quantity: 2
                          thumbnail_url: https://cdn.example.com/produtos/tenis-azul-sm.webp
                      invoices:
                        - id: 5b4f...
                          type: nfe
                          status: completed
                          invoice_key: "35260411222333000144550010000012341123456789"
                          invoice_number: "1234"
                          invoice_value: 239.9
                          pdf_url: null
                          created_at: "2026-04-26T15:01:22Z"
                      notes: null
                      shipping:
                        shipment_option_id: opt_001
                        carrier_id: 7
                        service_id: 3
                        carrier_name: Correios PAC
                        display_name: Correios PAC
                        delivery_time_hours: 72
                        carrier_fiscal:
                          carrier_id: 12
                          carrier_uid: 01K8CARRIER000CORREIOS
                          name: Correios PAC
                          operates_with_cnpj: true
                          legal_name: Empresa Brasileira de Correios e Telégrafos
                          document:
                            type: cnpj
                            number: "34028316000103"
                            formatted: 34.028.316/0001-03
                          state_registration: null
                          billing_address: null
                      delivery_method: shipping
                      delivery_method_label: Envio
                      picked_up_at: null
                      is_production_order: false
                      payment_method: pix
                      payment_method_label: PIX
                      payment_expires_at: null
                      created_at: "2026-04-26T14:32:11Z"
                      updated_at: "2026-04-26T14:35:42Z"
                sem_cnpj:
                  summary: Transportadora sem CNPJ (carrier_fiscal nulo)
                  value:
                    success: true
                    code: 200
                    message_code: SUCCESS
                    description: Requisicao realizada com sucesso.
                    data:
                      order_number: ORD-000124
                      checkout_group_id: 01K8PB1QXYZ000124
                      store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      store:
                        id: 01K8PB0KMM36AY2P0NKSZBP2NA
                        name: Minha Loja Premium
                        logo_url: https://cdn.example.com/logo.webp
                        website_domain: minhaloja.com.br
                      customer_id: 01K8PB2RCUST00002
                      customer:
                        id: 01K8PB2RCUST00002
                        name: Maria Souza
                        email: maria@example.com
                      status: paid
                      status_label: Pago
                      totals:
                        products: 80
                        shipping: 15
                        discounts: 0
                        order: 95
                      items: []
                      shipping:
                        shipment_option_id: opt_777
                        carrier_id: 99
                        service_id: 1
                        carrier_name: Transportadora Local (MEI)
                        display_name: Entrega econômica
                        delivery_time_hours: 120
                        carrier_fiscal: null
                      delivery_method: shipping
                      delivery_method_label: Envio
                      created_at: "2026-04-27T09:00:00Z"
                      updated_at: "2026-04-27T09:00:00Z"
                com_cnpj_e_endereco:
                  summary: Transportadora com CNPJ e endereço fiscal
                  value:
                    success: true
                    code: 200
                    message_code: SUCCESS
                    description: Requisicao realizada com sucesso.
                    data:
                      order_number: ORD-000125
                      checkout_group_id: 01K8PB1QXYZ000125
                      store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      store:
                        id: 01K8PB0KMM36AY2P0NKSZBP2NA
                        name: Minha Loja Premium
                        logo_url: https://cdn.example.com/logo.webp
                        website_domain: minhaloja.com.br
                      customer_id: 01K8PB2RCUST00003
                      customer:
                        id: 01K8PB2RCUST00003
                        name: Carlos Lima
                        email: carlos@example.com
                      status: preparing
                      status_label: Em preparação
                      totals:
                        products: 220
                        shipping: 29.9
                        discounts: -10
                        order: 239.9
                      items: []
                      shipping:
                        shipment_option_id: opt_001
                        carrier_id: 12
                        service_id: 3
                        carrier_name: Correios PAC
                        display_name: Correios PAC
                        delivery_time_hours: 72
                        carrier_fiscal:
                          carrier_id: 12
                          carrier_uid: 01K8CARRIER000CORREIOS
                          name: Correios PAC
                          operates_with_cnpj: true
                          legal_name: Empresa Brasileira de Correios e Telégrafos
                          document:
                            type: cnpj
                            number: "34028316000103"
                            formatted: 34.028.316/0001-03
                          state_registration: null
                          billing_address:
                            uid: 01K8CARRIERADDR0001
                            label: Matriz
                            recipient: Correios
                            address: SBN Quadra 1, Bloco A
                            number: S/N
                            complement: Edifício Sede
                            district: Asa Norte
                            city: Brasília
                            state: DF
                            zip_code: "70002900"
                            country: BRA
                            latitude: -15.7934
                            longitude: -47.8822
                            place_id: null
                      delivery_method: shipping
                      delivery_method_label: Envio
                      created_at: "2026-04-27T10:00:00Z"
                      updated_at: "2026-04-27T10:05:00Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/possible-statuses":
    get:
      tags:
        - Pedidos
      summary: Próximos Passos Válidos
      description: |-
        **Esta é a rota que você deve chamar antes de qualquer mudança de status.** Para o estado atual do pedido, devolve as transições aceitáveis com tudo que o integrador precisa para chamar a rota única `/status`:
        
        | Campo | Para que serve |
        |---|---|
        | `action` | Slug semântico (`mark-preparing`, `cancel`, `start-production`). Útil para analytics ou para mapear ícone/botão. |
        | `target_status` | O valor a enviar em `status` na rota única. |
        | `label` | Texto pronto para botão da UI (já em pt-BR). |
        | `payload_schema` | JSONSchema do `data` aceito. `null` quando a transição não exige payload. |
        
        Padrão de uso: renderize um botão por `next_action`, monte `data` a partir do `payload_schema` (quando houver), envie em [`POST /orders/{id}/status`](/reference/pedidos#tag/pedidos/POST/api/v1/sellers/orders/{id}/status). Se você sempre passa por aqui antes de mudar status, é impossível mandar uma transição inválida.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Transições válidas
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  current_status: paid
                  workflow_type: standard
                  next_actions:
                    - action: mark-preparing
                      target_status: preparing
                      label: Iniciar preparação
                      payload_schema: null
                    - action: cancel
                      target_status: cancelled
                      label: Cancelar pedido
                      payload_schema:
                        type: object
                        properties:
                          reason:
                            type: string
                            maxLength: 500
                            description: Motivo (opcional, registrado no histórico)
                  possible_transitions:
                    - preparing
                    - cancelled
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/events":
    get:
      tags:
        - Pedidos
      summary: Histórico Granular de Eventos
      description: |-
        Linha do tempo completa do pedido. Cada mudança de status, registro fiscal, anotação interna ou ação de operador vira um evento com autor, descrição e metadata. Use para timeline detalhada, auditoria ou SAC.
        
        Para a versão consolidada de **rastreio de entrega** (apenas tracking_code, datas-chave), prefira `/tracking` — é uma visão simplificada do mesmo dado.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Eventos listados
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - id: 4501
                    event_type: status_changed
                    event_label: Status alterado
                    author_type: seller
                    author_label: Seller
                    author_id: 87
                    author_name: João Operador
                    description: Pedido movido de paid para preparing.
                    metadata:
                      from: paid
                      to: preparing
                    created_at: "2026-04-26T15:01:22Z"
                  - id: 4502
                    event_type: invoice_issued
                    event_label: Nota fiscal emitida
                    author_type: seller
                    author_label: Seller
                    author_id: 87
                    author_name: João Operador
                    description: NF-e 1234 registrada e DANFE em geração.
                    metadata:
                      invoice_id: 5b4f...
                      invoice_number: "1234"
                    created_at: "2026-04-26T15:10:00Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/notes":
    put:
      tags:
        - Pedidos
      summary: Atualizar Anotações Internas
      description: |-
        Texto livre da loja sobre o pedido — **não é visível ao cliente**. Use para lembretes operacionais ("ligar antes de despachar", "embalagem para presente", "cliente vai retirar quarta").
        
        É um PUT puro: você envia o texto inteiro a cada chamada (não é append/diff) ou `null` para limpar. Não há limite de chamadas — registre quantas vezes precisar.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                notes:
                  type: string
                  nullable: true
                  maxLength: 2000
                  description: Texto da anotação interna. Envie `null` para limpar.
            example:
              notes: Cliente solicitou embalagem para presente.
      responses:
        "200":
          description: Anotações atualizadas — devolve o pedido completo atualizado em `data`
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/{id}/financial":
    get:
      tags:
        - Pedidos
      summary: Detalhamento Financeiro
      description: |-
        Visão financeira aprofundada do pedido: **quem recebe quanto** (splits de receita entre seller, marketplace e sales agent), audit de comissão por item com taxa e valor líquido, e benefícios aplicados (cupons, descontos, regras absorvidas pelo marketplace).
        
        Use para tela de "extrato do pedido" ou conferência de repasse esperado. Para totais simplificados do pedido (sem splits), os campos `totals` no `/orders/{id}` já são suficientes.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Dados financeiros obtidos
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  order_number: ORD-000123
                  checkout_group_id: 01K8PB1QABCDEFGHIJK
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  totals:
                    products: 220
                    shipping: 29.9
                    discounts: -10
                    order: 239.9
                  paid: 239.9
                  splits:
                    - id: 01K8SPLIT00000SELLER
                      order_id: 01K8PB1QORDER0005501
                      recipient: seller
                      recipient_label: Loja
                      beneficiary_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      beneficiary_name: Minha Loja Premium
                      type: items_net_value
                      type_label: Valor líquido dos itens
                      description: Repasse ao seller após comissão e descontos
                      amount: 195.1
                      created_at: "2026-04-26T14:35:42Z"
                    - id: 01K8SPLIT0000MKTPLACE
                      order_id: 01K8PB1QORDER0005501
                      recipient: platform
                      recipient_label: Marketplace
                      beneficiary_id: null
                      beneficiary_name: Plataforma
                      type: commission
                      type_label: Comissão
                      description: Comissão padrão
                      amount: 34.8
                      created_at: "2026-04-26T14:35:42Z"
                  audit_commissions:
                    - id: 991
                      order_item_id: 9981
                      commission_rate: 14.5
                      commission_value: 31.9
                      absorbed_discount: 0
                      net_value: 188.1
                      created_at: "2026-04-26T14:32:11Z"
                      updated_at: "2026-04-26T14:35:42Z"
                  applied_benefits:
                    - id: 871
                      order_item_id: 9981
                      benefit_type: discount
                      amount: -10
                      source_type: coupon
                      source_id: 23
                      beneficiary: customer
                      absorbed_by: marketplace
                      description: Cupom BEMVINDO10
                      snapshot:
                        code: BEMVINDO10
                        rule: first_purchase
                      created_at: "2026-04-26T14:32:11Z"
                  created_at: "2026-04-26T14:32:11Z"
                  updated_at: "2026-04-26T14:35:42Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/tracking":
    get:
      tags:
        - Pedidos
      summary: Rastreio Consolidado
      description: |-
        "Onde está meu pedido" — view simples de acompanhamento. Devolve status atual, `tracking_code`, `tracking_url` pública (quando o transportador fornece), `estimated_delivery_at`, `delivered_at` e `picked_up_at` (em retiradas).
        
        É a rota certa para uma tela de acompanhamento ao cliente final. Para o histórico granular (toda mudança de estado com autor e metadata), use [`/events`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/events).
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Tracking obtido
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  order_number: ORD-000123
                  checkout_group_id: 01K8PB1QABCDEFGHIJK
                  store_id: 01K8PB0KMM36AY2P0NKSZBP2NA
                  customer_id: 01K8PB2RCUST00001
                  status: shipped
                  status_label: Enviado
                  tracking_code: BR123456789BR
                  tracking_url: https://rastreio.exemplo.com/BR123456789BR
                  estimated_delivery_at: "2026-05-02T00:00:00Z"
                  delivered_at: null
                  picked_up_at: null
                  workflow_info:
                    type: standard
                    type_label: Padrão
                  created_at: "2026-04-26T14:32:11Z"
                  updated_at: "2026-04-28T09:15:00Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/status":
    post:
      tags:
        - Pedidos
      summary: Avançar Status (rota única)
      description: |-
        **Toda mudança de estado de um pedido passa por aqui** — iniciar preparação, marcar enviado, confirmar retirada, concluir produção, cancelar. O integrador não precisa mapear rota por workflow.
        
        **Padrão de uso correto:**
        
        1. Chame [`GET /orders/{id}/possible-statuses`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/possible-statuses).
        2. Escolha uma das `next_actions` retornadas.
        3. Envie aqui `{ "status": <target_status>, "data": <payload conforme payload_schema> }`.
        
        **Quando dá errado:**
        
        - **`422` com `errors.status`** — a transição não é permitida a partir do estado atual ("Transição inválida: não é possível ir de X para Y"). Geralmente significa que o pedido avançou (concorrência) ou que o cache de `possible-statuses` está velho. Refaça `/possible-statuses`.
        - **`422` com `errors` por campo** — payload em `data` faltando ou divergindo do `payload_schema` (ex.: `confirmation_code` ausente em `picked_up`).
        - **`404`** — `order_number` inexistente ou de outra loja.
        
        > Cancelamento é uma transição como qualquer outra: `status=cancelled` (com `data.reason` opcional). Não procure por uma rota só de cancelar — ela não existe de propósito; tudo passa por aqui. **Atenção:** o seller só consegue cancelar enquanto o pedido não entrou em preparação/produção; a partir de `preparing`, `production_started`, `ready_to_ship` etc., o cancelamento é restrito à operação da plataforma.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - status
              properties:
                status:
                  type: string
                  description: Status alvo. Use o `target_status` devolvido por `possible-statuses`.
                  enum:
                    - preparing
                    - ready_to_ship
                    - shipped
                    - delivered
                    - cancelled
                    - production_started
                    - awaiting_materials
                    - production_in_progress
                    - production_finished
                    - reserved
                    - ready_for_pickup
                    - picked_up
                    - pickup_expired
                    - available
                    - restocked
                data:
                  type: object
                  description: |-
                    Payload específico da transição. Forma definida por `payload_schema` em `possible-statuses`. Omitir quando `payload_schema=null`.
                    
                    | status | data |
                    |---|---|
                    | `shipped` | `{ tracking_code?: string (max 100) }` |
                    | `cancelled` | `{ reason?: string (max 500) }` |
                    | `production_started` | `{ estimated_days?: integer }` |
                    | `awaiting_materials` | `{ materials?: array }` |
                    | `picked_up` | `{ confirmation_code: string }` (**obrigatório**) |
                    | demais | sem `data` |
                  additionalProperties: true
                  properties:
                    tracking_code:
                      type: string
                      maxLength: 100
                      description: Para `status=shipped`. Opcional.
                    reason:
                      type: string
                      maxLength: 500
                      description: Para `status=cancelled`. Opcional.
                    estimated_days:
                      type: integer
                      minimum: 1
                      description: Para `status=production_started`. Opcional.
                    materials:
                      type: array
                      items:
                        type: string
                      description: Para `status=awaiting_materials`. Opcional.
                    confirmation_code:
                      type: string
                      maxLength: 50
                      description: Para `status=picked_up`. **Obrigatório** nessa transição.
            examples:
              iniciar_preparacao:
                summary: Iniciar preparação (sem payload)
                value:
                  status: preparing
              marcar_enviado_com_rastreio:
                summary: Marcar enviado com tracking
                value:
                  status: shipped
                  data:
                    tracking_code: BR123456789BR
              iniciar_producao:
                summary: Iniciar produção (workflow=production)
                value:
                  status: production_started
                  data:
                    estimated_days: 7
              aguardando_materiais:
                summary: Aguardando materiais (workflow=production)
                value:
                  status: awaiting_materials
                  data:
                    materials:
                      - Tecido azul
                      - Zíper 20cm
              confirmar_retirada:
                summary: Confirmar retirada (workflow=in_store_pickup)
                value:
                  status: picked_up
                  data:
                    confirmation_code: A1B2C3
              cancelar:
                summary: Cancelar com motivo
                value:
                  status: cancelled
                  data:
                    reason: Cliente solicitou cancelamento por arrependimento.
      responses:
        "200":
          description: Transição realizada — devolve o pedido atualizado (versão condensada, sem relações carregadas) em `data`
          content:
            "application/json":
              example:
                data:
                  order_number: ORD-000123
                  checkout_group_id: 01K8PB1QABCDEFGHIJK
                  customer_id: 01K8PB2RCUST00001
                  customer_data_released: true
                  agent_group_uid: null
                  physical_store_id: null
                  type: standard
                  type_label: Padrão
                  channel: marketplace
                  channel_label: Marketplace
                  workflow_type: standard
                  workflow_type_label: Padrão
                  status: preparing
                  status_label: Preparando
                  status_color: blue
                  cancellation_message: null
                  risk_analysis: null
                  totals:
                    products: 220
                    shipping: 29.9
                    discounts: -10
                    order: 239.9
                  notes: null
                  metadata: null
                  delivery_method: shipping
                  delivery_method_label: Envio
                  picked_up_at: null
                  is_production_order: false
                  payment_expires_at: null
                  created_at: "2026-04-26T14:32:11Z"
                  updated_at: "2026-04-27T14:32:11Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Transição inválida ou payload incorreto
          content:
            "application/json":
              examples:
                transicao_invalida:
                  summary: Status alvo não disponível para o estado/workflow atual
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validacao na requisicao.
                    data: []
                    errors:
                      status:
                        - "Transição inválida: não é possível ir de \"Pago\" para \"Enviado\""
                payload_invalido:
                  summary: Payload faltando ou inválido
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validacao na requisicao.
                    data: []
                    errors:
                      confirmation_code:
                        - O campo confirmation_code é obrigatório.
  "/api/v1/sellers/orders/{id}/fiscal":
    get:
      tags:
        - Fiscal
      summary: Documentos Fiscais do Pedido
      description: |-
        **Primeira chamada do fluxo fiscal.** Devolve:
        
        - `invoices[]` — documentos já emitidos para o pedido (vazio na primeira vez). Cada item traz `id`, `type` (`nfe` ou `declaration`), `status` e `has_pdf` — use `has_pdf` para saber se o PDF já está pronto para download.
        - `declaration_available` — se a sua loja tem a feature `fiscal_declaration` ativa. Se vier `false`, só resta NF-e.
        
        Também é a rota de **polling** depois que você dispara emissão de Declaração (`POST /fiscal/declaration`) ou registra NF-e com XML (`POST /fiscal/nfe/key`) — chame em loop curto até o `has_pdf` virar `true`, então baixe via `/fiscal/{invoiceId}/pdf`.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Documentos listados
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Documentos fiscais listados
                data:
                  invoices:
                    - id: 5b4f...
                      type: nfe
                      status: completed
                      error_message: null
                      has_pdf: true
                      invoice_key: "35260411222333000144550010000012341123456789"
                      invoice_number: "1234"
                      invoice_value: 249.9
                      created_at: "2026-04-26T15:10:00Z"
                  declaration_available: true
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/fiscal/declaration":
    post:
      tags:
        - Fiscal
      summary: Emitir Declaração de Conteúdo
      description: |-
        **Para lojas que não emitem NF-e** (típico em MEI ou PF de baixo volume). A plataforma gera o PDF a partir dos dados do pedido — você só dispara.
        
        A geração é **assíncrona** (`202 Accepted`). Depois disso é polling em [`GET /orders/{id}/fiscal`](/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal) até o invoice ficar com `has_pdf=true`, então download em `/fiscal/{invoiceId}/pdf`.
        
        **Pré-condições:**
        
        - Pedido em status `preparing` em diante (não pode ser `paid`). Tentar em `paid` retorna 422.
        - Loja com a feature `fiscal_declaration` ativa (`declaration_available=true` em `/fiscal`). Sem a feature, retorna 403.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "202":
          description: Geração enfileirada
          content:
            "application/json":
              example:
                success: true
                code: 202
                message_code: SUCCESS
                description: Declaração de conteúdo solicitada. PDF sendo gerado
                data:
                  invoice:
                    id: 8c7e...
                    type: declaration
                    status: pending
                    error_message: null
                    has_pdf: false
                    invoice_key: null
                    invoice_number: null
                    invoice_value: null
                    created_at: "2026-04-26T15:10:00Z"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/{id}/fiscal/nfe/key":
    post:
      tags:
        - Fiscal
      summary: Registrar NF-e (chave + XML)
      description: |-
        **Caminho preferido quando você emite NF-e no seu ERP e tem o XML em mãos.** Registra a chave de acesso e o XML autorizado de uma só vez. A partir do XML, a plataforma gera o **DANFE** (PDF) em background — esse é o documento que vai junto com a mercadoria.
        
        Resposta `201 Created`. O DANFE entra na fila e tipicamente fica pronto em alguns segundos — siga o fluxo de polling em [`GET /orders/{id}/fiscal`](/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal).
        
        **Quando esta rota não é o caminho certo:**
        
        - Se você tem **XML em arquivo separado** sem registrar a chave: use [`POST /orders/{id}/invoice/upload-xml`](/reference/pedidos#tag/fiscal/POST/api/v1/sellers/orders/{id}/invoice/upload-xml) — upload `multipart` que extrai a chave do próprio XML.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - type
                - invoice_key
                - invoice_xml
              properties:
                type:
                  type: string
                  enum:
                    - nfe
                  description: Tipo do documento. Atualmente sempre `nfe` nesta rota.
                invoice_key:
                  type: string
                  minLength: 44
                  maxLength: 44
                  description: Chave de acesso da NF-e (44 dígitos).
                invoice_number:
                  type: string
                  nullable: true
                  description: Número da NF-e (opcional).
                invoice_value:
                  type: number
                  nullable: true
                  description: Valor total da nota (opcional).
                invoice_xml:
                  type: string
                  description: XML autorizado da NF-e. **Obrigatório** — usado para gerar o DANFE.
            example:
              type: nfe
              invoice_key: "35260411222333000144550010000012341123456789"
              invoice_number: "1234"
              invoice_value: 249.9
              invoice_xml: <?xml version="1.0"?>...<nfeProc>...</nfeProc>
      responses:
        "201":
          description: NF-e registrada
          content:
            "application/json":
              example:
                success: true
                code: 201
                message_code: SUCCESS
                description: NF-e registrada. DANFE sendo gerado
                data:
                  invoice:
                    id: 5b4f...
                    type: nfe
                    status: pending
                    error_message: null
                    has_pdf: false
                    invoice_key: "35260411222333000144550010000012341123456789"
                    invoice_number: "1234"
                    invoice_value: 249.9
                    created_at: "2026-04-26T15:10:00Z"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/{id}/invoice/upload-xml":
    post:
      tags:
        - Fiscal
      summary: Upload Manual de XML
      description: |-
        **Use quando você tem o arquivo .xml em mãos** mas a chave ainda não foi registrada na plataforma. Cenário típico: XML salvo do email do contador, exportado de outro sistema, ou recuperado de backup.
        
        Envio em `multipart/form-data` com campo `xml` contendo o arquivo. A chave de acesso é **extraída automaticamente do próprio XML** (atributo `Id="NFe..."` em `infNFe`) e validada por mod-11 — você não precisa enviar a chave separada.
        
        O XML é persistido, o `OrderInvoice` entra em `processing` e o DANFE é gerado em background. Mesmo fluxo de polling de [`GET /orders/{id}/fiscal`](/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal).
        
        **Quando falha:**
        
        - **`422`** — XML sem chave válida, mod-11 inválido, arquivo vazio, extensão diferente de `.xml`, ou >1 MB.
        - **`409 chave_already_used`** — essa chave de NF-e já está vinculada a outro pedido (geralmente erro humano: arquivo do pedido errado).
        - **`409 invoice_already_validated`** — o invoice deste pedido já foi validado; reabra antes de reenviar.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: Número do pedido (`order_number`).
          schema:
            type: string
            example: ORD-000123
      requestBody:
        required: true
        content:
          "multipart/form-data":
            schema:
              type: object
              required:
                - xml
              properties:
                xml:
                  type: string
                  format: binary
                  description: "Arquivo XML autorizado da NF-e. Extensão `.xml` obrigatória. Tamanho máximo: 1 MB."
      responses:
        "200":
          description: XML aceito e persistido
          content:
            "application/json":
              example:
                order_invoice_id: 5b4f...
                provider: upload
                chave: "35260411222333000144550010000012341123456789"
                xml_url: https://s3.amazonaws.com/.../35260411222333000144550010000012341123456789.xml?X-Amz-Signature=...
                xml_url_expires_at: "2026-04-26T15:25:00Z"
        "409":
          description: Conflito — chave já vinculada a outro pedido ou invoice já validado
          content:
            "application/json":
              examples:
                chave_duplicada:
                  summary: Chave já vinculada a outro pedido
                  value:
                    error: chave_already_used
                    message: Esta NF-e já está vinculada a outro pedido.
                invoice_validado:
                  summary: OrderInvoice já validado
                  value:
                    error: invoice_already_validated
                    message: O OrderInvoice deste pedido já está validado/completo. Refaça o invoice antes de reenviar XML.
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/pdf":
    get:
      tags:
        - Fiscal
      summary: Baixar PDF (DANFE ou Declaração)
      description: |-
        Mesma rota para os dois tipos de documento — o `filename` da resposta já vem com o prefixo certo (`danfe_` ou `declaracao_`).
        
        Devolve uma URL S3 assinada **válida por 15 minutos**. Não armazene a URL — se expirar, chame esta rota de novo para obter uma nova. `{invoiceId}` é o `id` que veio em `invoices[]` na resposta de `/fiscal`.
        
        Quando o PDF ainda não terminou de ser gerado, retorna **`202 Accepted`** com `status=pending` — não é erro, é sinal de "tente de novo". Volte para o polling em `/fiscal` até `has_pdf=true` e tente baixar de novo.
        
        > Quando o consumidor é gateway/proxy que prefere JSON, devolve `{ url, filename, expires_in }`. Em acesso direto sem proxy, segue redirect direto pro S3.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
            example: 5b4f...
      responses:
        "200":
          description: URL do PDF gerada
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: URL do PDF gerada
                data:
                  url: https://s3.amazonaws.com/.../danfe_ORD-000123.pdf?X-Amz-Signature=...
                  filename: danfe_ORD-000123.pdf
                  expires_in: 15 minutos
        "202":
          description: PDF ainda sendo gerado
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/view":
    get:
      tags:
        - Fiscal
      summary: Dados Completos do Documento (sem PDF)
      description: |-
        Devolve **todos os atributos persistidos** do invoice — chave, número, valor, status, tipo, metadata da declaração, timestamps. Útil para uma tela de "detalhe fiscal" no painel ou para conferência rápida sem precisar baixar o PDF.
        
        Para o PDF em si, use [`/fiscal/{invoiceId}/pdf`](/reference/pedidos#tag/fiscal/GET/api/v1/sellers/orders/{id}/fiscal/{invoiceId}/pdf).
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
            example: 5b4f...
      responses:
        "200":
          description: Documento obtido
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/shipments":
    get:
      tags:
        - Etiquetas
      summary: Shipments e Etiquetas do Pedido
      description: |-
        Lista os **pacotes físicos** do pedido com suas etiquetas embutidas. Um pedido pode ter um ou vários shipments; cada shipment pode ter uma ou várias etiquetas (formatos diferentes).
        
        **É a rota de polling depois de disparar geração de etiquetas** (`POST /shipment-labels/generate`). Consulte em loop até `labels_ready === labels_total` em cada shipment — daí passe para download via `/logistics/shipments/{id}/labels/{labelId}/download` usando os IDs daqui.
        
        A resposta também traz `tracking_code`, `carrier`, dimensões dos volumes e o flag `label_generation_blocked` — útil para a tela de detalhe logístico.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "200":
          description: Shipments listados
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Shipments do pedido listados
                data:
                  shipments:
                    - id: 4421
                      uid: shp_a1b2c3
                      type: sales_order
                      status: ready_to_ship
                      status_label: Pronto para Envio
                      delivery_method: third_party
                      is_pickup: false
                      is_free_shipping: false
                      tracking_code: BR123456789BR
                      external_id: null
                      invoice_number: "1234"
                      freight_cost: 29.9
                      weight: 1.6
                      volumes_count: 1
                      label_generation_blocked: false
                      label_url: null
                      carrier:
                        id: 12
                        name: Correios
                      volumes:
                        - volume_index: 1
                          width: 28
                          height: 12
                          length: 35
                          physical_weight: 1.6
                          chargeable_weight: 1.6
                      labels:
                        - id: lbl_xyz
                          format: a4
                          format_label: A4 (PDF)
                          source: platform
                          source_label: Plataforma
                          status: completed
                          status_label: Disponível
                          is_ready: true
                          completed_at: "2026-04-26T16:02:00Z"
                          expires_at: null
                      labels_ready: 1
                      labels_total: 1
                      created_at: "2026-04-26T14:35:42Z"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/{id}/shipment-labels/generate":
    post:
      tags:
        - Etiquetas
      summary: (Re)gerar Etiquetas dos Shipments
      description: |-
        Enfileira **um job por shipment** do pedido para gerar as etiquetas. Operação **assíncrona** — retorna `202 Accepted` com a lista de shipments efetivamente enfileirados.
        
        **Chamar de novo regenera tudo.** Não é incremental: uma chamada apaga etiquetas anteriores e dispara jobs novos para todos os shipments não-bloqueados. Use com consciência — se 1 de 3 deu errado, regerar afeta os outros 2 também.
        
        **Shipments pulados:** os com `label_generation_blocked=true` não entram em `queued_shipments`. O `total` pode ser menor que o número real de shipments. Esse flag fica em `true` quando o sistema sabe que há um problema impossível de resolver automaticamente (endereço inválido, conta do transportador suspensa) — caso de operação.
        
        Depois desta chamada, faça polling em [`/orders/{id}/shipments`](/reference/pedidos#tag/etiquetas/GET/api/v1/sellers/orders/{id}/shipments).
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: ORD-000123
      responses:
        "202":
          description: Geração enfileirada
          content:
            "application/json":
              example:
                success: true
                code: 202
                message_code: SUCCESS
                description: Etiquetas redefinidas. Geração em andamento.
                data:
                  queued_shipments:
                    - 4421
                  total: 1
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/seller/logistics/shipments/{id}/labels":
    get:
      tags:
        - Etiquetas
      summary: Listar Etiquetas de um Shipment
      description: |-
        Devolve apenas o array de etiquetas de um shipment específico — mesma informação que vem em `shipments[].labels` em `/orders/{id}/shipments`, mas servida isolada.
        
        Use na tela "detalhe do shipment", quando você já tem o `shipment.id` e não quer recarregar todos os shipments do pedido só para conferir as etiquetas.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID numérico do shipment
          schema:
            type: integer
            example: 4421
      responses:
        "200":
          description: Etiquetas listadas
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  shipment_id: 4421
                  labels:
                    - id: lbl_xyz
                      format: a4
                      format_label: A4 (PDF)
                      source: platform
                      source_label: Plataforma
                      status: completed
                      status_label: Disponível
                      is_ready: true
                      completed_at: "2026-04-26T16:02:00Z"
                  formats_pending: []
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/seller/logistics/shipments/{id}/labels/{labelId}/download":
    get:
      tags:
        - Etiquetas
      summary: Baixar PDF da Etiqueta
      description: |-
        Devolve URL S3 assinada **válida por 15 minutos** para o PDF da etiqueta. Não armazene a URL — se expirar, chame esta rota de novo.
        
        `{id}` é o `shipment.id` (numérico); `{labelId}` é o `label.id` (vem em `shipments[].labels[].id`).
        
        Se a etiqueta ainda não terminou de ser gerada (`is_ready=false`), retorna **`422 UNPROCESSABLE_ENTITY`** com mensagem `Etiqueta ainda não disponível`. Volte para o polling em `/orders/{id}/shipments` antes de tentar de novo.
        
        > Em telas de impressão em massa, gere as URLs sob demanda (no clique em "Imprimir"), não antecipadamente — para evitar que expirem no meio do uso.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID numérico do shipment
          schema:
            type: integer
            example: 4421
        - name: labelId
          in: path
          required: true
          description: ID da etiqueta
          schema:
            type: string
            example: lbl_xyz
      responses:
        "200":
          description: URL gerada
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  label_id: lbl_xyz
                  format: a4
                  url: https://s3.amazonaws.com/.../label_4421.pdf?X-Amz-Signature=...
                  expires_in: 15 minutos
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Etiqueta ainda não disponível
          content:
            "application/json":
              example:
                success: false
                code: 422
                message_code: UNPROCESSABLE_ENTITY
                description: "Etiqueta ainda não disponível. Status: Em processamento"
                data: []
                errors:
                  - Etiqueta ainda não disponível.
  "/api/v1/sellers/orders/enums":
    get:
      tags:
        - Enums de Pedido
      summary: Catálogo de Enums do Pedido
      description: |-
        **Fonte única** dos tipos e valores do ecossistema de pedidos, num só request. Cada chave de `data` é um enum, e cada item traz `value` (o valor que a API aceita/devolve) e `label` (pt-BR pronto para UI). `statuses` e `payment_statuses` também trazem `color` (token de badge).
        
        Use para popular filtros, legendas e badges sem hardcodar listas — os valores só mudam em deploy. Para a versão dos status **agrupada por workflow** (com transições alcançáveis), veja [`GET /api/global/enums/order/status`](/reference/pedidos#tag/enums-de-pedido/GET/api/global/enums/order/status).
        
        > **Nota sobre `event_types`/`event_author_types`:** o `value` é o slug textual (ex.: `status_changed`, `seller`) — o mesmo que aparece em [`GET /orders/{id}/events`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/events).
      security:
        - bearer: []
      responses:
        "200":
          description: Catálogo completo de enums do pedido
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  channels:
                    - value: marketplace
                      label: Marketplace
                    - value: website
                      label: Site da Loja
                    - value: direct
                      label: Vendas Diretas
                    - value: store_seller
                      label: Vendedor da Loja
                    - value: physical_store
                      label: Loja Física
                    - value: mobile_app
                      label: App Mobile
                    - value: whatsapp
                      label: WhatsApp
                    - value: pos
                      label: Ponto de Venda
                    - value: api
                      label: API/Integração
                  types:
                    - value: standard
                      label: Padrão
                    - value: production
                      label: Sob Produção
                    - value: pre_order
                      label: Pré-venda
                    - value: backorder
                      label: Sob Encomenda
                    - value: physical_store
                      label: Loja Física
                  workflows:
                    - value: standard
                      label: Padrão
                    - value: production
                      label: Produção
                    - value: in_store_pickup
                      label: Retirada em Loja
                    - value: pre_order
                      label: Pré-venda
                    - value: backorder
                      label: Sob Encomenda
                    - value: collective
                      label: Compra Coletiva
                  statuses:
                    - value: created
                      label: Criado
                      color: gray
                    - value: waiting_payment
                      label: Aguardando Pagamento
                      color: yellow
                    - value: pending
                      label: Pendente
                      color: yellow
                    - value: paid
                      label: Pago
                      color: green
                    - value: in_risk_analysis
                      label: Em análise antifraude
                      color: yellow
                    - value: preparing
                      label: Preparando
                      color: blue
                    - value: ready_to_ship
                      label: Pronto para Envio
                      color: indigo
                    - value: shipped
                      label: Enviado
                      color: indigo
                    - value: delivered
                      label: Entregue
                      color: green
                    - value: cancelled
                      label: Cancelado
                      color: red
                    - value: production_started
                      label: Produção Iniciada
                      color: orange
                    - value: awaiting_materials
                      label: Aguardando Materiais
                      color: amber
                    - value: production_in_progress
                      label: Em Fabricação
                      color: cyan
                    - value: production_finished
                      label: Produção Concluída
                      color: teal
                    - value: reserved
                      label: Reservado
                      color: violet
                    - value: ready_for_pickup
                      label: Pronto para Retirada
                      color: fuchsia
                    - value: picked_up
                      label: Retirado
                      color: emerald
                    - value: pickup_expired
                      label: Retirada Expirada
                      color: rose
                    - value: awaiting_availability
                      label: Aguardando Disponibilidade
                      color: purple
                    - value: available
                      label: Disponível
                      color: lime
                    - value: awaiting_restock
                      label: Aguardando Reposição
                      color: orange
                    - value: restocked
                      label: Reposto
                      color: lime
                    - value: awaiting_campaign_conclusion
                      label: Aguardando Encerramento Campanha
                      color: purple
                    - value: completed
                      label: Concluído
                      color: emerald
                    - value: payment_expired
                      label: Pagamento Expirado
                      color: rose
                  delivery_methods:
                    - value: shipping
                      label: Envio
                    - value: pickup
                      label: Retirada
                  payment_methods:
                    - value: pix
                      label: PIX
                    - value: credit_card
                      label: Cartão de Crédito
                    - value: boleto
                      label: Boleto Bancário
                    - value: boleto_prazo
                      label: Boleto a Prazo
                    - value: other
                      label: Outro
                  payment_statuses:
                    - value: pending
                      label: Pendente
                      color: amber
                    - value: processing
                      label: Processando
                      color: blue
                    - value: waiting_payment
                      label: Aguardando Pagamento
                      color: amber
                    - value: paid
                      label: Pago
                      color: green
                    - value: failed
                      label: Falhou
                      color: red
                    - value: cancelled
                      label: Cancelado
                      color: gray
                    - value: refunded
                      label: Reembolsado
                      color: orange
                  return_statuses:
                    - value: pending
                      label: Aguardando Análise
                    - value: forwarded_to_seller
                      label: Encaminhado ao Vendedor
                    - value: approved
                      label: Aprovado
                    - value: rejected
                      label: Recusado
                    - value: cancelled
                      label: Cancelado pelo Cliente
                    - value: label_generated
                      label: Etiqueta Gerada
                    - value: return_in_progress
                      label: Em Trânsito
                    - value: received
                      label: Recebido
                    - value: refunded
                      label: Estorno Registrado
                    - value: closed
                      label: Encerrado
                  invoice_statuses:
                    - value: pending
                      label: Aguardando
                    - value: validating
                      label: Validando
                    - value: valid
                      label: Válido
                    - value: invalid
                      label: Inválido
                    - value: processing
                      label: Processando
                    - value: completed
                      label: Completado
                    - value: error
                      label: Erro
                  invoice_types:
                    - value: declaration
                      label: Declaração de Conteúdo
                    - value: nfe
                      label: Nota Fiscal Eletrônica
                  event_types:
                    - value: order_created
                      label: Pedido criado
                    - value: status_changed
                      label: Status alterado
                    - value: order_cancelled
                      label: Pedido cancelado
                    - value: preparation_started
                      label: Preparação iniciada
                    - value: shipment_created
                      label: Envio criado
                    - value: tracking_updated
                      label: Rastreio atualizado
                    - value: order_delivered
                      label: Pedido entregue
                    - value: pickup_ready
                      label: Pronto para retirada
                    - value: picked_up
                      label: Retirado pelo cliente
                    - value: invoice_issued
                      label: Nota fiscal emitida
                    - value: payment_confirmed
                      label: Pagamento confirmado
                    - value: refund_requested
                      label: Reembolso solicitado
                    - value: refund_processed
                      label: Reembolso processado
                    - value: delivery_attempt
                      label: Tentativa de entrega
                    - value: delivery_occurrence
                      label: Ocorrência de entrega
                    - value: seller_annotation
                      label: Anotação do seller
                    - value: system_annotation
                      label: Anotação do sistema
                  event_author_types:
                    - value: system
                      label: Sistema
                    - value: admin
                      label: Admin
                    - value: seller
                      label: Seller
                    - value: subagent
                      label: Subconta
                    - value: driver
                      label: Motorista
                    - value: sales_agent
                      label: Representante
                    - value: customer
                      label: Cliente
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/api/global/enums/order/status":
    get:
      tags:
        - Enums de Pedido
      summary: Status de Pedido (por workflow)
      description: |-
        Catálogo completo dos status de pedido **agrupados por `workflow_type`**. Para cada workflow, devolve apenas os status que um pedido daquele fluxo pode assumir, já com `label` (pt-BR), `color` (token de cor para badge) e `auxiliary` (status secundário/transitório, útil para esconder de filtros principais).
        
        Rota **pública** e cacheada por 24h — a resposta só muda em deploy. Use para montar legendas, filtros e badges sem hardcodar valores. Para as transições válidas a partir do estado atual de um pedido específico, use [`GET /orders/{id}/possible-statuses`](/reference/pedidos#tag/pedidos/GET/api/v1/sellers/orders/{id}/possible-statuses).
      security: []
      responses:
        "200":
          description: Status agrupados por workflow
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - workflow: standard
                    label: Padrão
                    statuses:
                      - value: paid
                        label: Pago
                        color: green
                        auxiliary: false
                      - value: preparing
                        label: Preparando
                        color: blue
                        auxiliary: false
                      - value: ready_to_ship
                        label: Pronto para Envio
                        color: indigo
                        auxiliary: false
                      - value: shipped
                        label: Enviado
                        color: indigo
                        auxiliary: false
                      - value: delivered
                        label: Entregue
                        color: green
                        auxiliary: false
                      - value: cancelled
                        label: Cancelado
                        color: red
                        auxiliary: false
                  - workflow: in_store_pickup
                    label: Retirada em Loja
                    statuses:
                      - value: reserved
                        label: Reservado
                        color: violet
                        auxiliary: false
                      - value: ready_for_pickup
                        label: Pronto para Retirada
                        color: fuchsia
                        auxiliary: false
                      - value: picked_up
                        label: Retirado
                        color: emerald
                        auxiliary: true
                      - value: pickup_expired
                        label: Retirada Expirada
                        color: rose
                        auxiliary: true
  "/api/logistics/enums/delivery-methods":
    get:
      tags:
        - Enums de Pedido
      summary: Métodos de Entrega
      description: |-
        Lista os métodos de entrega disponíveis. Cada item indica se exige um motorista (`requires_driver`) e se aceita confirmação manual de entrega pelo seller (`allows_manual_confirmation`) — flags que mudam quais ações de logística o integrador pode oferecer.
        
        Rota **pública**.
      security: []
      responses:
        "200":
          description: Métodos de entrega disponíveis
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - value: with_driver
                    label: Entrega com Motorista
                    requires_driver: true
                    allows_manual_confirmation: false
                  - value: manual_confirmation
                    label: Confirmação Manual
                    requires_driver: false
                    allows_manual_confirmation: true
                  - value: third_party
                    label: Transportadora Terceirizada
                    requires_driver: false
                    allows_manual_confirmation: false
                  - value: pickup_in_store
                    label: Retirada no Local
                    requires_driver: false
                    allows_manual_confirmation: true
  "/api/logistics/enums/shipment-statuses":
    get:
      tags:
        - Enums de Pedido
      summary: Status de Shipment
      description: |-
        Lista os status possíveis de um shipment (envio). Distinto do status do **pedido** — um pedido pode ter vários shipments, cada um com seu próprio ciclo. Cada item traz `value` e `label` (pt-BR).
        
        Rota **pública**.
      security: []
      responses:
        "200":
          description: Status de shipment disponíveis
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - value: pending
                    label: Pendente
                  - value: processing
                    label: Processando
                  - value: ready_to_ship
                    label: Pronto para Envio
                  - value: in_transit
                    label: Em Trânsito
                  - value: delivered
                    label: Entregue
                  - value: cancelled
                    label: Cancelado
                  - value: return_in_progress
                    label: Em Devolução
                  - value: returned
                    label: Devolvido
                  - value: awaiting_return
                    label: Aguardando Retorno
                  - value: failed
                    label: Falha na Entrega
                  - value: error
                    label: Erro na Integração
  "/api/logistics/enums/shipment-types":
    get:
      tags:
        - Enums de Pedido
      summary: Tipos de Shipment
      description: |-
        Lista os tipos de shipment: venda (`sales_order`), devolução (`return`), transferência entre locais (`transfer`) e coleta (`pickup`). Cada item traz `value` e `label` (pt-BR).
        
        Rota **pública**.
      security: []
      responses:
        "200":
          description: Tipos de shipment disponíveis
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - value: sales_order
                    label: Pedido de Venda
                  - value: return
                    label: Devolução
                  - value: transfer
                    label: Transferência
                  - value: pickup
                    label: Coleta
  "/api/logistics/enums/freight-table-layouts":
    get:
      tags:
        - Enums de Pedido
      summary: Layouts de Tabela de Frete
      description: |-
        Lista os layouts de tabela de frete aceitos no upload de tabelas próprias. Sem `integration_type` na query, devolve os layouts **nativos** (`default`, `vtex`). Com `?integration_type=<slug>`, devolve os layouts declarados pela integração de transportadora correspondente (ou vazio se o slug for desconhecido).
        
        Rota **pública**.
      security: []
      parameters:
        - name: integration_type
          in: query
          required: false
          description: Slug da integração de transportadora. Quando informado, retorna os layouts suportados por aquela integração.
          schema:
            type: string
            example: j3tms
      responses:
        "200":
          description: Layouts de tabela de frete disponíveis
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: Requisicao realizada com sucesso.
                data:
                  - value: default
                    label: Padrão do Sistema
                    context_fields: []
                  - value: vtex
                    label: Modelo VTEX
                    context_fields: []
  "/api/v1/sellers/orders/returns":
    get:
      tags:
        - Devoluções
      summary: Listar Devoluções
      description: |-
        Devolve **todas as devoluções da loja**, da mais recente para a mais antiga — inclusive as que ainda estão em triagem na plataforma (`pending`) e as que já encerraram. Combine os filtros para recortar:
        
        - Fila de decisão (aguardando sua resposta): `?status=forwarded_to_seller`.
        - Ver todas as devoluções de um pedido específico: `?order_id=01JMG9ZB1QDC0R8Y6W3ETKHM2V`.
        - Auditar devoluções recusadas: `?status=rejected`.
        - Buscar por número de pedido: `?q=ORD-0001`.
        - Janela temporal: `?date_from=...&date_to=...` (datas inválidas são ignoradas silenciosamente).
        
        A resposta inclui `seller_response_deadline_at` e `sla_exceeded` para destacar visualmente devoluções com SLA estourado.
      security:
        - bearer: []
      parameters:
        - name: order_id
          in: query
          required: false
          description: Filtra pelas devoluções de um pedido específico (ID do pedido, 26 caracteres).
          schema:
            type: string
            example: 01JMG9ZB1QDC0R8Y6W3ETKHM2V
        - name: status
          in: query
          required: false
          description: Filtra por status da devolução. Use `forwarded_to_seller` para a fila de devoluções aguardando sua decisão.
          schema:
            type: string
            enum:
              - pending
              - forwarded_to_seller
              - approved
              - rejected
              - cancelled
              - label_generated
              - return_in_progress
              - received
              - refunded
              - closed
        - name: q
          in: query
          required: false
          description: Busca por `order_number` (LIKE) ou ID exato do pedido.
          schema:
            type: string
        - name: date_from
          in: query
          required: false
          description: Data inicial de criação da devolução. Datas inválidas são ignoradas.
          schema:
            type: string
            format: date-time
        - name: date_to
          in: query
          required: false
          description: Data final de criação da devolução. Datas inválidas são ignoradas.
          schema:
            type: string
            format: date-time
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            example: 15
      responses:
        "200":
          description: Devoluções listadas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 1
                        records: 1
                  data:
                    - id: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
                      order_id: 01JMG9ZB1QDC0R8Y6W3ETKHM2V
                      order_number: ORD-000123
                      status: forwarded_to_seller
                      status_label: Encaminhado ao Vendedor
                      return_reason_key: defective
                      items:
                        - order_item_id: 01JMG9ZC4T8RWX2QHV6DKN3M7E
                          quantity: 1
                          reason_key: defective
                      notes: Produto com defeito
                      seller_notes: null
                      rejection_reason: null
                      forwarded_to_seller_at: "2026-04-26T10:15:00-03:00"
                      seller_response_deadline_at: "2026-04-28T10:15:00-03:00"
                      sla_exceeded: false
                      approved_at: null
                      rejected_at: null
                      cancelled_at: null
                      resolution: null
                      resolution_notes: null
                      return_shipment_id: null
                      pickup_method: null
                      pickup_address: null
                      pickup_window_from: null
                      pickup_window_to: null
                      pickup_contact_phone: null
                      created_at: "2026-04-26T10:15:00-03:00"
                      updated_at: "2026-04-26T10:15:00-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/api/v1/sellers/orders/returns/summary":
    get:
      tags:
        - Devoluções
      summary: Totais de Devoluções por status
      description: |-
        Contadores de devoluções da loja agrupados por status, mais o `total` geral. Não aplica filtros — sempre considera todas as devoluções da loja, igual à listagem sem filtros.
        
        Alimenta os chips de contagem nas abas de status do RMA no painel. `by_status` traz todas as chaves do enum de status (zero quando não há devoluções naquele estado).
      security:
        - bearer: []
      responses:
        "200":
          description: Totais por status
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  total: 7
                  by_status:
                    pending: 1
                    forwarded_to_seller: 2
                    approved: 2
                    rejected: 1
                    cancelled: 0
                    label_generated: 0
                    return_in_progress: 1
                    received: 0
                    refunded: 0
                    closed: 0
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/api/v1/sellers/orders/returns/{id}":
    get:
      tags:
        - Devoluções
      summary: Detalhe da Devolução
      description: |-
        Registro completo de uma devolução: motivo informado pelo cliente (`reason`), itens devolvidos (`items[]`), `pickup_address` (montado a partir do snapshot do pedido), janela de coleta acordada, timestamps (`forwarded_to_seller_at`, `approved_at`, `rejected_at`, etc.) e o `seller_response_deadline_at` para o SLA.
        
        Após o produto retornar, os campos de desfecho são preenchidos: `resolution` (`refunded` = solicitação de estorno criada | `resolved_externally` = encerrada sem estorno, resolvida fora da plataforma) e `resolution_notes` (justificativa registrada pela operação). Veja **[Devoluções — Resolução](/guia/devolucoes-resolucao)**.
        
        É o que a tela "Detalhe da devolução" consome para mostrar tudo antes da decisão de aprovar/rejeitar.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da devolução
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      responses:
        "200":
          description: Devolução obtida
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/returns/{id}/possible-actions":
    get:
      tags:
        - Devoluções
      summary: Ações possíveis
      description: |-
        Devolve as ações que o seller pode executar na devolução **no status atual** — espelho declarativo do workflow, pensado para montar UI condicional sem replicar a máquina de estados no cliente.
        
        | Status | Ações oferecidas |
        |---|---|
        | `forwarded_to_seller` | `approve`, `reject` |
        | `approved` | `generate_reverse_label` |
        | `label_generated`, `return_in_progress` | `mark_received` |
        | demais | nenhuma |
        
        Cada ação traz `id`, `label`, `icon`, `variant`, `endpoint`, `method` e `requires_input` (mapa campo → `required`/`optional`, ou `null` quando não há corpo). `is_terminal` indica que a devolução encerrou (`rejected`, `cancelled`, `closed`) e nada mais acontece.
        
        A decisão de estorno (após `received`) é exclusiva da plataforma — nunca aparece aqui.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da devolução
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      responses:
        "200":
          description: Ações para o status atual
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        description: Status atual da devolução.
                      status_label:
                        type: string
                        description: Rótulo do status, em português.
                      actions:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              enum:
                                - approve
                                - reject
                                - generate_reverse_label
                                - mark_received
                            label:
                              type: string
                            icon:
                              type: string
                            variant:
                              type: string
                            endpoint:
                              type: string
                              description: Caminho relativo da ação (anexe ao prefixo `/api/v1/sellers`).
                            method:
                              type: string
                            requires_input:
                              type:
                                - object
                                - "null"
                              description: Mapa campo → `required`/`optional`; `null` quando a ação não tem corpo.
                            note:
                              type: string
                              description: Orientação contextual sobre a ação (quando presente).
                      is_terminal:
                        type: boolean
              example:
                success: true
                data:
                  status: label_generated
                  status_label: Etiqueta Gerada
                  actions:
                    - id: mark_received
                      label: Confirmar recebimento
                      icon: package-check
                      variant: primary
                      endpoint: /orders/returns/01JMGAZ8K6Q2W7XV5DNRTN0M4P/mark-received
                      method: POST
                      requires_input: null
                      note: Confirme quando o produto devolvido chegar à loja. A decisão de estorno fica com a plataforma.
                  is_terminal: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/returns/{id}/approve":
    post:
      tags:
        - Devoluções
      summary: Aprovar Devolução
      description: |-
        Marca a devolução como `approved`. A partir deste estado, a próxima ação é **gerar a coleta reversa** em [`/reverse/generate`](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/reverse/generate) (modo `carrier` ou `manual`).
        
        `seller_notes` é opcional e **fica apenas no histórico interno** — não é exibido ao cliente. Use para justificar a aprovação internamente ("conferi com o setor X", "defeito visível nas fotos enviadas").
        
        > Aprovação é definitiva — não existe um "des-aprovar" de propósito, para a decisão valer junto ao cliente. Se aprovou por engano, fale com o suporte da plataforma para tratar o caso manualmente.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: object
              properties:
                seller_notes:
                  type: string
                  maxLength: 1000
                  nullable: true
                  description: Anotações internas do seller, registradas no histórico da devolução.
            example:
              seller_notes: Defeito confirmado. Reembolso autorizado.
      responses:
        "200":
          description: Devolução aprovada
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/returns/{id}/reject":
    post:
      tags:
        - Devoluções
      summary: Rejeitar Devolução
      description: |-
        Marca a devolução como `rejected`. **`reason` é obrigatório e é exibido ao cliente** — escreva como uma resposta de SAC: profissional, clara, sem jargão interno. Boas práticas:
        
        - Cite o motivo concreto (prazo legal, condição do produto, política da loja).
        - Inclua datas relevantes quando aplicável ("entregue em 12/03, solicitação em 28/03").
        - Evite tom defensivo.
        
        Para anotações internas que **não** vão para o cliente, use `seller_notes` no fluxo de [aprovação](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/approve).
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - reason
              properties:
                reason:
                  type: string
                  maxLength: 1000
                  description: Motivo da rejeição, exibido ao cliente.
            example:
              reason: Produto fora do prazo de devolução (60 dias).
      responses:
        "200":
          description: Devolução rejeitada
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/returns/{id}/mark-received":
    post:
      tags:
        - Devoluções
      summary: Confirmar Recebimento
      description: |-
        Marca a devolução como `received` — o produto devolvido chegou fisicamente à loja. Disponível a partir de `label_generated` ou `return_in_progress`; como o destino da coleta reversa é o endereço da loja, é o seller quem está em posição de confirmar a chegada.
        
        Na coleta `manual` não há rastreio nenhum, então esta confirmação é a **única** forma de a devolução avançar — não esqueça dela, ou o caso fica parado e o cliente sem resposta.
        
        A partir de `received`, a decisão do desfecho (criar solicitação de estorno ou encerrar sem estorno) é da plataforma; acompanhe pelos campos `status` e `resolution`.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      responses:
        "200":
          description: Recebimento confirmado (`status=received`)
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/sellers/orders/returns/{id}/reverse/eligible-carriers":
    get:
      tags:
        - Devoluções
      summary: Carriers Elegíveis para Coleta Reversa
      description: |-
        Lista os **transportadores parceiros que cobrem o CEP do cliente** com frete estimado de cada um. Use para montar a UI de escolha do transportador antes de chamar [`/reverse/generate`](/reference/devolucoes#tag/devolucoes/POST/api/v1/sellers/orders/returns/{id}/reverse/generate) em modo `carrier`.
        
        **`has_coverage=false`** significa que nenhum carrier parceiro atende o endereço — o modo `carrier` não está disponível para esta devolução, só `manual` (ou negociar suporte da plataforma).
        
        O `estimated_freight` retornado é uma estimativa baseada na tabela do carrier. Se você negociou um valor diferente, use o negociado em `freight_cost` na hora de gerar.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da devolução
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      responses:
        "200":
          description: Carriers listados
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  has_coverage: true
                  zip_code: 01310-100
                  carriers:
                    - id: 7
                      uid: 01HM0Z6E...
                      name: Correios PAC
                      estimated_freight: 18.9
                    - id: 9
                      uid: 01HM0X1A...
                      name: Loggi Reverso
                      estimated_freight: 22.5
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/v1/sellers/orders/returns/{id}/reverse/generate":
    post:
      tags:
        - Devoluções
      summary: Gerar Coleta Reversa
      description: |-
        Cria a coleta reversa para uma devolução já `approved`. **Dois modos** — escolha conforme a situação:
        
        - **`method=carrier`** — a plataforma cria o shipment reverso com o transportador escolhido (carrier parceiro). Exige `carrier_id` (use o `id` retornado em [`/reverse/eligible-carriers`](/reference/devolucoes#tag/devolucoes/GET/api/v1/sellers/orders/returns/{id}/reverse/eligible-carriers)). A devolução passa a `label_generated` e a resposta traz o `shipment` criado (status `pending`; o `tracking_code` é preenchido quando a etiqueta é emitida).
        
        - **`method=manual`** — você organiza a coleta por fora (motoboy próprio, cliente leva na loja, retirada combinada). **Nenhum shipment é criado**; a devolução avança para `label_generated` com `pickup_method=manual` e `notes` fica no histórico. Neste modo a resposta traz só a devolução atualizada, sem o objeto `shipment`. Use quando `has_coverage=false`, quando o seller tem logística própria, ou em acordos específicos.
        
        **Pré-condição:** a devolução precisa estar em `approved`. Tentar gerar em outro status retorna `422 — A devolução precisa estar aprovada para gerar a coleta reversa.`
        
        Campos opcionais úteis: `pickup_window_from`/`pickup_window_to` (janela combinada com o cliente), `pickup_contact_phone` (telefone no endereço de coleta), `notes` (observações livres, recomendado em `manual`).
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da devolução
          schema:
            type: string
            example: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - method
              properties:
                method:
                  type: string
                  enum:
                    - carrier
                    - manual
                  description: "Modo de geração. `carrier`: usa um transportador parceiro (exige `carrier_id`). `manual`: seller organiza a coleta por fora."
                carrier_id:
                  type: integer
                  description: Obrigatório quando `method=carrier`. Use o `id` retornado por `/reverse/eligible-carriers`.
                freight_cost:
                  type: number
                  minimum: 0
                  description: Valor do frete reverso. Opcional — se omitido, é registrado como 0. Use o `estimated_freight` ou um valor negociado.
                notes:
                  type: string
                  maxLength: 1000
                  description: Observações livres. Recomendado em `method=manual`.
                pickup_window_from:
                  type: string
                  format: date-time
                  description: Início da janela de coleta (opcional).
                pickup_window_to:
                  type: string
                  format: date-time
                  description: Fim da janela de coleta. Quando enviado com `pickup_window_from`, precisa ser maior ou igual.
                pickup_contact_phone:
                  type: string
                  maxLength: 32
                  description: Telefone de contato no endereço de coleta (opcional).
            examples:
              carrier:
                summary: Modo carrier (com transportadora)
                value:
                  method: carrier
                  carrier_id: 7
                  freight_cost: 18.9
                  pickup_contact_phone: "+5511999999999"
              manual:
                summary: Modo manual (seller organiza coleta)
                value:
                  method: manual
                  notes: Cliente vai trazer pessoalmente na próxima semana.
      responses:
        "200":
          description: Coleta reversa gerada
          content:
            "application/json":
              examples:
                carrier:
                  summary: Modo carrier — devolução + shipment criado
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      order_return:
                        id: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
                        order_id: 01JMG9ZB1QDC0R8Y6W3ETKHM2V
                        order_number: ORD-000123
                        status: label_generated
                        status_label: Etiqueta Gerada
                        return_reason_key: defective
                        items:
                          - order_item_id: 01JMG9ZC4T8RWX2QHV6DKN3M7E
                            quantity: 1
                            reason_key: defective
                        notes: Produto com defeito
                        seller_notes: Defeito confirmado. Reembolso autorizado.
                        rejection_reason: null
                        forwarded_to_seller_at: "2026-04-26T10:15:00-03:00"
                        seller_response_deadline_at: "2026-04-28T10:15:00-03:00"
                        sla_exceeded: false
                        approved_at: "2026-04-27T09:00:00-03:00"
                        rejected_at: null
                        cancelled_at: null
                        resolution: null
                        resolution_notes: null
                        return_shipment_id: 9921
                        pickup_method: carrier
                        pickup_address:
                          zip_code: 01310-100
                          street: Av. Paulista
                          number: "1000"
                          city: São Paulo
                          state: SP
                        pickup_window_from: null
                        pickup_window_to: null
                        pickup_contact_phone: "+5511999999999"
                        created_at: "2026-04-26T10:15:00-03:00"
                        updated_at: "2026-04-27T10:30:00-03:00"
                      shipment:
                        id: 9921
                        uid: 01HM1Z3QK4W8XV2D7RTNB6M5PE
                        tracking_code: null
                        status: pending
                manual:
                  summary: Modo manual — só a devolução atualizada, sem shipment
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      id: 01JMGAZ8K6Q2W7XV5DNRTN0M4P
                      order_id: 01JMG9ZB1QDC0R8Y6W3ETKHM2V
                      order_number: ORD-000123
                      status: label_generated
                      status_label: Etiqueta Gerada
                      return_reason_key: defective
                      items:
                        - order_item_id: 01JMG9ZC4T8RWX2QHV6DKN3M7E
                          quantity: 1
                          reason_key: defective
                      notes: Produto com defeito
                      seller_notes: Defeito confirmado. Reembolso autorizado.
                      rejection_reason: null
                      forwarded_to_seller_at: "2026-04-26T10:15:00-03:00"
                      seller_response_deadline_at: "2026-04-28T10:15:00-03:00"
                      sla_exceeded: false
                      approved_at: "2026-04-27T09:00:00-03:00"
                      rejected_at: null
                      cancelled_at: null
                      resolution: null
                      resolution_notes: null
                      return_shipment_id: null
                      pickup_method: manual
                      pickup_address:
                        zip_code: 01310-100
                        street: Av. Paulista
                        number: "1000"
                        city: São Paulo
                        state: SP
                      pickup_window_from: null
                      pickup_window_to: null
                      pickup_contact_phone: null
                      created_at: "2026-04-26T10:15:00-03:00"
                      updated_at: "2026-04-27T10:30:00-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/seller/conversations":
    get:
      tags:
        - Central de Atendimento
      summary: Listar Conversas
      description: |-
        Lista paginada das conversas da loja autenticada, ordenadas pela mensagem mais recente (`last_message_at` desc).
        
        Aceita filtros simples por `status`, `type` e `subject_id` (categoria do chamado pós-venda), além de uma busca direta por `order_id` via parâmetro `q`.
      security:
        - bearer: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `20`.
          schema:
            type: integer
            example: 20
        - name: status
          in: query
          required: false
          description: Filtra por status da conversa.
          schema:
            type: string
            enum:
              - open
              - closed
        - name: type
          in: query
          required: false
          description: Filtra por tipo de conversa.
          schema:
            type: string
            enum:
              - pre_delivery
              - post_delivery
        - name: subject_id
          in: query
          required: false
          description: "Filtra por assunto (slug do catálogo de assuntos pós-venda, ex: `defective_product`, `wrong_item`)."
          schema:
            type: string
            example: defective_product
        - name: q
          in: query
          required: false
          description: Busca por `order_id` exato (ULID do pedido). Por enquanto este parâmetro só casa o pedido exato — busca textual por nome do cliente ainda não passa por aqui; filtre essas situações no seu lado.
          schema:
            type: string
            example: 01K8PB2RORD00001ABCDEFGHJK
      responses:
        "200":
          description: Conversas listadas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 20
                      last_page: 3
                      has_prev_page: false
                      has_next_page: true
                      records:
                        from: 1
                        to: 20
                        records: 47
                  data:
                    - id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      type: pre_delivery
                      status: open
                      closed_reason: null
                      subject_id: null
                      subject_label: null
                      order_id: 01K8PB2RORD00001ABCDEFGHJK
                      order:
                        ulid: 01K8PB2RORD00001ABCDEFGHJK
                        order_number: ORD-000123
                        status: preparing
                        status_label: Em preparação
                        total: 239.9
                        created_at: "2026-05-15T10:14:22-03:00"
                      store_id: 42
                      customer_id: 1234
                      customer:
                        name: João Silva
                        display_name: João Silva
                        email: joao@example.com
                        avatar_url: null
                        avatar_color: "#16a34a"
                      last_message_at: "2026-05-18T09:42:17-03:00"
                      closed_at: null
                      created_at: "2026-05-15T10:20:08-03:00"
                    - id: 01K8PB0KMM36AY2P0NKSZBP2NB
                      type: post_delivery
                      status: closed
                      closed_reason: customer_closed
                      subject_id: wrong_item
                      subject_label: Recebi item errado
                      order_id: 01K8PB2RORD00002ABCDEFGHJK
                      order:
                        ulid: 01K8PB2RORD00002ABCDEFGHJK
                        order_number: ORD-000119
                        status: delivered
                        status_label: Entregue
                        total: 84.5
                        created_at: "2026-04-29T18:02:51-03:00"
                      store_id: 42
                      customer_id: 1180
                      customer:
                        name: Mariana Souza
                        display_name: Mariana Souza
                        email: mariana@example.com
                        avatar_url: null
                        avatar_color: "#F97316"
                      last_message_at: "2026-05-12T17:08:00-03:00"
                      closed_at: "2026-05-13T08:30:11-03:00"
                      created_at: "2026-05-09T11:00:00-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/v1/seller/conversations/unread-count":
    get:
      tags:
        - Central de Atendimento
      summary: Contar Conversas Não Lidas
      description: |-
        Retorna o total de **mensagens não lidas** pela loja, somando todas as conversas abertas (`status=open`).
        
        A contagem é feita do ponto de vista do participante `store` — mensagens enviadas pela própria loja não contam. Útil para badges no menu lateral do portal seller.
      security:
        - bearer: []
      responses:
        "200":
          description: Contagem obtida
          content:
            "application/json":
              examples:
                with_unread:
                  summary: Existem mensagens não lidas
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      count: 7
                no_unread:
                  summary: Tudo lido
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      count: 0
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/v1/seller/conversations/config":
    get:
      tags:
        - Central de Atendimento
      summary: Configuração da Central de Atendimento
      description: |-
        Retorna as configurações globais aplicáveis ao seller para o módulo de conversas.
        
        Expõe o **SLA de resposta esperado**, em horas, definido pela plataforma (`platform settings → conversations.sla_response_hours`, default `24`) — frontends usam este valor para destacar conversas próximas do estouro de prazo — e o catálogo de **assuntos** (`subjects`) disponíveis para chamados, cada um com `id` (slug usado no filtro `subject_id`), `label` e `applies_to` (`pre`, `post` ou `both`). Apenas assuntos ativos são retornados.
      security:
        - bearer: []
      responses:
        "200":
          description: Configuração obtida
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  sla_response_hours: 24
                  subjects:
                    - id: defective_product
                      label: Produto com defeito
                      applies_to: post
                    - id: wrong_item
                      label: Recebi item errado
                      applies_to: post
                    - id: shipping_question
                      label: Dúvida sobre a entrega
                      applies_to: both
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/v1/seller/conversations/{conversation}":
    get:
      tags:
        - Central de Atendimento
      summary: Detalhes da Conversa
      description: |-
        Retorna a conversa completa, incluindo dados do pedido vinculado, dados resumidos do cliente e a lista de **participantes**.
        
        A conversa precisa pertencer à loja autenticada — caso contrário, retorna `403` (`CONVERSATION_NOT_PARTICIPANT`). ID inexistente retorna `404`. Mensagens são paginadas em endpoint separado (`/messages`).
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      responses:
        "200":
          description: Conversa obtida
          content:
            "application/json":
              examples:
                pre_delivery_open:
                  summary: Pré-entrega aberta
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      type: pre_delivery
                      status: open
                      closed_reason: null
                      subject_id: null
                      subject_label: null
                      order_id: 01K8PB2RORD00001ABCDEFGHJK
                      order:
                        ulid: 01K8PB2RORD00001ABCDEFGHJK
                        order_number: ORD-000123
                        status: preparing
                        status_label: Em preparação
                        total: 239.9
                        created_at: "2026-05-15T10:14:22-03:00"
                      store_id: 42
                      customer_id: 1234
                      customer:
                        name: João Silva
                        display_name: João Silva
                        email: joao@example.com
                        avatar_url: null
                        avatar_color: "#16a34a"
                      last_message_at: "2026-05-18T09:42:17-03:00"
                      closed_at: null
                      created_at: "2026-05-15T10:20:08-03:00"
                      participants:
                        - type: customer
                          display_name: null
                          participant_id: 1234
                        - type: store
                          display_name: null
                          participant_id: 42
                post_delivery_with_platform:
                  summary: Pós-entrega com intervenção da plataforma
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      id: 01K8PB0KMM36AY2P0NKSZBP2NB
                      type: post_delivery
                      status: open
                      closed_reason: null
                      subject_id: defective_product
                      subject_label: Produto com defeito
                      order_id: 01K8PB2RORD00002ABCDEFGHJK
                      order:
                        ulid: 01K8PB2RORD00002ABCDEFGHJK
                        order_number: ORD-000119
                        status: delivered
                        status_label: Entregue
                        total: 84.5
                        created_at: "2026-04-29T18:02:51-03:00"
                      store_id: 42
                      customer_id: 1180
                      customer:
                        name: Mariana Souza
                        display_name: Mariana Souza
                        email: mariana@example.com
                        avatar_url: null
                        avatar_color: "#F97316"
                      last_message_at: "2026-05-19T11:30:55-03:00"
                      closed_at: null
                      created_at: "2026-05-17T14:02:00-03:00"
                      participants:
                        - type: customer
                          display_name: null
                          participant_id: 1180
                        - type: store
                          display_name: null
                          participant_id: 42
                        - type: platform
                          display_name: Atendimento Plataforma
                          participant_id: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/v1/seller/conversations/{conversation}/messages":
    get:
      tags:
        - Central de Atendimento
      summary: Listar Mensagens
      description: |-
        Lista paginada das mensagens da conversa em ordem **cronológica ascendente** (mais antiga primeiro).
        
        Para polling incremental, informe `after_message_ulid` com o ULID da última mensagem já recebida — só virão mensagens mais novas que ela.
        
        Cada mensagem tem `kind`:
        - `user` — escrita por um participante humano
        - `system` — gerada automaticamente pela plataforma (ex: "Pedido marcado como entregue")
        
        O bloco `sender` é enriquecido com `display_name`, `avatar_url` e `badge` resolvidos a partir do tipo do remetente — útil para renderizar o balão de chat sem novas requisições.
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `50`.
          schema:
            type: integer
            example: 50
        - name: after_message_ulid
          in: query
          required: false
          description: Retorna apenas mensagens criadas **após** a mensagem indicada (uso típico em polling). Se o ULID não pertencer a esta conversa, o filtro é ignorado.
          schema:
            type: string
            example: 01K8PBMSG00001VWXYZ12345678
      responses:
        "200":
          description: Mensagens listadas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 50
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 4
                        records: 4
                  data:
                    - id: 01K8PBMSG00001VWXYZ12345678
                      body: Olá! Confirmo que seu pedido já entrou em separação.
                      kind: user
                      sender_type: store
                      sender_id: 42
                      sender_user_type: customer
                      sender_user_id: 88
                      created_at: "2026-05-15T10:21:44-03:00"
                      sender:
                        type: store
                        display_name: Atendente Loja
                        avatar_url: null
                        avatar_color: "#2563eb"
                        store_name: Minha Loja Premium
                        badge: Loja
                        participant_id: 42
                      attachments: []
                    - id: 01K8PBMSG00002VWXYZ12345678
                      body: Obrigado! Existe previsão de envio para hoje?
                      kind: user
                      sender_type: customer
                      sender_id: 1234
                      sender_user_type: null
                      sender_user_id: null
                      created_at: "2026-05-15T10:25:02-03:00"
                      sender:
                        type: customer
                        display_name: João Silva
                        avatar_url: null
                        avatar_color: "#16a34a"
                        store_name: null
                        badge: null
                        participant_id: 1234
                      attachments: []
                    - id: 01K8PBMSG00003VWXYZ12345678
                      body: Segue foto da etiqueta de envio.
                      kind: user
                      sender_type: store
                      sender_id: 42
                      sender_user_type: customer
                      sender_user_id: 88
                      created_at: "2026-05-15T11:02:18-03:00"
                      sender:
                        type: store
                        display_name: Atendente Loja
                        avatar_url: null
                        avatar_color: "#2563eb"
                        store_name: Minha Loja Premium
                        badge: Loja
                        participant_id: 42
                      attachments:
                        - id: 01K8PBATT00001VWXYZ12345678
                          original_name: etiqueta-envio.pdf
                          mime_type: application/pdf
                          size_bytes: 184320
                          kind: pdf
                          width: null
                          height: null
                          duration_seconds: null
                    - id: 01K8PBMSG00004VWXYZ12345678
                      body: Pedido marcado como entregue.
                      kind: system
                      sender_type: store
                      sender_id: 42
                      sender_user_type: null
                      sender_user_id: null
                      created_at: "2026-05-18T09:42:17-03:00"
                      sender:
                        type: store
                        display_name: null
                        avatar_url: null
                        avatar_color: null
                        store_name: null
                        badge: null
                        participant_id: 42
                      attachments: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
    post:
      tags:
        - Central de Atendimento
      summary: Enviar Mensagem
      description: |-
        Envia uma nova mensagem na conversa como participante `store`.
        
        A mensagem pode conter apenas `body`, apenas `attachments` (até 5 ULIDs de anexos previamente reservados via `attachments/intent`), ou ambos. O backend valida participação na conversa e o status (não é possível enviar em conversas encerradas).
        
        Ao retornar, o objeto inclui os anexos já enriquecidos e o bloco `sender` com `display_name` e `badge` da loja.
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                body:
                  type: string
                  maxLength: 5000
                  nullable: true
                  description: Texto da mensagem. Opcional quando `attachments` está presente.
                attachments:
                  type: array
                  maxItems: 5
                  items:
                    type: string
                    description: ULID de anexo retornado por `POST /attachments/intent`.
            examples:
              text_only:
                summary: Apenas texto
                value:
                  body: Boa tarde! O pedido já foi despachado, código de rastreio segue em anexo no próximo retorno.
              with_attachments:
                summary: Texto + anexos
                value:
                  body: Segue a nota fiscal e a foto da embalagem.
                  attachments:
                    - 01K8PBATT00001VWXYZ12345678
                    - 01K8PBATT00002VWXYZ12345678
              attachments_only:
                summary: Apenas anexos
                value:
                  attachments:
                    - 01K8PBATT00003VWXYZ12345678
      responses:
        "200":
          description: Mensagem enviada
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K8PBMSG00005VWXYZ12345678
                  body: Segue a nota fiscal e a foto da embalagem.
                  kind: user
                  sender_type: store
                  sender_id: 42
                  sender_user_type: customer
                  sender_user_id: 88
                  created_at: "2026-05-19T11:32:08-03:00"
                  sender:
                    type: store
                    display_name: Atendente Loja
                    avatar_url: null
                    avatar_color: "#2563eb"
                    store_name: Minha Loja Premium
                    badge: Loja
                    participant_id: 42
                  attachments:
                    - id: 01K8PBATT00001VWXYZ12345678
                      original_name: nota-fiscal.pdf
                      mime_type: application/pdf
                      size_bytes: 92160
                      kind: pdf
                      width: null
                      height: null
                      duration_seconds: null
                    - id: 01K8PBATT00002VWXYZ12345678
                      original_name: embalagem.jpg
                      mime_type: image/jpeg
                      size_bytes: 612400
                      kind: image
                      width: 1024
                      height: 768
                      duration_seconds: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          description: Não participa da conversa
          content:
            "application/json":
              examples:
                not_participant:
                  summary: Loja não é participante da conversa
                  value:
                    success: false
                    code: 403
                    message_code: CONVERSATION_NOT_PARTICIPANT
                    description: Você não participa desta conversa.
                    data: []
                    errors:
                      - Você não participa desta conversa.
                    meta: []
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Conversa encerrada, anexo inválido ou validação dos campos
          content:
            "application/json":
              examples:
                conversation_closed:
                  summary: Conversa encerrada
                  value:
                    success: false
                    code: 422
                    message_code: CONVERSATION_CLOSED
                    description: Conversa encerrada.
                    data: []
                    errors:
                      - Conversa encerrada.
                    meta: []
                attachment_not_confirmed:
                  summary: Anexo ainda não confirmado
                  value:
                    success: false
                    code: 422
                    message_code: CONVERSATION_ATTACHMENT_NOT_CONFIRMED
                    description: Anexo ainda não foi confirmado no S3.
                    data: []
                    errors:
                      - Anexo ainda não foi confirmado no S3.
                    meta: []
                validation:
                  summary: Erro de validação dos campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Os dados informados são inválidos.
                    data: []
                    errors:
                      body:
                        - The body must not be greater than 5000 characters.
                      attachments:
                        - The attachments must not have more than 5 items.
                    meta: []
  "/v1/seller/conversations/{conversation}/read":
    post:
      tags:
        - Central de Atendimento
      summary: Marcar como Lida
      description: |-
        Atualiza o **ponteiro de leitura** da loja para a conversa, marcando como lida toda mensagem até (e incluindo) `last_message_ulid`.
        
        O contador de não lidas (`GET /unread-count`) usa esse ponteiro. Operação idempotente — chamar duas vezes com o mesmo ULID não tem efeito adicional. Retorna `204 No Content` em caso de sucesso.
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - last_message_ulid
              properties:
                last_message_ulid:
                  type: string
                  description: ULID da última mensagem que a loja visualizou.
            example:
              last_message_ulid: 01K8PBMSG00004VWXYZ12345678
      responses:
        "204":
          description: Ponteiro de leitura atualizado (sem corpo).
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/seller/conversations/{conversation}/attachments/intent":
    post:
      tags:
        - Central de Atendimento
      summary: Reservar Upload de Anexo
      description: |-
        Inicia o envio de um anexo em duas etapas: o servidor reserva um slot para o arquivo e devolve uma **URL pré-assinada** (`PUT`) para o cliente subir o conteúdo diretamente ao storage (S3/MinIO).
        
        Fluxo completo:
        1. `POST /attachments/intent` com `original_name`, `mime_type` e `size_bytes` — recebe `attachment_ulid`, `upload_url` e `expires_at`.
        2. Cliente faz `PUT upload_url` com o binário do arquivo.
        3. `POST /messages` com `attachments: [attachment_ulid]` — o backend confirma o upload (verificando existência no storage) e vincula o anexo à mensagem.
        
        Restrições aplicadas pela plataforma:
        - **Tamanho máximo**: 25 MB (`CONVERSATIONS_ATTACHMENT_MAX_SIZE`).
        - **Tipos permitidos**: `image/jpeg`, `image/png`, `image/webp`, `image/gif`, `application/pdf`, `video/mp4`, `video/webm`, `video/quicktime`.
        - **URL de upload** expira em 15 minutos.
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - original_name
                - mime_type
                - size_bytes
              properties:
                original_name:
                  type: string
                  description: Nome original do arquivo (com extensão).
                mime_type:
                  type: string
                  description: MIME type do arquivo. Precisa estar na lista de permitidos.
                  enum:
                    - image/jpeg
                    - image/png
                    - image/webp
                    - image/gif
                    - application/pdf
                    - video/mp4
                    - video/webm
                    - video/quicktime
                size_bytes:
                  type: integer
                  minimum: 1
                  description: Tamanho do arquivo em bytes (máx. 26214400 = 25 MB).
            examples:
              image:
                summary: Foto da embalagem
                value:
                  original_name: embalagem.jpg
                  mime_type: image/jpeg
                  size_bytes: 612400
              pdf:
                summary: Nota fiscal em PDF
                value:
                  original_name: nota-fiscal.pdf
                  mime_type: application/pdf
                  size_bytes: 92160
      responses:
        "200":
          description: Slot reservado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  attachment_ulid: 01K8PBATT00001VWXYZ12345678
                  upload_url: https://cdn-content.s3.amazonaws.com/01K8PB0KMM36AY2P0NKSZBP2NA/01K8PBBKT00001ABCDE/01K8PBATT00001VWXYZ12345678.jpg?X-Amz-Signature=...
                  method: PUT
                  expires_at: "2026-05-19T11:47:08-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          description: Loja não participa da conversa
          content:
            "application/json":
              example:
                success: false
                code: 403
                message_code: CONVERSATION_NOT_PARTICIPANT
                description: Você não participa desta conversa.
                data: []
                errors:
                  - Você não participa desta conversa.
                meta: []
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Arquivo rejeitado ou erro de validação
          content:
            "application/json":
              examples:
                too_large:
                  summary: Excede 25 MB
                  value:
                    success: false
                    code: 422
                    message_code: CONVERSATION_ATTACHMENT_TOO_LARGE
                    description: Anexo excede o tamanho máximo permitido.
                    data: []
                    errors:
                      - Anexo excede o tamanho máximo permitido.
                    meta: []
                mime_not_allowed:
                  summary: Tipo de arquivo não permitido
                  value:
                    success: false
                    code: 422
                    message_code: CONVERSATION_ATTACHMENT_MIME_NOT_ALLOWED
                    description: Tipo de arquivo não permitido.
                    data: []
                    errors:
                      - Tipo de arquivo não permitido.
                    meta: []
                validation:
                  summary: Campos obrigatórios ausentes
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Os dados informados são inválidos.
                    data: []
                    errors:
                      original_name:
                        - The original name field is required.
                      mime_type:
                        - The mime type field is required.
                      size_bytes:
                        - The size bytes must be at least 1.
                    meta: []
  "/v1/seller/conversations/{conversation}/attachments/{attachment}/access":
    get:
      tags:
        - Central de Atendimento
      summary: URL Temporária de Acesso ao Anexo
      description: |-
        Gera uma **URL pré-assinada de download** (GET) válida por 30 minutos para o anexo da conversa.
        
        A URL é entregue pelo storage (S3/MinIO) e bypassa o backend — o cliente deve fazer requisição direta a ela. A cada chamada é gerada uma URL nova; URLs antigas continuam válidas até expirarem.
        
        O acesso só é concedido a participantes da conversa (a loja precisa estar registrada como participante `store`).
      security:
        - bearer: []
      parameters:
        - name: conversation
          in: path
          required: true
          description: ULID da conversa.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
        - name: attachment
          in: path
          required: true
          description: ULID do anexo.
          schema:
            type: string
            example: 01K8PBATT00001VWXYZ12345678
      responses:
        "200":
          description: URL gerada
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  url: https://cdn-content.s3.amazonaws.com/01K8PB0KMM36AY2P0NKSZBP2NA/01K8PBBKT00001ABCDE/01K8PBATT00001VWXYZ12345678.jpg?X-Amz-Signature=...
                  expires_at: "2026-05-19T12:02:08-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          description: Loja não participa da conversa
          content:
            "application/json":
              example:
                success: false
                code: 403
                message_code: CONVERSATION_NOT_PARTICIPANT
                description: Você não participa desta conversa.
                data: []
                errors:
                  - Você não participa desta conversa.
                meta: []
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/seller/interactions/questions":
    get:
      tags:
        - Perguntas e Respostas
      summary: Listar Perguntas
      description: |-
        Lista paginada de perguntas das publicações vinculadas à loja autenticada, ordenadas da mais recente para a mais antiga.
        
        Cada item carrega:
        - Dados mínimos do **cliente** que perguntou (nome, avatar).
        - A **publicação** associada (item_id, título, thumbnail e link para o storefront).
        - As **respostas** já cadastradas, com seu status de moderação.
        
        Perguntas em qualquer status (`PENDING`, `APPROVED`, `REJECTED`) são retornadas — utilize o campo `status` para filtrar na UI.
      security:
        - bearer: []
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
        - name: per_page
          in: query
          required: false
          description: "Quantidade de itens por página. Default: 15."
          schema:
            type: integer
            example: 15
        - name: item_id
          in: query
          required: false
          description: "Filtra perguntas de uma publicação específica pelo `item_id` (identificador público da publicação, ex.: `US123456`)."
          schema:
            type: string
            example: US123456
      responses:
        "200":
          description: Perguntas listadas
          content:
            "application/json":
              examples:
                perguntas_mistas:
                  summary: Lista com perguntas pendentes e respondidas
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      meta:
                        search_query: ""
                        filters: []
                        pagination:
                          page: 1
                          per_page: 15
                          last_page: 2
                          has_prev_page: false
                          has_next_page: true
                          records:
                            from: 1
                            to: 15
                            records: 23
                      data:
                        - uid: 01K8PB0KMM36AY2P0NKSZBP2NA
                          content: Esse produto é compatível com a versão 2024?
                          status: APPROVED
                          created_at: "2026-05-18T14:32:11.000000Z"
                          customer:
                            name: João
                            avatar_url: null
                            avatar_color: "#F97316"
                          publication:
                            item_id: US123456
                            title: Furadeira de Impacto 750W
                            type: default
                            thumbnail_url: https://cdn.example.com/produtos/furadeira-sm.webp
                            frontend_url: https://www.minha-loja.com.br/furadeira-de-impacto-750w/p/US123456
                          answers: []
                        - uid: 01K8PB1QRT7XYZAB2P0NKSZBP2
                          content: Acompanha maleta?
                          status: APPROVED
                          created_at: "2026-05-17T09:10:00.000000Z"
                          customer:
                            name: Maria
                            avatar_url: https://cdn.example.com/avatars/maria.webp
                            avatar_color: "#22C55E"
                          publication:
                            item_id: US123456
                            title: Furadeira de Impacto 750W
                            type: default
                            thumbnail_url: https://cdn.example.com/produtos/furadeira-sm.webp
                            frontend_url: https://www.minha-loja.com.br/furadeira-de-impacto-750w/p/US123456
                          answers:
                            - uid: 01K8PB2ANSW00000000000000A
                              content: Sim, acompanha maleta plástica com brocas.
                              status: APPROVED
                              author_type: App\Domains\Stores\Models\Store
                              created_at: "2026-05-17T15:42:00.000000Z"
                        - uid: 01K8PB3PENDING000000000000
                          content: Posso usar 220V direto?
                          status: PENDING
                          created_at: "2026-05-19T08:00:00.000000Z"
                          customer:
                            name: Cliente
                            avatar_url: null
                            avatar_color: "#3B82F6"
                          publication:
                            item_id: US987654
                            title: Cafeteira Elétrica 1.2L
                            type: default
                            thumbnail_url: https://cdn.example.com/produtos/cafeteira-sm.webp
                            frontend_url: https://www.minha-loja.com.br/cafeteira-eletrica-1-2l/p/US987654
                          answers: []
                lista_vazia:
                  summary: Loja sem perguntas
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      meta:
                        search_query: ""
                        filters: []
                        pagination:
                          page: 1
                          per_page: 15
                          last_page: 1
                          has_prev_page: false
                          has_next_page: false
                          records:
                            from: 0
                            to: 0
                            records: 0
                      data: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/interactions/questions/{uid}/answer":
    post:
      tags:
        - Perguntas e Respostas
      summary: Responder Pergunta
      description: |-
        Cria uma resposta para a pergunta identificada por `uid`, em nome da loja autenticada.
        
        **Comportamento de moderação:**
        - Por padrão, a resposta entra como `PENDING` e aguarda moderação para se tornar visível ao público.
        - Se a loja tiver a *feature* `trusted_answers` habilitada, a resposta é publicada **imediatamente** como `APPROVED` (modo `asAdmin`).
        
        O `author_type` retornado será sempre `App\Domains\Stores\Models\Store` — o autor da resposta é a loja, não o usuário logado.
      security:
        - bearer: []
      parameters:
        - name: uid
          in: path
          required: true
          description: ULID da pergunta que será respondida.
          schema:
            type: string
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - content
              properties:
                content:
                  type: string
                  minLength: 2
                  description: Texto da resposta. Mínimo de 2 caracteres.
            example:
              content: Sim, este produto é compatível com a versão 2024 e acompanha cabo USB-C.
      responses:
        "201":
          description: Resposta criada
          content:
            "application/json":
              examples:
                resposta_pendente_moderacao:
                  summary: Loja padrão — resposta entra em moderação
                  value:
                    success: true
                    message_code: RESOURCE_CREATED
                    data:
                      uid: 01K8PB2ANSW00000000000000A
                      content: Sim, este produto é compatível com a versão 2024 e acompanha cabo USB-C.
                      status: PENDING
                      author_type: App\Domains\Stores\Models\Store
                      created_at: "2026-05-19T10:15:42.000000Z"
                resposta_trusted_answers:
                  summary: Loja com feature trusted_answers — resposta já aprovada
                  value:
                    success: true
                    message_code: RESOURCE_CREATED
                    data:
                      uid: 01K8PB2ANSW00000000000000B
                      content: Sim, este produto é compatível com a versão 2024 e acompanha cabo USB-C.
                      status: APPROVED
                      author_type: App\Domains\Stores\Models\Store
                      created_at: "2026-05-19T10:15:42.000000Z"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          description: Usuário autenticado, porém sem loja associada
          content:
            "application/json":
              example:
                success: false
                code: 403
                message_code: FORBIDDEN
                description: Você não tem permissão para acessar este recurso.
                data: []
                errors:
                  - No store associated with user.
                meta: []
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/api/seller/interactions/unanswered-count":
    get:
      tags:
        - Perguntas e Respostas
      summary: Contador de Perguntas Sem Resposta
      description: |-
        Retorna o **número de perguntas aprovadas e ainda não respondidas** da loja autenticada, junto com a idade (em horas) da pergunta sem resposta mais antiga.
        
        Usado pelos *widgets* de dashboard do portal do seller para sinalizar atrasos no atendimento de Q&A.
        
        **Regra de contagem:** apenas `Question` com `status = APPROVED` que **não possuem nenhuma** `Answer` com `status = APPROVED`. Respostas pendentes ou rejeitadas não "zeram" o contador.
        
        O campo `deep_link` é um caminho relativo do portal do seller para a tela já filtrada por perguntas não respondidas.
      security:
        - bearer: []
      responses:
        "200":
          description: Contagem retornada
          content:
            "application/json":
              examples:
                com_perguntas_pendentes:
                  summary: Loja com perguntas aguardando resposta
                  value:
                    success: true
                    message_code: Perguntas sem resposta
                    data:
                      count: 2
                      oldest_age_hours: 14
                      deep_link: /portal/interacoes/perguntas?status=unanswered
                sem_pendencias:
                  summary: Loja sem perguntas em aberto
                  value:
                    success: true
                    message_code: Perguntas sem resposta
                    data:
                      count: 0
                      oldest_age_hours: null
                      deep_link: /portal/interacoes/perguntas?status=unanswered
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences":
    get:
      tags:
        - Ocorrências
      summary: Listar Ocorrências
      description: |-
        Lista paginada das ocorrências visíveis à loja autenticada. Por padrão exclui as `auto_closed`.
        
        Atenção a um detalhe ao parsear a resposta: aqui a paginação vem aninhada em **`meta.pagination`** (padrão `PaginatedResource`), diferente da spec de `pedidos`, que usa um `meta` plano. Aponte seu parser para o lugar certo.
      security:
        - bearer: []
      parameters:
        - name: matrix
          in: query
          required: false
          description: "Filtra pelo `slug` da matriz (ex.: `rma`, `logistics`, `orders`)."
          schema:
            type: string
            example: rma
        - name: status
          in: query
          required: false
          description: "Lista de status separados por vírgula. Aceita: `open`, `resolved`, `cancelled`, `auto_closed`. Quando ausente, oculta `auto_closed`."
          schema:
            type: string
            example: open,resolved
        - name: date_from
          in: query
          required: false
          description: Data inicial (`created_at >=`). Aceita ISO 8601.
          schema:
            type: string
            format: date-time
        - name: date_to
          in: query
          required: false
          description: Data final (`created_at <=`). Aceita ISO 8601.
          schema:
            type: string
            format: date-time
        - name: per_page
          in: query
          required: false
          description: Itens por página. Default `20`.
          schema:
            type: integer
            default: 20
            example: 20
        - name: page
          in: query
          required: false
          schema:
            type: integer
            example: 1
      responses:
        "200":
          description: Ocorrências listadas
          content:
            "application/json":
              examples:
                com_resultados:
                  summary: Loja com ocorrências de devolução em andamento
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      meta:
                        search_query: ""
                        filters: []
                        pagination:
                          page: 1
                          per_page: 20
                          last_page: 1
                          has_prev_page: false
                          has_next_page: false
                          records:
                            from: 1
                            to: 2
                            records: 2
                      data:
                        - id: 01K8PB0KMM36AY2P0NKSZBP2NA
                          title: Devolução solicitada para pedido ORD-000123
                          severity: high
                          severity_label: Alta
                          severity_color: orange
                          status: open
                          status_label: Aberta
                          origin: system
                          origin_label: Sistema
                          matrix:
                            id: 4
                            name: Devoluções
                            slug: rma
                            color: red
                          type:
                            id: 11
                            name: Devolução padrão
                          step:
                            id: 14
                            name: Analisando
                            color: yellow
                            position: 2
                          assigned_to:
                            type: operator
                            id: 7
                            name: null
                          relations_count: 2
                          is_overdue: false
                          visible_to_store: true
                          due_at: "2026-05-22T18:00:00-03:00"
                          created_at: "2026-05-18T09:12:33-03:00"
                          updated_at: "2026-05-19T11:05:01-03:00"
                        - id: 01K8PC2HQE1JTAEV5W6RFZ9N4P
                          title: Cliente reclamou de produto avariado
                          severity: critical
                          severity_label: Crítica
                          severity_color: red
                          status: open
                          status_label: Aberta
                          origin: manual
                          origin_label: Manual
                          matrix:
                            id: 4
                            name: Devoluções
                            slug: rma
                            color: red
                          type:
                            id: 12
                            name: Produto avariado
                          step:
                            id: 13
                            name: Novo
                            color: blue
                            position: 1
                          assigned_to: null
                          relations_count: 1
                          is_overdue: true
                          visible_to_store: true
                          due_at: "2026-05-18T10:00:00-03:00"
                          created_at: "2026-05-17T15:42:00-03:00"
                          updated_at: "2026-05-17T15:42:00-03:00"
                vazio:
                  summary: Loja sem ocorrências
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      meta:
                        search_query: ""
                        filters: []
                        pagination:
                          page: 1
                          per_page: 20
                          last_page: 1
                          has_prev_page: false
                          has_next_page: false
                          records:
                            from: 0
                            to: 0
                            records: 0
                      data: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences/dashboard":
    get:
      tags:
        - Ocorrências
      summary: Dashboard de Ocorrências
      description: |-
        Resumo das ocorrências abertas da loja:
        
        - `open_count` — total de ocorrências em status `open`.
        - `requires_action` — ocorrências abertas com comentário shared do operator mais recente que `last_store_read_at` (ou nunca lidas).
        - `last_updated_at` — `max(updated_at)` entre as ocorrências abertas.
        - `items` — top 10 ocorrências abertas ordenadas por `severity` (CRITICAL > HIGH > MEDIUM > LOW) e depois por `created_at` ASC. Cada item inclui o flag `has_unread_message`.
      security:
        - bearer: []
      responses:
        "200":
          description: Dashboard carregado
          content:
            "application/json":
              examples:
                com_indicadores:
                  summary: Loja com ocorrências abertas e ações pendentes
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      open_count: 7
                      requires_action: 3
                      last_updated_at: "2026-05-19T14:21:08-03:00"
                      items:
                        - id: 01K8PC2HQE1JTAEV5W6RFZ9N4P
                          title: Cliente reclamou de produto avariado
                          severity: critical
                          severity_label: Crítica
                          severity_color: red
                          status: open
                          status_label: Aberta
                          origin: manual
                          origin_label: Manual
                          matrix:
                            id: 4
                            name: Devoluções
                            slug: rma
                            color: red
                          type:
                            id: 12
                            name: Produto avariado
                          step:
                            id: 13
                            name: Novo
                            color: blue
                            position: 1
                          assigned_to: null
                          relations_count: 1
                          is_overdue: true
                          visible_to_store: true
                          due_at: "2026-05-18T10:00:00-03:00"
                          created_at: "2026-05-17T15:42:00-03:00"
                          updated_at: "2026-05-17T15:42:00-03:00"
                          has_unread_message: true
                zerado:
                  summary: Loja sem ocorrências abertas
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      open_count: 0
                      requires_action: 0
                      last_updated_at: null
                      items: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences/matrices":
    get:
      tags:
        - Ocorrências
      summary: Listar Matrizes
      description: |-
        Lista todas as matrizes (`occ_matrices`) ativas, ordenadas por `sort_order`, já com a coleção `steps` carregada na ordem de `position`.
        
        Use esta rota para popular filtros de matriz, montar o seletor de quadro kanban e descobrir os `stepId` válidos antes de chamar `/kanban/column/{stepId}`.
      security:
        - bearer: []
      responses:
        "200":
          description: Matrizes listadas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - id: 1
                    name: Pedidos
                    slug: orders
                    color: blue
                    icon: shopping-cart
                    sla_hours: 24
                    steps:
                      - id: 1
                        name: Novo
                        slug: new
                        color: blue
                        position: 1
                        is_initial: true
                        is_final: false
                      - id: 2
                        name: Analisando
                        slug: analyzing
                        color: yellow
                        position: 2
                        is_initial: false
                        is_final: false
                      - id: 3
                        name: Aguardando
                        slug: waiting
                        color: gray
                        position: 3
                        is_initial: false
                        is_final: false
                      - id: 4
                        name: Resolvendo
                        slug: resolving
                        color: orange
                        position: 4
                        is_initial: false
                        is_final: false
                      - id: 5
                        name: Resolvida
                        slug: resolved
                        color: green
                        position: 5
                        is_initial: false
                        is_final: true
                      - id: 6
                        name: Cancelada
                        slug: cancelled
                        color: red
                        position: 6
                        is_initial: false
                        is_final: true
                    types_count: null
                  - id: 4
                    name: Devoluções
                    slug: rma
                    color: red
                    icon: package-x
                    sla_hours: 48
                    steps:
                      - id: 13
                        name: Novo
                        slug: new
                        color: blue
                        position: 1
                        is_initial: true
                        is_final: false
                      - id: 14
                        name: Analisando
                        slug: analyzing
                        color: yellow
                        position: 2
                        is_initial: false
                        is_final: false
                      - id: 15
                        name: Aguardando
                        slug: waiting
                        color: gray
                        position: 3
                        is_initial: false
                        is_final: false
                      - id: 16
                        name: Resolvendo
                        slug: resolving
                        color: orange
                        position: 4
                        is_initial: false
                        is_final: false
                      - id: 17
                        name: Resolvida
                        slug: resolved
                        color: green
                        position: 5
                        is_initial: false
                        is_final: true
                      - id: 18
                        name: Cancelada
                        slug: cancelled
                        color: red
                        position: 6
                        is_initial: false
                        is_final: true
                    types_count: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences/matrix-counts":
    get:
      tags:
        - Ocorrências
      summary: Contagem por Matriz
      description: |-
        Mapa `{slug: total}` com a quantidade de ocorrências **abertas** da loja em cada matriz.
        
        Resultado **cacheado por 2 minutos** por loja (`occ_matrix_counts:store:{id}`). Matrizes sem ocorrências abertas são omitidas do mapa.
      security:
        - bearer: []
      responses:
        "200":
          description: Contagens obtidas
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  rma: 4
                  logistics: 2
                  orders: 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences/attention":
    get:
      tags:
        - Ocorrências
      summary: Itens em Atenção
      description: |-
        Top N matrizes com mais ocorrências abertas da loja, prontas para cards de destaque.
        
        Cada item retorna `accent` (cor derivada da maior severidade entre as ocorrências abertas daquela matriz: `critical→red`, `high→amber`, `medium→blue`, `low→purple`) e `href` (deep-link no portal). Ordenado por `count` decrescente.
      security:
        - bearer: []
      parameters:
        - name: limit
          in: query
          required: false
          description: Quantidade máxima de cards. Default `4`, intervalo `1..10`.
          schema:
            type: integer
            minimum: 1
            maximum: 10
            default: 4
      responses:
        "200":
          description: Atenção carregada
          content:
            "application/json":
              examples:
                tres_matrizes:
                  summary: Três matrizes com ocorrências abertas
                  value:
                    success: true
                    message_code: Atenção carregada
                    data:
                      - matrix_id: 4
                        title: Devoluções
                        subtitle: ocorrências abertas
                        count: 4
                        accent: red
                        href: /portal/ocorrencias?matrix=rma
                      - matrix_id: 2
                        title: Logística
                        subtitle: ocorrências abertas
                        count: 2
                        accent: amber
                        href: /portal/ocorrencias?matrix=logistics
                      - matrix_id: 1
                        title: Pedidos
                        subtitle: ocorrências abertas
                        count: 1
                        accent: blue
                        href: /portal/ocorrencias?matrix=orders
                vazio:
                  summary: Loja sem ocorrências abertas
                  value:
                    success: true
                    message_code: Atenção carregada
                    data: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
  "/api/seller/occurrences/enums":
    get:
      tags:
        - Enums de Ocorrências
      summary: Catálogo de Enums de Ocorrências
      description: "**Fonte única** dos tipos e valores de ocorrências. Cada chave de `data` é um enum, com `value` (o que a API aceita/devolve) e `label` (pt-BR). `severities` também traz `color` (token de destaque). Use para popular filtros e badges sem hardcodar listas — os valores só mudam em deploy."
      security:
        - bearer: []
      responses:
        "200":
          description: Catálogo de enums de ocorrências
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  origins:
                    - value: manual
                      label: Manual
                    - value: system
                      label: Sistema
                    - value: scheduled
                      label: Agendado
                  severities:
                    - value: low
                      label: Baixa
                      color: blue
                    - value: medium
                      label: Média
                      color: yellow
                    - value: high
                      label: Alta
                      color: orange
                    - value: critical
                      label: Crítica
                      color: red
                  statuses:
                    - value: open
                      label: Aberta
                    - value: resolved
                      label: Resolvida
                    - value: cancelled
                      label: Cancelada
                    - value: auto_closed
                      label: Encerrada Automaticamente
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/api/seller/occurrences/{id}":
    get:
      tags:
        - Ocorrências
      summary: Detalhes da Ocorrência
      description: |-
        Retorna a ocorrência completa, incluindo `relations` (com `display` hidratado pelo `OccurrenceRelationHydrator`) e `comments` (apenas `visibility=shared`, em ordem cronológica).
        
        **Side effect:** marca `last_store_read_at = now()` em cada chamada — esse timestamp alimenta o flag `has_unread_message` nas demais rotas. Chamar `/show` é o que zera a contagem de "não lidas" da loja.
        
        Retorna **403** quando `visible_to_store=false` ou quando não existe `OccurrenceRelation(entity_type="store", entity_id=<loja>)`.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da ocorrência (26 caracteres).
          schema:
            type: string
            minLength: 26
            maxLength: 26
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      responses:
        "200":
          description: Ocorrência obtida
          content:
            "application/json":
              examples:
                devolucao:
                  summary: Ocorrência da matriz `rma` (Devoluções)
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      id: 01K8PB0KMM36AY2P0NKSZBP2NA
                      title: Devolução solicitada para pedido ORD-000123
                      description: Cliente solicitou devolução por arrependimento (CDC art. 49). Pedido entregue em 2026-05-15.
                      severity: high
                      severity_label: Alta
                      severity_color: orange
                      status: open
                      status_label: Aberta
                      origin: system
                      origin_label: Sistema
                      close_reason: null
                      store_notified_at: "2026-05-18T09:12:35-03:00"
                      trigger_key: order.return_requested
                      trigger_ref: ORD-000123
                      metadata:
                        order_number: ORD-000123
                        reason_code: regret
                      matrix:
                        id: 4
                        name: Devoluções
                        slug: rma
                        color: red
                      type:
                        id: 11
                        name: Devolução padrão
                      step:
                        id: 14
                        name: Analisando
                        color: yellow
                        position: 2
                      assigned_to:
                        type: operator
                        id: 7
                        name: null
                      relations_count: 2
                      is_overdue: false
                      visible_to_store: true
                      due_at: "2026-05-22T18:00:00-03:00"
                      resolved_at: null
                      auto_closed_at: null
                      last_store_read_at: "2026-05-19T14:30:00-03:00"
                      relations:
                        - entity_type: store
                          entity_id: "42"
                          role: owner
                          display:
                            label: Minha Loja Premium
                            href: /portal/lojas/42
                        - entity_type: order
                          entity_id: ORD-000123
                          role: context
                          display:
                            label: Pedido ORD-000123
                            href: /portal/pedidos/ORD-000123
                      comments:
                        - id: 01K8PE7AVNQGS3XKR1ZJUPM2WD
                          author_type: system
                          author_id: null
                          content: Ocorrência criada automaticamente a partir do evento order.return_requested.
                          is_system: true
                          visibility: shared
                          created_at: "2026-05-18T09:12:33-03:00"
                        - id: 01K8PF1BCMR8H2W7TYNZQK4VJC
                          author_type: operator
                          author_id: "7"
                          content: Por favor, envie foto do produto recebido para análise.
                          is_system: false
                          visibility: shared
                          created_at: "2026-05-19T11:05:01-03:00"
                      created_at: "2026-05-18T09:12:33-03:00"
                      updated_at: "2026-05-19T14:30:00-03:00"
                logistica:
                  summary: Ocorrência da matriz `logistics`
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      id: 01K8PD9MZF3HRTNXA4SYPM6KQE
                      title: Coleta não realizada — transportadora não compareceu
                      description: Transportadora MeloEnvio agendou coleta para 2026-05-18 e não compareceu. Reagendar.
                      severity: medium
                      severity_label: Média
                      severity_color: yellow
                      status: open
                      status_label: Aberta
                      origin: system
                      origin_label: Sistema
                      close_reason: null
                      store_notified_at: "2026-05-19T08:00:01-03:00"
                      trigger_key: shipment.pickup_failed
                      trigger_ref: SHP-000456
                      metadata:
                        carrier: melhor_envio
                        shipment_id: SHP-000456
                      matrix:
                        id: 2
                        name: Logística
                        slug: logistics
                        color: purple
                      type:
                        id: 5
                        name: Coleta falhou
                      step:
                        id: 7
                        name: Novo
                        color: blue
                        position: 1
                      assigned_to: null
                      relations_count: 1
                      is_overdue: false
                      visible_to_store: true
                      due_at: null
                      resolved_at: null
                      auto_closed_at: null
                      last_store_read_at: "2026-05-19T14:31:12-03:00"
                      relations:
                        - entity_type: store
                          entity_id: "42"
                          role: owner
                          display:
                            label: Minha Loja Premium
                            href: /portal/lojas/42
                      comments: []
                      created_at: "2026-05-19T08:00:00-03:00"
                      updated_at: "2026-05-19T08:00:00-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
  "/api/seller/occurrences/{id}/comments":
    post:
      tags:
        - Comentários
      summary: Adicionar Comentário
      description: |-
        Adiciona um comentário do seller na ocorrência. O comentário é criado sempre com `author_type="store"`, `author_id=<store_id>` e `visibility="shared"` (visível ao time interno).
        
        As mesmas regras de acesso de `/{id}` se aplicam: `visible_to_store=true` E vínculo via `OccurrenceRelation(entity_type="store")`.
      security:
        - bearer: []
      parameters:
        - name: id
          in: path
          required: true
          description: ID da ocorrência (26 caracteres).
          schema:
            type: string
            minLength: 26
            maxLength: 26
            example: 01K8PB0KMM36AY2P0NKSZBP2NA
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - content
              properties:
                content:
                  type: string
                  maxLength: 2000
                  description: Texto do comentário (até 2000 caracteres).
                  example: Segue foto do produto recebido com avaria na lateral.
      responses:
        "200":
          description: Comentário criado
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K8PG3DEFM6Q4Z8XTBNR5HAKL
                  author_type: store
                  author_id: "42"
                  content: Segue foto do produto recebido com avaria na lateral.
                  is_system: false
                  visibility: shared
                  created_at: "2026-05-19T14:35:22-03:00"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
components:
  securitySchemes:
    integrationToken:
      type: http
      scheme: bearer
      description: "Token de Integração da loja (formato: sk_...). Criado em Configurações → Tokens de Integração no painel do seller."
  schemas:
    Product:
      type: object
      properties:
        id:
          type: string
          description: ID do produto (26 caracteres)
        name:
          type: string
        sku:
          type: string
        price_type:
          type: string
          enum:
            - default
            - wholesale
        base_unit:
          type: string
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        dimensions:
          type: object
          properties:
            width:
              type: number
              description: Largura em cm
            height:
              type: number
              description: Altura em cm
            length:
              type: number
              description: Comprimento em cm
            weight:
              type: number
              description: Peso em kg
        is_available_for_sale:
          type: boolean
          description: Indica se o produto está disponível para venda
          default: true
        is_industrializable:
          type: boolean
          description: Indica se o produto pode ser fabricado sob demanda
        auto_wholesale_pricing:
          type: boolean
          description: Indica se os preços das faixas de atacado são derivados automaticamente do percentual de desconto
        allow_sale_without_stock:
          type: boolean
          description: Permite venda sem estoque disponível (pedido sob encomenda)
        dimensions_formatted:
          type: object
          nullable: true
          description: Dimensões formatadas para exibição (somente se include_dimensions_formatted=true)
          properties:
            width:
              type: string
              nullable: true
              example: 10,00 cm
            height:
              type: string
              nullable: true
              example: 10,00 cm
            length:
              type: string
              nullable: true
              example: 10,00 cm
            weight:
              type: string
              nullable: true
              example: 500g
        quantity:
          type: number
          description: Quantidade total em estoque
        reserved_quantity:
          type: number
          description: Quantidade reservada
        available_quantity:
          type: number
          description: Quantidade disponível para venda (quantity - reserved_quantity)
        price:
          type: number
          description: Preço de venda (somente para price_type=default)
        min_price:
          type: number
          description: Menor preço entre as faixas (somente para price_type=wholesale)
        max_price:
          type: number
          description: Maior preço entre as faixas (somente para price_type=wholesale)
        thumbnail:
          type: object
          nullable: true
          description: Imagem de destaque do produto (variantes thumbnail e md)
          properties:
            id:
              type: string
              description: ID da imagem
            resources:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                    description: Nome da variante (thumbnail, sm, md, lg)
                  size:
                    type: string
                    description: "Dimensões da variante (ex: 400x400)"
                  url:
                    type: string
                    format: uri
    ProductInput:
      type: object
      required:
        - name
        - sku
        - base_unit
        - price_type
        - dimensions
      properties:
        name:
          type: string
          maxLength: 255
        sku:
          type: string
          maxLength: 50
          description: Deve ser único por loja
        base_unit:
          type: string
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        price_type:
          type: string
          enum:
            - default
            - wholesale
        is_available_for_sale:
          type: boolean
          default: true
        is_industrializable:
          type: boolean
          nullable: true
        allow_sale_without_stock:
          type: boolean
          nullable: true
          description: Permite venda sem estoque disponível (pedido sob encomenda)
        auto_wholesale_pricing:
          type: boolean
          description: Opcional. Deriva automaticamente os preços das faixas de atacado a partir do percentual de desconto
        dimensions:
          type: object
          required:
            - width
            - height
            - length
            - weight
          properties:
            width:
              type: number
              minimum: 0.001
              description: Largura em cm
            height:
              type: number
              minimum: 0.001
              description: Altura em cm
            length:
              type: number
              minimum: 0.001
              description: Comprimento em cm
            weight:
              type: number
              minimum: 0.001
              description: Peso em kg
    ProductUpdateInput:
      type: object
      description: Corpo da atualização de produto. Diferente da criação, o `sku` não é aceito (é ignorado se enviado) e apenas `name` e `dimensions` são obrigatórios.
      required:
        - name
        - dimensions
      properties:
        name:
          type: string
          maxLength: 255
        base_unit:
          type: string
          description: Opcional. Mantém o valor atual quando omitido
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        price_type:
          type: string
          description: Opcional. Veja as regras de troca na descrição do endpoint
          enum:
            - default
            - wholesale
        is_available_for_sale:
          type: boolean
          description: Opcional
        is_industrializable:
          type: boolean
          description: Opcional
        allow_sale_without_stock:
          type: boolean
          description: Opcional. Permite venda sem estoque disponível (pedido sob encomenda)
        auto_wholesale_pricing:
          type: boolean
          description: Opcional. Deriva automaticamente os preços das faixas de atacado a partir do percentual de desconto
        dimensions:
          type: object
          required:
            - width
            - height
            - length
            - weight
          properties:
            width:
              type: number
              minimum: 0.001
              description: Largura em cm
            height:
              type: number
              minimum: 0.001
              description: Altura em cm
            length:
              type: number
              minimum: 0.001
              description: Comprimento em cm
            weight:
              type: number
              minimum: 0.001
              description: Peso em kg
    Price:
      type: object
      properties:
        value:
          type: number
          description: Valor unitário da faixa
        min_quantity:
          type: integer
          description: Quantidade mínima para a faixa valer
        discount_percent:
          type: number
          nullable: true
          description: Percentual de desconto da faixa em relação ao preço-base (usado no atacado automático)
        package_weight:
          type: number
          nullable: true
          description: Peso da embalagem da faixa em kg
        package_height:
          type: number
          nullable: true
          description: Altura da embalagem da faixa em cm
        package_width:
          type: number
          nullable: true
          description: Largura da embalagem da faixa em cm
        package_length:
          type: number
          nullable: true
          description: Comprimento da embalagem da faixa em cm
    Stock:
      type: object
      properties:
        location_stock:
          type: object
          properties:
            location_id:
              type: string
              description: ID do local de estoque
            store_id:
              type: string
              description: ID da loja
            name:
              type: string
            status:
              type: string
              enum:
                - available
                - unavailable
            location_type:
              type: string
              enum:
                - seller_location
                - fulfillment
            allows_pickup:
              type: boolean
              description: Local permite retirada
            allows_production:
              type: boolean
              description: Local permite produção sob demanda
        quantity:
          type: number
        reserved_quantity:
          type: number
        available_quantity:
          type: number
    PaginationMeta:
      type: object
      properties:
        search_query:
          type: string
        filters:
          type: array
          items:
            type: string
        pagination:
          type: object
          properties:
            page:
              type: integer
            per_page:
              type: integer
            last_page:
              type: integer
            has_prev_page:
              type: boolean
            has_next_page:
              type: boolean
            records:
              type: object
              properties:
                from:
                  type: integer
                to:
                  type: integer
                records:
                  type: integer
    PriceInput:
      type: object
      properties:
        price:
          type: number
          nullable: true
          description: Valor monetário da faixa. Obrigatório na precificação manual (se omitido vira 0). Em produtos `wholesale` com `auto_wholesale_pricing` ligado é ignorado nas faixas `min_quantity >= 2` — o preço é calculado a partir do preço base e do `discount_percent`.
        min_quantity:
          type: integer
          default: 1
          description: "Quantidade mínima para ativar este preço (Padrão: 1). Produtos `default` só aceitam `min_quantity = 1`."
        discount_percent:
          type: number
          nullable: true
          minimum: 0
          description: Percentual de desconto sobre o preço base unitário (0 a menos de 100). Usado nas faixas de atacado automáticas; os descontos precisam ser progressivos (faixa de quantidade maior exige desconto maior).
          exclusiveMaximum: 100
        package_weight:
          type: number
          nullable: true
          minimum: 0.001
          description: Peso da embalagem fechada, em kg. Faz parte das medidas obrigatórias das faixas de atacado (`min_quantity >= 2`).
        package_height:
          type: number
          nullable: true
          minimum: 0.01
          description: Altura da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
        package_width:
          type: number
          nullable: true
          minimum: 0.01
          description: Largura da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
        package_length:
          type: number
          nullable: true
          minimum: 0.01
          description: Comprimento da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
    ProductStockOverview:
      type: object
      properties:
        total:
          type: integer
          description: Quantidade total de estoque somando todas as localizações.
          example: 150
        stocks:
          type: array
          description: Lista detalhada de estoque do produto em cada localização.
          items:
            "$ref": "#/components/schemas/ProductStockDetail"
        locations:
          type: array
          description: Lista de todas as localizações disponíveis para a loja.
          items:
            "$ref": "#/components/schemas/LocationStock"
    ProductStock:
      type: object
      properties:
        id:
          type: integer
          description: Identificador interno do registro de estoque.
          example: 321
        product_id:
          type: string
          description: ID do produto.
          example: 01H81AV32307PVBSV4RXF15EK9
        location_id:
          type: string
          description: ID da localização de estoque.
          example: 01H81AV32307PVBSV4RXF15LOC
        quantity:
          type: integer
          description: Quantidade atual em estoque.
          example: 50
        reserved_quantity:
          type: integer
          description: Quantidade reservada (não disponível para venda imediata).
          example: 0
        available_quantity:
          type: integer
          description: Quantidade disponível para venda (quantity - reserved_quantity).
          example: 50
    ProductStockDetail:
      type: object
      description: Registro de estoque do produto em uma localização, com o produto e o local aninhados.
      properties:
        id:
          type: integer
          description: Identificador interno do registro de estoque.
          example: 321
        product_sku:
          type: object
          description: Dados do produto (id, name, sku, base_unit, dimensions, flags de venda, prices, totais de estoque, thumbnail e images).
        location_stock:
          "$ref": "#/components/schemas/LocationStock"
        quantity:
          type: integer
          description: Quantidade física nesta localização.
          example: 100
        reserved_quantity:
          type: integer
          description: Quantidade reservada em carrinhos/pedidos abertos.
          example: 5
        available_quantity:
          type: integer
          description: Quantidade disponível para venda (quantity - reserved_quantity).
          example: 95
    LocationStock:
      type: object
      properties:
        location_id:
          type: string
          description: ID da localização.
          example: 01H81AV32307PVBSV4RXF15LOC
        store_id:
          type: integer
          description: Identificador interno da loja dona do local.
          example: 42
        name:
          type: string
          description: Nome da localização.
          example: Matriz - São Paulo
        status:
          type: string
          enum:
            - available
            - unavailable
          description: Status da localização.
          example: available
        location_type:
          type: string
          enum:
            - seller_location
            - fulfillment
          description: Tipo da localização.
          example: seller_location
        allows_pickup:
          type: boolean
          description: Indica se o local permite retirada em mãos.
          example: false
        allows_production:
          type: boolean
          description: Indica se o local permite produção sob demanda.
          example: false
    StockUpdateInput:
      type: object
      required:
        - location_id
        - quantity
      properties:
        location_id:
          type: string
          description: ID da localização onde o estoque será alterado.
          example: 01H81AV32307PVBSV4RXF15LOC
        quantity:
          type: integer
          minimum: 1
          description: Quantidade a ser definida, incrementada ou decrementada.
          example: 10
        increment:
          type: boolean
          description: Se verdadeiro, soma a quantidade ao estoque atual.
          default: false
          example: false
        decrement:
          type: boolean
          description: Se verdadeiro, subtrai a quantidade do estoque atual.
          default: false
          example: false
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          type: object
        meta:
          type: object
          description: Presente apenas quando há metadados a retornar.
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        code:
          type: integer
          example: 400
        message_code:
          type: string
          example: BAD_REQUEST
        description:
          type: string
          example: Erro na requisição.
        data:
          type: array
          items: {}
          example: []
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        meta:
          type: array
          items: {}
          example: []
    LocationStockInput:
      type: object
      required:
        - name
        - status
        - location_type
      properties:
        name:
          type: string
        status:
          type: string
          enum:
            - available
            - unavailable
          default: available
        location_type:
          type: string
          enum:
            - seller_location
          default: seller_location
        allows_pickup:
          type: boolean
          default: false
          description: Permite retirada no local
        allows_production:
          type: boolean
          default: false
          description: Permite producao sob demanda
        company_branch_uid:
          type: string
          nullable: true
          description: ID da filial a vincular ao local. Precisa ser uma filial ativa da empresa; omita para deixar o local respondendo pela matriz.
    Address:
      type: object
      properties:
        id:
          type: string
          description: ID do endereco
        uid:
          type: string
          description: Mesmo valor de id (mantido por compatibilidade)
        street:
          type: string
        number:
          type: string
        complement:
          type: string
          nullable: true
        reference:
          type: string
          nullable: true
          description: Ponto de referencia
        can_deliver_to_neighbor:
          type: boolean
          description: Se o entregador pode deixar com um vizinho
        delivery_instructions:
          type: string
          nullable: true
          description: Instrucoes de entrega
        district:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zip_code:
          type: string
          description: CEP formatado (00000-000)
        latitude:
          type: number
        longitude:
          type: number
        place_id:
          type: string
          nullable: true
          description: Identificador do lugar no provedor de geocodificacao
        address_components:
          nullable: true
          description: Componentes estruturados do endereco retornados pelo provedor de geocodificacao
        full_address:
          type: string
          description: Endereco completo em uma unica linha
        is_complete:
          type: boolean
          description: Indica se o endereco ja possui coordenadas resolvidas
        type:
          type: string
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: O tipo do endereco. Pelo menos um endereco do tipo 'logistics' e obrigatorio para o local de estoque operar.
        type_label:
          type: string
          nullable: true
          description: Rotulo legivel do tipo
    AddressInput:
      type: object
      required:
        - zip_code
        - street
        - number
        - district
        - city
        - state
        - type
      properties:
        zip_code:
          type: string
          description: CEP (apenas numeros ou com traco, formato 00000-000 ou 00000000)
        street:
          type: string
          minLength: 3
        number:
          type: string
          maxLength: 20
        complement:
          type: string
          nullable: true
          maxLength: 255
        reference:
          type: string
          nullable: true
          maxLength: 60
          description: Ponto de referencia
        can_deliver_to_neighbor:
          type: boolean
          nullable: true
          description: Se o entregador pode deixar com um vizinho
        delivery_instructions:
          type: string
          nullable: true
          maxLength: 150
          description: Instrucoes de entrega
        district:
          type: string
          minLength: 2
        city:
          type: string
          minLength: 2
        state:
          type: string
          minLength: 2
          maxLength: 2
        country:
          type: string
          minLength: 2
          maxLength: 2
          default: BR
        latitude:
          type: number
          nullable: true
          description: Se omitido, a plataforma tenta resolver as coordenadas pelo CEP ou por geocodificacao
        longitude:
          type: number
          nullable: true
        place_id:
          type: string
          nullable: true
        address_components:
          type: array
          nullable: true
          items: {}
        type:
          type: string
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: "Tipo de endereco para este local. Valores validos: logistics, local_real, local_devolucao"
    AddressUpdateInput:
      type: object
      description: Mesmos campos do cadastro, porem todos opcionais — envie apenas o que quer alterar.
      properties:
        zip_code:
          type: string
          description: CEP (apenas numeros ou com traco, formato 00000-000 ou 00000000)
        street:
          type: string
          minLength: 3
        number:
          type: string
          maxLength: 20
        complement:
          type: string
          nullable: true
          maxLength: 255
        reference:
          type: string
          nullable: true
          maxLength: 60
        can_deliver_to_neighbor:
          type: boolean
          nullable: true
        delivery_instructions:
          type: string
          nullable: true
          maxLength: 150
        district:
          type: string
          minLength: 2
        city:
          type: string
          minLength: 2
        state:
          type: string
          minLength: 2
          maxLength: 2
        country:
          type: string
          minLength: 2
          maxLength: 2
        latitude:
          type: number
          nullable: true
        longitude:
          type: number
          nullable: true
        place_id:
          type: string
          nullable: true
        address_components:
          type: array
          nullable: true
          items: {}
        type:
          type: string
          nullable: true
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: Novo tipo do vinculo do endereco com o local
    CategoriesResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: array
          items:
            "$ref": "#/components/schemas/Category"
      required:
        - success
        - message_code
        - data
    Category:
      type: object
      properties:
        id:
          type: integer
          description: ID da categoria
        name:
          type: string
        description:
          type:
            - string
            - "null"
        departament:
          "$ref": "#/components/schemas/Department"
        parent:
          "$ref": "#/components/schemas/ParentCategory"
        is_visible:
          type: boolean
        priority:
          type: integer
        domain:
          type: string
        level:
          type: integer
        hierarchy:
          type: array
          description: Breadcrumb completo da categoria (cada nível com id e nome)
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
        settings:
          type: array
          items: {}
        catalog_overrides:
          type:
            - object
            - "null"
        total_children:
          type: integer
        children:
          type: array
          description: Subcategorias (recursivo). Na busca, só voltam folhas — portanto vazio.
          items:
            "$ref": "#/components/schemas/Category"
      required:
        - id
        - name
        - departament
        - parent
    HierarchyLevel:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
      required:
        - id
        - name
    Department:
      type: object
      properties:
        id:
          type: integer
          description: ID do departamento
        name:
          type: string
        icon_svg:
          type:
            - string
            - "null"
      required:
        - id
        - name
    ParentCategory:
      type: object
      properties:
        id:
          type: integer
        parent_id:
          type:
            - integer
            - "null"
        name:
          type: string
        hierarchy:
          type: array
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
      required:
        - id
        - parent_id
        - name
    CategoryAttributesResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: array
          description: Com `matrix=true`, lista de grupos (`AttributeGroup`). Sem `matrix`, lista plana de atributos (`AttributeItem`).
          items:
            oneOf:
              - "$ref": "#/components/schemas/AttributeGroup"
              - "$ref": "#/components/schemas/AttributeItem"
      required:
        - success
        - message_code
        - data
    AttributeGroup:
      type: object
      properties:
        key:
          type:
            - string
            - "null"
          description: "Chave da matriz (ex: identification, appearance, technical). `null` para atributos sem matriz."
        label:
          type: string
        items:
          type: array
          items:
            "$ref": "#/components/schemas/AttributeItem"
      required:
        - key
        - label
        - items
    AttributeItem:
      type: object
      properties:
        id:
          type: integer
          description: ID do vínculo categoria-atributo
        attribute_id:
          type: integer
          description: ID do atributo
        category_id:
          type: integer
          description: ID da categoria
        is_required:
          type: boolean
          description: Indica se o atributo e obrigatorio para a categoria
        is_feature:
          type: boolean
          description: Indica se e uma caracteristica do produto
        is_variant:
          type: boolean
          description: Indica se e um atributo de variacao
        is_combinable:
          type: boolean
          description: Indica se pode ser usado em combinacoes para variacoes
        is_filter:
          type: boolean
          description: Indica se o atributo aparece como filtro na vitrine
        attribute_data:
          "$ref": "#/components/schemas/AttributeData"
      required:
        - id
        - attribute_id
        - category_id
        - is_required
        - is_feature
        - is_variant
        - is_combinable
        - is_filter
    AttributeData:
      type: object
      description: "Definição do atributo. O bloco `value_definition` é autodescritivo: traz os campos esperados (`fields`), as opções pré-cadastradas (`options`, quando houver) e as unidades disponíveis."
      properties:
        id:
          type: integer
          description: ID do atributo
        name:
          type: string
        type:
          type: string
          enum:
            - default
            - default_unit
            - default_list
            - dimension_3d
            - dimension_2d
            - color
            - image
            - brand
            - packaging
          description: Tipo estrutural do atributo. `default_unit` exige `unit`; `dimension_2d/3d` exigem width/height(/depth)+unit; `color` aceita opção pré-cadastrada ou cor customizada (value + hex).
        value_type:
          type:
            - string
            - "null"
          enum:
            - text
            - float
            - number
            - date
            - boolean
            - select
            - radio
            - checkbox
            - null
          description: Tipo do valor para atributos default. Tipos especializados (color, image, brand, packaging) ignoram este campo.
        is_feature:
          type: boolean
        is_variant:
          type: boolean
        is_calculable:
          type: boolean
          description: "Atributo usado em cálculos (ex: Quantidade, que precisa casar com a unidade base do SKU)"
        domain:
          type:
            - string
            - "null"
        ui:
          type: object
          properties:
            public_name:
              type:
                - string
                - "null"
            description:
              type:
                - string
                - "null"
            matrix:
              type:
                - string
                - "null"
              description: Matriz de organização (identification, appearance, technical, dimensions, ...)
            select_type:
              type: string
              enum:
                - select
                - radio
                - checkbox
                - text
              description: Como o valor é escolhido. `text` = digitação livre; os demais exigem opção pré-cadastrada (exceto atributos de cor, que aceitam cor customizada).
        value_definition:
          type: object
          properties:
            fields:
              type: object
              description: Schema dos campos esperados no valor (montagem dinâmica de formulário)
            options:
              type: array
              description: Opções pré-cadastradas (presente apenas quando o atributo tem opções)
              items:
                "$ref": "#/components/schemas/AttributeOption"
            default_unit:
              type:
                - string
                - "null"
            available_units:
              type: array
              items:
                type: string
    AttributeOption:
      type: object
      properties:
        id:
          type: integer
          description: ID da opção (use em `value_id` ao criar o anúncio)
        attribute_id:
          type: integer
        value:
          type: string
        component:
          type: object
          description: "Payload visual da opção, varia por tipo de atributo. Para cor: `{ value, main_color, hex, brightness }`."
          additionalProperties: true
        position:
          type: integer
        is_default:
          type: boolean
      required:
        - id
        - attribute_id
        - value
        - component
        - position
        - is_default
    AttributeCombinationsResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: object
          properties:
            combinations:
              type: array
              items:
                "$ref": "#/components/schemas/AttributeCombination"
            primary_attribute:
              "$ref": "#/components/schemas/PrimaryAttributeInfo"
          required:
            - combinations
            - primary_attribute
      required:
        - success
        - message_code
        - data
    AttributeCombination:
      type: object
      properties:
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/CombinationAttribute"
        attributes_details:
          type: object
          additionalProperties:
            type: string
        description:
          type: string
      required:
        - attributes
        - attributes_details
        - description
    CombinationAttribute:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type: string
        value:
          type: string
        unit:
          type: string
          description: Unidade de medida (presente apenas quando aplicável)
        hex:
          type: string
          description: Cor em hexadecimal (presente apenas para atributos de cor)
        main_color:
          type: string
          description: Cor principal (presente apenas para atributos de cor)
        brightness:
          type: string
          enum:
            - light
            - dark
          description: Luminosidade calculada da cor (presente apenas para atributos de cor)
        width:
          type:
            - string
            - number
          description: Largura (presente apenas para atributos de dimensão)
        height:
          type:
            - string
            - number
          description: Altura (presente apenas para atributos de dimensão)
      required:
        - attribute_id
        - attribute_name
        - value
    PrimaryAttributeInfo:
      type: object
      description: Atributo principal das combinações — facilita organizar fotos por variação no front.
      properties:
        id:
          type: integer
          description: ID do atributo principal
        name:
          type:
            - string
            - "null"
        has_options:
          type: boolean
        default_unit:
          type:
            - string
            - "null"
        values:
          type: array
          items:
            type: object
            properties:
              value:
                type: string
              label:
                type: string
              hex:
                type: string
              main_color:
                type: string
              brightness:
                type: string
            required:
              - value
              - label
      required:
        - id
        - name
        - has_options
        - default_unit
        - values
    CreateAdvertisementRequest:
      type: object
      description: É obrigatório informar `seller_sku` (anúncio sem variações) ou `variations` — um dos dois precisa estar presente. Os SKUs devem existir previamente na loja.
      properties:
        title:
          type: string
          maxLength: 100
          description: Titulo do anuncio
        category_id:
          type: integer
          description: ID da categoria selecionada (precisa ser categoria folha existente)
        gtin:
          "$ref": "#/components/schemas/GTIN"
        seller_sku:
          type:
            - string
            - "null"
          description: SKU do produto para anúncio sem variações. Obrigatório quando `variations` não for enviado. O SKU precisa existir na loja.
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/AdvertisementAttribute"
          description: Atributos de caracteristicas do produto. Obrigatório quando a categoria possui atributos com `is_required=true`.
        variations:
          type: array
          items:
            "$ref": "#/components/schemas/AdvertisementVariation"
          description: Variacoes do produto. Obrigatório quando `seller_sku` não for enviado.
        images:
          type: array
          items:
            "$ref": "#/components/schemas/ImageReference"
          description: Imagens do anuncio principal. Opcional na criação, mas o checklist exige pelo menos uma imagem para publicar.
        description:
          "$ref": "#/components/schemas/PublicationDescription"
          description: Descrição longa do anúncio. Opcional. Pode ser informada na criação ou depois via PUT /api/items/{id}/description.
        technical_sheets:
          type: array
          items:
            "$ref": "#/components/schemas/TechnicalSheetInput"
          description: Fichas técnicas do anúncio. Opcionais. Também podem ser criadas ou editadas depois via /api/items/{id}/technical-sheets.
      required:
        - title
        - category_id
    PublicationDescription:
      type: object
      description: Aceita um objeto `{ layout, raw_content }` ou uma string simples (legado, equivale a `layout=markdown`).
      properties:
        layout:
          type: string
          enum:
            - text
            - html
            - markdown
          description: "Formato do `raw_content`. Default: `markdown`."
        raw_content:
          type: string
          description: Conteúdo bruto da descrição no formato indicado por `layout`.
    TechnicalSheetInput:
      type: object
      required:
        - type
        - title
      properties:
        type:
          type: string
          enum:
            - technical_specification
            - characteristics
            - materials
            - installation
            - maintenance
            - safety
            - environmental
            - custom
          description: Tipo da ficha técnica.
        title:
          type: string
          maxLength: 255
          description: Título exibido para a seção da ficha.
        items:
          type: array
          items:
            type: object
            required:
              - item_key
              - item_value
            properties:
              item_key:
                type: string
                maxLength: 255
              item_value:
                type: string
                maxLength: 1000
              display_order:
                type: integer
                minimum: 0
    GTIN:
      type: object
      properties:
        type:
          type:
            - integer
            - string
          enum:
            - 8
            - 12
            - 13
            - 14
            - "8"
            - "12"
            - "13"
            - "14"
          description: "Tipo de GTIN: 8, 12, 13 ou 14 dígitos (13 para EAN-13). Nas respostas, vem como string."
        value:
          type: string
          description: Numero do codigo de barras
    AdvertisementAttribute:
      type: object
      description: "Valor de um atributo do anúncio. Campos extras dependem do tipo do atributo: cor customizada usa `hex`/`main_color`/`brightness`; dimensões usam `width`/`height`/`depth` + `unit`; listas usam `items`."
      properties:
        id:
          type: integer
          description: ID do atributo
        value_id:
          type:
            - integer
            - "null"
          description: ID da opção selecionada (para atributos com opcoes pré-cadastradas)
        option_id:
          type:
            - integer
            - "null"
          description: Alias aceito para `value_id`
        value:
          type:
            - string
            - integer
            - "null"
          description: Valor do atributo
        unit:
          type:
            - string
            - "null"
          description: Unidade de medida (obrigatória em atributos do tipo default_unit; precisa estar em available_units)
        hex:
          type:
            - string
            - "null"
          description: "Cor customizada em hexadecimal (#RGB ou #RRGGBB). Para atributos de cor, `value` (nome da cor) + `hex` válido dispensam opção pré-cadastrada."
        main_color:
          type:
            - string
            - "null"
          description: "Cor principal da cor customizada (ex: Vermelho)"
        brightness:
          type:
            - string
            - "null"
          description: Luminosidade da cor (light/dark). Se omitido, é calculada a partir do hex.
        width:
          type:
            - string
            - number
            - "null"
          description: Largura (atributos dimension_2d/dimension_3d)
        height:
          type:
            - string
            - number
            - "null"
          description: Altura (atributos dimension_2d/dimension_3d)
        depth:
          type:
            - string
            - number
            - "null"
          description: Profundidade (atributos dimension_3d)
        fields:
          type:
            - object
            - "null"
          description: "Forma alternativa de enviar dimensões: `{ width, height, depth, unit }`"
          properties:
            width: {}
            height: {}
            depth: {}
            unit:
              type: string
        items:
          type:
            - array
            - "null"
          description: Itens do atributo (obrigatório em atributos do tipo default_list)
    AdvertisementVariation:
      type: object
      properties:
        seller_sku:
          type: string
          description: SKU do produto para esta variacao (precisa existir na loja)
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/VariationAttribute"
          description: Atributos que definem esta variacao
        images:
          type: array
          items:
            "$ref": "#/components/schemas/ImageReference"
          description: Imagens especificas desta variacao
      required:
        - seller_sku
    VariationAttribute:
      type: object
      description: Informe `attribute_id` e `value` (ou `value_id` para opção pré-cadastrada). Atributos de cor aceitam cor customizada com `value` + `hex` (+ `main_color`/`brightness` opcionais), sem opção pré-cadastrada.
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type:
            - string
            - "null"
        value:
          type:
            - string
            - integer
            - "null"
        unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
          description: ID da opção pré-cadastrada
        option_id:
          type:
            - integer
            - "null"
          description: Alias aceito para `value_id`
        hex:
          type:
            - string
            - "null"
          description: "Cor customizada em hexadecimal (#RGB ou #RRGGBB)"
        main_color:
          type:
            - string
            - "null"
        brightness:
          type:
            - string
            - "null"
        width:
          type:
            - string
            - number
            - "null"
        height:
          type:
            - string
            - number
            - "null"
        depth:
          type:
            - string
            - number
            - "null"
        fields:
          type:
            - object
            - "null"
          description: "Forma alternativa de enviar dimensões: `{ width, height, depth, unit }`"
    ImageReference:
      type: object
      properties:
        id:
          type: string
          description: ID da imagem (UUID retornado por POST /api/media/upload)
      required:
        - id
    SimulatedAdvertisementResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/CreatedAdvertisementData"
      required:
        - success
        - message_code
        - data
    CreatedAdvertisementResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/CreatedAdvertisementData"
      required:
        - success
        - message_code
        - data
    CreatedAdvertisementData:
      type: object
      description: Objeto do anúncio, retornado por create, publish e simulate.
      properties:
        item_id:
          type: string
          description: "ID do anúncio: prefixo da plataforma + 13 dígitos (ex: SAM-0000000000007)"
        title:
          type: string
        short_description:
          type:
            - string
            - "null"
        slug:
          type:
            - string
            - "null"
        type:
          allOf:
            - "$ref": "#/components/schemas/TypeValue"
          description: "Tipo do anúncio: `default` (Padrão) ou `catalog` (Catálogo)"
        status:
          allOf:
            - "$ref": "#/components/schemas/StatusValue"
          description: "Status do anúncio: `draft`, `pending_review`, `active`, `paused` ou `inactive`"
        seller_sku:
          type:
            - string
            - "null"
          description: SKU vinculado diretamente ao anúncio (quando não há variações)
        price_range:
          type:
            - array
            - "null"
          description: Faixa de preço calculada a partir das ofertas (pode ser null em rascunhos)
        score:
          type:
            - integer
            - "null"
          description: Score de qualidade do anúncio
        campaigns:
          type: array
        pricing_context:
          type: array
          description: Contexto de precificação por faixa de quantidade (descontos, campanhas)
        frontend_sync_status:
          type: string
          description: "Status de sincronização com a vitrine (ex: pending)"
        last_frontend_sync_at:
          type:
            - string
            - "null"
          description: Formato Y-m-d H:i:s
        submitted_for_review_at:
          type:
            - string
            - "null"
          description: Quando o anúncio foi enviado para revisão. Formato Y-m-d H:i:s
        reviewed_at:
          type:
            - string
            - "null"
          description: Quando a plataforma revisou o anúncio. Formato Y-m-d H:i:s
        rejection_reason:
          type:
            - string
            - "null"
          description: Motivo da rejeição quando o anúncio volta para rascunho
        gtin:
          allOf:
            - "$ref": "#/components/schemas/GTIN"
          description: Presente apenas quando informado
        store:
          allOf:
            - "$ref": "#/components/schemas/Store"
          description: Loja dona do anúncio (presente quando carregada)
        department:
          "$ref": "#/components/schemas/Department"
        category:
          "$ref": "#/components/schemas/CategoryRef"
        thumbnail:
          type:
            - object
            - "null"
          description: "Miniatura no tamanho `sm`: `{ id, resource }`"
        images:
          type: array
          items:
            "$ref": "#/components/schemas/Image"
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/PublicationAttributeOut"
        variations:
          type: array
          items:
            "$ref": "#/components/schemas/PublicationVariationOut"
      required:
        - item_id
        - title
        - type
        - status
        - department
        - category
        - images
        - attributes
        - variations
    Image:
      type: object
      properties:
        id:
          type: string
          description: ID da imagem (UUID)
        resources:
          type: array
          description: Variações de tamanho da imagem (`{ name, url, ... }`)
          items:
            type: object
      required:
        - id
        - resources
    PublicationAttributeOut:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type:
            - string
            - "null"
        attribute_value:
          type:
            - string
            - integer
            - "null"
        value_unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
        component:
          type:
            - object
            - "null"
          description: "Payload visual do valor. Para cor: `{ value, main_color, hex, brightness }`."
      required:
        - attribute_id
        - attribute_name
        - attribute_value
        - value_unit
        - value_id
        - component
    PublicationVariationOut:
      type: object
      description: Variação do anúncio. Em anúncios padrão o identificador vem como `offer_id`; em anúncios de catálogo, como `option_id`.
      properties:
        offer_id:
          type: string
          description: ID da variação (26 caracteres). Presente em anúncios padrão.
        option_id:
          type: string
          description: ID da opção de catálogo (26 caracteres). Presente em anúncios de catálogo.
        description:
          type: string
          description: "Descrição legível da combinação (ex: Voltagem: 110 V, Cor: Azul)"
        attributes:
          type: array
          description: Atributos da variação (`{ attribute_id, attribute_name, value, unit?, value_id?, hex?, main_color?, brightness? }`)
          items:
            type: object
        status:
          type: string
          enum:
            - active
            - pending
            - hidden
          description: Status da variação
        type:
          type: string
          enum:
            - offer
            - option
        images:
          type: array
          items:
            "$ref": "#/components/schemas/Image"
        seller_sku:
          type:
            - string
            - "null"
          description: Presente quando `type=offer`
        offer:
          "$ref": "#/components/schemas/OfferData"
      required:
        - description
        - attributes
        - status
        - type
        - images
        - offer
    OfferData:
      type: object
      properties:
        price_type:
          type: string
          description: "Tipo de precificação (ex: default, wholesale)"
        price_from:
          type: number
          description: Preço "De" quando há desconto
        prices:
          type: array
          description: Faixas de preço (atacado)
          items:
            type: object
        quantity:
          type: integer
        price:
          type: number
          description: Preço "Por"
        original_price:
          type:
            - number
            - "null"
        has_discount:
          type: boolean
        discount_percent:
          type: integer
        wholesale_tiers:
          type: array
          description: Presente apenas quando há faixas de atacado
          items:
            type: object
        buybox:
          type: object
          description: Dados da buybox (presente apenas em anúncios de catálogo)
      required:
        - price_type
        - price_from
        - prices
        - quantity
        - price
        - original_price
        - has_discount
        - discount_percent
    TypeValue:
      type: object
      properties:
        value:
          type: string
          enum:
            - default
            - catalog
        label:
          type: string
      required:
        - value
        - label
    StatusValue:
      type: object
      properties:
        value:
          type: string
          enum:
            - draft
            - pending_review
            - active
            - paused
            - inactive
        label:
          type: string
      required:
        - value
        - label
    Store:
      type: object
      properties:
        store_id:
          type: string
          description: ID da loja (26 caracteres)
        store_name:
          type: string
        store_code:
          type: string
        store_status:
          type: string
        store_url:
          type:
            - string
            - "null"
        store_type:
          type: string
        commission_table_id:
          type:
            - integer
            - string
            - "null"
          description: ID da tabela de comissão
        max_locations:
          type:
            - integer
            - "null"
        features:
          type: array
        company:
          type: object
          description: Dados cadastrais da empresa
        shares_products:
          type: boolean
        shares_stock_locations:
          type: boolean
        shares_logistics:
          type: boolean
        is_primary:
          type: boolean
        primary_store:
          type:
            - object
            - "null"
      required:
        - store_id
        - store_name
        - store_code
    CategoryRef:
      type: object
      properties:
        id:
          type: integer
          description: ID da categoria
        parent_id:
          type:
            - integer
            - "null"
        name:
          type: string
        hierarchy:
          type: array
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
      required:
        - id
        - parent_id
        - name
    AttachSKURequest:
      type: object
      properties:
        seller_sku:
          type: string
          maxLength: 64
          description: SKU do produto a vincular. Precisa pertencer (ou estar associado) à loja autenticada.
        status:
          type: string
          description: "Status inicial da oferta. Default: `active`."
          enum:
            - active
            - paused
            - inactive
      required:
        - seller_sku
    AttachedSKUResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/AttachedSKUData"
      required:
        - success
        - message_code
        - data
    AttachedSKUData:
      type: object
      properties:
        offer_id:
          type: string
          description: ID da oferta criada (26 caracteres). Use em PUT/DELETE /api/items/catalog/offers/{offerId}.
        seller_sku:
          type: string
        status:
          type: string
          enum:
            - active
            - paused
            - inactive
            - pending
        is_buybox_winner:
          type: boolean
          description: Recalculado em background após o vínculo — costuma vir `false` na resposta imediata
        last_buybox_calculation:
          type:
            - string
            - "null"
        price_data:
          "$ref": "#/components/schemas/PriceData"
        stock_data:
          "$ref": "#/components/schemas/StockData"
        publication:
          type:
            - object
            - "null"
          description: Resumo do anúncio de catálogo
          properties:
            item_id:
              type: string
              description: ID do anúncio
            title:
              type: string
            status:
              type: string
            type:
              type: string
            thumbnail_id:
              type:
                - string
                - "null"
              description: ID da imagem de capa (UUID)
        option:
          "$ref": "#/components/schemas/OptionData"
      required:
        - offer_id
        - seller_sku
        - status
        - is_buybox_winner
        - last_buybox_calculation
        - price_data
        - stock_data
        - publication
        - option
    PriceData:
      type: object
      description: "Snapshot de preço da oferta. Preço único: `{ price }`. Atacado: `{ type: \"wholesale\", tiers: [...] }`."
      properties:
        price:
          type: number
          description: Preço único (presente quando não há faixas de atacado)
        type:
          type: string
          enum:
            - wholesale
          description: Presente apenas quando há faixas de atacado
        tiers:
          type: array
          description: Faixas de preço por quantidade (presente apenas no atacado)
          items:
            "$ref": "#/components/schemas/PriceTier"
    PriceTier:
      type: object
      properties:
        min_quantity:
          type: integer
        price:
          type: number
      required:
        - min_quantity
        - price
    StockData:
      type: object
      properties:
        id:
          type: integer
          description: ID interno do registro de estoque
        product_id:
          type: string
          description: ID do SKU do produto (26 caracteres)
        location_id:
          type: string
          description: ID do local de estoque (26 caracteres)
        quantity:
          type: integer
        reserved_quantity:
          type: integer
        available_quantity:
          type: integer
      required:
        - id
        - product_id
        - location_id
        - quantity
        - reserved_quantity
        - available_quantity
    OptionData:
      type: object
      properties:
        uid:
          type: string
          description: ID da opção de catálogo (26 caracteres)
        description:
          type: string
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/OptionAttribute"
        type:
          type: string
          enum:
            - option
            - offer
        status:
          type: string
          enum:
            - active
            - pending
            - hidden
        images:
          type: array
          items:
            type: object
      required:
        - uid
        - description
        - attributes
        - type
        - status
        - images
    OptionAttribute:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type: string
        value:
          type: string
        unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
      required:
        - attribute_id
        - attribute_name
        - value
    TechnicalSheet:
      type: object
      description: "Ficha técnica de um anúncio: lista ordenada de pares chave/valor agrupada por tipo (especificações, materiais, segurança etc.)."
      properties:
        sheet_id:
          type: string
          description: ID da ficha técnica (26 caracteres)
        publication_id:
          type: string
          description: "`item_id` do anúncio dono da ficha"
        type:
          type: object
          description: Tipo da ficha técnica
          properties:
            value:
              type: string
              enum:
                - technical_specification
                - characteristics
                - materials
                - installation
                - maintenance
                - safety
                - environmental
                - custom
            label:
              type: string
              description: "Rótulo amigável do tipo (ex.: Ficha Técnica, Materiais)"
            description:
              type: string
          required:
            - value
            - label
            - description
        title:
          type: string
          maxLength: 255
        items:
          type: array
          items:
            "$ref": "#/components/schemas/TechnicalSheetItem"
      required:
        - sheet_id
        - publication_id
        - type
        - title
        - items
    TechnicalSheetItem:
      type: object
      description: Linha da ficha técnica (par chave/valor).
      properties:
        item_id:
          type: string
          description: ID do item da ficha (26 caracteres)
        item_key:
          type: string
          maxLength: 255
          description: "Nome da característica (ex.: Potência)"
        item_value:
          type: string
          maxLength: 1000
          description: "Valor da característica (ex.: 650 W)"
        display_order:
          type: integer
          minimum: 0
          description: Posição de exibição (0 = primeiro)
      required:
        - item_id
        - item_key
        - item_value
        - display_order
    MediaCreatedResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: RESOURCE_CREATED
        data:
          "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaItemResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaCollectionResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          type: object
          properties:
            meta:
              "$ref": "#/components/schemas/PaginationMeta"
            data:
              type: array
              items:
                "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaItem:
      type: object
      description: Objeto que representa uma imagem/mídia na API de seller. Campos internos de propriedade (`owner_id`, `owner_model`) ficam de fora do payload de propósito — você não precisa deles para integrar, então não vai encontrá-los aqui.
      properties:
        id:
          type: string
          description: Identificador único da imagem (26 caracteres). É o valor usado como `{file_hash}` nas rotas de detalhe e exclusão.
          example: 01JMZXZ72881MD2P37N2K97YJJ
        original_source:
          type: string
          format: uri
          description: URL da imagem original, já convertida para WebP em alta qualidade.
          example: https://cdn.example.com/products/2026/05/28/original_01JMZXZ72881MD2P37N2K97YJJ.webp
        urls:
          type: array
          description: "Variações de tamanho geradas automaticamente (todas em WebP). Uma entrada por variação: `thumbnail` (135x135), `sm` (200x200), `md` (400x400), `lg` (800x800) e `xl` (1600x1600)."
          items:
            type: object
            properties:
              name:
                type: string
                enum:
                  - thumbnail
                  - sm
                  - md
                  - lg
                  - xl
                description: Nome da variação de tamanho.
                example: thumbnail
              size:
                type: string
                description: Dimensões máximas da variação, no formato largura x altura.
                example: 135x135
              url:
                type: string
                format: uri
                description: URL pública da variação.
                example: https://cdn.example.com/products/2026/05/28/thumbnail_01JMZXZ72881MD2P37N2K97YJJ.webp
            required:
              - name
              - size
              - url
        in_use:
          type: boolean
          description: Indica se a imagem está associada a algum recurso.
          example: false
        reference_id:
          type: string
          nullable: true
          description: ID do recurso referenciado (se houver).
        reference_model:
          type: string
          nullable: true
          enum:
            - product
            - publication
            - store
          description: Tipo do recurso vinculado, em formato curto. Use o mesmo valor no filtro `filter[reference_model]=...`.
        created_at:
          type: string
          format: date-time
          description: Data de criação da imagem.
          example: "2026-05-28T10:00:00-03:00"
        updated_at:
          type: string
          format: date-time
          description: Data da última atualização da imagem.
          example: "2026-05-28T10:00:00-03:00"
      required:
        - id
        - original_source
        - urls
        - in_use
        - reference_id
        - reference_model
        - created_at
        - updated_at
    MediaCloneFromSkusResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: OK
        data:
          type: object
          properties:
            results:
              type: object
              description: Mapa key → lista de mídias clonadas. As keys são exatamente as enviadas nos targets.
              additionalProperties:
                type: array
                items:
                  "$ref": "#/components/schemas/ClonedMediaItem"
            ignored_skus:
              type: array
              description: SKUs que não pertencem (ou não estão associados) à loja autenticada. Não geram erro — as keys correspondentes voltam vazias.
              items:
                type: string
              example:
                - CAMISETA-VERM-M
          required:
            - results
            - ignored_skus
      required:
        - success
        - data
    ClonedMediaItem:
      type: object
      description: Mídia recém-clonada a partir de uma foto de produto. Nasce sem vínculo (`in_use=false`) — vincule ao anúncio normalmente via `images[].id`; se não for usada, a limpeza diária remove a cópia após ~72 horas.
      properties:
        id:
          type: string
          description: ID da nova mídia (26 caracteres). Use este valor nas `images` do anúncio/variação.
          example: 01JNB2T4G93QRT5V6W7X8Y9Z0A
        source_image_id:
          type: string
          description: ID da imagem de origem (foto da galeria do produto). Útil para rastrear de onde a cópia veio e evitar duplicar sugestões.
          example: 01JMZXZ72881MD2P37N2K97YJJ
        original_source:
          type: string
          format: uri
          description: URL da cópia em alta qualidade (WebP).
          example: https://cdn.example.com/products/2026/06/11/original_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
        urls:
          type: array
          description: "Variações de tamanho copiadas da imagem de origem (mesmos nomes do upload: `thumbnail`, `sm`, `md`, `lg`, `xl`)."
          items:
            type: object
            properties:
              name:
                type: string
                example: sm
              size:
                type: string
                nullable: true
                example: 200x200
              url:
                type: string
                format: uri
                example: https://cdn.example.com/products/2026/06/11/sm_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
            required:
              - name
              - url
      required:
        - id
        - source_image_id
        - original_source
        - urls
  responses:
    Unauthorized:
      description: Token não fornecido, inválido ou expirado
      content:
        "application/json":
          example:
            success: false
            code: 401
            message_code: UNAUTHORIZED
            description: A autenticação é necessária e falhou ou não foi fornecida.
            data: []
            errors:
              - O access_token da loja é obrigatório no cabeçalho Authorization
            meta: []
    Forbidden:
      description: Sem permissão para acessar este recurso
      content:
        "application/json":
          example:
            success: false
            code: 403
            message_code: FORBIDDEN
            description: Você não tem permissão para acessar este recurso.
            data: []
            errors:
              - "Você não tem permissão para acessar este recurso (escopos ausentes: store_products_write)"
            meta: []
    NotFound:
      description: Recurso não encontrado
      content:
        "application/json":
          example:
            success: false
            code: 404
            message_code: NOT_FOUND
            description: O recurso solicitado não foi encontrado.
            data: []
            errors:
              - Produto não encontrado.
            meta: []
    Duplicated:
      description: "Recurso duplicado (ex: SKU já existe nesta loja)"
      content:
        "application/json":
          example:
            success: false
            code: 409
            message_code: DUPLICATED
            description: Este SKU já está cadastrado para esta loja.
            data: []
            errors:
              - Este SKU já está cadastrado para esta loja.
            meta: []
    ValidationError:
      description: Erro de validação nos campos enviados
      content:
        "application/json":
          example:
            success: false
            code: 422
            message_code: VALIDATION_ERROR
            description: Foram encontrados erros de validação na requisição.
            data: []
            errors:
              sku:
                - O campo SKU é obrigatório.
              name:
                - O campo Nome é obrigatório.
              "dimensions.height":
                - O campo dimensions.height é obrigatório.
            meta: []
    Conflict:
      description: Endereco identico ja vinculado ao local de estoque
      content:
        "application/json":
          example:
            success: false
            message_code: CONFLICT
            data:
              message: Já existe um endereço idêntico vinculado ao local de estoque.
security:
  - integrationToken: []
x-tagGroups:
  - name: Produtos
    tags:
      - Produtos
      - Dados Auxiliares
      - Galeria de Imagens
  - name: Preços
    tags:
      - Preços de Produtos
  - name: Estoque
    tags:
      - Estoque de Produtos
      - Locais de Estoque
      - Enderecos de Estoque
  - name: Anúncios
    tags:
      - Categorias
      - Publicacao
  - name: Media
    tags:
      - Media
  - name: Minha Loja
    tags:
      - Sessão
      - Página da Loja
      - Avaliação
  - name: Pedidos
    tags:
      - Pedidos
      - Fiscal
      - Etiquetas
  - name: Devoluções
    tags:
      - Devoluções
  - name: Comunicação
    tags:
      - Central de Atendimento
      - Perguntas e Respostas
  - name: Ocorrências
    tags:
      - Ocorrências
      - Comentários
