openapi: 3.0.3
info:
  title: Nuvartis API — superfície estável
  version: 1.0.1
  description: |
    Contrato OpenAPI **v1** da superfície **estável** para integradores e o desk.

    **Portal:** https://nuvartis.com/documentacao (Swagger UI + download do YAML).

    ### No v1
    - **Auth** — merchant · platform · customer (storefront)
    - **Catálogo** — produtos merchant e vitrine
    - **Pedidos** — merchant e conta do comprador
    - **Outbound webhooks** — Negócio+ · HMAC `X-Nuvartis-Signature`

    ### Fora do v1 (não inventar no cliente)
    Marketplaces, ERP Bling/Tiny, frete (ME/Frenet/Shipsmart), CMS, IA, Aparência,
    e o restante do desk merchant. Sem `/v2` neste contrato.

    ### Convenções
    JSON `{ data, meta? }` · dinheiro em **cents** · timestamps **UTC** ·
    multi-tenant rígido (nunca `store_id` do body para escopo) ·
    merchant: Bearer + `X-Store-Id` · vitrine: Host / `X-Store-Host`.

    ### Começar
    No portal: baixe o YAML, o curl (`/docs/curl-getting-started.md`) e o sample
    outbound (`/docs/outbound-partner-delivery.json`).
    No repositório: `docs/api/examples/` · contrato outbound
    `docs/prd/outbound-webhooks-contract.md`.
  contact:
    name: Nuvartis
    url: https://nuvartis.com
  externalDocs:
    description: Portal Documentação (Swagger UI)
    url: https://nuvartis.com/documentacao

servers:
  - url: https://api.nuvartis.com/api
    description: Produção
  - url: http://api.nuvartis.localhost:8094/api
    description: Dev local (Caddy)

tags:
  - name: Auth Merchant
  - name: Auth Platform
  - name: Auth Customer
  - name: Catalog Merchant
  - name: Catalog Storefront
  - name: Orders Merchant
  - name: Orders Customer
  - name: Outbound Webhooks
  - name: Outbound Delivery (partner)

paths:
  /merchant/auth/login:
    post:
      tags: [Auth Merchant]
      summary: Login lojista
      operationId: merchantAuthLogin
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Token Sanctum merchant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantLoginResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /merchant/auth/register:
    post:
      tags: [Auth Merchant]
      summary: Cadastro lojista
      operationId: merchantAuthRegister
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password]
              properties:
                name: { type: string, maxLength: 120 }
                email: { type: string, format: email }
                password: { type: string, minLength: 8, maxLength: 72 }
      responses:
        '200':
          description: Token + usuário
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantLoginResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /merchant/auth/forgot-password:
    post:
      tags: [Auth Merchant]
      summary: Pedir reset de senha (resposta genérica)
      operationId: merchantAuthForgot
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailOnlyRequest'
      responses:
        '200':
          description: Sempre genérico (anti-enumeração)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'

  /merchant/auth/reset-password:
    post:
      tags: [Auth Merchant]
      summary: Redefinir senha com token
      operationId: merchantAuthReset
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '200':
          description: Senha atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'
        '422':
          $ref: '#/components/responses/ValidationError'

  /merchant/auth/logout:
    post:
      tags: [Auth Merchant]
      summary: Revogar token atual
      operationId: merchantAuthLogout
      security: [{ MerchantBearer: [] }]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }

  /merchant/auth/me:
    get:
      tags: [Auth Merchant]
      summary: Usuário + lojas do membership
      operationId: merchantAuthMe
      security: [{ MerchantBearer: [] }]
      responses:
        '200':
          description: Perfil merchant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantMeResponse'

  /platform/auth/login:
    post:
      tags: [Auth Platform]
      summary: Login super-admin
      operationId: platformAuthLogin
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Token platform
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformLoginResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /platform/auth/forgot-password:
    post:
      tags: [Auth Platform]
      summary: Pedir reset platform (resposta genérica)
      operationId: platformAuthForgot
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailOnlyRequest'
      responses:
        '200':
          description: Sempre genérico
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'

  /platform/auth/reset-password:
    post:
      tags: [Auth Platform]
      summary: Redefinir senha platform
      operationId: platformAuthReset
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '200':
          description: Senha atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'

  /platform/auth/me:
    get:
      tags: [Auth Platform]
      summary: Perfil platform
      operationId: platformAuthMe
      security: [{ PlatformBearer: [] }]
      responses:
        '200':
          description: Usuário platform
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformUser'

  /storefront/auth/login:
    post:
      tags: [Auth Customer]
      summary: Login comprador (por loja)
      operationId: storefrontAuthLogin
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Token customer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerLoginResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /storefront/auth/register:
    post:
      tags: [Auth Customer]
      summary: Cadastro comprador na loja
      operationId: storefrontAuthRegister
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                name: { type: string }
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
      responses:
        '200':
          description: Token customer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerLoginResponse'

  /storefront/auth/forgot-password:
    post:
      tags: [Auth Customer]
      summary: Reset senha comprador (por loja)
      operationId: storefrontAuthForgot
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailOnlyRequest'
      responses:
        '200':
          description: Sempre genérico
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'

  /storefront/auth/reset-password:
    post:
      tags: [Auth Customer]
      summary: Aplicar token reset comprador
      operationId: storefrontAuthReset
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '200':
          description: Senha atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenericOkMessage'

  /storefront/auth/me:
    get:
      tags: [Auth Customer]
      summary: Perfil comprador autenticado
      operationId: storefrontAuthMe
      security: [{ CustomerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      responses:
        '200':
          description: Customer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'

  /merchant/products:
    get:
      tags: [Catalog Merchant]
      summary: Listar produtos da loja
      operationId: merchantProductsIndex
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: q
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [draft, active, archived] }
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 30 }
      responses:
        '200':
          description: Página de produtos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'
    post:
      tags: [Catalog Merchant]
      summary: Criar produto (rascunho)
      operationId: merchantProductsStore
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductWrite'
      responses:
        '201':
          description: Produto criado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Product'
        '422':
          $ref: '#/components/responses/ValidationError'

  /merchant/products/{product}:
    parameters:
      - $ref: '#/components/parameters/StoreId'
      - name: product
        in: path
        required: true
        schema: { type: integer }
    get:
      tags: [Catalog Merchant]
      summary: Detalhe produto
      operationId: merchantProductsShow
      security: [{ MerchantBearer: [], StoreId: [] }]
      responses:
        '200':
          description: Produto
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Product'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Catalog Merchant]
      summary: Atualizar produto
      operationId: merchantProductsUpdate
      security: [{ MerchantBearer: [], StoreId: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductWrite'
      responses:
        '200':
          description: Produto atualizado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Product'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [Catalog Merchant]
      summary: Arquivar/excluir produto
      operationId: merchantProductsDestroy
      security: [{ MerchantBearer: [], StoreId: [] }]
      responses:
        '200':
          description: Removido
        '404':
          $ref: '#/components/responses/NotFound'

  /storefront/resolve:
    get:
      tags: [Catalog Storefront]
      summary: Resolver loja pelo host
      operationId: storefrontResolve
      security: []
      parameters:
        - name: host
          in: query
          schema: { type: string }
          description: Opcional; default = Host / X-Store-Host
      responses:
        '200':
          description: Contexto mínimo da loja
          content:
            application/json:
              schema:
                type: object
                properties:
                  slug: { type: string }
                  name: { type: string }
                  status: { type: string }

  /storefront/products:
    get:
      tags: [Catalog Storefront]
      summary: Catálogo público (SSR/Nuxt)
      operationId: storefrontProductsIndex
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
        - name: q
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 48 }
        - name: sort
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Produtos publicados
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'

  /storefront/products/{slug}:
    get:
      tags: [Catalog Storefront]
      summary: PDP por slug
      operationId: storefrontProductShow
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
        - name: slug
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Produto
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Product'
        '404':
          $ref: '#/components/responses/NotFound'

  /storefront/plans:
    get:
      tags: [Catalog Storefront]
      summary: Catálogo público de planos (marketing)
      operationId: storefrontPlansIndex
      security: []
      responses:
        '200':
          description: Planos is_public ordenados por sort_order
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PublicPlan'

  /storefront/categories:
    get:
      tags: [Catalog Storefront]
      summary: Categorias públicas
      operationId: storefrontCategories
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      responses:
        '200':
          description: Lista
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      additionalProperties: true

  /storefront/search:
    get:
      tags: [Catalog Storefront]
      summary: Busca pública
      operationId: storefrontSearch
      security: []
      parameters:
        - $ref: '#/components/parameters/StoreHost'
        - name: q
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Resultados
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /merchant/orders:
    get:
      tags: [Orders Merchant]
      summary: Listar pedidos da loja
      operationId: merchantOrdersIndex
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: status
          in: query
          schema: { type: string }
        - name: source
          in: query
          schema: { type: string }
        - name: channel
          in: query
          schema: { type: string }
        - name: q
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: per_page
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        '200':
          description: Página de pedidos
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderListResponse'
    post:
      tags: [Orders Merchant]
      summary: Criar pedido manual
      operationId: merchantOrdersStore
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '201':
          description: Pedido criado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Order'

  /merchant/orders/{order}:
    get:
      tags: [Orders Merchant]
      summary: Detalhe pedido
      operationId: merchantOrdersShow
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: order
          in: path
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Pedido
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Order'
        '404':
          $ref: '#/components/responses/NotFound'

  /merchant/orders/{order}/status:
    patch:
      tags: [Orders Merchant]
      summary: Atualizar status do pedido
      operationId: merchantOrdersUpdateStatus
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: order
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: { type: string }
      responses:
        '200':
          description: Pedido atualizado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Order'

  /storefront/account/orders:
    get:
      tags: [Orders Customer]
      summary: Pedidos do comprador autenticado
      operationId: customerOrdersIndex
      security: [{ CustomerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreHost'
      responses:
        '200':
          description: Lista
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'

  /storefront/account/orders/{number}:
    get:
      tags: [Orders Customer]
      summary: Detalhe pedido por número
      operationId: customerOrdersShow
      security: [{ CustomerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreHost'
        - name: number
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Pedido
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '404':
          $ref: '#/components/responses/NotFound'

  /merchant/outbound-webhooks:
    get:
      tags: [Outbound Webhooks]
      summary: Listar endpoints outbound (Negócio+)
      operationId: merchantOutboundIndex
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
      responses:
        '200':
          description: Endpoints + meta do contrato
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutboundWebhookListResponse'
    post:
      tags: [Outbound Webhooks]
      summary: Cadastrar endpoint
      operationId: merchantOutboundStore
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutboundWebhookWrite'
      responses:
        '201':
          description: Criado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/OutboundWebhook'
        '422':
          $ref: '#/components/responses/ValidationError'

  /merchant/outbound-webhooks/{id}:
    parameters:
      - $ref: '#/components/parameters/StoreId'
      - name: id
        in: path
        required: true
        schema: { type: integer }
    put:
      tags: [Outbound Webhooks]
      summary: Atualizar endpoint
      operationId: merchantOutboundUpdate
      security: [{ MerchantBearer: [], StoreId: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutboundWebhookWrite'
      responses:
        '200':
          description: Atualizado
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/OutboundWebhook'
    delete:
      tags: [Outbound Webhooks]
      summary: Remover endpoint
      operationId: merchantOutboundDestroy
      security: [{ MerchantBearer: [], StoreId: [] }]
      responses:
        '200':
          description: Removido

  /merchant/outbound-webhooks/{id}/ping:
    post:
      tags: [Outbound Webhooks]
      summary: Enviar evento ping de teste
      operationId: merchantOutboundPing
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Resultado do ping
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    additionalProperties: true

  /merchant/outbound-webhooks/deliveries:
    get:
      tags: [Outbound Webhooks]
      summary: Log de entregas
      operationId: merchantOutboundDeliveries
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: event
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: per_page
          in: query
          schema: { type: integer, default: 20 }
      responses:
        '200':
          description: Entregas paginadas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutboundDeliveryListResponse'

  /merchant/outbound-webhooks/deliveries/{deliveryId}/redeliver:
    post:
      tags: [Outbound Webhooks]
      summary: Reenviar delivery (mesmo event_id)
      operationId: merchantOutboundRedeliver
      security: [{ MerchantBearer: [], StoreId: [] }]
      parameters:
        - $ref: '#/components/parameters/StoreId'
        - name: deliveryId
          in: path
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Reenfileirado

components:
  securitySchemes:
    MerchantBearer:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
      description: Token de `POST /merchant/auth/login` (abilities merchant).
    PlatformBearer:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
      description: Token de `POST /platform/auth/login`.
    CustomerBearer:
      type: http
      scheme: bearer
      bearerFormat: Sanctum
      description: Token de `POST /storefront/auth/login` (escopo da loja).
    StoreId:
      type: apiKey
      in: header
      name: X-Store-Id
      description: |
        ID da loja do membership. O servidor valida membership;
        **nunca** aceite `store_id` do body como fonte de verdade.

  parameters:
    StoreId:
      name: X-Store-Id
      in: header
      required: true
      schema: { type: integer }
      description: Loja ativa do merchant (membership).
    StoreHost:
      name: X-Store-Host
      in: header
      required: false
      schema: { type: string }
      description: |
        Host da loja (ex. `slug.nuvartis.localhost`). Alternativa ao `Host`
        quando a API é chamada cross-origin.

  responses:
    ValidationError:
      description: Erro de validação Laravel
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
    NotFound:
      description: Não encontrado (inclui cross-tenant)
      content:
        application/json:
          schema:
            type: object
            properties:
              message: { type: string }

  schemas:
    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email }
        password: { type: string }

    EmailOnlyRequest:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }

    ResetPasswordRequest:
      type: object
      required: [email, token, password]
      properties:
        email: { type: string, format: email }
        token: { type: string, minLength: 20 }
        password: { type: string, minLength: 8, maxLength: 72 }

    GenericOkMessage:
      type: object
      properties:
        ok: { type: boolean }
        message: { type: string }

    MerchantLoginResponse:
      type: object
      required: [token, token_type, user]
      properties:
        token: { type: string }
        token_type: { type: string, example: Bearer }
        user:
          type: object
          properties:
            id: { type: integer }
            name: { type: string }
            email: { type: string }
            type: { type: string, example: merchant }

    PlatformLoginResponse:
      type: object
      required: [token, token_type, user]
      properties:
        token: { type: string }
        token_type: { type: string, example: Bearer }
        user:
          $ref: '#/components/schemas/PlatformUser'

    PlatformUser:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        email: { type: string }
        type: { type: string, example: platform }

    MerchantMeResponse:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        email: { type: string }
        type: { type: string }
        stores:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              slug: { type: string }
              name: { type: string }
              status: { type: string }

    CustomerLoginResponse:
      type: object
      properties:
        token: { type: string }
        token_type: { type: string, example: Bearer }
        customer:
          $ref: '#/components/schemas/Customer'

    Customer:
      type: object
      properties:
        id: { type: integer }
        name: { type: string, nullable: true }
        email: { type: string }

    Product:
      type: object
      properties:
        id: { type: integer }
        store_id: { type: integer }
        name: { type: string }
        slug: { type: string }
        status: { type: string, enum: [draft, active, archived] }
        brand_id: { type: integer, nullable: true }
        brand_name: { type: string, nullable: true }
        price_cents: { type: integer, description: Centavos (BRL) }
        compare_at_cents: { type: integer, nullable: true }
        cover_url: { type: string, nullable: true }
        sales_count: { type: integer }

    ProductWrite:
      type: object
      properties:
        name: { type: string }
        slug: { type: string }
        status: { type: string, enum: [draft, active, archived] }
        brand_id: { type: integer, nullable: true }
        primary_category_id: { type: integer, nullable: true }
        price_cents: { type: integer }
        compare_at_cents: { type: integer, nullable: true }
        description: { type: string, nullable: true }
        is_digital: { type: boolean }
        weight_grams:
          type: integer
          minimum: 1
          nullable: true
          description: Obrigatório (≥1) se is_digital=false (Melhoria 270).
        height_cm:
          type: integer
          minimum: 1
          nullable: true
          description: Obrigatório com width_cm e length_cm se produto físico.
        width_cm: { type: integer, minimum: 1, nullable: true }
        length_cm: { type: integer, minimum: 1, nullable: true }
      additionalProperties: true

    ProductListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Product'
        meta:
          $ref: '#/components/schemas/PageMeta'

    PublicPlan:
      type: object
      required:
        - code
        - name
        - price_month_cents
        - max_products
        - max_staff
        - ai_monthly_quota
        - mentoring_hours_total
        - sort_order
      properties:
        code: { type: string, example: negocio }
        name: { type: string, example: Negócio }
        price_month_cents: { type: integer, example: 25900 }
        price_year_cents: { type: integer, nullable: true, example: 264180 }
        max_products: { type: integer, example: 2000 }
        max_staff: { type: integer, example: 12 }
        ai_monthly_quota: { type: integer, example: 0 }
        appearance_ai_daily: { type: integer, example: 3 }
        appearance_banner_image_daily: { type: integer, example: 1 }
        mentoring_hours_total:
          type: integer
          minimum: 0
          description: Bolsa de mentoria no total no ciclo de 12 meses (0 = sem bolsa fixa).
          example: 3
        features:
          type: array
          items: { type: string }
        sort_order: { type: integer, example: 30 }

    Order:
      type: object
      properties:
        id: { type: integer }
        store_id: { type: integer }
        number: { type: string }
        status: { type: string }
        source: { type: string, nullable: true }
        channel: { type: string, nullable: true }
        currency: { type: string, example: BRL }
        subtotal_cents: { type: integer }
        shipping_cents: { type: integer, nullable: true }
        discount_cents: { type: integer, nullable: true }
        total_cents: { type: integer }
        created_at: { type: string, format: date-time }

    OrderListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Order'
        meta:
          allOf:
            - $ref: '#/components/schemas/PageMeta'
            - type: object
              properties:
                counters:
                  type: object
                  additionalProperties: true

    PageMeta:
      type: object
      properties:
        current_page: { type: integer }
        last_page: { type: integer }
        total: { type: integer }
        per_page: { type: integer }

    OutboundWebhook:
      type: object
      properties:
        id: { type: integer }
        url: { type: string, format: uri }
        events:
          type: array
          items: { type: string }
        is_active: { type: boolean }
        failure_count: { type: integer }
        created_at: { type: string, format: date-time }

    OutboundWebhookWrite:
      type: object
      required: [url, events]
      properties:
        url: { type: string, format: uri }
        secret: { type: string, description: Usado no HMAC; não reexposto em claro }
        events:
          type: array
          items:
            type: string
            enum:
              - order.paid
              - order.shipped
              - order.refunded
              - order.cancelled
              - product.updated
              - return.created
              - return.approved
              - return.rejected
              - ping
        is_active: { type: boolean }

    OutboundWebhookListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OutboundWebhook'
        meta:
          type: object
          properties:
            feature: { type: boolean }
            allowed_events:
              type: array
              items: { type: string }
            webhook_version: { type: integer, example: 1 }
            contract_doc: { type: string }
            failure_disable_threshold: { type: integer, example: 10 }

    OutboundDeliveryListResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              webhook_id: { type: integer }
              event: { type: string }
              event_id: { type: string, format: uuid }
              status: { type: string }
              http_status: { type: integer, nullable: true }
              attempt: { type: integer }
              last_error: { type: string, nullable: true }
              created_at: { type: string, format: date-time }
        meta:
          $ref: '#/components/schemas/PageMeta'

    OutboundPartnerPayload:
      type: object
      description: |
        Payload mínimo entregue ao endpoint do parceiro
        (ver `docs/prd/outbound-webhooks-contract.md`).
      required: [id, event_id, event, store_id, sent_at]
      properties:
        id: { type: string, format: uuid }
        event_id: { type: string, format: uuid }
        event: { type: string, example: order.paid }
        store_id: { type: integer }
        sent_at: { type: string, format: date-time }
      additionalProperties: true

    ValidationError:
      type: object
      properties:
        message: { type: string }
        errors:
          type: object
          additionalProperties:
            type: array
            items: { type: string }
