openapi: 3.1.0

info:
  title: Appmax API
  version: 1.0.0
  description: |
    API oficial da Appmax — plataforma de pagamentos com gateway, antifraude e
    adquirência integrados. Esta especificação está em prova de conceito;
    inicialmente cobre apenas `POST /v1/orders` (criação de pedido).
  contact:
    name: Suporte Appmax
    url: https://docs.appmax.com.br

servers:
  - url: https://api.appmax.com.br
    description: Produção
  - url: https://api.sandboxappmax.com.br
    description: Sandbox

tags:
  - name: Customers
    description: Cadastro de clientes vinculados aos pedidos.
  - name: Products
    description: |
      CRUD do catálogo de produtos do merchant. Cada produto pertence a uma
      *company*, resolvida automaticamente a partir das credenciais da requisição.
  - name: Orders
    description: |
      Gestão de pedidos. Pedidos sempre são vinculados a um cliente previamente
      criado em [`POST /v1/customers`](/api-reference/customers/criar-atualizar).
  - name: Payments
    description: Processamento de pagamentos por cartão, Pix, boleto, Apple Pay e Google Pay.
  - name: Subscriptions
    description: |
      Transforma um pedido aprovado em cobrança recorrente e gerencia todo o
      ciclo de vida da assinatura — status, produtos, dia de cobrança e endereço
      de entrega. Cada assinatura pertence a uma loja (*site*), resolvida a partir
      das credenciais.
  - name: Refunds
    description: Solicitação de estornos totais ou parciais.
  - name: Payment Links
    description: |
      Criação e consulta de links de pagamento — uma URL de checkout hospedada
      pela Appmax, sem necessidade de criar pedido e pagamento pela API.
  - name: Split
    description: |
      Onboarding de recebedores (KYC) e divisão de valores de pedidos. Veja a
      [visão geral](/guides/split-pagamentos) para o fluxo completo.
  - name: PagarMe
    description: |
      Rotas de recebedor no contrato da PagarMe v5, para quem já integrou
      recebedores na PagarMe operar na Appmax sem reescrever a integração.

security:
  - bearerAuth: []

paths:
  /v1/orders:
    post:
      operationId: createOrder
      tags: [Orders]
      summary: Criar um pedido
      description: |
        Cria um novo pedido na Appmax. Um pedido deve estar sempre vinculado a um
        cliente previamente criado.

        ::: warning Pré-requisito
        Para criar um pedido, você precisa ter o `customer_id` do cliente. Caso
        ainda não tenha, veja
        [Criar ou atualizar cliente](/api-reference/customers/criar-atualizar).
        :::

        Armazene o `order_id` retornado, pois ele será necessário para efetuar o
        pagamento ou consultar o status do pedido. Para entender como o valor
        total é calculado, veja
        [Cálculo do valor total](/api-reference/orders/calculo-valor).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            examples:
              default:
                summary: Pedido com 1 produto digital
                value:
                  customer_id: 29
                  products_value: 12300
                  discount_value: 0
                  shipping_value: 3999
                  products:
                    - sku: '9000010'
                      name: Livro de receitas
                      quantity: 1
                      unit_value: 12300
                      type: digital
      responses:
        '201':
          description: Pedido criado com sucesso. O pedido é criado com status `pendente` até que o pagamento seja processado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateOrderResponse'
              examples:
                default:
                  value:
                    data:
                      order:
                        id: 1
                        status: pendente
        '404':
          description: Cliente ou dados não encontrados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              examples:
                merchantNotFound:
                  value:
                    error:
                      message: Merchant not found
        '422':
          description: Erro de validação dos campos do payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        products_value:
                          - The products value must be an integer.
                        shipping_value:
                          - The shipping value must be an integer.
                        products:
                          - The products field is required.

  /v1/orders/{order_id}:
    get:
      operationId: getOrder
      tags: [Orders]
      summary: Consultar dados de um pedido
      description: |
        Consulta os detalhes de um pedido previamente criado para um merchant.
        Basta informar o ID do pedido na URL.

        Veja a [lista completa de status](/guides/status-pedidos) para entender
        o campo `status` retornado.
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: integer
          description: ID do pedido na Appmax.
          example: 3531
      responses:
        '200':
          description: Detalhes do pedido retornados com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetOrderResponse'
              examples:
                creditcard:
                  summary: Pedido aprovado e estornado
                  value:
                    data:
                      order:
                        id: 3531
                        status: estornado
                        total_paid: 8916
                        amounts:
                          sub_total: 4662
                          shipping_value: 2738
                          discount: 0
                          installment_fee: 1516
                        created_at: '2025-02-13 14:09:48'
                        updated_at: '2025-02-13 14:11:55'
                      customer:
                        id: 2023
                        name: Junior Almeida
                        email: junior.almeida@teste.com
                        document_number: '19100000000'
                      payment:
                        method: creditcard
                        installments: 12
                        installments_amount: 743
                        card:
                          brand: visa
                          number: 400000****0010
                        paid_at: '2025-02-13 14:10:20'
                      refund:
                        refunded_at: '2025-02-13 14:11:55'
                pix:
                  summary: Pedido pago com PIX
                  value:
                    data:
                      order:
                        id: 12150
                        status: aprovado
                        total_paid: 5000
                        amounts:
                          sub_total: 4000
                          shipping_value: 1000
                          discount: 0
                        created_at: '2026-02-11 09:23:26'
                        updated_at: '2026-09-10 15:27:03'
                      customer:
                        id: 6768
                        name: Vegeta
                        email: vegetaprincipe@email.com
                        document_number: '19100000000'
                      payment:
                        method: pix
                        pix:
                          end_to_end_id: E09089356202210251208APIcdbe38b4
                          pay_reference: 12150f98e957540ba46b297be99afce971c
                          creation_date: '2026-02-11 09:23:29'
                          expiration_date: '2026-02-11 09:53:29'
                        paid_at: '2026-09-10 15:27:03'
                      split_orders: null
        '404':
          description: Pedido não encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              examples:
                orderNotFound:
                  value:
                    error:
                      message: Order not found

  /v1/orders/unified-order:
    post:
      operationId: createUnifiedOrder
      tags: [Orders]
      summary: Criar um pedido unificado
      description: |
        Permite criar pedidos completos em uma única requisição, centralizando
        dados do cliente, produtos, frete, pagamentos e split de pagamentos.

        **Responsabilidade da rota**

        - Receber payloads de pedido em formato unificado
        - Criar o cliente automaticamente quando *customer_id* não for informado
        - Criar a configuração de *split de pagamentos*
        - Validar estrutura e regras de negócio
        - Normalizar os dados para o modelo aceito pela Appmax
        - Encaminhar o pedido para o fluxo de cobrança

        **Métodos de pagamento suportados**

        | Método | Regras principais |
        |--------|-------------------|
        | Cartão de crédito | Exige *token* ou objeto *card* completo |
        | Pix | Exige *document_number* |
        | Boleto | Exige *document_number* |
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customer, customer_id, items]
              properties:
                customer:
                  type: array
                  description: Dados do cliente. Obrigatório caso o *customer_id* não seja informado.
                  items:
                    type: object
                    required: [name, email, phones]
                    properties:
                      name:
                        type: string
                        description: 'Nome do cliente. Max: 64 caracteres.'
                      email:
                        type: string
                        description: 'E-mail do cliente. Max: 64 caracteres.'
                      phones:
                        type: array
                        default: []
                customer_id:
                  type: integer
                  format: int32
                  description: Código do cliente. Obrigatório, caso não seja informado o objeto *customer*
                ip:
                  type: string
                  description: 'Endereço IP do dispositivo que solicitou a compra no formato: IPV4 e IPV6'
                items:
                  type: array
                  description: Itens do pedido.
                  items:
                    type: object
                    required: [amount, description, quantity, code]
                    properties:
                      amount:
                        type: integer
                        format: int32
                        description: Valor unitário. Obrigatoriamente maior que zero.
                      description:
                        type: string
                        description: Descrição do item.
                      quantity:
                        type: integer
                        format: int32
                        description: Quantidade de itens.
                      code:
                        type: string
                        description: Código do item no sistema da loja.
                shipping:
                  type: array
                  description: Dados para entrega. (OPCIONAL)
                  items:
                    type: object
                    required: [amount]
                    properties:
                      amount:
                        type: integer
                        format: int32
                        description: Valor da entrega.
                      address:
                        type: array
                        description: Endereço de entrega
                        items:
                          type: object
                          required: [line_1, line_2, state, city, zip_code]
                          properties:
                            line_1:
                              type: string
                              description: Dados principais do endereço. Neste campo deve ser informado Número, Rua, Bairro, nesta ordem e separados por vírgula.
                            line_2:
                              type: string
                              description: Dados complementares do endereço. Neste campo pode ser informado complemento, referências.
                            state:
                              type: string
                              description: Estado (Código do estado no formato ISO 3166-2).
                            city:
                              type: string
                              description: Cidade.
                            zip_code:
                              type: integer
                              format: int32
                              description: Código Postal (CEP).
                payments:
                  type: object
                  description: Dados de pagamento. Billing adress obrigatório.
                  properties:
                    split:
                      type: object
                      description: Objeto com configurações para split de pagamento
                      properties: {}
                payment:
                  type: object
                  properties:
                    split:
                      type: object
                      properties:
                        '*':
                          type: object
                          required: [amount, recipient_id]
                          properties:
                            amount:
                              type: integer
                              format: int32
                              description: Valor que deve ser splitado
                            recipient_id:
                              type: string
                              description: HASH do recebedor que deve receber o valor splitado
                            refund_charge_percentage:
                              type: integer
                              format: int32
                              description: Porcentagem que o recebedor deve ser cobrado sobre o valor líquido em caso de estorno do pedido. (0 - 100)
            examples:
              'Pedido com Boleto':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - boleto:
                        document_number: '19100000000'
                      payment_method: boleto
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
                          refund_charge_percentage: 0
              'Pedido com Pix':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - pix:
                        document_number: '19100000000'
                      payment_method: pix
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
                          refund_charge_percentage: 0
              'Pedido com Cartão de Crédito':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - credit_card:
                        card:
                          number: '4000000000000010'
                          holder_name: Tony Stark
                          holder_document: '19100000000'
                          exp_month: 1
                          exp_year: 30
                          cvv: '123'
                        installments: 1
                      payment_method: credit_card
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
                          refund_charge_percentage: 0
              'Pedido com Token de Cartão de Crédito':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - credit_card:
                        installments: 1
                        token: 123456ABCDE
                      payment_method: credit_card
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
                          refund_charge_percentage: 0
              'Pedido com Boleto e Customer ID':
                value:
                  customer_id: '86'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: 10880, Malibu Point, Malibu Central
                      line_2: Comp op
                      zip_code: '90265'
                      city: Malibu
                      state: CA
                    amount: 1000
                  payments:
                    - boleto:
                        document_number: '47683264474'
                      payment_method: boleto
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
                          refund_charge_percentage: 0
      responses:
        '201':
          description: '201'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      order_id:
                        type: integer
                        example: 48523
                        default: 0
                      payment:
                        type: object
                        properties:
                          cashback:
                            type: integer
                            example: 0
                            default: 0
                          pay_reference:
                            type: string
                            example: tran_jkl345mno678
                          boleto_expiration_date:
                            type: string
                            example: '2026-06-27'
                          boleto_digitable_line:
                            type: string
                            example: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                          boleto_payment_code:
                            type: string
                            example: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                          boleto_link_pdf:
                            type: string
                            example: 'https://api.example.com/boleto/tran_jkl345mno678.pdf'
              examples:
                Boleto:
                  value:
                    data:
                      order_id: 48523
                      payment:
                        cashback: 0
                        pay_reference: tran_jkl345mno678
                        boleto_expiration_date: '2026-06-27'
                        boleto_digitable_line: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                        boleto_payment_code: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                        boleto_link_pdf: 'https://api.example.com/boleto/tran_jkl345mno678.pdf'
        '500':
          description: '500'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Order Payment error
              examples:
                Result:
                  value:
                    data:
                      error: Order Payment error

  /v1/orders/unified-order/partner-flexible:
    post:
      operationId: createUnifiedOrderFlexible
      tags: [Orders]
      summary: Criar um pedido unificado flexível
      description: |
        Permite criar pedidos completos em uma única requisição, centralizando
        dados do cliente, produtos, frete, pagamentos e split de pagamentos. Para
        pagamentos via PIX, o customer pode ser opcional conforme configuração
        interna.

        **Responsabilidade da rota**

        - Receber payloads de pedido em formato unificado
        - Resolver a identificação do cliente:
          - Utilizar customer_id quando informado
          - Criar um novo cliente quando apenas customer for informado
          - Em cenários de PIX com configuração habilitada, cria automaticamente um system customer (cliente com dados reaproveitados de um usuário vinculado ao site), tornando opcional customer, customer_id e document_number (pix).
        - Criar a configuração de *split de pagamentos*
        - Validar estrutura e regras de negócio
        - Normalizar os dados para o modelo aceito pela Appmax
        - Encaminhar o pedido para o fluxo de cobrança

        **Métodos de pagamento suportados**

        | Método | Regras principais |
        |--------|-------------------|
        | Cartão de crédito | Exige *token* ou objeto *card* completo |
        | Pix | Não exige o *document_number* quando a configuração de customer opcional estiver habilitada. |
        | Boleto | Exige *document_number* |
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items, payments]
              properties:
                customer:
                  type: array
                  description: Dados do cliente. Obrigatório caso o customer_id não seja informado. Para pagamentos via PIX, pode ser omitido quando houver configuração interna habilitando customer opcional — nesse caso, um customer técnico será utilizado automaticamente.
                  items:
                    type: object
                    required: [name, email, phones]
                    properties:
                      name:
                        type: string
                        description: 'Nome do cliente. Max: 64 caracteres.'
                      email:
                        type: string
                        description: 'E-mail do cliente. Max: 64 caracteres.'
                      phones:
                        type: array
                        default: []
                customer_id:
                  type: integer
                  format: int32
                  description: Código do cliente. Obrigatório caso não seja informado o objeto customer. Para pagamentos via PIX, pode ser omitido quando houver configuração interna habilitando customer opcional.
                ip:
                  type: string
                  description: 'Endereço IP do dispositivo que solicitou a compra no formato: IPV4 e IPV6'
                items:
                  type: array
                  description: Itens do pedido.
                  items:
                    type: object
                    required: [amount, description, quantity, code]
                    properties:
                      amount:
                        type: integer
                        format: int32
                        description: Valor unitário. Obrigatoriamente maior que zero.
                      description:
                        type: string
                        description: Descrição do item.
                      quantity:
                        type: integer
                        format: int32
                        description: Quantidade de itens.
                      code:
                        type: string
                        description: Código do item no sistema da loja.
                shipping:
                  type: array
                  description: Dados para entrega. (OPCIONAL)
                  items:
                    type: object
                    required: [amount]
                    properties:
                      amount:
                        type: integer
                        format: int32
                        description: Valor da entrega.
                      address:
                        type: array
                        description: Endereço de entrega
                        items:
                          type: object
                          required: [line_1, line_2, state, city, zip_code]
                          properties:
                            line_1:
                              type: string
                              description: Dados principais do endereço. Neste campo deve ser informado Número, Rua, Bairro, nesta ordem e separados por vírgula.
                            line_2:
                              type: string
                              description: Dados complementares do endereço. Neste campo pode ser informado complemento, referências.
                            state:
                              type: string
                              description: Estado (Código do estado no formato ISO 3166-2).
                            city:
                              type: string
                              description: Cidade.
                            zip_code:
                              type: integer
                              format: int32
                              description: Código Postal (CEP).
                payments:
                  type: array
                  description: Dados de pagamento. Billing adress obrigatório.
                  items:
                    type: object
                    properties:
                      pix:
                        type: array
                        description: Dados sobre o pagamento com pix (obrigatório caso o payment_method seja pix)
                        items:
                          type: object
                          properties:
                            document_number:
                              type: string
                              description: 'Documento do pagador (CPF). Máx.: 16 caracteres. Obrigatório, exceto em PIX com customer opcional habilitado, quando será preenchido automaticamente via system customer.'
                      boleto:
                        type: array
                        description: Dados sobre o pagamento com boleto (obrigatório caso o payment_method seja boleto).
                        items:
                          type: object
                          required: [document_number]
                          properties:
                            document_number:
                              type: string
                              description: 'Identificador do boleto. Max: 16 caracteres.'
                      creditcard:
                        type: array
                        description: Dados sobre o pagamento com cartão de crédito (obrigatório caso o payment_method seja credit_card).
                        items:
                          type: object
                          required: [installments]
                          properties:
                            installments:
                              type: integer
                              format: int32
                              description: 'Quantidade de parcelas. O teto é o configurado para a loja (12 por padrão, maior com parcelamento estendido). No Apple Pay o limite é fixo em 12.'
                            card:
                              type: array
                              description: Cartão de crédito.
                              default: []
                            card_token:
                              type: string
                              description: Token do cartão gerado pelo checkout transparente (OBRIGATÓRIO na ausência do objeto Card)
            examples:
              'Pedido com Pix sem customer e documento':
                value:
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  payments:
                    - payment_method: pix
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
              'Pedido com Pix':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - pix:
                        document_number: '19100000000'
                      payment_method: pix
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
              'Pedido com Cartão de Crédito':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - credit_card:
                        card:
                          number: '4000000000000010'
                          holder_name: Tony Stark
                          holder_document: '19100000000'
                          exp_month: 1
                          exp_year: 30
                          cvv: '123'
                        installments: 1
                      payment_method: credit_card
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
              'Pedido com Token de Cartão de Crédito':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - credit_card:
                        installments: 1
                        token: 123456ABCDE
                      payment_method: credit_card
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
              'Pedido com Boleto e Customer ID':
                value:
                  customer_id: '86'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: 10880, Malibu Point, Malibu Central
                      line_2: Comp op
                      zip_code: '90265'
                      city: Malibu
                      state: CA
                    amount: 1000
                  payments:
                    - boleto:
                        document_number: '47683264474'
                      payment_method: boleto
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
              'Pedido com Boleto':
                value:
                  customer:
                    name: Vegeta Príncipe
                    email: vegetaprincipe@email.com
                    phones:
                      mobile_phone:
                        area_code: '11'
                        number: '988887777'
                  ip: 1.2.3.4
                  items:
                    - amount: 2000
                      description: Chaveiro do Caneta
                      quantity: 2
                      code: sku910
                  shipping:
                    address:
                      line_1: Rua do Rei Vegeta, 999, Distrito Real
                      line_2: Palácio de Vegeta, Sala do Trono
                      zip_code: '90265'
                      city: Cidade Vegeta
                      state: BA
                    amount: 1000
                  payments:
                    - boleto:
                        document_number: '19100000000'
                      payment_method: boleto
                      split:
                        - amount: 1200
                          recipient_id: 72ccc884-edb6-5699-9eee-51ab34165e14
      responses:
        '201':
          description: '201'
          content:
            application/json:
              schema:
                oneOf:
                  - title: Boleto
                    type: object
                    properties:
                      data:
                        type: object
                        properties:
                          order_id:
                            type: integer
                            example: 48523
                            default: 0
                          payment:
                            type: object
                            properties:
                              cashback:
                                type: integer
                                example: 0
                                default: 0
                              pay_reference:
                                type: string
                                example: tran_jkl345mno678
                              boleto_expiration_date:
                                type: string
                                example: '2026-06-27'
                              boleto_digitable_line:
                                type: string
                                example: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                              boleto_payment_code:
                                type: string
                                example: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                              boleto_link_pdf:
                                type: string
                                example: 'https://api.example.com/boleto/tran_jkl345mno678.pdf'
                  - title: Pix
                    type: object
                    properties:
                      data:
                        type: object
                        properties:
                          order_id:
                            type: integer
                            example: 48522
                            default: 0
                          payment:
                            type: object
                            properties:
                              cashback:
                                type: integer
                                example: 0
                                default: 0
                              pay_reference:
                                type: string
                                example: tran_xyz789ghi012
                              pix_qrcode:
                                type: string
                                example: 'https://api.example.com/pix/qrcode/tran_xyz789ghi012.png'
                              pix_emv:
                                type: string
                                example: '00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540510.005802BR5913Loja Exemplo6008Sao Paulo62070503***63041D3D'
                              pix_expiration_date:
                                type: string
                                example: '2026-06-25T00:00:00'
              examples:
                Boleto:
                  value:
                    data:
                      order_id: 48523
                      payment:
                        cashback: 0
                        pay_reference: tran_jkl345mno678
                        boleto_expiration_date: '2026-06-27'
                        boleto_digitable_line: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                        boleto_payment_code: '23793.38128 60000.000003 00000.000400 1 92570000010000'
                        boleto_link_pdf: 'https://api.example.com/boleto/tran_jkl345mno678.pdf'
                Pix:
                  value:
                    data:
                      order_id: 48522
                      payment:
                        cashback: 0
                        pay_reference: tran_xyz789ghi012
                        pix_qrcode: 'https://api.example.com/pix/qrcode/tran_xyz789ghi012.png'
                        pix_emv: '00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540510.005802BR5913Loja Exemplo6008Sao Paulo62070503***63041D3D'
                        pix_expiration_date: '2026-06-25T00:00:00'
        '500':
          description: '500'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Order Payment error
              examples:
                Result:
                  value:
                    data:
                      error: Order Payment error

  /v1/orders/total-liquid/{order_id}:
    get:
      operationId: getOrderTotalLiquid
      tags: [Orders]
      summary: Total líquido do pedido
      description: |
        Retorna o detalhamento do valor líquido de um pedido (`order_id`),
        incluindo taxas, comissões e o total líquido a receber pelo produtor.

        **Autenticação:** header `secret` (API Gateway).
      security:
        - secretHeader: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: integer
          description: ID do pedido na Appmax.
          example: 3531
      responses:
        '200':
          description: Detalhamento do valor líquido retornado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      site:
                        type: string
                        description: Nome do site/loja vinculado ao pedido.
                        example: Teste applink
                      currency:
                        type: string
                        description: Moeda dos valores retornados.
                        example: BRL
                      producer:
                        type: object
                        description: Detalhamento dos valores do produtor.
                        properties:
                          name:
                            type: string
                            description: Nome do produtor.
                            example: Produtor teste
                          total_liquid:
                            type: number
                            description: Valor líquido a receber pelo produtor.
                            example: 148.87
                          total_order:
                            type: number
                            description: Valor total do pedido.
                            example: 159.9
                          interest:
                            type: number
                            nullable: true
                            description: Juros aplicados ao pedido, quando houver.
                          installment_fee:
                            type: number
                            nullable: true
                            description: Taxa de parcelamento, quando houver.
                          processing_fee:
                            type: array
                            description: Taxas de processamento aplicadas ao pedido.
                            items:
                              type: object
                              properties:
                                key:
                                  type: string
                                  description: Descrição da taxa.
                                  example: Taxa de processamento (6.9%)
                                value:
                                  type: string
                                  description: Valor da taxa.
                                  example: '11.03'
                                internal_name:
                                  type: string
                                  description: Identificador interno da taxa.
                                  example: creditcard-processing-rate
                          cashback:
                            type: number
                            description: Valor de cashback do pedido.
                            example: 0
                          coproducer_commission:
                            type: number
                            nullable: true
                            description: Comissão do coprodutor, quando houver.
                          affiliate_commission:
                            type: number
                            nullable: true
                            description: Comissão do afiliado, quando houver.
                          debts:
                            type: object
                            description: Débitos e saldos utilizados no pedido.
                            properties:
                              cashBalance:
                                type: array
                                description: Saldos em caixa utilizados.
                                items:
                                  type: object
                              cashBalanceTotal:
                                type: number
                                description: Total de saldo em caixa utilizado.
                                example: 0
                              splitsTotal:
                                type: number
                                description: Total destinado a splits.
                                example: 0
                      total:
                        type: number
                        description: Total líquido do pedido.
                        example: 148.87
                      split_orders:
                        type: object
                        nullable: true
                        description: Dados de split do pedido, quando houver.
              examples:
                default:
                  value:
                    data:
                      site: Teste applink
                      currency: BRL
                      producer:
                        name: Produtor teste
                        total_liquid: 148.87
                        total_order: 159.9
                        interest: null
                        installment_fee: null
                        processing_fee:
                          - key: Taxa de processamento (6.9%)
                            value: '11.03'
                            internal_name: creditcard-processing-rate
                        cashback: 0
                        coproducer_commission: null
                        affiliate_commission: null
                        debts:
                          cashBalance: []
                          cashBalanceTotal: 0
                          splitsTotal: 0
                      total: 148.87
                      split_orders: null
        '404':
          description: Retornado quando o `order_id` não existe ou não pertence ao site autenticado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Order not found
              examples:
                default:
                  value:
                    data:
                      error: Order not found
        '500':
          description: Retornado quando ocorre uma falha inesperada ao calcular o valor líquido do pedido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Internal Server Error
              examples:
                default:
                  value:
                    data:
                      error: Internal Server Error

  /v1/balances:
    get:
      operationId: getBalances
      tags: [Split]
      summary: Consultar saldos
      description: |
        Retorna os saldos financeiros da empresa autenticada, separados por tipo.

        **Autenticação:** header `secret` (API Gateway).

        | type | descrição |
        |------|-----------|
        | `available` | Saldo disponível para saque |
        | `to_release` | Saldo a liberar |
      security:
        - secretHeader: []
      responses:
        '200':
          description: Saldos retornados com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    description: Lista de saldos da empresa, um item por tipo.
                    items:
                      type: object
                      properties:
                        type:
                          type: string
                          description: 'Tipo do saldo: `available` (disponível para saque) ou `to_release` (a liberar).'
                          example: available
                        value:
                          type: string
                          description: Valor do saldo.
                          example: '6220.80'
              examples:
                default:
                  value:
                    data:
                      - type: available
                        value: '6220.80'
                      - type: to_release
                        value: '24729.89'
        '404':
          description: Retornado quando não é possível identificar a empresa (`company_id`) na sessão da requisição.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Company ID not found in session
              examples:
                default:
                  value:
                    data:
                      error: Company ID not found in session
        '500':
          description: Retornado quando ocorre uma falha inesperada ao consultar os saldos.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
                        example: Internal Server Error
              examples:
                default:
                  value:
                    data:
                      error: Internal Server Error

  /v1/customers:
    post:
      operationId: createOrUpdateCustomer
      tags: [Customers]
      summary: Criar ou atualizar cliente
      description: |
        Cria ou atualiza um cliente identificado pela combinação
        `first_name + last_name + email + phone + ip`.

        ::: warning
        Para criar um cliente, você precisa ter feito a coleta de IP utilizando
        o script [Appmax JS](/guides/appmax-js).
        :::

        ::: tip
        Se enviar apenas os campos obrigatórios, o cliente será registrado como
        "carrinho abandonado" e pode ser atualizado depois pela mesma rota.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrUpdateCustomerRequest'
            examples:
              default:
                value:
                  first_name: Junior
                  last_name: Almeida
                  email: junior.almeida@email.com
                  phone: '51983655100'
                  document_number: '25226493029'
                  address:
                    postcode: '91520270'
                    street: Rua Francisco Carneiro da Rocha
                    number: '582'
                    complement: Casa
                    district: Moinhos de Ventos
                    city: Porto Alegre
                    state: RS
                  ip: 127.0.0.1
                  products:
                    - sku: '9000010'
                      name: Livro de receitas
                      quantity: 1
                      unit_value: 12300
                      type: digital
                  tracking:
                    utm_source: google
                    utm_campaign: teste
      responses:
        '201':
          description: Cliente criado com sucesso. Guarde o `customer_id` para criar o pedido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateCustomerResponse'
              examples:
                default:
                  value:
                    data:
                      customer:
                        id: 1
        '422':
          description: Erro de validação dos campos do payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'

  /v1/orders/shipping-tracking-code:
    post:
      operationId: setTrackingCode
      tags: [Orders]
      summary: Cadastrar código de rastreio
      description: |
        Cadastra um código de rastreio em um pedido criado na Appmax.

        ::: warning
        Para que os saques do merchant sejam aprovados, é necessário atualizar
        o pedido com o código de rastreamento da entrega.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackingCodeRequest'
            examples:
              default:
                value:
                  order_id: 2
                  shipping_tracking_code: EEEASDASDAS1239A
      responses:
        '201':
          description: Código de rastreio incluído com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      message: tracking accepted
        '400':
          description: Pedido não encontrado ou falha ao armazenar o código.
        '422':
          description: Erro de validação dos campos do payload.

  /v1/orders/upsell:
    post:
      operationId: createUpsell
      tags: [Orders]
      summary: Criar um upsell
      description: |
        Cria um upsell vinculado a um pedido cujo pagamento já gerou o
        `upsell_hash` (retornado pelo pagamento por cartão de crédito).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsellRequest'
            examples:
              default:
                value:
                  upsell_hash: '4000114202503117156088040208561001715608804'
                  products_value: 12300
                  products:
                    - sku: '9000010'
                      name: Livro de receitas
                      quantity: 1
                      unit_value: 12300
                      type: digital
      responses:
        '201':
          description: Upsell criado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      message: Transacao efetuada com sucesso
                      redirect_url: example.com/order/success-by-order?hash=...
        '404':
          description: Pedido não encontrado.
        '422':
          description: Erro de validação dos campos do payload.

  /v1/payments/credit-card:
    post:
      operationId: payCreditCard
      tags: [Payments]
      summary: Pagamento com cartão de crédito
      description: |
        Cria um pagamento por cartão de crédito vinculado a um pedido existente.
        Permite parcelamento e personalização do soft descriptor.

        ::: warning
        Antes de criar um pagamento, você precisa ter:
        - `order_id` — ID do pedido ([criar pedido](/api-reference/orders/criar-pedido))
        - `customer_id` — ID do cliente ([criar cliente](/api-reference/customers/criar-atualizar))
        :::

        ::: info
        Consulte [Cálculo de parcelas](/api-reference/payments/parcelas) para
        obter os valores corretos com as taxas configuradas antes de enviar um
        pagamento parcelado.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreditCardPaymentRequest'
            examples:
              default:
                value:
                  order_id: 12345
                  customer_id: 407
                  payment_data:
                    credit_card:
                      token: 422146c7523a46119d6073ea56193913
                      holder_document_number: '19100000000'
                      holder_name: Teste teste
                      installments: 1
                      soft_descriptor: MYSTORE
      responses:
        '201':
          description: Pagamento criado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      order:
                        id: 12345
                        status: autorizado
                      payment:
                        method: creditcard
                        installments: 1
                        paid_at: '2025-03-15 14:30:00'
                      upsell_hash: '4000114202503117156088040208561001715608804'
        '400':
          description: 'Erro ao processar (ex.: pedido já pago).'
        '404':
          description: Pedido não encontrado.

  /v1/payments/pix:
    post:
      operationId: payPix
      tags: [Payments]
      summary: Pagamento via Pix
      description: |
        Gera as instruções de pagamento via Pix para um pedido existente:
        QR Code (imagem base64) e código EMV (copia-e-cola).

        ::: tip Exiba um cronômetro de expiração
        Na página de sucesso, mostre o QR Code, o copia-e-cola e um cronômetro
        com o tempo restante até `pix_expiration_date`. Calcule a contagem
        regressiva **dinamicamente a partir desse campo** — não fixe um tempo
        de expiração no seu código, pois esse valor pode variar.
        :::

        ::: info Vencimento personalizado
        Por padrão, o vencimento do Pix vem da configuração da loja. Para
        definir um vencimento por transação, envie
        `payment_data.pix.expiration_date` no formato `Y-m-d H:i:s`, no fuso
        de Brasília (`America/Sao_Paulo`) e com data futura.

        O campo é **opcional**: se não for enviado, o vencimento continua
        vindo da configuração da loja, sem nenhuma mudança de comportamento.
        :::

        ::: info Testando no sandbox
        No sandbox o Pix é **aprovado automaticamente** cerca de 5 segundos
        depois de gerado. O cenário depende do `document_number`:

        | `document_number` | Resultado no sandbox |
        | --- | --- |
        | Qualquer CPF ou CNPJ válido (ex.: `19100000000`) | Aprovado em ~5 s e depois integrado |
        | CPF `40827365144` ou CNPJ `40827365000109` | Fica **pendente** (o pagamento não é confirmado) |

        Os dois cenários estão prontos no **Corpo da Requisição**, nas abas
        **Sandbox: aprovado** e **Sandbox: pendente**. Webhooks recebidos em
        cada um: [Testar no sandbox](/guides/webhooks#testar-sandbox).
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PixPaymentRequest'
            examples:
              'Sandbox: aprovado':
                summary: 'Sandbox: aprovado automaticamente'
                value:
                  order_id: 113
                  payment_data:
                    pix:
                      document_number: '19100000000'
                      expiration_date: '2026-09-10 15:00:00'
              'Sandbox: pendente':
                summary: 'Sandbox: pedido fica pendente'
                value:
                  order_id: 113
                  payment_data:
                    pix:
                      document_number: '40827365000109'
      responses:
        '200':
          description: |
            Instruções Pix retornadas. Os campos `pix_qrcode` (PNG em base64,
            sem prefixo `data:`), `pix_emv` (BR Code copia-e-cola) e
            `pix_expiration_date` (data/hora de expiração do Pix, formato
            `Y-m-d H:i:s`) vivem em `data.payment`. Exiba o QR Code e o
            copia-e-cola ao cliente.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      payment:
                        cashback: 0
                        pay_reference: 115063889f2f0f1e51428104d1b542f8299
                        pix_qrcode: iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD...
                        pix_emv: '00020101021226850014br.gov.bcb.pix2563qrcodepix.bb.com.br/pix/v2/fab6ead0-131f-46b6-aa19-d17be75b00535204000...'
                        pix_expiration_date: '2026-05-21 05:34:30'
        '404':
          description: Pedido não encontrado ou já pago.

  /v1/payments/boleto:
    post:
      operationId: payBoleto
      tags: [Payments]
      summary: Pagamento via boleto
      description: |
        Cria um boleto bancário vinculado a um pedido existente. Retorna o link
        do PDF e a linha digitável.

        O boleto é uma opção de pagamento **offline**: o cliente paga em um
        banco, lotérica ou pelo internet banking, fora do seu checkout.

        ::: tip Exiba o boleto na página de sucesso
        Mostre a opção de baixar o PDF (`pdf_url`) e de copiar a linha
        digitável (`digitable_line`) na página de sucesso, para que o cliente
        possa efetuar o pagamento pelo canal de sua preferência.
        :::

        ::: warning Não abra o PDF em iframe
        O download do boleto (`pdf_url`) deve **redirecionar o cliente para
        outra página**, e não ser aberto embutido em um iframe na sua página
        de checkout/sucesso.
        :::

        ::: info Testando no sandbox
        No sandbox o boleto é **aprovado automaticamente** cerca de 5 segundos
        depois de gerado. O cenário depende do `document_number`:

        | `document_number` | Resultado no sandbox |
        | --- | --- |
        | Qualquer CPF ou CNPJ válido (ex.: `19100000000`) | Aprovado em ~5 s e depois integrado |
        | CPF `40827365144` ou CNPJ `40827365000109` | Fica **pendente** (o pagamento não é confirmado) |

        Os dois cenários estão prontos no **Corpo da Requisição**, nas abas
        **Sandbox: aprovado** e **Sandbox: pendente**. Webhooks recebidos em
        cada um: [Testar no sandbox](/guides/webhooks#testar-sandbox).
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BoletoPaymentRequest'
            examples:
              'Sandbox: aprovado':
                summary: 'Sandbox: aprovado automaticamente'
                value:
                  order_id: 113
                  payment_data:
                    boleto:
                      document_number: '19100000000'
              'Sandbox: pendente':
                summary: 'Sandbox: pedido fica pendente'
                value:
                  order_id: 113
                  payment_data:
                    boleto:
                      document_number: '40827365144'
      responses:
        '201':
          description: Boleto gerado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      order:
                        id: 113
                        status: pendente
                      boleto:
                        pdf_url: 'https://boleto.appmax.com.br/pdf/abc123...'
                        digitable_line: '23793.38128 60000.000003 00000.000400 1 84340000012300'
                        due_date: '2025-03-22'
        '404':
          description: Pedido não encontrado ou já pago.

  /v1/payments/apple-pay:
    post:
      operationId: payApplePay
      tags: [Payments]
      summary: Pagamento com Apple Pay
      description: |
        O Apple Pay é uma solução de pagamento digital da Apple que permite realizar compras com cartão de crédito de forma rápida, segura e
        conveniente em dispositivos compatíveis (iPhone, iPad, Apple Watch e Mac).

        Utilizando tokenização e autenticação biométrica (Face ID ou Touch ID),
        o Apple Pay elimina a digitação manual dos dados do cartão a cada compra,
        reduzindo o tempo de checkout e aumentando a conversão. Além disso,
        transações via Apple Pay não possuem risco de chargeback por fraude.

        ::: warning Compatibilidade
        O Apple Pay só é exibido no navegador Safari (macOS ou iOS) e em
        dispositivos Apple com suporte ao método. Em outros navegadores, o botão
        não aparece.
        :::

        ## Antes de começar: escolha o seu modelo de integração

        Esta é a decisão que mais gera dúvidas. Existem dois modelos de integração
        com Apple Pay pela Appmax, e a diferença está em quem registra o domínio
        junto à Apple e em quem controla o `merchantIdentifier`.

        ::: warning Em todos os modelos, você hospeda o arquivo `.well-known`
        Independentemente do modelo abaixo, é sempre responsabilidade do lojista
        (ou do integrador, em nome da loja) publicar o arquivo
        `.well-known/apple-developer-merchantid-domain-association` na raiz de
        cada domínio onde o Apple Pay será usado — inclusive no modelo integrado.
        Veja o passo a passo completo, com o conteúdo do arquivo e as receitas de
        `Content-Type` por servidor, em
        [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio).
        :::

        | Aspecto | Modelo integrado (loja integrada / integrador via AppStore) | Fluxo direto |
        | --- | --- | --- |
        | Quem cadastra o domínio na Apple | A Appmax, por API, sob a conta/merchant Apple da Appmax | O próprio lojista, na conta Apple dele |
        | Quem hospeda o `.well-known` | O lojista/integrador, em todos os casos | O lojista hospeda o arquivo na raiz do domínio |
        | `merchantIdentifier` | Gerido pela Appmax, compartilhado entre vários domínios do integrador | Do próprio lojista |
        | Indicado para | Plataformas/integradores que operam várias lojas (ex.: e-commerce SaaS) | Lojista único que gerencia a própria conta Apple |

        ::: info Como saber qual é o seu caso?
        Se você é uma plataforma/integrador e instala o app da Appmax em nome de
        várias lojas (fluxo de AppStore, com `client_id`/`external_id` por loja),
        você está no **modelo integrado**. Se você é um lojista configurando o
        Apple Pay diretamente na sua própria conta Apple, você está no
        **fluxo direto**. Nos dois casos, você precisa publicar o arquivo
        `.well-known` (veja o aviso acima).
        :::

        Os passos abaixo indicam, quando relevante, o que muda entre os dois modelos.

        ## Glossário

        | Termo | Descrição |
        | --- | --- |
        | Apple Pay | Solução de pagamento da Apple para compras seguras via Apple Wallet, com autenticação biométrica e criptografia ponta a ponta. |
        | Apple Token (Apple Pay Token) | Objeto JSON criptografado retornado pela PaymentSheet após o usuário confirmar o pagamento, contendo `paymentData`, `paymentMethod` e `transactionIdentifier`. É o cartão tokenizado e serve para efetivar o pagamento na API Appmax. Gerado por transação. |
        | PaymentSheet | Interface nativa do Apple Pay que exibe valor, métodos disponíveis e solicita autenticação (Face ID, Touch ID ou senha). |
        | Merchant Session | Sessão JSON assinada pela Apple, válida para um domínio e `merchantIdentifier`. Valida o site antes de exibir a PaymentSheet. Obtida em tempo real a cada transação. |
        | `.well-known/apple-developer-merchantid-domain-association` | Arquivo estático de validação de domínio da Apple, vinculado ao `merchantIdentifier` (não à transação). Responsabilidade do lojista em **todos** os modelos de integração — veja [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio). |
        | `external_id` | UUID que identifica o par (app instalado + loja). Gerado na instalação e usado nas chamadas da CDN. Veja [Instalação do Aplicativo](/guides/instalacao). |

        ## Pré-requisitos

        - **Navegador compatível** — Safari (macOS ou iOS).
        - **Carteira Apple configurada** — ao menos um cartão Visa ou Mastercard no Apple Wallet.
        - **Instalação do app** feita com o parâmetro `domain_name` (passo 1).
        - **Arquivo `.well-known` publicado** e domínio validado junto à Apple (passo 2).
        - **Appmax JS incluído e inicializado** com os callbacks (passo 3).

        Componentes do fluxo:

        - **Script JS:** `https://scripts.appmax.com.br/appmax.min.js` (produção) — em sandbox use `https://scripts.sandboxappmax.com.br/appmax.min.js`.
        - **Domínio validado** junto à Apple.
        - **Requisição de pagamento** para a API Appmax com `appleToken`, `order_id` e `customer_id` válidos.

        ## 1. Autorizar a instalação do aplicativo

        Registre o aplicativo na Appmax informando o domínio:

        ```bash
        curl --location 'https://api.appmax.com.br/app/authorize' \
          --header 'Content-Type: application/json' \
          --header 'Authorization: Bearer SEU_TOKEN' \
          --data '{
            "app_id": "APP_ID",
            "external_key": "EXTERNAL_KEY",
            "url_callback": "URL_CALLBACK",
            "domain_name": "subdominio.dominio.com.br"
          }'
        ```

        ::: info
        O parâmetro `domain_name` deve conter subdomínio + domínio (ex.:
        `minhaloja.minhaintegracao.com.br`). Também é possível enviar vários
        domínios com `domain_names` (array). Mais detalhes em
        [Instalação do Aplicativo](/guides/instalacao).
        :::

        ## 2. Publicar o `.well-known` e validar o domínio

        ### 2.1 Publicar o arquivo `.well-known` (obrigatório em todos os modelos)

        Em **todos** os modelos de integração, cada domínio onde o botão Apple Pay
        será exibido precisa servir, na raiz do site, o arquivo de validação:

        ```
        https://subdominio.dominio.com.br/.well-known/apple-developer-merchantid-domain-association
        ```

        O arquivo, o passo a passo de publicação e o comando para verificar se ele
        está acessível estão em
        [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio).

        ::: info
        O conteúdo do `.well-known` **não** é o Apple Token. O Apple Token é o
        cartão tokenizado, gerado por transação. O `.well-known` é um arquivo
        estático de validação de domínio, vinculado ao `merchantIdentifier` — o
        mesmo arquivo é usado em todos os domínios da sua implementação.
        :::

        ### 2.2 Cadastrar o domínio junto à Apple

        ::: warning
        Este passo muda conforme o modelo de integração.
        :::

        **Modelo integrado (loja integrada / integrador):** você não precisa
        cadastrar o domínio pelo painel da Apple. O registro é feito por API,
        pela Appmax, sob o `merchantIdentifier` da Appmax. Basta que o domínio
        tenha sido informado na instalação (passo 1) e que o arquivo `.well-known`
        (passo 2.1) já esteja publicado — a Apple confere o arquivo ao validar o
        domínio.

        Se o domínio do lojista mudar: o novo domínio é tratado como um domínio
        novo e precisa ser registrado novamente. Não é necessário criar um novo
        merchant.

        **Fluxo direto:** o cadastro do domínio é feito por você, diretamente no
        painel de desenvolvedor da Apple, na sua própria conta. A Apple valida o
        arquivo `.well-known` publicado no passo 2.1 no momento do cadastro.

        ## 3. Configuração e inicialização do script no front-end

        ::: tip
        Esta seção resume o essencial. Para o passo a passo completo — seletores
        do botão, ordem de carregamento em SPA, contrato de DOM e troubleshooting
        — veja [Implementando o botão Apple Pay com o Appmax JS](/api-reference/payments/apple-pay-appmax-js).
        :::

        ::: warning
        O `externalId` esperado aqui é o mesmo `external_id` que você retornou com
        HTTP 200 na etapa de [Instalação do Aplicativo](/guides/instalacao).
        :::

        O `appmax.min.js` faz três coisas:

        - Estiliza o botão com o design oficial da Apple (opcional).
        - Inicializa o botão para abrir a PaymentSheet com os dados do carrinho.
        - Dispara os callbacks de sucesso, erro, atualização e autorização.

        ### 3.1 Inicializar com onSuccess, onError, externalId, onUpdate e onAuthorize

        ```html
        <script>
          // Callback de autorização: recebe o Apple Token e efetiva o pagamento
          const onAuthorize = async (appleToken) => {
            await processPayment(appleToken);
          };
          // Callback de erro
          const onError = (err) => {
            console.error('Erro no Apple Pay:', err);
          };
          // Callback do IP do cliente (a coleta é obrigatória e exige um
          // form[data-appmax-customer] no DOM antes do init)
          const onIp = ({ ip }) => {
            customer.value.ip = ip;
          };
          // Callback de update: deve retornar os dados atuais do checkout
          const onUpdate = () => getCheckoutData();
          onMounted(() => {
            const script = document.createElement('script');
            // Produção: https://scripts.appmax.com.br/appmax.min.js
            // Sandbox:  https://scripts.sandboxappmax.com.br/appmax.min.js
            script.src = 'https://scripts.appmax.com.br/appmax.min.js';
            script.onload = () => {
              if (window.AppmaxScripts) {
                window.AppmaxScripts.init({ externalId, onIp, onError, onUpdate, onAuthorize });
              } else {
                console.error('AppmaxScripts não carregado.');
              }
            };
            document.head.appendChild(script);
          });
        </script>
        ```

        ::: warning
        Atenção aos nomes dos callbacks: o que recebe o Apple Token é o
        `onAuthorize` (não `onAutorize`). A forma posicional
        `init(onSuccess, onError, externalId, onUpdate, onAuthorize)` continua
        funcionando, mas exige essa ordem exata — o objeto de opções não tem esse
        risco.

        A referência normativa do `init` (as duas formas, todos os callbacks e o
        contrato do `onError`) está em
        [Appmax JS](/guides/appmax-js#como-usar). Esta página cobre só o que é
        específico do Apple Pay.
        :::

        **Convenção de nomes:** `externalId` é o parâmetro do `init()` no
        JavaScript; `external_id` é o campo da API e da instalação. É o mesmo
        valor, escrito na convenção de cada lado.

        ### 3.2 Mantendo o onUpdate atualizado (JS puro)

        O `onUpdate` deve retornar os dados atuais do checkout para alimentar a
        PaymentSheet (valor, frete, desconto, parcelas, itens):

        ```js
        function getCheckoutData() {
          const products = Array.from(document.querySelectorAll('.product-item')).map(item => ({
            name:     item.querySelector('.product-name').textContent,
            price:    parseFloat(item.querySelector('.product-price').value),
            quantity: parseInt(item.querySelector('.product-quantity').value, 10)
          }));
          const freight    = parseFloat(document.getElementById('freight').value  || 0);
          const discount   = parseFloat(document.getElementById('discount').value || 0);
          const totalItems = products.reduce((sum, p) => sum + p.price * p.quantity, 0);
          return {
            total:        totalItems + freight - discount,
            freight:      freight,
            discount:     discount,
            installments: parseInt(document.getElementById('installments').value, 10) || 1,
            products:     products
          };
        }
        ```

        ::: info
        Sobre o `orderId` no `onUpdate`: o campo aqui serve à montagem da
        PaymentSheet. O `order_id` obrigatório é o do request de pagamento
        (`POST /v1/payments/apple-pay`) — ele não precisa necessariamente ser
        setado no `onUpdate`, mas precisa ser o `order_id` de uma Order já
        existente na Appmax (veja o passo 4).
        :::

        ## 4. Processar o pagamento

        O pagamento Apple Pay exige três elementos: `customer_id`, `order_id` e o
        `appleToken`. A ordem é: criar customer → criar order → efetivar o pagamento.

        ```js
        async function processPayment(appleToken) {
          // 1. Criar customer — POST /v1/customers  (veja /api-reference/customers/criar-atualizar)
          // 2. Criar order    — POST /v1/orders     (veja /api-reference/orders/criar-pedido)
          // 3. Efetivar pagamento — POST /v1/payments/apple-pay
          const paymentPayload = {
            order_id,        // Order já existente na Appmax
            customer_id,
            payment_data: {
              apple_pay: {
                installments: 3,                         // 1 a 12
                holder_document_number: "22233344450",   // obrigatório
                soft_descriptor: "EXEMPLOLOJA",          // opcional, máx. 13 caracteres
                // Do appleToken.paymentData:
                payment_data: {
                  version: "EC_v1",
                  data: "exemplo",
                  signature: "signature",
                  header: {
                    ephemeralPublicKey: "MFk...==",
                    transactionId: "trx....wvu"
                  }
                },
                // Do appleToken.paymentMethod:
                payment_method: {
                  displayName: "Visa •••• 3714",
                  network: "Visa",
                  type: "credit"
                },
                // Do appleToken.transactionIdentifier:
                transaction_identifier: "trx....wvu"
              }
            }
          };
          const resp = await fetch('https://api.appmax.com.br/v1/payments/apple-pay', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', Authorization: `Bearer SEU_TOKEN` },
            body: JSON.stringify(paymentPayload)
          });
          if (!resp.ok) throw new Error('Falha no pagamento');
          return resp.json();
        }
        ```

        Mapeamento do Apple Token para o payload:

        | Campo do `appleToken` | Vai em |
        | --- | --- |
        | `paymentData` | `payment_data.apple_pay.payment_data` |
        | `paymentMethod` | `payment_data.apple_pay.payment_method` |
        | `transactionIdentifier` | `payment_data.apple_pay.transaction_identifier` |

        ::: info
        Campos obrigatórios: `order_id`, `installments` (1–12) e
        `holder_document_number`. `soft_descriptor` é opcional (máx. 13 caracteres).
        :::

        ::: warning O teto de parcelas do Apple Pay é 12 — no cartão pode ser maior
        Aqui o limite é **fixo em 12**. No endpoint de
        [cartão de crédito](/api-reference/payments/cartao-credito) o teto é o
        configurado para a loja, que por padrão também é 12, mas **pode ser maior**
        quando ela tem parcelamento estendido habilitado.

        Na prática: uma loja com parcelamento estendido aceita 18x no cartão e
        **recusa** 18x no Apple Pay. Se você oferece as duas formas de pagamento na
        mesma tela, limite as opções de parcela do Apple Pay a 12 — ou o comprador
        escolhe um valor que vai ser recusado só na hora de pagar.
        :::

        ## Validação em tempo real (Merchant Session)

        A validação do domínio junto à Apple não é única — a Apple revalida o
        domínio/merchant a cada transação, no momento em que a PaymentSheet é
        aberta (a Merchant Session é obtida em tempo real). É um processo rápido e
        transparente para o usuário; o Appmax JS cuida disso automaticamente. Veja
        [`POST /v1/apple-pay/merchant-session`](/api-reference/payments/apple-pay-merchant-session).

        ## Testes e Sandbox

        ::: warning
        Não é possível testar o Apple Pay em `localhost`. O Apple Pay exige um
        domínio público, com HTTPS válido e devidamente registrado/configurado. A
        Apple não valida domínios não públicos.
        :::

        ::: warning Não existe merchant session de sandbox
        A validação de domínio/merchant é sempre feita contra a infraestrutura de
        **produção** da Apple, mesmo em ambiente de sandbox — não existe uma
        segunda instância só para teste. Trate sandbox e produção como
        assimétricos nesse ponto específico: o script de sandbox aponta para o
        mesmo endpoint de validação que o de produção.
        :::

        Cartões de teste da Apple: a referência oficial é
        [developer.apple.com/apple-pay/sandbox-testing](https://developer.apple.com/apple-pay/sandbox-testing)
        — que exige uma Sandbox Apple Account própria.

        ::: warning Modelo integrado
        Como o pagamento roda sob a conta Apple da Appmax, não é possível usar
        cartões de teste da Apple da sua própria conta (não conseguimos provisionar
        credenciais de teste de terceiros na nossa conta Apple, por segurança).
        Nesse caso, a forma prática de testar é usar um cartão real e
        cancelar/estornar as ordens pelo admin.
        :::

        ## Ambientes

        |  | Sandbox | Produção |
        | --- | --- | --- |
        | Script JS | `https://scripts.sandboxappmax.com.br/appmax.min.js` | `https://scripts.appmax.com.br/appmax.min.js` |
        | API | `https://api.sandboxappmax.com.br` | `https://api.appmax.com.br` |

        ## Perguntas frequentes (FAQ)

        **O `appleToken` é usado para quê?**

        É o cartão tokenizado pela Apple. Ele precisa ser enviado ao
        `POST /v1/payments/apple-pay` (nos campos `payment_data`, `payment_method`,
        `transaction_identifier`). Sem ele, o pagamento não é processado. É de uso
        único por transação.

        **Preciso hospedar o arquivo `.well-known` em cada domínio?**

        Sim, em **todos** os modelos de integração — isso não muda entre o modelo
        integrado e o fluxo direto. O que muda entre os modelos é apenas quem
        cadastra o domínio junto à Apple (a Appmax, por API, no modelo integrado;
        você mesmo, pelo painel da Apple, no fluxo direto). Veja
        [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio).

        **O `order_id` do `onUpdate` precisa ser de uma Order real da Appmax?**

        O `order_id` obrigatório é o do request de pagamento e precisa ser de uma
        Order existente (criada via `POST /v1/orders`). Ele não precisa vir do
        `onUpdate`, mas precisa estar no payload do pagamento.

        **Se o domínio do lojista mudar, preciso de um novo merchant?**

        Não. O novo domínio é registrado novamente na instalação existente.

        **Existe um token/arquivo por lojista, ou um geral?**

        O Apple Token é sempre por transação. O conteúdo do arquivo de validação de
        domínio é o mesmo para todos os domínios vinculados ao `merchantIdentifier`
        gerido pela Appmax — não é gerado por lojista. Ainda assim, cada domínio
        precisa hospedar sua própria cópia desse mesmo arquivo (veja
        [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio)). O que
        é por lojista são as credenciais (`client_id`/`client_secret`) e o
        `external_id`.

        **A validação do domínio demora?**

        O registro do domínio é rápido (chamada síncrona). A validação da Merchant
        Session ocorre em tempo real, a cada transação — também rápida.

        **Consigo testar pelo sandbox da Appmax?**

        É preciso um domínio público registrado e configurado. No modelo integrado,
        o caminho prático é cartão real + estorno (veja Testes e Sandbox).

        ## Conclusão — checklist

        1. Autorizar a instalação com `domain_name`.
        2. Publicar o arquivo `.well-known` em cada domínio (obrigatório em todos os modelos) e cadastrar o domínio junto à Apple — Appmax por API (modelo integrado) ou você mesmo (fluxo direto). Veja [Configuração de domínios para Apple Pay](/api-reference/payments/apple-pay-dominio).
        3. Incluir e inicializar o Appmax JS com `onSuccess`, `onError`, `externalId`, `onUpdate`, `onAuthorize`.
        4. Criar customer → criar order → processar o `appleToken` via `POST /v1/payments/apple-pay`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApplePayPaymentRequest'
            examples:
              default:
                value:
                  order_id: 123
                  customer_id: 456
                  payment_data:
                    apple_pay:
                      installments: 3
                      holder_document_number: '22233344450'
                      soft_descriptor: EXEMPLOLOJA
                      payment_data:
                        data: exemplo
                        signature: signature
                        header:
                          publicKeyHash: 'Z...'
                          ephemeralPublicKey: 'MFk...=='
                          transactionId: trx....wvu
                        version: EC_v1
                      payment_method:
                        displayName: Visa 3714
                        network: Visa
                        type: credit
                      transaction_identifier: trx....wvu
      responses:
        '201':
          description: Pagamento Apple Pay aprovado.

  /v1/payments/google-pay:
    post:
      operationId: payGooglePay
      tags: [Payments]
      summary: Pagamento com Google Pay
      description: |
        O Google Pay é a carteira digital do Google. Com ele, o comprador paga com
        um cartão já salvo em sua conta Google — em Android ou em qualquer
        navegador suportado — sem informar número, validade ou CVV: seleciona o
        cartão na folha de pagamento do Google e confirma. O cartão trafega
        cifrado do Google diretamente para a adquirente; nem a loja nem a Appmax
        têm acesso ao número, o que elimina do checkout o formulário de cartão e
        a exposição a dados sensíveis.

        Na **App Store da Appmax**, o Google Pay é um meio de pagamento
        disponível a qualquer aplicativo instalado por um lojista. Ele utiliza as
        mesmas [credenciais do merchant](/guides/autenticacao), o mesmo fluxo de
        [cliente](/api-reference/customers/criar-atualizar) e
        [pedido](/api-reference/orders/criar-pedido) e o mesmo
        [estorno](/api-reference/refunds/criar-estorno) já empregados para
        cartão de crédito,
        Pix e
        Boleto. A única diferença está na
        origem dos dados do cartão, que passam a vir do Google em vez de um
        formulário da loja.

        **Esta página** cobre o endpoint: o que a loja precisa ter antes de
          começar, como autorizar a instalação, como montar o payload a partir
          do que o Google devolve e o que fazer com cada resposta.
        
        **[Implementando o botão Google Pay com o Appmax JS](/api-reference/payments/google-pay-appmax-js)**
          cobre o navegador: onde ancorar o botão, como inicializar o script e
          como os callbacks `onUpdate` e `onAuthorize` se comportam.

        Para quem já integrou o [Apple Pay](/api-reference/payments/apple-pay),
        o caminho é o mesmo e mais curto: o `init()` do Appmax JS é o mesmo, e
        não há domínio a cadastrar nem arquivo `.well-known` a publicar.

        ::: warning Compatibilidade
        O botão do Google Pay é exibido em Chrome, Firefox, Safari, Edge, Opera
        e UC Browser, em páginas servidas por HTTPS, tanto em desktop quanto em
        dispositivos móveis. O comprador precisa estar autenticado em uma conta
        Google com ao menos um cartão Visa, Mastercard ou Elo salvo.

        | Contexto | O que o comprador vê |
        | --- | --- |
        | Android, Windows ou Linux em navegador suportado | botão do Google Pay |
        | iOS ou macOS com Apple Pay disponível | botão do Apple Pay — a seleção é feita automaticamente pelo script |
        | Navegador sem suporte à Google Pay API | nenhum botão; o bloco permanece oculto |

        Em **sandbox**, o Google exibe sua própria lista de cartões de teste
        assim que a folha abre, em qualquer navegador suportado. Isso permite
        validar botão, folha e callbacks sem um dispositivo físico — veja
        "Testes e Sandbox".
        :::

        ## Como funciona

        A integração é realizada por meio do [Appmax JS](/guides/appmax-js), o
        mesmo script empregado na tokenização de cartão e no
        [Apple Pay](/api-reference/payments/apple-pay-appmax-js). A loja ancora
        um botão e implementa dois callbacks; o script executa as demais etapas.

        A folha do Google abre **dentro de um iframe da Appmax**, e não na
        página da loja. O Google valida a origem de onde a folha é aberta contra
        o domínio aprovado em seu console; ao abrir no iframe da Appmax, um
        único domínio aprovado atende a todos os integradores. Por isso não é
        necessário criar perfil no Google Pay & Wallet Console, cadastrar
        domínio nem configurar credenciais do Google ou da adquirente.

        ::: info Diferença em relação ao Apple Pay
        Não há equivalente ao arquivo `.well-known` nem ao parâmetro
        `domain_name` da instalação. Se o aplicativo já está instalado para
        cartão, Pix ou Apple Pay, nenhuma nova autorização é necessária.
        :::

        | O que fica com você | O que fica com a Appmax |
        | --- | --- |
        | Ancorar o botão e chamar o `init()` com `externalId`, `onUpdate` e `onAuthorize` | Montar a folha do Google e abrir o iframe — domínio aprovado e configuração da folha |
        | Devolver o total do carrinho no `onUpdate` | Exibir o botão oficial do Google e tratar o clique |
        | Enviar o `paymentMethodData` do `onAuthorize` ao seu backend e chamar `POST /v1/payments/google-pay` | Validar o token, identificar o tipo de cartão e autorizar na adquirente |

        ## Glossário

        | Termo | Descrição |
        | --- | --- |
        | Google Pay | Carteira digital do Google. O comprador paga com um cartão salvo em sua conta Google, sem digitá-lo. |
        | Folha (payment sheet) | Interface do Google na qual o comprador seleciona o cartão salvo e confirma o pagamento. Aberta em um iframe da Appmax. |
        | `paymentMethodData` | Objeto devolvido pelo Google após a confirmação e recebido no `onAuthorize`. É enviado à Appmax integralmente, em `payment_data.google_pay.payment_data`. Gerado por transação. |
        | Token | String em `tokenizationData.token`, dentro do `paymentMethodData`, com o cartão cifrado para a adquirente. Repassado sem alteração. |
        | `onUpdate` | Callback implementado pela loja: retorna o total do carrinho antes da abertura da folha. |
        | `onAuthorize` | Callback implementado pela loja: recebe o `paymentMethodData` após a confirmação do comprador. |
        | `external_id` | UUID que identifica o par (app instalado + loja). Gerado na instalação e usado pelo `init()` do Appmax JS. Veja [Instalação do aplicativo](/guides/instalacao). |
        | `pay_reference` | Identificador da cobrança, retornado no `201`. Utilizado na conciliação e no [estorno](/api-reference/refunds/criar-estorno). |

        ## Pré-requisitos

        Do lado do comprador, para o botão aparecer:

        - **Conta Google** com ao menos um cartão Visa, Mastercard ou Elo salvo.
        - **Navegador suportado** — Chrome, Firefox, Safari, Edge, Opera ou UC Browser.
        - **Página servida por HTTPS.**

        Do lado da integração:

        - **Instalação do app** autorizada, com o `external_id` disponível (passo 1).
        - **Appmax JS incluído e inicializado** com `onUpdate` e `onAuthorize` (passo 2).
        - **Token Bearer do merchant**, obtido em `POST /oauth2/token` com as credenciais do merchant — não as do aplicativo. Veja [Autenticação](/guides/autenticacao).

        Componentes do fluxo:

        - **Script JS:** `https://scripts.appmax.com.br/appmax.min.js` (produção) — em sandbox use `https://scripts.sandboxappmax.com.br/appmax.min.js`. Veja [Ambientes](/guides/ambientes).
        - **Requisição de pagamento** para a API Appmax com o `paymentMethodData`, `order_id` e `customer_id` válidos.

        ## 1. Autorizar a instalação do aplicativo

        O fluxo é o mesmo de qualquer outro meio de pagamento da API: o lojista
        autoriza a instalação (`POST /app/authorize` → redirecionamento →
        `POST /app/client/generate`), a Appmax aciona a URL de validação do
        aplicativo e, ao final, são entregues as
        [credenciais do merchant](/guides/autenticacao) e o `external_id`
        daquela instalação. Veja [Instalação do aplicativo](/guides/instalacao)
        para o fluxo completo, com diagrama de sequência de todas as chamadas.

        ```bash
        curl --location 'https://api.appmax.com.br/app/authorize' \
          --header 'Content-Type: application/json' \
          --header 'Authorization: Bearer SEU_TOKEN' \
          --data '{
            "app_id": "APP_ID",
            "external_key": "EXTERNAL_KEY",
            "url_callback": "URL_CALLBACK"
          }'
        ```

        ::: info
        Diferente do Apple Pay, o Google Pay não utiliza o parâmetro
        `domain_name`. O passo a passo completo da instalação está em
        [Instalação do aplicativo](/guides/instalacao) e
        [Valide sua URL de validação](/guides/validar-url). O `external_id` é
        detalhado em [`external-id`](/guides/external-id).
        :::

        ## 2. Incluir e inicializar o Appmax JS

        ::: tip
        Para o passo a passo completo — seletores
        do botão, ordem de carregamento em SPA, contrato do `onUpdate`, o
        comportamento do `onAuthorize` e troubleshooting — veja
        [Implementando o botão Google Pay com o Appmax JS](/api-reference/payments/google-pay-appmax-js).
        :::

        ::: warning
        O `externalId` esperado aqui é o mesmo `external_id` que você retornou
        com HTTP 200 na etapa de
        [Instalação do aplicativo](/guides/instalacao#health-check).
        :::

        O `appmax.min.js` faz três coisas:

        - Monta a folha do Google e a abre em um iframe da Appmax — você não configura domínio nem credenciais do Google.
        - Renderiza o botão oficial do Google na âncora `[data-appmax-google-pay]` e trata o clique.
        - Dispara os callbacks de atualização (`onUpdate`), autorização (`onAuthorize`) e erro (`onError`).

        ### 2.1 Inicializar com externalId, onIp, onError, onUpdate e onAuthorize

        ```html
        <!-- Gatilho da coleta de IP: obrigatório, e precisa existir antes do init -->
        <form data-appmax-customer hidden></form>

        <div data-appmax-checkout data-appmax-merchant-name="Nome da loja">
          <div data-appmax-google-pay-wrapper>
            <button data-appmax-google-pay style="height: 48px; width: 100%"></button>
          </div>
        </div>

        <script>
          // Produção: https://scripts.appmax.com.br/appmax.min.js
          // Sandbox:  https://scripts.sandboxappmax.com.br/appmax.min.js
          const onUpdate = () => ({ total: 150.90 }); // em reais, como número
          const onAuthorize = async (paymentMethodData) => {
            await processGooglePayPayment(paymentMethodData); // veja o passo 3
          };
          const checkoutData = {
            customer: {}
          };

          const onIp = ({ ip }) => {
            checkoutData.customer.ip = ip;
          };
          const onError = ({ code, message, stage }) => console.error('Erro no Google Pay:', code, stage, message);

          window.AppmaxScripts.init({ externalId, onIp, onError, onUpdate, onAuthorize });
        </script>
        ```

        ::: warning Valores do `onUpdate` são informados em reais

         O `onUpdate` é um contrato do **Appmax JS**, e não da API. Por isso, o campo `total` deve ser informado em **reais**, enquanto os valores enviados para a API são informados em **centavos**.

        Por exemplo, para uma cobrança de **R$ 150,90**:

        - No `onUpdate`: `total: 150.90`
        - Em `POST /v1/orders`: `15090`, pois a API utiliza valores monetários inteiros em centavos.

        Essa diferença é importante porque utilizar o formato da API no `onUpdate` não gera um erro, mas altera o valor exibido ao comprador. Por exemplo, `total: 15090` será interpretado como **R$ 15.090,00**.

        Além disso, o corpo da requisição `POST /v1/payments/google-pay` não possui um campo para informar o valor da cobrança: o pagamento utiliza **o valor total do pedido**. Dessa forma, um `total` incorreto no `onUpdate` pode fazer com que o comprador veja um valor na folha de pagamento diferente do valor efetivamente cobrado.

        Para mais detalhes, consulte [O que o `onUpdate` deve retornar](/api-reference/payments/google-pay-appmax-js#o-que-o-onupdate-deve-retornar) e [Valores monetários](/api-reference/introduction#valores-monetarios).
        :::


        ::: info
        No Google Pay, a folha já está fechada quando o `onAuthorize` é
        invocado e o valor retornado não é utilizado — a aprovação ou recusa é
        tratada na sua tela, a partir da resposta do passo 3. Isso difere do
        Apple Pay; veja
        [Como o `onAuthorize` se comporta](/api-reference/payments/google-pay-appmax-js#como-o-onauthorize-se-comporta).
        :::

        ## 3. Processar o pagamento

        O `onAuthorize` recebe o `paymentMethodData`, que deve ser enviado ao
        backend da loja. O backend executa a sequência padrão da API: cliente,
        pedido e pagamento.

        1. [`POST /v1/customers`](/api-reference/customers/criar-atualizar) — cria ou atualiza o cliente; armazene `data.customer.id`.
        2. [`POST /v1/orders`](/api-reference/orders/criar-pedido) — cria o pedido; armazene `data.order.id`.
        3. `POST /v1/payments/google-pay` — efetua o pagamento, com o `customer_id` e o `order_id` dos passos anteriores.

        O objeto recebido no `onAuthorize` tem esta forma:

        ```json
        {
          "type": "CARD",
          "description": "Visa •••• 1234",
          "info": {
            "cardNetwork": "VISA",
            "cardDetails": "1234",
            "assuranceDetails": {
              "accountVerified": true,
              "cardHolderAuthenticated": true
            }
          },
          "tokenizationData": {
            "type": "PAYMENT_GATEWAY",
            "token": "{\"signature\":\"MEUCIQ...\",\"protocolVersion\":\"ECv2\",\"signedMessage\":\"{...}\"}"
          }
        }
        ```

        É esse objeto, **integral e sem modificação**, que vai em
        `payment_data.google_pay.payment_data`.

        ::: warning O `gatewayMerchantId` é configurado internamente pela Appmax

        Não envie, substitua ou tente definir esse identificador no payload.
        Encaminhe o `paymentMethodData` recebido no `onAuthorize` integralmente
        e sem alterações, em `payment_data`. O payload público é o do exemplo
        abaixo — nenhum campo além desses é necessário.

        :::

        ```js
        async function processGooglePayPayment(paymentMethodData) {
          // 1. Criar customer — POST /v1/customers  (veja /api-reference/customers/criar-atualizar)
          // 2. Criar order    — POST /v1/orders     (veja /api-reference/orders/criar-pedido)
          // 3. Efetivar pagamento — POST /v1/payments/google-pay
          const paymentPayload = {
            order_id,        // Order já existente na Appmax
            customer_id,
            payment_data: {
              google_pay: {
                installments: 2,                         // 1 a 12
                holder_document_number: "51425014038",   // obrigatório
                soft_descriptor: "MINHALOJA",            // opcional, máx. 13 caracteres
                payment_data: paymentMethodData          // o objeto do onAuthorize, inteiro
              }
            }
          };
          const resp = await fetch('https://api.appmax.com.br/v1/payments/google-pay', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', Authorization: `Bearer SEU_TOKEN` },
            body: JSON.stringify(paymentPayload)
          });
          if (!resp.ok) throw new Error('Falha no pagamento');
          const { data } = await resp.json();
          // Guarde data.payment.pay_reference no seu banco. Ao front-end,
          // devolva só o status — nunca o pay_reference, o documento ou o token.
          return { status: 'aprovado' };
        }
        ```

        ::: danger Atenção

        - **Não aplique `JSON.parse` ao `token`.** O `token` é uma string e deve ser enviado exatamente como recebido. O parse é realizado pela Appmax.
        - **Não remova campos do objeto.** Envie o objeto completo, incluindo `info`. Esses dados são utilizados pela Appmax para validar a transação e compor o comprovante, incluindo a bandeira e os últimos dígitos do cartão. O número completo do cartão não trafega.

        :::

        ::: warning Integração via backend

        A requisição deve ser realizada **exclusivamente pelo backend**. Credenciais e dados sensíveis, como o [token Bearer](/guides/autenticacao), nunca devem ser expostos ao navegador.

        O front-end deve receber apenas o resultado necessário para a interface: status da operação (aprovado ou recusado) e, opcionalmente, uma mensagem. Dados como credenciais, `pay_reference`, `holder_document_number` e respostas completas da API devem permanecer no backend.

        :::


        ::: info
        - Campos obrigatórios: `order_id`, `customer_id`, `installments` (1–12),
        `holder_document_number` e `payment_data`.
        
        - `soft_descriptor` é opcional
        (máx. 13 caracteres).
        :::

        A resposta `201` traz o pagamento em `data.payment`:

        ```json
        {
          "data": {
            "payment": {
              "pay_reference": "test_localhost_1_2b5c0",
              "upsell_hash": "92447142021040-1704397469-0391056001704397469",
              "cashback": null
            }
          }
        }
        ```

        - `pay_reference` identifica a cobrança e deve ser armazenado para
        conciliação e estorno.
        - `upsell_hash` é utilizado no upsell. 
        - O
        status do pedido é informado por webhooks e pela
        [consulta do pedido](/api-reference/orders/consultar-pedido), como em
        qualquer pagamento com cartão.

        Erros retornam `{ "errors": { "message": "..." } }`, exceto o `403`,
        que usa o envelope antigo. Os códigos e mensagens estão em
        **Respostas**, abaixo. Recusas da adquirente chegam como
        `Pagamento não autorizado: <motivo>`, com o status correspondente ao
        motivo — exiba o motivo ao comprador e permita nova tentativa.

        ## Testes e Sandbox

        ::: tip Em sandbox não é necessário cartão real
        Assim que a folha abre, o Google exibe sua própria lista de cartões de
        teste, em qualquer navegador suportado — inclusive desktop. Não é
        preciso provisionar cartão no Google Wallet para começar.
        :::

        Em sandbox, o Google retorna dados de cartão simulados e nenhuma
        cobrança é efetuada. É possível exercitar o fluxo completo — botão,
        folha, token e chamada de pagamento — sem envolver o cartão do
        comprador.

        ::: warning
        O ambiente é definido pela Appmax, conforme a URL do script incluído.
        Utilize script, API e credenciais de **sandbox** em conjunto
        ([Ambientes](/guides/ambientes)); a combinação de ambientes distintos
        resulta em `401`.
        :::

        O que dá para validar em sandbox:

        - O botão e a folha do Google, com os cartões de teste.
        - Os callbacks `onUpdate`, `onAuthorize` e `onError`.
        - O contrato da chamada de pagamento e o tratamento dos erros.
        - A criação de customer e order.

        Antes da publicação em produção, homologue com um pagamento real em um
        dispositivo com cartão salvo no Google Wallet.

        ## Ambientes

        |  | Sandbox | Produção |
        | --- | --- | --- |
        | Script JS | `https://scripts.sandboxappmax.com.br/appmax.min.js` | `https://scripts.appmax.com.br/appmax.min.js` |
        | API | `https://api.sandboxappmax.com.br` | `https://api.appmax.com.br` |
        | Autenticação | `https://auth.sandboxappmax.com.br` | `https://auth.appmax.com.br` |
        | Cartão na folha | lista de teste do Google | cartão do comprador |
        | Cobrança | não há | real |

        Script e `external_id` devem pertencer ao **mesmo** ambiente — veja
        [Ambientes](/guides/ambientes).

        ## Perguntas frequentes (FAQ)

        **A folha não abre e nada aparece no console.**

        Se o checkout da loja é executado dentro de um iframe, esse iframe
        precisa de `allow="payment"`. Sem isso o navegador bloqueia a folha
        silenciosamente.

        **O comprador fechou a folha. É necessário exibir erro?**

        Não. O Google retorna `CANCELED`, que corresponde a desistência — basta
        permitir um novo clique. O mesmo código é retornado quando o Google
        recusa o estabelecimento ou a conta e exibe o erro dentro da folha; do
        ponto de vista da página da loja, os dois casos são indistinguíveis.

        **Foi retornado `400` com `Invalid Google Pay token`. O problema está no cartão?**

        Não. Na quase totalidade dos casos o `paymentMethodData` foi alterado
        antes do envio — um `JSON.parse` no `token` ou campos removidos.
        Repasse o objeto exatamente como recebido no `onAuthorize`. Se o erro
        persistir, acione o suporte da Appmax.

        **É necessário hospedar algum arquivo ou cadastrar domínio?**

        Não. Não há equivalente ao
        [`.well-known` do Apple Pay](/api-reference/payments/apple-pay-dominio),
        e não é necessário perfil no Google Pay & Wallet Console. A folha abre
        em um iframe da Appmax, cujo domínio já está aprovado.

        **É possível parcelar?**

        Sim. Informe `installments` de 1 a 12 no corpo do pagamento. Juros e
        valores por parcela seguem o retornado por
        [Parcelas](/api-reference/payments/parcelas) para a loja. O
        `installments` do `onUpdate` não é utilizado pelo Google Pay.

        **Como é feito o estorno?**

        Pelo mesmo endpoint dos demais meios:
        [Criar estorno](/api-reference/refunds/criar-estorno), com o
        `pay_reference` retornado no `201`. A transação é de cartão de crédito
        para todos os efeitos.

        **O comprovante mostra os quatro últimos dígitos de qual cartão?**

        Dos que o Google devolve em `info.cardDetails`. Como o número do cartão
        não trafega nesta integração, é a única fonte — por isso o campo não
        deve ser removido do objeto.

        **Posso oferecer Apple Pay e Google Pay na mesma página?**

        Sim. Ancore os seletores das duas carteiras e use o mesmo `init()`. O
        script exibe o Apple Pay onde ele está disponível e o Google Pay nos
        demais navegadores suportados; o `onAuthorize` recebe o token da
        carteira utilizada — veja
        [Implementando o botão Apple Pay com o Appmax JS](/api-reference/payments/apple-pay-appmax-js).

        ## Conclusão — checklist

        1. Autorizar a instalação do aplicativo e armazenar o `external_id` — [Instalação do aplicativo](/guides/instalacao).
        2. Incluir o Appmax JS do ambiente correto ([Ambientes](/guides/ambientes)) e ancorar o botão com `data-appmax-google-pay`, dentro de um `data-appmax-checkout` com `data-appmax-merchant-name` — veja [Implementando o botão Google Pay](/api-reference/payments/google-pay-appmax-js).
        3. Invocar `window.AppmaxScripts.init()` com `externalId`, `onIp`, `onError`, `onUpdate` e `onAuthorize`.
        4. Retornar o carrinho no `onUpdate` em reais, com valores numéricos — não em centavos, diferente dos endpoints da API.
        5. No `onAuthorize`, enviar o `paymentMethodData` ao backend, integral e sem parse do `token`.
        6. Criar [cliente](/api-reference/customers/criar-atualizar) → criar [pedido](/api-reference/orders/criar-pedido) → processar em `POST /v1/payments/google-pay`, sempre pelo backend, com o token do merchant ([Autenticação](/guides/autenticacao)).
        7. Tratar a folha fechada como desistência, sem mensagem de erro.
        8. Se o checkout é executado em iframe, garantir `allow="payment"` nele.
        9. Validar em sandbox e homologar com um pagamento real antes da publicação em produção.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GooglePayPaymentRequest'
            examples:
              default:
                value:
                  order_id: 3
                  customer_id: 7
                  payment_data:
                    google_pay:
                      installments: 2
                      holder_document_number: '51425014038'
                      soft_descriptor: MINHALOJA
                      payment_data:
                        type: CARD
                        description: 'Visa •••• 1234'
                        info:
                          cardNetwork: VISA
                          cardDetails: '1234'
                          assuranceDetails:
                            accountVerified: true
                            cardHolderAuthenticated: true
                        tokenizationData:
                          type: PAYMENT_GATEWAY
                          token: '{"signature":"MEUCIQ...","protocolVersion":"ECv2","signedMessage":"{...}"}'
      responses:
        '201':
          description: 'Pagamento Google Pay aprovado. `data.payment.pay_reference` identifica a cobrança.'
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      payment:
                        pay_reference: test_localhost_1_2b5c0
                        upsell_hash: '92447142021040-1704397469-0391056001704397469'
                        cashback: null
        '400':
          description: 'Requisição recusada. `errors.message` indica o motivo: `Invalid Google Pay token` (o `paymentMethodData` foi alterado antes do envio — reenvie-o exatamente como recebido no `onAuthorize`), `Document number invalid`, `Order Already Paid`, `Order is Cancelled` ou `Order is Refunded`.'
        '401':
          description: Token ausente, expirado ou emitido com credenciais do aplicativo em vez do merchant.
        '403':
          description: 'Documento em lista restritiva. Envelope distinto dos demais erros: `{ success: false, error, data: { errorCode, message }, status: 403 }`.'
        '404':
          description: 'Pedido não encontrado (`Order not found`). Verifique o `order_id` e se ele pertence ao merchant do token.'
        '422':
          description: 'Validação de campos — `errors.message` é um objeto `{ "campo": ["motivo"] }` — ou cartão não aceito pela Appmax (`Google Pay token was not authenticated...`). Neste segundo caso, trate como recusa e ofereça outro meio de pagamento.'
        '500':
          description: 'Falha inesperada (`Error to process Google Pay payment`). Repita a chamada; se persistir, acione o suporte informando o `order_id`.'

  /v1/payments/tokenize:
    post:
      operationId: tokenizeCard
      tags: [Payments]
      summary: Tokenização de cartão de crédito
      description: |
        Substitui os dados reais do cartão (número, CVV, validade) por um token
        único e seguro, permitindo realizar transações sem expor as informações
        originais.

        ::: danger Tokenização server-side exige PCI-DSS
        Quando você tokeniza pelo seu backend, o seu servidor toca o número de
        cartão e o CVV em claro. Isso **só é permitido** se sua arquitetura
        está em escopo PCI-DSS. Se você não tem certeza, use o caminho via CDN
        — o script Appmax JS isola os dados sensíveis do seu servidor.
        :::

        Existem dois caminhos de autenticação distintos para este endpoint:
        via CDN (header `external-id`) ou via backend (header `Authorization:
        Bearer`). Veja [`external-id`](/guides/external-id) para o contexto.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenizeRequest'
            examples:
              default:
                value:
                  payment_data:
                    credit_card:
                      number: '4444222222222222'
                      cvv: '123'
                      expiration_month: '12'
                      expiration_year: '28'
                      holder_name: John Doe
      responses:
        '201':
          description: Token gerado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      token: 422146c7523a46119d6073ea56193913
        '401':
          description: Nem `external-id` nem `Authorization` foram enviados.
        '404':
          description: '`external-id` enviado mas não corresponde a nenhuma instalação ativa.'
        '422':
          description: Body inválido (campos faltando ou em formato incorreto).

  /v1/payments/installments:
    post:
      operationId: calculateInstallments
      tags: [Payments]
      summary: Cálculo de parcelas
      description: |
        Retorna o valor total com os juros aplicados em cada modalidade de
        parcelamento. A integração deve realizar a divisão para obter o valor
        de cada parcela.

        ::: info
        A quantidade e os valores das parcelas podem ser personalizados de 1 a
        12 parcelas. Os valores são configuráveis individualmente para cada
        merchant.
        :::

        **Modalidades**
        - **PP** (Simples por parcela): a taxa de juros é aplicada diretamente sobre o valor de cada parcela.
        - **AM** (Financiamento): a taxa de juros é calculada mensalmente sobre o saldo devedor total.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstallmentsRequest'
            examples:
              default:
                value:
                  installments: 10
                  total_value: 10000
                  settings: true
      responses:
        '200':
          description: Cálculo realizado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      installments:
                        '1': { total: 10000 }
                        '2': { total: 10200 }
                        '3': { total: 10404 }
                        '10': { total: 11600 }
                      settings:
                        modality: PP
                        max_installments: 12
                        min_installment_value: 500

  /v1/orders/refund-request:
    post:
      operationId: createRefund
      tags: [Refunds]
      summary: Criar um estorno
      description: |
        Cria uma solicitação de estorno (total ou parcial) para um pedido.

        ::: warning Estorno de boleto não é suportado via API
        Pedidos pagos com **boleto** não podem ser estornados por este
        endpoint. O estorno de boleto só pode ser feito pelo **painel
        administrativo da Appmax**, onde são informados os dados bancários do
        cliente para o ressarcimento do valor.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundRequest'
            examples:
              total:
                summary: Estorno total
                value:
                  order_id: 1
                  type: total
              partial:
                summary: Estorno parcial
                value:
                  order_id: 1
                  type: partial
                  value: 5000
      responses:
        '201':
          description: Estorno aceito.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      message: Refund request accepted
        '400':
          description: Erro de validação ou regra de negócio.
        '404':
          description: Pedido não encontrado.
        '500':
          description: Erro interno ao processar o estorno.

  /v1/apple-pay/merchant-session:
    post:
      operationId: appleMerchantSession
      tags: [Payments]
      summary: Apple Pay merchant session
      description: |
        Obtém a Merchant Session assinada pela Apple para iniciar o fluxo Apple
        Pay no navegador. O retorno é repassado a
        `session.completeMerchantValidation(...)` da `ApplePaySession`.

        ::: warning Header `external-id` é obrigatório
        Diferente do que uma versão anterior desta página chegou a afirmar,
        este endpoint **exige** o header `external-id` — além do campo
        `external_id` no body. Envie os dois com o mesmo valor. Sem o header,
        a requisição falha com parâmetro ausente.
        :::
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MerchantSessionRequest'
            examples:
              default:
                value:
                  store_url: checkout.minhaloja.com.br
                  external_id: 37bb0791-ee0b-457d-860c-186e32978bcd
      responses:
        '200':
          description: Merchant Session retornada (opaque para o integrador).
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      epochTimestamp: 1715623200000
                      expiresAt: 1715626800000
                      merchantSessionIdentifier: 'SSH...'
                      nonce: abc12345
                      merchantIdentifier: merchant.com.exemplo
                      domainName: checkout.minhaloja.com.br
                      displayName: Appmax
                      signature: '...'
        '400':
          description: Domínio enviado não está validado para Apple Pay nesta instalação.
        '422':
          description: '`store_url` ou `external_id` ausente.'
        '500':
          description: Apple recusou a sessão.

      # NOTA — resposta embrulhada em "data": o corpo real vem como
      # { "data": { ...campos... } }, não os campos na raiz. Documentado
      # explicitamente acima porque o formato "sem data" já circulou nesta
      # página e causa erro de parsing em quem reimplementa o cliente.
      # displayName "Appmax": no modelo integrado, a merchant session roda sob
      # o merchant Apple da Appmax — o comprador sempre vê "Appmax" na tela de
      # confirmação, não o nome da loja. Ver /api-reference/payments/apple-pay-dominio.

  /v1/recipient:
    post:
      operationId: createRecipient
      tags: [Split]
      summary: Criar um recebedor
      description: |
        Cadastra um recebedor para receber valores de [split de
        pagamentos](/guides/split-pagamentos).

        O cadastro é só a primeira etapa. Para completar o fluxo, o recebedor
        também precisa concluir o
        [facematch (KYC)](/api-reference/split/facematch-link) — enquanto o KYC
        não é aprovado, ele não participa de splits. Os três status possíveis do
        onboarding são:

        | Status | Significado |
        | --- | --- |
        | `Awaiting face match completion` | Aguardando o recebedor concluir a etapa de facematch (KYC). |
        | `Onboarding on verification` | Recebemos todos os dados do formulário + facematch e estamos analisando as informações. |
        | `Onboarding completed` | Cadastro completo e aprovado. |

        Referência completa em
        [Status do split de pagamentos](/guides/split-status#status-do-recebedor-recipient).

        A conta bancária (`bankAccount`) pode ser enviada já no cadastro. Os
        códigos de banco (COMPE) homologados e os tipos de conta aceitos estão
        em [Bancos homologados](/guides/bancos-homologados).

        ::: warning
        Uma vez criado, o recebedor **não pode ser excluído via API**. Parte dos
        dados (titular, endereço e conta bancária) pode ser alterada em
        [Editar recebedor](/api-reference/split/editar-recebedor);
        CPF, e-mail, razão social e CNPJ não podem ser alterados — revise-os
        antes de enviar.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRecipientRequest'
      responses:
        '201':
          description: Recebedor criado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      recipient_hash:
                        type: string
                        description: HASH do recebedor criado.
                        example: bbd96f57-3e3c-5ea1-aa60-cb2518578faf

        '403':
          description: Token ausente ou inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Token de autenticação ausente
                        example: Missing Authentication Token
        '422':
          description: Erro de validação dos dados enviados ou regra de negócio.
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Erro de validação
                    properties:
                      data:
                        type: object
                        properties:
                          message:
                            type: object
                            description: Mensagens de erro detalhadas para cada campo inválido ou ausente.
                  - type: object
                    title: Erro de negócio
                    properties:
                      error:
                        type: string
                        description: Motivo da recusa.
                        example: User already exists, invalid value for enableAutomaticWithdraw, automatic withdraw cannot be enabled.
              examples:
                camposObrigatorios:
                  summary: Campos obrigatórios ausentes
                  value:
                    data:
                      message:
                        account.name:
                          - O campo account.name é obrigatório.
                        account.cpf:
                          - O campo account.cpf é obrigatório.
                        company.companyName:
                          - O campo company.company name é obrigatório.
                        company.companyDocumentNumber:
                          - O campo company.company document number é obrigatório.
                        company.companyPostcode:
                          - O campo company.company postcode é obrigatório.
                        company.companyAddress:
                          - O campo company.company address é obrigatório.
                        company.companyAddressNumber:
                          - O campo company.company address number é obrigatório.
                        company.companyAddressState:
                          - O campo company.company address state é obrigatório.
                        company.companyCity:
                          - O campo company.company city é obrigatório.
                        company.companyAddressNeighborhood:
                          - O campo company.company address neighborhood é obrigatório.
                        triage.revenue:
                          - O campo triage.revenue é obrigatório.
                        triage.storeUrl:
                          - O campo triage.store url é obrigatório.
                documentoInvalido:
                  summary: CPF da conta ou documento da empresa em formato inválido
                  value:
                    data:
                      message:
                        account.cpf:
                          - O CPF da conta não é um CPF válido.
                        company.companyDocumentNumber:
                          - O número do documento da empresa não é um CPF ou CNPJ válido.
                        company.personType:
                          - O número do documento da empresa não permite determinar um tipo de pessoa válido (CPF ou CNPJ).
                formaRecebimentoNaoInformada:
                  summary: Nem bankAccount nem pixKey foram enviados
                  value:
                    data:
                      message:
                        bankAccount:
                          - O campo bank account é obrigatório quando pix key não está presente.
                        pixKey:
                          - O campo pix key é obrigatório quando bank account não está presente.
                dadosBancariosIncompletos:
                  summary: bankAccount enviado sem os subcampos obrigatórios
                  value:
                    data:
                      message:
                        bankAccount.bank:
                          - O campo bank account.bank é obrigatório quando bank account está presente.
                        bankAccount.agency:
                          - O campo bank account.agency é obrigatório quando bank account está presente.
                        bankAccount.account:
                          - O campo bank account.account é obrigatório quando bank account está presente.
                        bankAccount.bankAccountType:
                          - O campo bank account.bank account type é obrigatório quando bank account está presente.
                bancoOuTipoContaInvalido:
                  summary: Banco não homologado ou tipo de conta fora da lista permitida
                  value:
                    data:
                      message:
                        bankAccount.bank:
                          - The bank is not valid.
                        bankAccount.bankAccountType:
                          - "O tipo de conta selecionado é inválido: CC,CD,PG,PP."
                chavePixFormatoInvalido:
                  summary: pixKey não corresponde a CPF, CNPJ, e-mail, telefone ou chave aleatória
                  value:
                    data:
                      message:
                        pixKey:
                          - A chave Pix informada não corresponde a nenhum formato válido (CPF, CNPJ, e-mail, telefone ou chave aleatória).
                configuracaoInvalida:
                  summary: sendAccountAccess ou enableAutomaticWithdraw não booleano
                  value:
                    data:
                      message:
                        config.sendAccountAccess:
                          - O campo config.send account access deverá ter o valor verdadeiro ou falso.
                        config.enableAutomaticWithdraw:
                          - O campo config.enable automatic withdraw deverá ter o valor verdadeiro ou falso.
                saqueAutomaticoUsuarioExistente:
                  summary: Saque automático pedido para um CPF que já é usuário da Appmax
                  value:
                    error: User already exists, invalid value for enableAutomaticWithdraw, automatic withdraw cannot be enabled.

        '500':
          description: Erro do Servidor Interno.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Erro do Servidor Interno
                        example: Internal Server Error
                        
  /v1/recipient/flexible:
    post:
      operationId: createRecipientFlexible
      tags: [Split]
      summary: Criar um recebedor flexível
      description: |
        Cadastra um recebedor de forma flexível para receber valores de
        [split de pagamentos](/guides/split-pagamentos). Esta rota permite o
        envio de conjuntos parciais de dados da conta (`account`).

        O cadastro é só a primeira etapa. Para completar o fluxo, o recebedor
        também precisa concluir o
        [facematch (KYC)](/api-reference/split/facematch-link) — enquanto o KYC
        não é aprovado, ele não participa de splits. Os três status possíveis do
        onboarding são:

        | Status | Significado |
        | --- | --- |
        | `Awaiting face match completion` | Aguardando o recebedor concluir a etapa de facematch (KYC). |
        | `Onboarding on verification` | Recebemos todos os dados do formulário + facematch e estamos analisando as informações. |
        | `Onboarding completed` | Cadastro completo e aprovado. |

        Referência completa em
        [Status do split de pagamentos](/guides/split-status#status-do-recebedor-recipient).

        A conta bancária (`bankAccount`) pode ser enviada já no cadastro. Os
        códigos de banco (COMPE) homologados e os tipos de conta aceitos estão
        em [Bancos homologados](/guides/bancos-homologados).

        ::: warning
        Uma vez criado, o recebedor **não pode ser excluído via API**. Parte dos
        dados (titular, endereço e conta bancária) pode ser alterada em
        [Editar recebedor](/api-reference/split/editar-recebedor);
        CPF, e-mail, razão social e CNPJ não podem ser alterados — revise-os
        antes de enviar.
        :::

        ::: tip
        Um CNPJ só pode ser cadastrado uma vez. Tentar recriar retorna `422`
        com a mensagem `"O valor indicado para o campo company.company
        document number já se encontra utilizado."`.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRecipientFlexibleRequest'
      responses:
        '201':
          description: Recebedor criado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      recipient_hash:
                        type: string
                        description: HASH do recebedor criado.
                        example: bbd96f57-3e3c-5ea1-aa60-cb2518578faf

        '403':
          description: Token ausente ou inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Token de autenticação ausente
                        example: Missing Authentication Token
        '422':
          description: Erro de validação dos dados enviados ou regra de negócio.
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Erro de validação
                    properties:
                      data:
                        type: object
                        properties:
                          message:
                            type: object
                            description: Mensagens de erro detalhadas para cada campo inválido ou ausente.
                  - type: object
                    title: Erro de negócio
                    properties:
                      error:
                        type: string
                        description: Motivo da recusa.
                        example: User already exists, invalid value for enableAutomaticWithdraw, automatic withdraw cannot be enabled.
              examples:
                camposObrigatorios:
                  summary: Campos obrigatórios ausentes
                  value:
                    data:
                      message:
                        account.name:
                          - O campo account.name é obrigatório.
                        account.cpf:
                          - O campo account.cpf é obrigatório.
                        company.companyName:
                          - O campo company.company name é obrigatório.
                        company.companyDocumentNumber:
                          - O campo company.company document number é obrigatório.
                        company.companyPostcode:
                          - O campo company.company postcode é obrigatório.
                        company.companyAddress:
                          - O campo company.company address é obrigatório.
                        company.companyAddressNumber:
                          - O campo company.company address number é obrigatório.
                        company.companyAddressState:
                          - O campo company.company address state é obrigatório.
                        company.companyCity:
                          - O campo company.company city é obrigatório.
                        company.companyAddressNeighborhood:
                          - O campo company.company address neighborhood é obrigatório.
                        triage.revenue:
                          - O campo triage.revenue é obrigatório.
                        triage.storeUrl:
                          - O campo triage.store url é obrigatório.
                documentoInvalido:
                  summary: CPF da conta ou documento da empresa em formato inválido
                  value:
                    data:
                      message:
                        account.cpf:
                          - O CPF da conta não é um CPF válido.
                        company.companyDocumentNumber:
                          - O número do documento da empresa não é um CPF ou CNPJ válido.
                        company.personType:
                          - O número do documento da empresa não permite determinar um tipo de pessoa válido (CPF ou CNPJ).
                formaRecebimentoNaoInformada:
                  summary: Nem bankAccount nem pixKey foram enviados
                  value:
                    data:
                      message:
                        bankAccount:
                          - O campo bank account é obrigatório quando pix key não está presente.
                        pixKey:
                          - O campo pix key é obrigatório quando bank account não está presente.
                dadosBancariosIncompletos:
                  summary: bankAccount enviado sem os subcampos obrigatórios
                  value:
                    data:
                      message:
                        bankAccount.bank:
                          - O campo bank account.bank é obrigatório quando bank account está presente.
                        bankAccount.agency:
                          - O campo bank account.agency é obrigatório quando bank account está presente.
                        bankAccount.account:
                          - O campo bank account.account é obrigatório quando bank account está presente.
                        bankAccount.bankAccountType:
                          - O campo bank account.bank account type é obrigatório quando bank account está presente.
                bancoOuTipoContaInvalido:
                  summary: Banco não homologado ou tipo de conta fora da lista permitida
                  value:
                    data:
                      message:
                        bankAccount.bank:
                          - The bank is not valid.
                        bankAccount.bankAccountType:
                          - "O tipo de conta selecionado é inválido: CC,CD,PG,PP."
                chavePixFormatoInvalido:
                  summary: pixKey não corresponde a CPF, CNPJ, e-mail, telefone ou chave aleatória
                  value:
                    data:
                      message:
                        pixKey:
                          - A chave Pix informada não corresponde a nenhum formato válido (CPF, CNPJ, e-mail, telefone ou chave aleatória).
                dataNascimentoObrigatoria:
                  summary: Recebedor pessoa física sem data de nascimento
                  value:
                    data:
                      message:
                        account.dateOfBirth:
                          - A data de nascimento é obrigatória para recebedores pessoa física (CPF).
                dataNascimentoFormatoInvalido:
                  summary: Data de nascimento fora do formato AAAA-MM-DD
                  value:
                    data:
                      message:
                        account.dateOfBirth:
                          - A data de nascimento deve estar no formato AAAA-MM-DD.
                dataNascimentoInvalida:
                  summary: Data de nascimento inexistente (ex. 2003-02-30)
                  value:
                    data:
                      message:
                        account.dateOfBirth:
                          - A data de nascimento informada não é uma data válida.
                configuracaoInvalida:
                  summary: sendAccountAccess ou enableAutomaticWithdraw não booleano
                  value:
                    data:
                      message:
                        config.sendAccountAccess:
                          - O campo config.send account access deverá ter o valor verdadeiro ou falso.
                        config.enableAutomaticWithdraw:
                          - O campo config.enable automatic withdraw deverá ter o valor verdadeiro ou falso.
                saqueAutomaticoUsuarioExistente:
                  summary: Saque automático pedido para um CPF que já é usuário da Appmax
                  value:
                    error: User already exists, invalid value for enableAutomaticWithdraw, automatic withdraw cannot be enabled.


  /v1/recipient/{recipient_hash}/facematch-link:
    post:
      operationId: createFacematchLink
      tags: [Split]
      summary: Criar link de facematch (KYC)
      description: |
        Gera o link de facematch (KYC) para um recebedor recém-criado e envia
        um SMS com esse link para o telefone informado.

        ::: warning
        SMS **não são enviados em homologação**. Teste o disparo do facematch
        em produção.
        :::

        ::: info
        O telefone enviado neste endpoint **não precisa ser o mesmo** do
        `account.phone` usado na criação do recebedor.
        :::
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FacematchLinkRequest'
            examples:
              default:
                value:
                  phone: '11999999999'
      responses:
        '201':
          description: Link de facematch criado e SMS disparado.
          content:
            application/json:
              examples:
                default:
                  value:
                    message: Facematch created successfully
        '500':
          description: Erro interno.

  /v1/recipient/{recipient_hash}/status:
    get:
      operationId: getRecipientStatus
      tags: [Split]
      summary: Consultar status do recebedor
      description: |
        Retorna o status atual do onboarding do recebedor. Use para descobrir
        se o recebedor está pronto para receber splits (`Onboarding completed`).

        Para a referência completa dos status, veja
        [Status do split de pagamentos](/guides/split-status).
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      responses:
        '200':
          description: Status retornado.
          content:
            application/json:
              examples:
                completed:
                  value:
                    data: Onboarding completed
                awaiting:
                  value:
                    data: Awaiting face match completion
                onVerification:
                  value:
                    data: Onboarding on verification
        '500':
          description: Erro interno.

  /v1/recipient/{recipient_hash}:
    put:
      operationId: updateRecipient
      tags: [Split]
      summary: Editar um recebedor
      description: |
        Atualiza os dados cadastrais de um recebedor criado em
        [`POST /v1/recipient`](/api-reference/split/criar-recebedor). A resposta
        devolve o `recipient_hash` do recebedor atualizado.

        A edição é **parcial**: todos os blocos (`account`, `company`,
        `bankAccount`) e todos os campos dentro deles são opcionais.
        Só os campos enviados são alterados — o que não for enviado permanece
        como está.

        Só é possível editar recebedores cadastrados pela própria empresa
        autenticada. Um `recipient_hash` inexistente ou de outra empresa
        retorna `404`.

        ::: warning Campos que não podem ser editados
        CPF e e-mail do titular (`account.cpf`, `account.email`), razão social
        e CPF/CNPJ da empresa (`company.companyName`,
        `company.companyDocumentNumber`, `company.personType`) e os dados de
        triagem (`triage`) **não são alterados** por este endpoint. Se forem
        enviados, são ignorados silenciosamente e a requisição retorna `200`.
        :::

        | Bloco | O que altera |
        | --- | --- |
        | `account` | Nome, telefone e data de nascimento do titular. |
        | `company` | Endereço da empresa. |
        | `bankAccount` | Conta bancária **principal** já cadastrada do recebedor. Indisponível para recebedores lojistas (ver abaixo). |

        **Telefone** — caracteres não numéricos são removidos antes de salvar.
        O número não pode estar em uso por outro usuário da Appmax.

        **Data de nascimento** — o titular precisa ser maior de idade (18 anos
        ou mais).

        **Conta bancária** — o endpoint edita a conta principal existente; ele
        não cria uma conta nova nem troca qual conta é a principal. A edição é
        recusada quando o recebedor não tem conta cadastrada, quando há uma
        solicitação de saque pendente ou quando a conta foi criada pelo Max.
        Códigos de banco (COMPE) e tipos de conta aceitos estão em
        [Bancos homologados](/guides/bancos-homologados).

        **Recebedor lojista** — quando o recebedor foi cadastrado para um
        usuário que já é lojista na Appmax (possui lojas), o bloco
        `bankAccount` não pode ser alterado por este endpoint. Enviá-lo para
        esse recebedor retorna `422` com
        `Recipient with merchant role cannot update bank account.`.

        ::: tip Validação antes da escrita
        Todas as regras de negócio acima são checadas antes de qualquer
        alteração ser salva. Se a requisição retornar `422`, **nenhum bloco
        foi alterado** — inclusive os demais blocos enviados na mesma
        requisição.
        :::
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateRecipientRequest'
      responses:
        '200':
          description: Recebedor atualizado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      recipient_hash:
                        type: string
                        description: HASH do recebedor atualizado.
                        example: d9bd0dae-3274-5e5a-939f-f50d867eb652
        '403':
          description: Token ausente ou inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Token de autenticação ausente
                        example: Missing Authentication Token
        '404':
          description: Recebedor não encontrado ou não pertence à empresa autenticada.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Mensagem de erro.
                    example: Recipient not found
        '422':
          description: |
            Erro na validação dos dados enviados ou regra de negócio que impede
            a atualização. Erros de validação trazem as mensagens agrupadas por
            campo; erros de negócio trazem a mensagem em `error`.
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    title: Erro de validação
                    description: Mensagens de erro agrupadas pelo campo inválido.
                    additionalProperties:
                      type: array
                      items:
                        type: string
                  - type: object
                    title: Erro de negócio
                    properties:
                      error:
                        type: string
                        description: Motivo da recusa.
                        example: The phone number already exists.
              examples:
                menorDeIdade:
                  summary: Titular menor de 18 anos
                  value:
                    account.dateOfBirth:
                      - O parceiro deve ser maior de idade.
                dataInvalida:
                  summary: Data de nascimento em formato inválido
                  value:
                    account.dateOfBirth:
                      - O campo account.date of birth não contém uma data válida.
                telefoneTamanhoInvalido:
                  summary: Telefone fora do tamanho permitido (10 a 11 caracteres)
                  value:
                    account.phone:
                      - O campo account.phone deverá conter no mínimo 10 caracteres.
                bancoInvalido:
                  summary: Banco não homologado
                  value:
                    bankAccount.bank:
                      - The bank is not valid.
                tipoContaInvalido:
                  summary: Tipo de conta fora da lista permitida
                  value:
                    bankAccount.bankAccountType:
                      - "O tipo de conta selecionado é inválido: CC,CD,PG,PP."
                telefoneEmUso:
                  summary: Telefone já utilizado por outro usuário
                  value:
                    error: The phone number already exists.
                semContaBancaria:
                  summary: Recebedor sem conta bancária cadastrada
                  value:
                    error: Recipient has no bank account to update.
                saquePendente:
                  summary: Conta bancária com solicitação de saque pendente
                  value:
                    error: Não foi possível atualizar a conta bancária, pois há uma solicitação de saque pendente.
                contaCriadaPeloMax:
                  summary: Conta bancária criada pelo Max
                  value:
                    error: Não é possível modificar uma chave pix criada pelo Max.
                bankAccountRecebedorLojista:
                  summary: Bloco bankAccount enviado para recebedor lojista
                  value:
                    error: Recipient with merchant role cannot update bank account.
        '500':
          description: Erro do Servidor Interno.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Erro do Servidor Interno
                    example: Internal Server Error

  /v1/recipients:
    get:
      operationId: listRecipients
      tags: [Split]
      summary: Listar recebedores
      description: |
        Lista, de forma paginada, os recebedores cadastrados pelo merchant
        autenticado, do mais recente para o mais antigo. Cada item traz o
        status de onboarding — o mesmo valor retornado por
        [Consultar status do recebedor](/api-reference/split/consultar-recebedor).

        Para a referência completa dos status, veja
        [Status do split de pagamentos](/guides/split-status#status-do-recebedor-recipient).

        ::: tip Filtros
        Os filtros são opcionais, enviados como query params, e podem ser
        combinados — o recebedor precisa atender a todos. Os documentos podem
        ser enviados com ou sem máscara.
        :::
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Página a ser retornada.
          example: 1
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Quantidade de recebedores por página.
          example: 20
        - name: email
          in: query
          required: false
          schema:
            type: string
            format: email
          description: E-mail da conta do recebedor (comparação exata).
          example: maria.souza@exemplo.com
        - name: recipient_document
          in: query
          required: false
          schema:
            type: string
          description: CPF da conta do recebedor. Precisa ser um CPF válido.
          example: 358.738.820-29
        - name: recipient_company_document
          in: query
          required: false
          schema:
            type: string
          description: |
            Documento da empresa do recebedor. Aceita CNPJ ou, para empresas de
            pessoa física, CPF. Precisa ser um documento válido.
          example: 97.761.238/0001-71
      responses:
        '200':
          description: Recebedores retornados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListRecipientsResponse'
              examples:
                default:
                  value:
                    data:
                      - recipient_hash: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                        status: Onboarding completed
                        account:
                          name: Maria Souza
                          email: maria.souza@exemplo.com
                          cpf: '35873882029'
                          phone: '11976187304'
                        company:
                          name: Souza Comércio LTDA
                          document_number: '97761238000171'
                        created_at: '2026-10-01 14:32:10'
                      - recipient_hash: 5d25c7b6-a52d-54e7-85a8-8a17f6e2549e
                        status: Awaiting face match completion
                        account:
                          name: João Lima
                          email: joao.lima@exemplo.com
                          cpf: '52998224725'
                          phone: '21988887777'
                        company:
                          name: João Lima
                          document_number: '52998224725'
                        created_at: '2026-09-28 09:15:47'
                    meta:
                      page: 1
                      pages: 1
                      total: 2
                      limit: 20
                empty:
                  summary: Nenhum recebedor encontrado
                  value:
                    data: []
                    meta:
                      page: 1
                      pages: 0
                      total: 0
                      limit: 20
        '403':
          description: Token ausente ou inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Token de autenticação ausente
                        example: Missing Authentication Token
        '422':
          description: Parâmetros de paginação ou filtros inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        email:
                          - The E-mail must be a valid email address.
                        recipient_document:
                          - O documento do recebedor não é um CPF válido.
        '500':
          description: Erro interno.
          content:
            application/json:
              examples:
                default:
                  value:
                    error: Internal Server Error

  /v1/p/recipients:
    post:
      operationId: createRecipientPagarme
      tags: [PagarMe]
      summary: Criar recebedor (contrato PagarMe)
      description: |
        Cadastra um recebedor usando o **mesmo contrato da PagarMe v5**
        (`POST /core/v5/recipients`). É para quem já integrou recebedores na
        PagarMe e quer operar na Appmax sem reescrever a integração. O
        recebedor fica vinculado ao marketplace autenticado.

        O retorno é o objeto `recipient` da PagarMe dentro do envelope `data`.
        O `id` devolvido é o `recipient_hash` do recebedor, o identificador
        usado nas demais rotas `/v1/p/recipients/{recipient_hash}`.

        ::: warning Diferenças em relação à PagarMe
        - **Campos obrigatórios a mais:** `register_information.site_url` e
          `register_information.phone_numbers` (o primeiro telefone da lista).
          Em PJ, `managing_partners` também é obrigatório.
        - **Representante legal (PJ):** o primeiro item de `managing_partners`
          é o representante legal e vira o titular do login do recebedor na
          Appmax. Os demais sócios não são gravados.
        - **Saque e antecipação automáticos não são configurados aqui.** Veja
          a seção **Saque automático e antecipação automática** abaixo.
        - **Status inicial:** o recebedor nasce com `status: registration` e
          passa a `active` quando o onboarding (KYC) é aprovado.
        :::

        **Regras de validação principais**

        | Regra | Detalhe |
        | --- | --- |
        | Documento × tipo | `individual` exige CPF; `corporation` exige CNPJ. |
        | CPF já cadastrado | Se o CPF do titular (o `document` em PF, `managing_partners[0].document` em PJ) já existir na Appmax, a rota retorna `422` com `Recebedor já cadastrado.`, mesmo que o cadastro seja de outro marketplace. |
        | Titular da conta | `default_bank_account.holder_document` precisa ser o próprio `register_information.document`. `holder_type` precisa ser `individual` para recebedor `individual` e `company` para `corporation`. |
        | Datas | `birthdate` em `dd/mm/aaaa` ou `aaaa-mm-dd`. |
        | Nomes | `name` (PF) e `managing_partners[].name` aceitam só letras, acentos e espaços (entre 3 e 255 caracteres). |
        | Valores | `monthly_income` e `annual_revenue` em **centavos**. |
        | Banco | `bank` é o código COMPE e precisa estar entre os [bancos homologados](/guides/bancos-homologados). |
        | Tamanho | `metadata`, `default_bank_account.metadata` e `managing_partners` têm limite de ~60 KB cada; `managing_partners` aceita até 20 sócios. |

        **Saque automático e antecipação automática**

        Neste cadastro, a Appmax não aplica as configurações de saque
        automático (`transfer_settings`) nem de antecipação automática
        (`automatic_anticipation_settings`). Elas serão configuradas em rotas
        próprias de atualização do recebedor, disponíveis só depois que ele for
        aprovado. Um recebedor recém-cadastrado ainda está em `registration` e
        não pode sacar.

        | Valor enviado | Resultado |
        | --- | --- |
        | Seção ausente | Aceito |
        | `transfer_settings.transfer_enabled: false` | Aceito, sem efeito |
        | `automatic_anticipation_settings.enabled: false` | Aceito, sem efeito |
        | Demais campos da seção (`transfer_interval`, `transfer_day`, `type`, `volume_percentage`, `delay`, `days`), com a flag desligada | Aceitos e ignorados |
        | `transfer_settings.transfer_enabled: true` (ou `1` / `"1"`) | `422`, e o recebedor não é criado |
        | `automatic_anticipation_settings.enabled: true` (ou `1` / `"1"`) | `422`, e o recebedor não é criado |

        Mensagem do `422`:

        > Saque automático e Antecipação automática têm endpoints próprios de
        > configuração, disponíveis depois da Aprovação do Recebedor. Esta rota
        > não os aplica.

        A configuração ligada é recusada, e não ignorada, de propósito: se
        fosse ignorada, você acreditaria que o repasse automático ficou ativo
        quando ele não ficou. Por isso dá para reaproveitar o seu payload atual
        da PagarMe, desde que essas seções estejam desligadas ou fora dele.

        Na resposta, as duas seções voltam sempre desligadas, refletindo o que
        de fato vale para o recebedor:

        ```json
        "transfer_settings": {
          "transfer_enabled": false,
          "transfer_interval": null,
          "transfer_day": 0
        },
        "automatic_anticipation_settings": {
          "enabled": false,
          "type": "full",
          "volume_percentage": 0,
          "delay": null
        }
        ```

        ::: info Como ativar (em breve)
        Depois da aprovação, o saque automático e a antecipação automática
        serão ligados e desligados pelas rotas de atualização do recebedor:

        - [Configurar saque automático](/api-reference/pagarme/configurar-saque-automatico)
          (`PATCH /v1/p/recipients/{recipient_hash}/transfer-settings`)
        - [Configurar antecipação automática](/api-reference/pagarme/configurar-antecipacao-automatica)
          (`PATCH /v1/p/recipients/{recipient_hash}/automatic-anticipation-settings`)

        Nessas rotas, só a configuração que a Appmax executa hoje será aceita:

        - **Frequência:** saque diário (`daily`), em dia útil às 08:00.
        - **Valor sacado:** todo o saldo disponível.
        - **Antecipação:** do tipo `full`, de todo o saldo a liberar.

        Configurações semanais, mensais, em dias específicos ou com percentual
        parcial vão responder `422`.
        :::

        ::: danger Sem idempotência
        Não repita a chamada depois de uma resposta `200`, mesmo que o corpo
        venha incompleto: se o cadastro foi feito e a montagem da resposta
        falhou, a rota devolve só `id` e `code` (e, se possível, `status`).
        Uma nova tentativa com o mesmo CPF retorna `422`.
        :::

        Envie o header `Accept: application/json`; sem ele, erros de
        validação não são devolvidos em JSON.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PagarmeCreateRecipientRequest'
            examples:
              individual:
                summary: Pessoa física (individual)
                value:
                  code: erp-maria-001
                  metadata:
                    origem: erp
                  register_information:
                    type: individual
                    name: Maria Souza
                    email: maria.souza@exemplo.com
                    document: '35873882029'
                    birthdate: 12/10/1995
                    monthly_income: 500000
                    professional_occupation: Vendedora
                    mother_name: Ana Souza
                    site_url: https://lojadamaria.com.br
                    phone_numbers:
                      - ddd: '11'
                        number: '976187304'
                        type: mobile
                    address:
                      street: Av. Paulista
                      street_number: '1000'
                      complementary: Sala 10
                      neighborhood: Bela Vista
                      city: São Paulo
                      state: SP
                      zip_code: '01310100'
                      reference_point: Próximo ao metrô
                  default_bank_account:
                    holder_name: Maria Souza
                    holder_type: individual
                    holder_document: '35873882029'
                    bank: '001'
                    branch_number: '1234'
                    branch_check_digit: '5'
                    account_number: '123456'
                    account_check_digit: '7'
                    type: checking
              corporation:
                summary: Pessoa jurídica (corporation)
                value:
                  code: erp-carlos-001
                  register_information:
                    type: corporation
                    company_name: Loja do Carlos
                    trading_name: Carlos Comércio LTDA
                    email: empresa@lojadocarlos.com.br
                    document: '97761238000171'
                    annual_revenue: 120000000
                    corporation_type: LTDA
                    founding_date: '2015-03-20'
                    site_url: https://lojadocarlos.com.br
                    phone_numbers:
                      - ddd: '11'
                        number: '976187304'
                    main_address:
                      street: Rua Augusta
                      street_number: '500'
                      complementary: Loja 2
                      neighborhood: Consolação
                      city: São Paulo
                      state: SP
                      zip_code: '01305000'
                    managing_partners:
                      - name: Carlos Lima
                        email: carlos@lojadocarlos.com.br
                        document: '52998224725'
                        birthdate: 09/12/1980
                        monthly_income: 1000000
                        professional_occupation: Empresário
                        self_declared_legal_representative: true
                  default_bank_account:
                    holder_name: Carlos Comércio LTDA
                    holder_type: company
                    holder_document: '97761238000171'
                    bank: '001'
                    branch_number: '1234'
                    account_number: '654321'
                    account_check_digit: '0'
                    type: checking
      responses:
        '200':
          description: |
            Recebedor criado. O corpo traz o objeto `recipient` completo,
            incluindo os campos só ecoados da entrada.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipient'
              examples:
                individual:
                  summary: Pessoa física criada
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                      code: erp-maria-001
                      name: Maria Souza
                      email: maria.souza@exemplo.com
                      document: '35873882029'
                      type: individual
                      payment_mode: bank_transfer
                      status: registration
                      created_at: '2026-10-07T14:32:10Z'
                      updated_at: '2026-10-07T14:32:10Z'
                      transfer_settings:
                        transfer_enabled: false
                        transfer_interval: null
                        transfer_day: 0
                      default_bank_account:
                        id: '8812'
                        holder_name: Maria Souza
                        holder_type: individual
                        holder_document: '35873882029'
                        bank: '001'
                        branch_number: '1234'
                        branch_check_digit: '5'
                        account_number: '123456'
                        account_check_digit: '7'
                        type: checking
                        status: active
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-07T14:32:10Z'
                        metadata: {}
                      gateway_recipients: []
                      automatic_anticipation_settings:
                        enabled: false
                        type: full
                        volume_percentage: 0
                        delay: null
                      metadata:
                        origem: erp
                      register_information:
                        email: maria.souza@exemplo.com
                        document: '35873882029'
                        type: individual
                        site_url: https://lojadamaria.com.br
                        phone_numbers:
                          - ddd: '11'
                            number: '976187304'
                            type: null
                        name: Maria Souza
                        mother_name: Ana Souza
                        birthdate: 12/10/1995
                        monthly_income: '500000'
                        professional_occupation: Vendedora
                        address:
                          street: Av. Paulista
                          complementary: Sala 10
                          street_number: '1000'
                          neighborhood: Bela Vista
                          city: São Paulo
                          state: SP
                          zip_code: '01310100'
                          reference_point: Próximo ao metrô
                degraded:
                  summary: Criado, mas a resposta completa não pôde ser montada
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                      code: erp-maria-001
                      status: registration
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '422':
          description: |
            Erro de validação. Há dois formatos:

            - **formato inválido** (campo ausente, tipo errado, regra da tabela
              acima): mapa `campo → [mensagens]` na raiz do corpo, **sem** o
              envelope `data`;
            - **regra de negócio** (CPF já cadastrado): `data.message` com a
              descrição.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PagarmeFieldErrors'
                  - $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                camposObrigatorios:
                  summary: Campos obrigatórios ausentes
                  value:
                    register_information.site_url:
                      - O campo register information.site url é obrigatório.
                    register_information.phone_numbers:
                      - O campo register information.phone numbers é obrigatório.
                documentoDoTipoErrado:
                  summary: CNPJ enviado em recebedor individual
                  value:
                    register_information.document:
                      - Recebedor do tipo individual exige CPF.
                titularDivergente:
                  summary: Conta bancária de outro titular
                  value:
                    default_bank_account.holder_document:
                      - O titular da conta bancária deve ser o próprio Recebedor.
                    default_bank_account.holder_type:
                      - O titular da conta deve ser do tipo 'company' para um Recebedor 'corporation'.
                saqueAutomaticoAtivo:
                  summary: transfer_settings com transfer_enabled true
                  value:
                    transfer_settings.transfer_enabled:
                      - Saque automático e Antecipação automática têm endpoints próprios de configuração, disponíveis depois da Aprovação do Recebedor. Esta rota não os aplica.
                dataInvalida:
                  summary: birthdate fora dos formatos aceitos
                  value:
                    register_information.birthdate:
                      - A data de nascimento deve estar em dd/mm/aaaa ou aaaa-mm-dd.
                cpfJaCadastrado:
                  summary: CPF do titular já cadastrado na Appmax
                  value:
                    data:
                      message: Recebedor já cadastrado.
        '429':
          description: Limite de validações cadastrais excedido. Tente novamente mais tarde.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
        '500':
          description: Erro interno ao criar o recebedor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient creation error
        '503':
          description: Serviço de validação cadastral indisponível. Tente novamente mais tarde.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'

    get:
      operationId: listRecipientsPagarme
      tags: [PagarMe]
      summary: Listar recebedores (contrato PagarMe)
      description: |
        Lista os recebedores do marketplace autenticado no formato da PagarMe
        v5 (`GET /core/v5/recipients`), do mais recente para o mais antigo.
        Só os recebedores cadastrados pelo próprio marketplace são retornados.

        Ao contrário das demais rotas `/v1/p/`, o corpo **não** usa o envelope
        `data` como objeto: segue o formato de listagem da PagarMe, com a lista
        em `data` e o total em `paging.total`.

        Cada item é um resumo do recebedor — sem `register_information`,
        `automatic_anticipation_settings` e `payment_mode`. Para o objeto
        completo, use
        [Consultar recebedor](/api-reference/pagarme/consultar-recebedor).

        ::: warning Diferenças em relação à PagarMe
        - Não há filtros: só `page` e `size`.
        - `size` tem teto de **100**. Acima disso a rota retorna `422`, em vez
          de reduzir o valor.
        :::
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Página a ser retornada.
          example: 1
        - name: size
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
          description: Quantidade de recebedores por página.
          example: 10
      responses:
        '200':
          description: Recebedores retornados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeListRecipientsResponse'
              examples:
                default:
                  value:
                    data:
                      - id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                        name: Maria Souza
                        email: maria.souza@exemplo.com
                        document: '35873882029'
                        type: individual
                        status: active
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-07T15:02:44Z'
                        transfer_settings:
                          transfer_enabled: false
                          transfer_interval: null
                          transfer_day: 0
                        default_bank_account:
                          id: '8812'
                          holder_name: Maria Souza
                          holder_type: individual
                          holder_document: '35873882029'
                          bank: '001'
                          branch_number: '12345'
                          account_number: '123456'
                          account_check_digit: '7'
                          type: checking
                          status: active
                          created_at: '2026-10-07T14:32:10Z'
                          updated_at: '2026-10-07T14:32:10Z'
                    paging:
                      total: 1
                empty:
                  summary: Nenhum recebedor
                  value:
                    data: []
                    paging:
                      total: 0
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '422':
          description: |
            `page` ou `size` inválidos. Mapa `campo → [mensagens]` na raiz do
            corpo, sem o envelope `data`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeFieldErrors'
              examples:
                sizeAcimaDoTeto:
                  summary: size acima de 100
                  value:
                    size:
                      - O campo size não deverá conter um valor superior a 100.
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient listing error

  /v1/p/recipients/{recipient_hash}:
    get:
      operationId: getRecipientPagarme
      tags: [PagarMe]
      summary: Consultar recebedor (contrato PagarMe)
      description: |
        Retorna um recebedor no formato da PagarMe v5
        (`GET /core/v5/recipients/{recipient_id}`). O identificador é o `id`
        devolvido no [cadastro](/api-reference/pagarme/criar-recebedor).

        A consulta é montada **só com o que a Appmax grava**, por isso o
        objeto difere do retornado no cadastro:

        - campos que a PagarMe trata como **obrigatórios** e que a Appmax não
          guarda saem `null` — `monthly_income`, `professional_occupation`,
          `trading_name`, `annual_revenue`;
        - campos **opcionais** que a Appmax não guarda são omitidos — `code`,
          `metadata`, `mother_name`, `site_url`, `corporation_type`,
          `founding_date`, o `type` dos telefones e o `metadata` da conta;
        - em PJ, `managing_partners` traz só o representante legal (o primeiro
          sócio enviado no cadastro);
        - na conta bancária, a agência volta com o dígito junto em
          `branch_number`, sem `branch_check_digit`. `default_bank_account` sai
          `null` se o recebedor não tiver conta.

        Um recebedor inexistente ou cadastrado por outro marketplace retorna o
        mesmo `404`.
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      responses:
        '200':
          description: Recebedor encontrado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipient'
              examples:
                corporation:
                  summary: Pessoa jurídica
                  value:
                    data:
                      id: 5d25c7b6-a52d-54e7-85a8-8a17f6e2549e
                      name: Loja do Carlos
                      email: empresa@lojadocarlos.com.br
                      document: '97761238000171'
                      type: corporation
                      payment_mode: bank_transfer
                      status: registration
                      created_at: '2026-10-07T14:32:10Z'
                      updated_at: '2026-10-07T14:32:10Z'
                      transfer_settings:
                        transfer_enabled: false
                        transfer_interval: null
                        transfer_day: 0
                      default_bank_account:
                        id: '8813'
                        holder_name: Loja do Carlos
                        holder_type: company
                        holder_document: '97761238000171'
                        bank: '001'
                        branch_number: '1234'
                        account_number: '654321'
                        account_check_digit: '0'
                        type: checking
                        status: active
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-07T14:32:10Z'
                      gateway_recipients: []
                      automatic_anticipation_settings:
                        enabled: false
                        type: full
                        volume_percentage: 0
                        delay: null
                      register_information:
                        email: empresa@lojadocarlos.com.br
                        document: '97761238000171'
                        type: corporation
                        phone_numbers:
                          - ddd: '11'
                            number: '976187304'
                        company_name: Loja do Carlos
                        trading_name: null
                        annual_revenue: null
                        main_address:
                          street: Rua Augusta
                          complementary: Loja 2
                          street_number: '500'
                          neighborhood: Consolação
                          city: São Paulo
                          state: SP
                          zip_code: '01305000'
                          reference_point: null
                        managing_partners:
                          - name: Carlos Lima
                            email: carlos@lojadocarlos.com.br
                            document: '52998224725'
                            birthdate: 09/12/1980
                            monthly_income: null
                            professional_occupation: null
                            self_declared_legal_representative: null
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient retrieval error

    put:
      operationId: updateRecipientPagarme
      tags: [PagarMe]
      summary: Editar recebedor (contrato PagarMe)
      description: |
        Edita dados cadastrais de um recebedor usando o contrato da PagarMe v5
        (`PUT /core/v5/recipients/{recipient_id}`).

        Você pode reenviar o objeto inteiro: **só os campos editáveis são
        gravados**, e os demais são ignorados sem erro. Cada campo editável
        enviado **sobrescreve** o valor atual. A regra da PagarMe de só
        preencher campos vazios não se aplica.

        | Dado | Pessoa física (`individual`) | Pessoa jurídica (`corporation`) |
        | --- | --- | --- |
        | Nome do titular | `register_information.name` | `register_information.managing_partners[0].name` |
        | Data de nascimento do titular | `register_information.birthdate` | `register_information.managing_partners[0].birthdate` |
        | Telefone | `register_information.phone_numbers[0]` (`ddd` e `number` juntos) | igual |
        | Endereço | `register_information.address.*` | `register_information.main_address.*` |

        Os campos de endereço editáveis são `street`, `street_number`,
        `complementary`, `neighborhood`, `city`, `state` e `zip_code`.

        ::: warning `type` é obrigatório
        Sempre que enviar `register_information`, inclua o `type` do
        recebedor (`individual` ou `corporation`). É ele que define quais
        caminhos acima são lidos, então precisa ser o tipo com que o recebedor
        foi cadastrado.
        :::

        Regras:

        - um campo enviado não pode ser vazio;
        - `birthdate` em `dd/mm/aaaa` ou `aaaa-mm-dd`, e o titular precisa ser
          maior de idade;
        - nome só com letras, acentos e espaços (entre 3 e 255 caracteres);
        - o telefone não pode pertencer a outro usuário da Appmax.

        Não são editáveis por aqui: e-mail, documento, nome da empresa,
        renda/faturamento e conta bancária. Para trocar a conta, use
        [Alterar domicílio bancário](/api-reference/pagarme/alterar-domicilio-bancario).

        A resposta de sucesso é um objeto vazio, como na PagarMe. Para ver os
        dados atualizados, use
        [Consultar recebedor](/api-reference/pagarme/consultar-recebedor).
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PagarmeUpdateRecipientRequest'
            examples:
              individual:
                summary: Pessoa física — telefone e endereço
                value:
                  register_information:
                    type: individual
                    phone_numbers:
                      - ddd: '21'
                        number: '988887777'
                    address:
                      street: Rua das Laranjeiras
                      street_number: '45'
                      complementary: Apto 302
                      neighborhood: Laranjeiras
                      city: Rio de Janeiro
                      state: RJ
                      zip_code: '22240003'
              corporation:
                summary: Pessoa jurídica — representante legal
                value:
                  register_information:
                    type: corporation
                    managing_partners:
                      - name: Carlos Eduardo Lima
                        birthdate: 09/12/1980
      responses:
        '200':
          description: Recebedor atualizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    description: Objeto vazio.
              examples:
                default:
                  value:
                    data: {}
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '422':
          description: |
            Erro de validação. Formato inválido volta como mapa
            `campo → [mensagens]` na raiz do corpo; regra de negócio volta em
            `data.message`.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PagarmeFieldErrors'
                  - $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                tipoAusente:
                  summary: register_information sem type
                  value:
                    register_information.type:
                      - O campo register information.type é obrigatório quando register information está presente.
                menorDeIdade:
                  summary: Titular menor de idade
                  value:
                    register_information.birthdate:
                      - O parceiro deve ser maior de idade.
                telefoneEmUso:
                  summary: Telefone de outro usuário
                  value:
                    data:
                      message: The phone number already exists.
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient update error

  /v1/p/recipients/{recipient_hash}/default-bank-account:
    patch:
      operationId: updateRecipientDefaultBankAccountPagarme
      tags: [PagarMe]
      summary: Alterar domicílio bancário (contrato PagarMe)
      description: |
        Troca a conta bancária padrão do recebedor usando o contrato da
        PagarMe v5 (`PATCH /core/v5/recipients/{recipient_id}/default-bank-account`).

        A conta atual é **atualizada no lugar**: o `id` da conta continua o
        mesmo e os dados anteriores não ficam disponíveis pela API.

        **O titular precisa ser o próprio recebedor.** `holder_document` tem
        que ser o documento do recebedor e `holder_type` tem que ser
        `individual` para recebedor pessoa física e `company` para pessoa
        jurídica. Só `bank`, agência, conta e `type` são gravados;
        `holder_name` e `metadata` são aceitos e ignorados.

        A troca é recusada com `422` quando:

        - o recebedor ainda não tem conta bancária (esta rota não cria a
          primeira conta);
        - há solicitação de saque pendente na conta atual;
        - a conta foi criada pela Max;
        - o recebedor também é merchant na Appmax.

        ::: warning Saques em andamento
        O bloqueio vale só para saques **pendentes**. Saques já aprovados ou em
        processamento passam a usar os dados da conta nova.
        :::

        A resposta é o recebedor completo, no mesmo formato de
        [Consultar recebedor](/api-reference/pagarme/consultar-recebedor). Se a
        conta foi trocada mas a resposta não pôde ser montada, o corpo traz só
        o `id`. A troca já foi feita nesse caso; confira com a consulta antes
        de repetir.
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bank_account]
              properties:
                bank_account:
                  $ref: '#/components/schemas/PagarmeBankAccountInput'
            examples:
              default:
                value:
                  bank_account:
                    holder_name: Maria Souza
                    holder_type: individual
                    holder_document: '35873882029'
                    bank: '341'
                    branch_number: '4321'
                    account_number: '98765'
                    account_check_digit: '4'
                    type: savings
      responses:
        '200':
          description: Conta trocada.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipient'
              examples:
                default:
                  summary: Recebedor com a conta nova
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                      name: Maria Souza
                      email: maria.souza@exemplo.com
                      document: '35873882029'
                      type: individual
                      payment_mode: bank_transfer
                      status: active
                      created_at: '2026-10-07T14:32:10Z'
                      updated_at: '2026-10-07T15:02:44Z'
                      transfer_settings:
                        transfer_enabled: false
                        transfer_interval: null
                        transfer_day: 0
                      default_bank_account:
                        id: '8812'
                        holder_name: Maria Souza
                        holder_type: individual
                        holder_document: '35873882029'
                        bank: '341'
                        branch_number: '4321'
                        account_number: '98765'
                        account_check_digit: '4'
                        type: savings
                        status: active
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-08T09:10:00Z'
                      gateway_recipients: []
                      automatic_anticipation_settings:
                        enabled: false
                        type: full
                        volume_percentage: 0
                        delay: null
                      register_information:
                        email: maria.souza@exemplo.com
                        document: '35873882029'
                        type: individual
                        phone_numbers:
                          - ddd: '11'
                            number: '976187304'
                        name: Maria Souza
                        birthdate: 12/10/1995
                        monthly_income: null
                        professional_occupation: null
                        address:
                          street: Av. Paulista
                          complementary: Sala 10
                          street_number: '1000'
                          neighborhood: Bela Vista
                          city: São Paulo
                          state: SP
                          zip_code: '01310100'
                          reference_point: null
                degraded:
                  summary: Conta trocada, resposta mínima
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '422':
          description: |
            Erro de validação. Formato inválido volta como mapa
            `campo → [mensagens]` na raiz do corpo; regras que dependem do
            recebedor voltam em `data.message`.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PagarmeFieldErrors'
                  - $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                formatoInvalido:
                  summary: Tipo de conta e banco inválidos
                  value:
                    bank_account.type:
                      - O tipo da conta deve ser checking ou savings.
                    bank_account.bank:
                      - O banco selecionado é inválido.
                titularDivergente:
                  summary: Conta de outro titular
                  value:
                    data:
                      message: O titular da conta bancária deve ser o próprio Recebedor.
                tipoDeTitularDivergente:
                  summary: holder_type diferente do tipo do recebedor
                  value:
                    data:
                      message: O titular da conta deve ser do tipo 'individual' para este Recebedor.
                semConta:
                  summary: Recebedor sem conta bancária
                  value:
                    data:
                      message: Recipient has no bank account to update.
                saquePendente:
                  summary: Saque pendente na conta atual
                  value:
                    data:
                      message: Não foi possível atualizar a conta bancária, pois há uma solicitação de saque pendente.
                contaDaMax:
                  summary: Conta criada pela Max
                  value:
                    data:
                      message: Não é possível modificar uma chave pix criada pelo Max.
                recebedorMerchant:
                  summary: Recebedor também é merchant
                  value:
                    data:
                      message: Recipient with merchant role cannot update bank account.
        '500':
          description: Erro interno ao trocar a conta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient bank account update error

  /v1/p/recipients/{recipient_hash}/balance:
    get:
      operationId: getRecipientBalancePagarme
      tags: [PagarMe]
      summary: Consultar saldo do recebedor (contrato PagarMe)
      description: |
        Retorna o saldo de um recebedor no formato da PagarMe v5
        (`GET /core/v5/recipients/{recipient_id}/balance`). Todos os valores
        vêm em **centavos**.

        | Campo | O que é |
        | --- | --- |
        | `available_amount` | Saldo disponível: já liberado e sacável agora. |
        | `waiting_funds_amount` | Saldo a liberar: já creditado, com data de liberação futura. |
        | `transferred_amount` | Total já transferido para a conta bancária do recebedor. |
        | `currency` | Moeda dos saldos (ISO 4217). `BRL` quando o recebedor ainda não tem saldo. |

        **Como o `transferred_amount` é calculado:** é a soma do valor
        **líquido** (descontada a taxa de saque) dos saques já pagos ou com
        liquidação concluída, inclusive os de antecipação. É o valor que bate
        com o extrato bancário do recebedor. Saques pendentes, aprovados, em
        processamento ou recusados não entram na soma.

        ::: warning Limitação
        Saques com liquidação **parcial** ainda não entram no
        `transferred_amount`.
        :::

        O bloco `recipient` traz o resumo do recebedor (`id`, `name`,
        `email`, `document`, `type`, `status`, `created_at` e `updated_at`).

        Um recebedor que ainda não recebeu nenhum valor retorna os saldos
        zerados. Um recebedor inexistente ou cadastrado por outro marketplace
        retorna `404`.
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      responses:
        '200':
          description: Saldo do recebedor.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipientBalance'
              examples:
                default:
                  value:
                    data:
                      currency: BRL
                      available_amount: 152030
                      waiting_funds_amount: 489900
                      transferred_amount: 1250000
                      recipient:
                        id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                        name: Maria Souza
                        email: maria.souza@exemplo.com
                        document: '35873882029'
                        type: individual
                        status: active
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-07T15:02:44Z'
                semSaldo:
                  summary: Recebedor que ainda não recebeu valores
                  value:
                    data:
                      currency: BRL
                      available_amount: 0
                      waiting_funds_amount: 0
                      transferred_amount: 0
                      recipient:
                        id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                        name: Maria Souza
                        email: maria.souza@exemplo.com
                        document: '35873882029'
                        type: individual
                        status: registration
                        created_at: '2026-10-07T14:32:10Z'
                        updated_at: '2026-10-07T14:32:10Z'
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'
              examples:
                default:
                  value:
                    data:
                      message: Recipient balance retrieval error

  /v1/p/recipients/{recipient_hash}/transfer-settings:
    patch:
      operationId: updateRecipientTransferSettingsPagarme
      tags: [PagarMe]
      summary: Configurar saque automático (contrato PagarMe)
      description: |
        Liga ou desliga o **saque automático** do recebedor usando o contrato
        da PagarMe v5 (`PATCH /core/v5/recipients/{recipient_id}/transfer-settings`).

        A Appmax aceita **só a configuração que executa hoje**: saque
        **diário**, em dia útil, às 08:00, de **todo o saldo disponível**,
        para a conta bancária padrão do recebedor. Qualquer outra
        configuração retorna `422`, em vez de ser aceita sem efeito.

        | Campo | Aceito | Fora disso |
        | --- | --- | --- |
        | `transfer_enabled` | `true` ou `false` (obrigatório) | `422` |
        | `transfer_interval` | `daily`, em qualquer capitalização (`Daily`, `DAILY`) | `weekly` e `monthly` retornam `422` |
        | `transfer_day` | `0` | `422` |

        **Ligar:**

        - exige que o recebedor já esteja aprovado (`status: active`) e tenha
          conta bancária padrão; sem isso, a rota retorna `422`;
        - reenviar a configuração com o saque já ligado não cria um segundo
          agendamento.

        **Desligar:** cancela o saque automático. Não tem pré-condição, e
        desligar um saque que já está desligado não tem efeito.

        ::: warning O que considerar
        - "Dia útil" é de segunda a sexta: o saque também roda em feriado
          bancário.
        - Se a empresa do recebedor estiver com o credenciamento bloqueado, o
          saque automático não roda, e o `status` do recebedor continua
          `active`.
        - Se a conta padrão for trocada em
          [Alterar domicílio bancário](/api-reference/pagarme/alterar-domicilio-bancario),
          os próximos saques vão para a conta nova.
        :::

        A resposta é o recebedor completo, com `transfer_settings` refletindo
        a configuração que ficou valendo.
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PagarmeTransferSettingsInput'
            examples:
              ligar:
                summary: Ligar o saque automático
                value:
                  transfer_enabled: true
                  transfer_interval: Daily
                  transfer_day: 0
              desligar:
                summary: Desligar o saque automático
                value:
                  transfer_enabled: false
                  transfer_interval: Daily
                  transfer_day: 0
      responses:
        '200':
          description: Configuração aplicada. O corpo traz o recebedor completo.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipient'
              examples:
                ligado:
                  summary: Trecho da resposta com o saque ligado
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                      status: active
                      transfer_settings:
                        transfer_enabled: true
                        transfer_interval: Daily
                        transfer_day: 0
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '422':
          description: |
            Configuração fora da aceita, recebedor ainda não aprovado ou sem
            conta bancária padrão. Nada é alterado. A mensagem informa o valor
            aceito.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PagarmeFieldErrors'
                  - $ref: '#/components/schemas/PagarmeMessageEnvelope'
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'

  /v1/p/recipients/{recipient_hash}/automatic-anticipation-settings:
    patch:
      operationId: updateRecipientAutomaticAnticipationSettingsPagarme
      tags: [PagarMe]
      summary: Configurar antecipação automática (contrato PagarMe)
      description: |
        Liga ou desliga a **antecipação automática** do recebedor usando o
        contrato da PagarMe v5
        (`PATCH /core/v5/recipients/{recipient_id}/automatic-anticipation-settings`).

        A Appmax aceita **só a configuração que executa hoje**: antecipação de
        **todo o saldo a liberar**, em dia útil, às 08:00, para a conta
        bancária padrão do recebedor. Qualquer outra configuração retorna
        `422`.

        | Campo | Aceito | Fora disso |
        | --- | --- | --- |
        | `enabled` | `true` ou `false` (obrigatório) | `422` |
        | `type` | `full` | `422`; `1025` (antecipação de vendas inteiras) não é suportado |
        | `volume_percentage` | `100` (número ou texto) | `422` |
        | `delay` | ausente, `null` ou igual à janela de liberação atual do recebedor | `422`: a janela não é configurável por aqui |
        | `days` | ausente ou `[]` (todo dia útil) | `422`: dias específicos do mês não são suportados |

        **Ligar:**

        - exige que o recebedor já esteja aprovado (`status: active`) e tenha
          conta bancária padrão; sem isso, a rota retorna `422`;
        - exige que o fluxo de recebíveis da empresa do recebedor suporte
          antecipação; caso contrário, `422`;
        - reenviar a configuração com a antecipação já ligada não cria um
          segundo agendamento.

        **Desligar:** cancela a antecipação automática. Não tem pré-condição.

        A janela de liberação (`delay`) não muda por esta rota, nem para este
        recebedor nem para os outros recebedores do marketplace.

        ::: warning O que considerar
        - "Dia útil" é de segunda a sexta: a antecipação também roda em
          feriado bancário.
        - Se a empresa do recebedor estiver com o credenciamento bloqueado, a
          antecipação automática não roda, e o `status` do recebedor continua
          `active`.
        :::

        A resposta é o recebedor completo, com
        `automatic_anticipation_settings` refletindo a configuração que ficou
        valendo. `delay` traz a janela de liberação atual.
      parameters:
        - $ref: '#/components/parameters/PagarmeRecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PagarmeAnticipationSettingsInput'
            examples:
              ligar:
                summary: Ligar a antecipação automática
                value:
                  enabled: true
                  type: full
                  volume_percentage: '100'
                  days: []
              desligar:
                summary: Desligar a antecipação automática
                value:
                  enabled: false
                  type: full
                  volume_percentage: '100'
      responses:
        '200':
          description: Configuração aplicada. O corpo traz o recebedor completo.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PagarmeRecipient'
              examples:
                ligado:
                  summary: Trecho da resposta com a antecipação ligada
                  value:
                    data:
                      id: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
                      status: active
                      automatic_anticipation_settings:
                        enabled: true
                        type: full
                        volume_percentage: 100
                        delay: 30
                        days: []
        '403':
          $ref: '#/components/responses/PagarmeForbidden'
        '404':
          $ref: '#/components/responses/PagarmeRecipientNotFound'
        '422':
          description: |
            Configuração fora da aceita, recebedor ainda não aprovado, sem
            conta bancária padrão ou fora do fluxo de recebíveis que suporta
            antecipação. Nada é alterado. A mensagem informa o valor aceito.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PagarmeFieldErrors'
                  - $ref: '#/components/schemas/PagarmeMessageEnvelope'
        '500':
          description: Erro interno.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PagarmeMessageEnvelope'

  /v1/recipient/{recipient_hash}/orders:
    get:
      operationId: listRecipientOrders
      tags: [Split]
      summary: Listar pedidos do recebedor
      description: |
        Lista, de forma paginada, os pedidos do merchant autenticado em que o
        recebedor participou do split, do mais recente para o mais antigo.
        Cada página traz até **100 pedidos**.

        Para cada pedido são retornados o valor total (`total`), o valor
        líquido (`liquid_total`) e quanto foi destinado ao recebedor
        (`recipient_amount`). Se o recebedor tiver mais de um split no mesmo
        pedido, o pedido aparece uma única vez e `recipient_amount` traz a soma
        dos splits.

        ::: info Valores em centavos
        Todos os valores da resposta (`total`, `liquid_total` e
        `recipient_amount`) são inteiros **em centavos**.
        :::
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Página a ser retornada.
          example: 1
      responses:
        '200':
          description: Pedidos retornados.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListRecipientOrdersResponse'
              examples:
                default:
                  value:
                    data:
                      - order_id: 501
                        total: 30000
                        liquid_total: 28550
                        recipient_amount: 10000
                      - order_id: 500
                        total: 15000
                        liquid_total: 14230
                        recipient_amount: 4035
                    meta:
                      page: 1
                      pages: 1
                      total: 2
                      limit: 100
                empty:
                  summary: Recebedor sem pedidos
                  value:
                    data: []
                    meta:
                      page: 1
                      pages: 0
                      total: 0
                      limit: 100
        '403':
          description: Token ausente ou inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Token de autenticação ausente
                        example: Missing Authentication Token
        '404':
          description: Recebedor não encontrado ou não cadastrado pelo merchant autenticado.
          content:
            application/json:
              examples:
                default:
                  value:
                    error: Recipient not found
        '422':
          description: Parâmetro `page` inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
        '500':
          description: Erro interno.
          content:
            application/json:
              examples:
                default:
                  value:
                    error: Internal Server Error

  /v1/orders/{orderId}/split-order:
    post:
      operationId: createOrderSplit
      tags: [Split]
      summary: Criar split de pedido
      description: |
        Divide o valor líquido de um pedido entre um ou mais recebedores. Os
        valores são informados em **centavos** e cada linha referencia um
        `recipient_hash` previamente aprovado (`Onboarding completed`).

        ::: tip Valores e taxas
        O split é limitado ao `partner_total` — valor do pedido menos as taxas
        da Appmax. A taxa é descontada automaticamente do marketplace; **não
        envie taxa no payload**. Se a soma ultrapassar o `partner_total`, a
        divisão é proporcional e o último recebedor recebe apenas o residual,
        **sem retorno de erro**.
        :::
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: integer
          description: ID do pedido na Appmax.
          example: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSplitOrderRequest'
            examples:
              default:
                value:
                  split:
                    - amount: 1000
                      recipient_hash: d9bd0dae-3274-5e5a-939f-f50d867eb652
                      refund_charge_percentage: 0
                    - amount: 500
                      recipient_hash: d9bd0dae-3274-5e5a-939f-f50d867eb653
                      refund_charge_percentage: 0
      responses:
        '201':
          description: Split criado com sucesso.
          content:
            application/json:
              examples:
                default:
                  value:
                    message: Split order created successfully
        '422':
          description: Erro de validação dos itens do split.

  /v1/recipient/{recipient_hash}/balances:
    get:
      operationId: getRecipientBalances
      tags: [Split]
      summary: Consultar saldos do recebedor
      description: |
        Retorna os saldos financeiros do recebedor. Pode haver dois tipos:
        - `available` — saldo disponível para saque imediato.
        - `to_release` — saldo ainda em compensação (pode ser antecipado).
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      responses:
        '200':
          description: Saldos retornados.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      - type: available
                        value: '150.00'
                      - type: to_release
                        value: '250.00'
        '404':
          description: Saldos não provisionados para esse recebedor.
        '500':
          description: Erro interno.

  /v1/recipient/{recipient_hash}/withdraw-request/anticipation/simulate:
    get:
      operationId: simulateAnticipation
      tags: [Split]
      summary: Simular antecipação de saque
      description: |
        Simula a antecipação de um saque e retorna valor bruto, valor líquido,
        taxa aplicada e percentual da taxa.

        ::: info
        **Nenhuma solicitação de saque é criada** por este endpoint. Ele é
        puramente informativo, para cálculo antes da confirmação real.
        :::
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
        - name: value
          in: query
          required: true
          schema:
            type: string
          description: Valor do saque a simular em **centavos**.
          example: '10000'
      responses:
        '200':
          description: Simulação concluída.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      value: 10000
                      net_value: 4700
                      withdraw_tax: 300
                      tax_percentage: 300
        '404':
          description: Recebedor não encontrado.
        '422':
          description: Conta bancária inválida.
        '423':
          description: Saque já em andamento.
        '500':
          description: Erro interno.

  /v1/recipient/{recipient_hash}/withdraw-request/anticipation:
    post:
      operationId: requestAnticipation
      tags: [Split]
      summary: Solicitar antecipação de saque
      description: |
        Cria uma solicitação real de antecipação usando saldo do tipo
        `to_release`. Ao contrário da simulação, este endpoint efetivamente
        movimenta o saldo.
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawRequestBody'
            examples:
              default:
                value:
                  value: 1000
      responses:
        '201':
          description: Antecipação criada com status `2 = pending`.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      withdraw_request_id: 1
                      status: 2
                      value: 10000
                      net_value: 4700
                      withdraw_tax: 5000
        '403':
          description: '`Withdraw not allowed` (pode indicar `withdrawal_blocked`).'
        '404':
          description: Recebedor não encontrado.
        '409':
          description: Saque em andamento para o mesmo recebedor.
        '422':
          description: Validação de input ou regra de negócio (saldo insuficiente, limite, conta inválida).
        '500':
          description: Erro interno.

  /v1/recipient/{recipient_hash}/withdraw-request/available:
    post:
      operationId: requestAvailableWithdraw
      tags: [Split]
      summary: Solicitar saque com saldo disponível
      description: |
        Cria uma solicitação de saque usando o saldo **available**. Sem taxa
        de antecipação — o saldo já está liberado.

        ::: tip
        Se o recebedor tem `to_release` mas não `available`, este endpoint
        retorna `Insufficient balance`. Nesse caso, use
        [antecipação](/api-reference/split/antecipacao).
        :::
      parameters:
        - $ref: '#/components/parameters/RecipientHash'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawRequestBody'
            examples:
              default:
                value:
                  value: 1000
      responses:
        '201':
          description: Saque criado com status `2 = pending`.
          content:
            application/json:
              examples:
                default:
                  value:
                    data:
                      withdraw_request_id: 1
                      status: 2
                      value: 0
                      net_value: 0
                      withdraw_tax: 0
        '403':
          description: Saque não permitido.
        '404':
          description: Recebedor não encontrado.
        '409':
          description: Saque em andamento.
        '422':
          description: Validação ou regra de negócio (`Insufficient balance`, `Invalid bank account`).
        '500':
          description: Erro interno.

  /v1/withdraw-request/{withdrawRequestId}:
    get:
      operationId: getWithdrawRequest
      tags: [Split]
      summary: Consultar solicitação de saque
      description: |
        Retorna os detalhes de uma solicitação de saque específica pelo seu
        identificador (`withdrawRequestId`), incluindo status atual, valores
        e a conta bancária vinculada.

        Cobre tanto solicitações criadas via
        [saldo disponível](/api-reference/split/saque-disponivel) quanto via
        [antecipação](/api-reference/split/antecipacao).
      parameters:
        - name: withdrawRequestId
          in: path
          required: true
          schema:
            type: integer
          description: ID da solicitação de saque.
          example: 1234
      responses:
        '200':
          description: Solicitação encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetWithdrawRequestResponse'
              examples:
                default:
                  value:
                    data:
                      withdraw_request_id: 69
                      status: pending
                      net_value: 984
                      gross_value: 1000
                      withdraw_tax: 0
                      currency: BRL
                      created_at: '2025-12-30 11:08:04'
                      source_type: recipient
                      source_id: 4471b2d1-5e30-5f4d-a64b-c18d72c82a79
                      bank_account:
                        id: 9
                        bank: '66'
                        agency: '512'
                        account: '9260373'
                        name: null
                        type: national_pj
                        profile: CC
                        register_number: null
                        pix_key: test@test.com
                        bank_account_type: null
        '403':
          description: Não autorizado.
          content:
            application/json:
              examples:
                default:
                  value:
                    message: Unauthorized
        '404':
          description: Solicitação de saque não encontrada.
          content:
            application/json:
              examples:
                default:
                  value:
                    error: Withdraw request not found
        '500':
          description: Erro interno.
          content:
            application/json:
              examples:
                default:
                  value:
                    error: Internal Server Error

  /v1/subscriptions:
    get:
      operationId: listSubscriptions
      tags: [Subscriptions]
      summary: Listar assinaturas
      description: |
        Lista as assinaturas da loja, com paginação e filtros por e-mail, status e
        intervalo de próxima cobrança. Retorna 10 itens por página.

        Informe **pelo menos um** entre `email`, `next_charge_from` e
        `next_charge_to`. Os filtros são combináveis: `email` sozinho devolve as
        assinaturas daquele cliente, o intervalo sozinho devolve as da loja inteira
        que cobram no período, e juntos devolvem as daquele cliente no período.
        As duas pontas do intervalo são opcionais entre si: só `next_charge_to`
        lista o que vai cobrar de agora até aquela data.

        ::: warning Formato de data na listagem
        Nesta rota `next_charge_at` e `charges[].charged_at` vêm no formato
        `dd/mm/aaaa`. No detalhe da assinatura, `next_charge_at` vem como
        `aaaa-mm-dd hh:mm:ss`. Já os filtros `next_charge_from` e `next_charge_to`
        são informados como `aaaa-mm-dd`.
        :::
      parameters:
        - name: email
          in: query
          required: false
          schema:
            type: string
            format: email
          description: |
            E-mail do cliente. Obrigatório quando nem `next_charge_from` nem
            `next_charge_to` forem informados.
          example: cliente@exemplo.com
        - name: next_charge_from
          in: query
          required: false
          schema:
            type: string
            format: date
          description: |
            Data inicial da próxima cobrança, no formato `aaaa-mm-dd` e no fuso de
            Brasília. Considera o dia inteiro. Pode ser usada sozinha, para listar
            tudo que cobra a partir daquele dia. Quando omitida e `next_charge_to`
            for informada, o início passa a ser o momento atual — ou seja, a
            listagem traz o que ainda vai cobrar daqui até a data final.
          example: '2026-09-18'
        - name: next_charge_to
          in: query
          required: false
          schema:
            type: string
            format: date
          description: |
            Data final da próxima cobrança, no formato `aaaa-mm-dd` e no fuso de
            Brasília. Considera o dia inteiro, então informar a mesma data em
            `next_charge_from` e `next_charge_to` lista as cobranças daquele dia.
            Deve ser maior ou igual a `next_charge_from`. Sozinha, lista o que vai
            cobrar do momento atual até essa data.
          example: '2026-09-18'
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [ACTIVE, PAUSED, CANCELLED, OVERDUE]
          description: |
            Filtra pelo status da assinatura. Aceita `ACTIVE`, `PAUSED`, `CANCELLED`
            e `OVERDUE`. Sem filtro, a listagem devolve todos os status, incluindo
            `SUSPENDED` e `FINISHED` — veja `status` no item da listagem.

            Ao usar o intervalo de próxima cobrança sem informar `status`, a listagem
            traz apenas o que de fato será cobrado no período: assinaturas `ACTIVE`
            não pausadas e `OVERDUE`. Informando `status`, ele prevalece e o intervalo
            apenas recorta aquele status.
          example: ACTIVE
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Página da listagem.
          example: 1
      responses:
        '200':
          description: Lista de assinaturas retornada com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListSubscriptionsResponse'
              examples:
                default:
                  value:
                    data:
                      subscriptions:
                        - subscription_id: 10
                          status: ACTIVE
                          products:
                            - name: Camiseta Preta
                              price: 199.9
                              quantity: 1
                              shopify_product_id: null
                              variant_id: null
                              product_id: 55
                              image_url: null
                              compare_at_price: null
                          value: 199.9
                          freight_value: 19.9
                          current_cycle: 3
                          customer:
                            name: Maria Silva
                            email: cliente@exemplo.com
                            document: '12345678909'
                            phone: '11999999999'
                            address:
                              country: BR
                              street: Rua São Bento
                              number: '111'
                              complement: Bloco 7
                              district: Centro
                              city: São Paulo
                              state: SP
                              postcode: '01010000'
                          payment:
                            payment_info:
                              brand: visa
                              final: '1234'
                              expiration: null
                          charges:
                            - cycle: 1
                              order_id: 12346
                              shopify_order_id: null
                              charged_at: 15/07/2026
                              value: 199.9
                              status: paid
                          next_charge_at: 15/08/2026
                      pagination:
                        total: 1
                        per_page: 10
                        current_page: 1
                        last_page: 1
        '422':
          description: Parâmetros inválidos (por exemplo, `email` ausente ou inválido).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        email:
                          - The email field is required.
        '500':
          description: Erro ao listar as assinaturas.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao listar as assinaturas.
    post:
      operationId: createSubscription
      tags: [Subscriptions]
      summary: Criar assinatura
      description: |
        Cria uma assinatura a partir de um pedido existente. O pedido informado
        em `order_id` é transformado em uma cobrança recorrente.

        ::: warning Pré-requisitos do pedido
        - Deve existir e pertencer à mesma loja das credenciais.
        - Forma de pagamento deve ser **cartão de crédito** ou **PIX**.
        - Status deve ser **aprovado** ou **integrado** (ou seja, já liberado pelo antifraude).
        :::

        Um mesmo pedido pode originar mais de uma assinatura.

        ::: tip Pedidos originados de um checkout Shopify
        Quando o pedido tem carrinho Shopify vinculado, `products` é **obrigatório** e
        cada item precisa de `shopify_product_id` **e** `shopify_variant_id` (o preço é
        lido da variante na Shopify, não do payload).

        - Com `interval` informado, a cadência enviada vale para todos os itens.
        - Sem `interval`, a cadência vem do plano de assinatura da variante e todos os
          itens precisam ter a mesma frequência.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSubscriptionRequest'
            examples:
              default:
                value:
                  order_id: 12345
                  interval: month
                  interval_count: 1
                  max_cycles: 12
                  products:
                    - product_id: 55
                      quantity: 1
                  freight_value: 19.9
                  discount: 10.0
      responses:
        '201':
          description: Assinatura criada com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSubscriptionResponse'
              examples:
                default:
                  value:
                    data:
                      id: 10
                      uuid: 6f1c0f1e-6a1c-4a2b-9f0e-3d5c7a9b1234
                      status: active
                      subscription_status_id: 1
                      interval: month
                      interval_count: 1
                      interval_name: Mensal
                      charge_day: 15
                      fail_max_tries: 3
                      fail_interval_hours: 24
                      max_cycles: 12
                      completed_cycles: 0
                      current_cycle: 1
                      next_charge_at: '2026-08-15 00:00:00'
                      created_at: '2026-07-15 10:00:00'
                      freight_value: 19.9
                      products:
                        - name: Camiseta Preta
                          price: 199.9
                          quantity: 1
                          product_id: 55
                          shopify_product_id: null
                          variant_id: null
                          sku: SKU-1
                          image_url: null
                          compare_at_price: null
                      customer:
                        name: Maria Silva
                        email: cliente@exemplo.com
                        document: '12345678909'
                        phone: '11999999999'
                        address:
                          country: BR
                          street: Rua São Bento
                          number: '111'
                          complement: Bloco 7
                          district: Centro
                          city: São Paulo
                          state: SP
                          postcode: '01010000'
                      payment:
                        payment_info:
                          brand: visa
                          final: '1234'
                          expiration: 12/2029
                      charges:
                        - cycle: 0
                          order_id: 12345
                          shopify_order_id: null
                          charged_at: 15/07/2026
                          value: 209.8
                          status: paid
                      panel_url: https://assinaturas.appmax.com.br/painel/6f1c0f1e-6a1c-4a2b-9f0e-3d5c7a9b1234
        '400':
          description: |
            Pedido inválido para virar assinatura (forma de pagamento, status,
            token reutilizável ausente ou produtos inválidos).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                paymentType:
                  value:
                    errors:
                      message: Apenas pedidos de cartão de crédito ou PIX podem virar assinatura.
                status:
                  value:
                    errors:
                      message: Pedido precisa estar aprovado ou integrado.
                shopifyProducts:
                  value:
                    errors:
                      message: Para pedidos Shopify é obrigatório informar os produtos da assinatura.
        '404':
          description: Pedido não encontrado (ou de outra loja).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Pedido não encontrado.
        '409':
          description: Conflito na seleção de produtos (o mesmo produto informado duas vezes).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Produto duplicado na seleção.
        '422':
          description: |
            Erro de validação dos campos ou regra de negócio (produto inválido,
            variante inexistente na Shopify, frequências divergentes ou desconto
            que deixa o valor abaixo do mínimo).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                minValue:
                  summary: Desconto abaixo do mínimo
                  value:
                    errors:
                      message: O desconto informado deixa o valor da assinatura abaixo do mínimo permitido.
                validation:
                  summary: Campo obrigatório ausente
                  value:
                    errors:
                      message:
                        order_id:
                          - The order id field is required.
        '500':
          description: Erro ao criar a assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao criar a assinatura.

  /v1/subscriptions/{id}:
    get:
      operationId: getSubscription
      tags: [Subscriptions]
      summary: Consultar assinatura
      description: |
        Retorna os detalhes completos da assinatura, incluindo produtos, ciclos e
        o histórico de cobranças.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Detalhes da assinatura retornados com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
              examples:
                default:
                  value:
                    data:
                      subscription_id: 10
                      status: ACTIVE
                      products:
                        - name: Camiseta Preta
                          shopify_product_id: null
                          variant_id: null
                          product_id: 55
                          sku: SKU-1
                          quantity: 1
                          price: 199.9
                          image_url: null
                          compare_at_price: null
                      value: 199.9
                      freight_value: 19.9
                      interval: month
                      interval_count: 1
                      current_cycle: 3
                      completed_cycles: 2
                      max_cycles: 12
                      customer:
                        name: Maria Silva
                        email: cliente@exemplo.com
                        document: '12345678909'
                        phone: '11999999999'
                        address:
                          country: BR
                          street: Rua São Bento
                          number: '111'
                          complement: Bloco 7
                          district: Centro
                          city: São Paulo
                          state: SP
                          postcode: '01010000'
                      payment:
                        payment_info:
                          brand: visa
                          final: '1234'
                          expiration: 12/2029
                      next_charge_at: '2026-08-15 00:00:00'
                      last_charge_at: 15/07/2026
                      charges:
                        - cycle: 2
                          order_id: 12347
                          shopify_order_id: null
                          charged_at: 15/07/2026
                          value: 199.9
                          status: paid
                      created_at: '2026-06-15 10:00:00'
                      canceled_at: null
                      paused_from: null
                      paused_until: null
        '404':
          description: Assinatura não encontrada (ou de outra loja).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao buscar a assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao buscar a assinatura.

  /v1/subscriptions/{id}/pause:
    patch:
      operationId: pauseSubscription
      tags: [Subscriptions]
      summary: Pausar assinatura
      description: |
        Pausa temporariamente a assinatura. Opcionalmente informe uma data até a
        qual ela permanece pausada e um motivo. Retorna o detalhe atualizado com
        `status: PAUSED`.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PauseSubscriptionRequest'
            examples:
              default:
                value:
                  paused_until: '2026-09-01'
                  reason: Cliente solicitou pausa temporária
      responses:
        '200':
          description: Assinatura pausada. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
              examples:
                default:
                  value:
                    data:
                      subscription_id: 10
                      status: PAUSED
                      paused_from: '2026-08-04 00:00:00'
                      paused_until: '2026-09-01 00:00:00'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: Erro de validação dos campos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
        '500':
          description: Erro ao pausar a assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/activate:
    patch:
      operationId: activateSubscription
      tags: [Subscriptions]
      summary: Reativar assinatura
      description: |
        Reativa uma assinatura pausada, retomando as cobranças recorrentes. Não
        requer corpo. Retorna o detalhe atualizado com `status: ACTIVE`.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Assinatura reativada. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
              examples:
                default:
                  value:
                    data:
                      subscription_id: 10
                      status: ACTIVE
                      paused_from: null
                      paused_until: null
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao reativar a assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cancel:
    patch:
      operationId: cancelSubscription
      tags: [Subscriptions]
      summary: Cancelar assinatura
      description: |
        Cancela a assinatura de forma definitiva — nenhuma nova cobrança será
        gerada. Opcionalmente registre um motivo. Retorna o detalhe atualizado
        com `status: CANCELLED` e `canceled_at` preenchido.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelSubscriptionRequest'
            examples:
              default:
                value:
                  reason: Cliente não deseja mais o produto
      responses:
        '200':
          description: Assinatura cancelada. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
              examples:
                default:
                  value:
                    data:
                      subscription_id: 10
                      status: CANCELLED
                      canceled_at: '2026-08-04 12:00:00'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: Erro de validação dos campos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
        '500':
          description: Erro ao cancelar a assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/charge-day:
    patch:
      operationId: updateSubscriptionChargeDay
      tags: [Subscriptions]
      summary: Alterar o dia de cobrança
      description: |
        Altera o dia fixo de cobrança da assinatura. A mudança vale para todos os
        ciclos futuros e a próxima data de cobrança (`next_charge_at`) é
        recalculada.

        Dias 29, 30 e 31 são aceitos: nos meses que não têm o dia escolhido a cobrança
        cai no último dia do mês, e o dia volta a valer no mês seguinte que o tiver.
        Uma assinatura no dia 31 cobra 31/01, 28/02, 31/03, 30/04.

        ### Quando o novo dia passa a valer

        A periodicidade da assinatura não muda — só o dia. A partir daí:

        - **o dia ainda vai ocorrer neste mês** → vale já na próxima cobrança;
        - **o dia já passou neste mês** → vale na próxima vez que ele ocorrer, ainda que
          isso desloque os meses da assinatura (uma bimestral de set/nov passa a cobrar
          em out/dez). Um mês de espera em vez de um ciclo inteiro;
        - **a data ficaria a menos de meio ciclo da última cobrança** → o novo dia vale a
          partir do ciclo seguinte, para que ninguém seja cobrado duas vezes por causa de
          um ajuste de calendário.

        Nos dois últimos casos a resposta vem com `adjusted: true` e o motivo em
        `adjusted_reason` / `adjusted_message`, para você saber por que a data entregue
        não é a que o dia escolhido produziria.

        Assinaturas com periodicidade em dias (`interval: day`) não têm dia fixo de
        cobrança: cada cobrança acontece N dias depois da anterior. Nelas a alteração é
        recusada com `422`.

        Ciclos já pagos adiantado por
        [antecipação de cobrança](/api-reference/subscriptions/antecipar-cobranca) e
        ciclos [pulados](/api-reference/subscriptions/pular-ciclo) são preservados: o
        novo dia é aplicado ao ciclo que já estava agendado. Quem antecipou dois ciclos
        continua com a próxima cobrança dois ciclos à frente, agora no dia escolhido.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateChargeDayRequest'
            examples:
              default:
                value:
                  charge_day: 15
      responses:
        '200':
          description: Dia de cobrança atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateChargeDayResponse'
              examples:
                default:
                  value:
                    data:
                      charge_day: 15
                      next_charge_at: '2026-08-15 00:00:00'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: '`charge_day` fora do intervalo permitido (1 a 31).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        charge_day:
                          - The charge day may not be greater than 28.
        '500':
          description: Erro ao alterar o dia de cobrança.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/force-charge:
    post:
      operationId: forceSubscriptionCharge
      tags: [Subscriptions]
      summary: Antecipar a cobrança
      description: |
        Antecipa o próximo ciclo agendado da assinatura: a cobrança é feita na hora e a
        resposta já traz o desfecho. Não recebe corpo — o ciclo cobrado é sempre o
        próximo, o mesmo que seria cobrado na data agendada.

        Quando aprovada, o ciclo é consumido como em uma renovação normal
        (`next_charge_at` avança para a data seguinte do calendário, sem deslocá-lo) e o
        pedido gerado é devolvido em `order_id`.

        Quando a operadora recusa, nada muda na assinatura: ela **não** entra em atraso,
        não acumula tentativas de falha e continua com a cobrança marcada para a data
        original. A resposta `402` traz o motivo informado pela adquirente.

        Disponível apenas para assinaturas ativas pagas com cartão de crédito.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Cobrança aprovada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ForceChargeResponse'
              examples:
                default:
                  value:
                    data:
                      charge_id: 41287
                      order_id: 126509440
                      cycle_index: 7
                      value: 189.9
                      currency: BRL
                      status: active
                      next_charge_at: '2026-10-20 00:00:00'
        '402':
          description: Cobrança recusada pela operadora. A assinatura segue inalterada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: 'Transação não autorizada - Saldo insuficiente'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: Já existe uma cobrança em andamento ou a próxima cobrança já está em processamento.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Existe uma cobrança em andamento; não é possível antecipar agora.
        '422':
          description: Assinatura não elegível (inativa, pausada, sem cartão ou com o próximo ciclo pulado).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Só é possível antecipar a cobrança de assinaturas ativas.
        '500':
          description: Erro ao antecipar a cobrança.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/frequency:
    patch:
      operationId: updateSubscriptionFrequency
      tags: [Subscriptions]
      summary: Alterar a periodicidade
      description: |
        Altera a periodicidade (`interval` + `interval_count`) da assinatura e
        recalcula `next_charge_at`. Retorna o detalhe atualizado da assinatura.

        A periodicidade enviada precisa ser oferecida pelo produto da assinatura.
        Para assinaturas de produtos Shopify, isso significa existir uma variante de
        assinatura naquela cadência; caso contrário a troca é recusada com `422`.

        ::: warning Quando a troca é recusada
        - assinatura não está ativa ou está pausada;
        - a periodicidade enviada é igual à atual;
        - já existe uma cobrança em processamento (`409`);
        - a próxima cobrança está dentro da janela do agendador (`409`).
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionFrequencyRequest'
            examples:
              default:
                value:
                  interval: month
                  interval_count: 2
      responses:
        '200':
          description: Periodicidade alterada. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
              examples:
                default:
                  value:
                    data:
                      subscription_id: 10
                      status: ACTIVE
                      interval: month
                      interval_count: 2
                      next_charge_at: '2026-10-15 00:00:00'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: |
            Existe uma cobrança em processamento ou a próxima cobrança já entrou
            na janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: charge_in_scheduler_window
        '422':
          description: |
            Periodicidade indisponível para o produto, igual à atual, assinatura
            inativa/pausada ou campos inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                notAvailable:
                  summary: Periodicidade não oferecida pelo produto
                  value:
                    errors:
                      message: frequency_not_available_for_product
                validation:
                  summary: Campo obrigatório ausente
                  value:
                    errors:
                      message:
                        interval:
                          - The interval field is required.
        '500':
          description: Erro ao alterar a periodicidade da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/{cycleIndex}/skip:
    patch:
      operationId: skipSubscriptionCycle
      tags: [Subscriptions]
      summary: Pular um ciclo
      description: |
        Marca um ciclo futuro para ser pulado: a assinatura segue ativa, mas
        aquele ciclo não gera cobrança nem pedido — a cobrança salta para o
        próximo ciclo não pulado.

        `cycleIndex` é o número do ciclo (1 = primeiro ciclo de recorrência).
        Só é possível pular ciclos ainda **não processados** — use
        `completed_cycles` do detalhe da assinatura como referência.

        Para desfazer, chame
        [desfazer o skip](/api-reference/subscriptions/desfazer-skip-ciclo).
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionCycleIndex'
      responses:
        '200':
          description: Ciclo marcado como pulado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionCycleResponse'
              examples:
                default:
                  value:
                    data:
                      cycle_index: 3
                      status: skipped
                      next_charge_at: '2026-10-15 00:00:00'
        '400':
          description: |
            Ciclo já processado, fora do limite de `max_cycles` ou assinatura
            que não está ativa.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                alreadyProcessed:
                  value:
                    errors:
                      message: Só é possível pular ciclos que ainda não foram processados.
                notActive:
                  value:
                    errors:
                      message: Só é possível pular ciclos de assinaturas ativas.
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao pular o ciclo da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/{cycleIndex}/unskip:
    patch:
      operationId: unskipSubscriptionCycle
      tags: [Subscriptions]
      summary: Desfazer o skip de um ciclo
      description: |
        Remove a marcação de "pulado" de um ciclo futuro, devolvendo-o à fila de
        cobrança. O ciclo precisa estar marcado como pulado e ainda não ter sido
        processado.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionCycleIndex'
      responses:
        '200':
          description: Skip desfeito. O ciclo volta a ser cobrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionCycleResponse'
              examples:
                default:
                  value:
                    data:
                      cycle_index: 3
                      status: unbilled
                      next_charge_at: '2026-09-15 00:00:00'
        '400':
          description: Ciclo já processado ou que não estava marcado como pulado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                notSkipped:
                  value:
                    errors:
                      message: O ciclo informado não está marcado como pulado.
                alreadyProcessed:
                  value:
                    errors:
                      message: Não é possível desfazer o skip de um ciclo já processado.
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao desfazer o skip do ciclo da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/products:
    post:
      operationId: addSubscriptionCycleProducts
      tags: [Subscriptions]
      summary: Adicionar produtos em um ciclo
      description: |
        Adiciona um ou mais produtos **apenas em um ciclo** da assinatura. A
        composição permanente não muda: o ciclo seguinte volta a cobrar os
        produtos de sempre, sem nenhuma ação adicional.

        `cycle_index` é opcional — sem ele, os produtos entram no **próximo
        ciclo** (`completed_cycles + 1`). Informando, precisa ser um ciclo ainda
        não processado. O ciclo usado vem em `cycle_index` na resposta.

        A forma de identificar o produto é a mesma de
        [adicionar produtos](/api-reference/subscriptions/adicionar-produtos):
        `product_id` para produtos internos da Appmax, `shopify_product_id` +
        `shopify_variant_id` para produtos da Shopify. O valor do ciclo passa a
        ser o valor da assinatura somado aos produtos adicionados.

        Este endpoint também **desfaz uma remoção**: se o produto informado é da
        assinatura e tinha sido removido daquele ciclo, ele volta à composição.

        ::: tip Produto avulso, sem exigência de periodicidade
        Como o produto vale só para aquele ciclo, a variante **não** precisa ter
        a mesma periodicidade da assinatura.
        :::

        ::: warning Ciclos pulados
        Um ciclo marcado para ser
        [pulado](/api-reference/subscriptions/pular-ciclo) não aceita alterações,
        e um ciclo alterado não pode ser pulado — desfaça as alterações antes.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddSubscriptionCycleProductsRequest'
            examples:
              nextCycle:
                summary: Próximo ciclo (sem informar o ciclo)
                value:
                  products:
                    - product_id: 55
                      quantity: 1
              specificCycle:
                summary: Ciclo específico
                value:
                  cycle_index: 3
                  products:
                    - product_id: 55
                      quantity: 1
              shopify:
                summary: Produto da Shopify
                value:
                  cycle_index: 3
                  products:
                    - shopify_product_id: '123'
                      shopify_variant_id: '456'
                      quantity: 1
      responses:
        '200':
          description: Produtos adicionados ao ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionCycleProductsResponse'
              examples:
                default:
                  value:
                    data:
                      cycle_index: 3
                      products:
                        - name: Produto A
                          price: 100
                          quantity: 1
                          product_id: 55
                          sku: SKU-A
                      added_products:
                        - product_id: 77
                          quantity: 1
                          price: 30
                      removed_products: []
                      value: 130
        '400':
          description: |
            Ciclo já processado, fora do limite de `max_cycles` ou marcado para
            ser pulado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                alreadyProcessed:
                  value:
                    errors:
                      message: Só é possível alterar ciclos que ainda não foram processados.
                skipped:
                  value:
                    errors:
                      message: Não é possível adicionar produtos em um ciclo pulado.
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '409':
          description: |
            Produto já presente no ciclo, ou assinatura com cobrança em andamento
            / dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                alreadyInCycle:
                  value:
                    errors:
                      message: Produto já está no ciclo informado.
        '422':
          description: |
            Assinatura não ativa, produto inválido/inativo ou variante não
            encontrada na Shopify.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '500':
          description: Erro ao adicionar produto no ciclo da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/products/{productRef}:
    delete:
      operationId: removeSubscriptionCycleProduct
      tags: [Subscriptions]
      summary: Remover produto de um ciclo
      description: |
        Remove um produto **apenas daquele ciclo**, sem alterar a composição
        permanente da assinatura. Vale para os dois casos:

        - um produto que tinha sido **adicionado** ao ciclo — desfaz a adição;
        - um produto **da assinatura** — ele deixa de ser cobrado e entregue
          naquele ciclo, e o valor do ciclo cai no preço dele.

        `cycle_index` é opcional na query string — sem ele, vale o **próximo
        ciclo** (`completed_cycles + 1`).

        Quando o ciclo volta integralmente à composição da assinatura, a
        alteração deixa de existir.

        ::: warning O ciclo não pode ficar vazio
        Sempre precisa sobrar ao menos um produto no ciclo, senão não haveria o
        que cobrar. A tentativa de remover o último responde `422`.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionCycleProductRef'
        - $ref: '#/components/parameters/SubscriptionCycleIndexQuery'
      responses:
        '200':
          description: Produto removido do ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionCycleProductsResponse'
              examples:
                default:
                  value:
                    data:
                      cycle_index: 3
                      products:
                        - name: Produto A
                          price: 100
                          quantity: 1
                          product_id: 55
                          sku: SKU-A
                      added_products: []
                      removed_products:
                        - product_id: 56
                          quantity: 1
                          price: 40
                      value: 110
        '400':
          description: Ciclo já processado ou fora do limite de `max_cycles`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '404':
          description: |
            Assinatura não encontrada, ou o produto não está no ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                notInCycle:
                  value:
                    errors:
                      message: Produto não está na assinatura.
        '409':
          description: Cobrança em andamento / dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '422':
          description: |
            Assinatura não ativa, ou a remoção deixaria o ciclo sem nenhum
            produto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                emptyCycle:
                  value:
                    errors:
                      message: O ciclo deve manter ao menos um produto.
        '500':
          description: Erro ao remover produto do ciclo da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/products/{productRef}/quantity:
    patch:
      operationId: updateSubscriptionCycleProductQuantity
      tags: [Subscriptions]
      summary: Alterar a quantidade de um produto em um ciclo
      description: |
        Altera a quantidade de um produto **apenas naquele ciclo**, sem mexer na
        composição permanente da assinatura. Vale tanto para um produto da
        assinatura quanto para um que foi
        [adicionado ao ciclo](/api-reference/subscriptions/adicionar-produtos-ciclo).

        `cycle_index` é opcional — sem ele, vale o **próximo ciclo**
        (`completed_cycles + 1`). O valor do ciclo acompanha a nova quantidade.

        Voltar à quantidade original desfaz a alteração; quando o ciclo fica
        igual à assinatura, a alteração deixa de existir.

        ::: warning Quantidade mínima
        A quantidade precisa ser no mínimo `1`. Para não cobrar o produto
        naquele ciclo, use
        [remover produto de um ciclo](/api-reference/subscriptions/remover-produto-ciclo).
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionCycleProductRef'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionCycleProductQuantityRequest'
            examples:
              nextCycle:
                summary: Próximo ciclo
                value:
                  quantity: 3
              specificCycle:
                summary: Ciclo específico
                value:
                  cycle_index: 3
                  quantity: 3
      responses:
        '200':
          description: Quantidade alterada para aquele ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionCycleProductsResponse'
              examples:
                default:
                  value:
                    data:
                      cycle_index: 3
                      products:
                        - name: Produto A
                          price: 100
                          quantity: 3
                          product_id: 55
                          sku: SKU-A
                      added_products: []
                      removed_products: []
                      quantity_changes:
                        - reference: '55'
                          quantity: 3
                          previous_quantity: 1
                          name: Produto A
                          sku: SKU-A
                          product_id: 55
                      value: 310
        '400':
          description: |
            Ciclo já processado, fora do limite de `max_cycles` ou marcado para
            ser pulado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '404':
          description: Assinatura não encontrada, ou o produto não está no ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '409':
          description: Cobrança em andamento / dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '422':
          description: Assinatura não ativa ou quantidade menor que 1.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '500':
          description: Erro ao alterar a quantidade do produto no ciclo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/address:
    get:
      operationId: getSubscriptionAddress
      tags: [Subscriptions]
      summary: Consultar o endereço de entrega
      description: |
        Devolve o endereço de entrega que vale hoje para a assinatura.

        Assinatura que ainda não tem endereço próprio — criada antes desta rota
        existir, por exemplo — responde com o endereço do cadastro do cliente.
        `data.address` só vem `null` quando nenhuma das duas origens tem endereço
        utilizável (sem logradouro e sem CEP).

        O `postcode` vem apenas com dígitos e, em endereço brasileiro, o `state`
        vem em maiúsculas.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      responses:
        '200':
          description: Endereço em vigor na assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetSubscriptionAddressResponse'
              examples:
                default:
                  summary: Assinatura com endereço
                  value:
                    data:
                      address:
                        country: BR
                        state: SP
                        city: São Paulo
                        district: Centro
                        street: Rua São Bento
                        number: '111'
                        complement: Bloco 7
                        postcode: '01010000'
                empty:
                  summary: Sem endereço na assinatura nem no cliente
                  value:
                    data:
                      address: null
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao retornar o endereço da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

    post:
      operationId: createSubscriptionAddress
      tags: [Subscriptions]
      summary: Cadastrar o endereço de entrega
      description: |
        Cadastra o endereço próprio da assinatura, usado nos próximos ciclos de
        cobrança.

        Use quando a assinatura ainda não tem endereço próprio. Se já tiver, a
        resposta é `409` — nesse caso o caminho é o `PATCH` desta mesma rota.

        O `postcode` é gravado sem pontuação e o `state` de endereço brasileiro
        é gravado em maiúsculas — a resposta devolve os valores já normalizados.

        ::: tip
        O endereço é da assinatura, não do cliente: cadastrá-lo não altera o
        cadastro do cliente nem as outras assinaturas dele.
        :::

        Um cadastro bem-sucedido dispara o webhook `subscription_address_updated`.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscriptionAddressRequest'
            examples:
              default:
                summary: Endereço nacional
                value:
                  address:
                    postcode: 01010-000
                    street: Rua São Bento
                    number: '111'
                    complement: Bloco 7
                    district: Centro
                    city: São Paulo
                    state: SP
              international:
                summary: Endereço internacional
                value:
                  address:
                    country: CL
                    postcode: '1240000'
                    street: Avenida Angamos
                    number: '601'
                    district: Antofagasta
                    city: Antofagasta
                    state: Antofagasta
      responses:
        '201':
          description: Endereço cadastrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionAddressResponse'
              examples:
                default:
                  value:
                    data:
                      address:
                        country: BR
                        state: SP
                        city: São Paulo
                        district: Centro
                        street: Rua São Bento
                        number: '111'
                        complement: Bloco 7
                        postcode: '01010000'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: A assinatura já tem endereço próprio — use o `PATCH`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: A assinatura 123 já possui endereço próprio.
        '422':
          description: Erro de validação dos campos do endereço.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        address.postcode:
                          - The address.postcode field is required.
        '500':
          description: Erro ao cadastrar o endereço da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

    patch:
      operationId: updateSubscriptionAddress
      tags: [Subscriptions]
      summary: Atualizar o endereço de entrega
      description: |
        Atualiza o endereço de entrega da assinatura. Aplica-se aos próximos
        ciclos de cobrança.

        O endereço é gravado na própria assinatura. Se ela ainda não tiver
        endereço próprio, esta rota **cadastra** o endereço enviado em vez de
        devolver erro — não é preciso descobrir antes qual é o caso.

        O `postcode` é gravado sem pontuação e o `state` de endereço brasileiro
        é gravado em maiúsculas — a resposta devolve os valores já normalizados.

        ::: tip
        O endereço é da assinatura, não do cliente: alterá-lo não muda o cadastro
        do cliente nem as outras assinaturas dele.
        :::

        Uma alteração bem-sucedida dispara o webhook `subscription_address_updated`.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscriptionAddressRequest'
            examples:
              default:
                value:
                  address:
                    postcode: 01010-000
                    street: Rua São Bento
                    number: '111'
                    complement: Bloco 7
                    district: Centro
                    city: São Paulo
                    state: SP
      responses:
        '200':
          description: Endereço atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionAddressResponse'
              examples:
                default:
                  value:
                    data:
                      address:
                        country: BR
                        state: SP
                        city: São Paulo
                        district: Centro
                        street: Rua São Bento
                        number: '111'
                        complement: Bloco 7
                        postcode: '01010000'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: Erro de validação dos campos do endereço.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        address.postcode:
                          - The address.postcode field is required.
        '500':
          description: Erro ao atualizar o endereço da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/cycles/discount:
    patch:
      operationId: setSubscriptionCycleDiscount
      tags: [Subscriptions]
      summary: Definir o desconto de um ciclo
      description: |
        Define um desconto que vale **apenas em um ciclo**, sobrepondo o desconto da
        assinatura naquela cobrança. Os demais ciclos seguem com o desconto normal.

        Omita `cycle_index` para aplicar no **próximo ciclo**. O desconto substitui o
        da assinatura, não soma — e no percentual incide sobre os produtos daquele
        ciclo, já com as alterações de produto que ele tiver.

        Use o `DELETE` deste mesmo caminho para remover o desconto do ciclo e devolvê-lo
        ao desconto da assinatura.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetCycleDiscountRequest'
            examples:
              proximo:
                summary: R$ 40,00 no próximo ciclo
                value:
                  discount: 40
                  discount_type: nominal
              especifico:
                summary: 15% no ciclo 4
                value:
                  cycle_index: 4
                  discount: 15
                  discount_type: percent
      responses:
        '200':
          description: Desconto do ciclo definido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CycleDiscountResponse'
        '400':
          description: |
            Ciclo já processado, ciclo pulado, ou fora do limite da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                processado:
                  value:
                    errors:
                      message: Só é possível alterar ciclos que ainda não foram processados.
                pulado:
                  value:
                    errors:
                      message: Não é possível alterar o desconto de um ciclo pulado.
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
        '422':
          description: |
            Erro de validação, assinatura não ativa, cobrança em andamento, ou desconto
            que deixaria o valor do ciclo abaixo do mínimo de R$ 5,00.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
    delete:
      operationId: removeSubscriptionCycleDiscount
      tags: [Subscriptions]
      summary: Remover o desconto de um ciclo
      description: |
        Remove o desconto próprio do ciclo. A cobrança daquele ciclo volta a usar o
        desconto da assinatura.

        Omita `cycle_index` para agir sobre o **próximo ciclo**. Remover um ciclo que não
        tinha desconto próprio não faz nada e responde `200`.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - name: cycle_index
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
          description: |
            Ciclo a limpar. Omitido, vale para o próximo ciclo.
          example: 4
      responses:
        '200':
          description: Desconto do ciclo removido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CycleDiscountResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/discount:
    patch:
      operationId: updateSubscriptionDiscount
      tags: [Subscriptions]
      summary: Alterar o desconto da assinatura
      description: |
        Define o desconto da assinatura. Pode ser chamado a qualquer momento — para
        adicionar, alterar ou remover o desconto — e vale a partir da próxima
        cobrança gerada.

        O desconto pode ser **nominal** (valor fixo em reais) ou **percentual**. No
        percentual, o desconto incide sobre o valor dos produtos e é recalculado a
        cada cobrança: se a composição da assinatura mudar, o desconto acompanha.
        O frete é somado depois do desconto e nunca é descontado.

        Envie `discount: 0` para remover o desconto.

        ::: tip
        Uma cobrança que já está em cobrança vencida (retentativa) não é afetada —
        ela reutiliza o pedido que já havia sido gerado. A alteração vale do
        próximo ciclo em diante.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionDiscountRequest'
            examples:
              nominal:
                summary: Desconto fixo de R$ 40,00
                value:
                  discount: 40
                  discount_type: nominal
              percent:
                summary: Desconto de 10% sobre os produtos
                value:
                  discount: 10
                  discount_type: percent
              remove:
                summary: Remove o desconto
                value:
                  discount: 0
      responses:
        '200':
          description: Desconto atualizado. Retorna o detalhe atualizado da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: |
            Erro de validação, ou desconto que deixaria o valor da assinatura abaixo
            do mínimo de R$ 5,00.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                minimo:
                  summary: Desconto acima do valor da assinatura
                  value:
                    errors:
                      message: O desconto deixaria o valor da assinatura abaixo do mínimo de R$ 5,00.
                percentual:
                  summary: Percentual acima de 100
                  value:
                    errors:
                      message:
                        discount:
                          - The Discount may not be greater than 100.

  /v1/subscriptions/{id}/tag:
    patch:
      operationId: updateSubscriptionTag
      tags: [Subscriptions]
      summary: Atualizar a tag da assinatura
      description: |
        Define o apelido da assinatura — um rótulo livre de até 255 caracteres que
        você escolhe para identificar a assinatura nos seus relatórios.

        Envie `tag` vazia (`""` ou `null`) para remover o apelido. Sem apelido
        definido, todas as respostas da API devolvem o padrão `#assinatura{id}`.

        ::: tip
        A tag não aparece no painel Appmax nem no portal do assinante — ela existe
        apenas nas respostas desta API.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionTagRequest'
            examples:
              default:
                summary: Define um apelido
                value:
                  tag: Clube do Whey
              remove:
                summary: Remove o apelido (volta ao padrão)
                value:
                  tag: ''
      responses:
        '200':
          description: Tag atualizada. Retorna o detalhe atualizado da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: Erro de validação da tag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        tag:
                          - The tag may not be greater than 255 characters.
        '500':
          description: Erro ao atualizar o apelido da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/payment-method:
    patch:
      operationId: updateSubscriptionPaymentMethod
      tags: [Subscriptions]
      summary: Alterar a forma de pagamento da assinatura
      description: |
        Troca o cartão de crédito usado nas próximas cobranças da assinatura.

        O endpoint aceita **somente cartão já tokenizado**: os dados do cartão
        nunca passam pelo seu servidor. São dois passos:

        1. **No navegador do assinante**, o [Appmax JS](/guides/appmax-js)
           recebe os dados do cartão e devolve um `token` no callback
           `onSuccess`. Esse passo exige o
           [`external-id`](/guides/external-id) da instalação do app na loja.
        2. **No seu backend**, chame este endpoint enviando esse `token`.

        A bandeira e os quatro últimos dígitos são derivados do próprio token —
        não precisa (nem deve) enviá-los.

        ::: danger Esta rota exige a permissão `subscription-payment-method`
        Diferente das outras rotas de assinatura, a credencial precisa carregar
        a permissão `subscription-payment-method`. Sem ela a chamada é recusada
        pelo gateway com `403`. Fale com o time da Appmax para habilitá-la na
        credencial da sua loja. Veja [Autenticação](/guides/autenticacao).
        :::

        ::: warning
        O token precisa ter sido gerado pela mesma loja da assinatura, e a loja
        precisa usar o mesmo meio de captura em que o token foi criado. Caso
        contrário a resposta é `400`.
        :::

        Se a assinatura estiver `overdue` ou `suspended`, a cobrança em aberto é
        reprocessada na hora com o cartão novo. Aprovando, a assinatura volta
        para `active` e a próxima cobrança é reagendada — a resposta já traz o
        `status` atualizado.

        ::: tip
        Neste momento o endpoint troca apenas entre cartões de crédito. Uma
        assinatura em PIX passa a ser cobrada no cartão informado.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionPaymentMethodRequest'
            examples:
              default:
                summary: Cartão tokenizado no navegador
                value:
                  payment_data:
                    token: add5cac283934dd887c0c51360bf6822
      responses:
        '200':
          description: |
            Forma de pagamento atualizada. Retorna o detalhe atualizado da
            assinatura, já com a nova bandeira e os novos quatro últimos
            dígitos em `payment.payment_info`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '400':
          description: |
            Token não encontrado, ou não pertence a esta loja.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                not_found:
                  summary: Token inexistente
                  value:
                    errors:
                      message: Token de cartão não encontrado.
                other_site:
                  summary: Token de outra loja
                  value:
                    errors:
                      message: Token de cartão inválido para esta loja.
        '403':
          description: |
            Recusado pelo gateway: a credencial não carrega a permissão
            `subscription-payment-method`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: Mensagem do gateway.
              examples:
                default:
                  value:
                    message: Unauthorized
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '422':
          description: Erro de validação do token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        payment_data.token:
                          - The payment data.token field is required.
        '500':
          description: Erro ao alterar a forma de pagamento.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/available-products:
    get:
      operationId: listSubscriptionAvailableProducts
      tags: [Subscriptions]
      summary: Listar produtos disponíveis
      description: |
        Lista os produtos que ainda não estão na assinatura e podem ser
        adicionados a ela, já filtrados pela periodicidade da assinatura.

        O formato de cada item depende da origem da assinatura:

        - **Assinaturas de produtos Shopify** — um item por produto, com a lista
          de `variants` compatíveis com a cadência da assinatura. `q` é
          obrigatório na prática: sem termo de busca a resposta vem vazia.
        - **Assinaturas de produtos internos da Appmax** — lista simples de
          produtos ativos da loja (`product_id`, `name`, `price`, `sku`), até 30
          itens. Sem `q`, retorna os primeiros produtos em ordem alfabética.
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - name: q
          in: query
          required: false
          schema:
            type: string
          description: |
            Termo de busca por nome ou SKU do produto. Para assinaturas Shopify,
            sem este parâmetro a resposta vem vazia.
          example: camiseta
      responses:
        '200':
          description: Produtos disponíveis retornados com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionAvailableProductsResponse'
              examples:
                shopify:
                  summary: Assinatura de produtos Shopify
                  value:
                    data:
                      products:
                        - shopify_product_id: '123'
                          title: Camiseta Branca
                          variants:
                            - shopify_variant_id: '456'
                              title: Mensal
                              price: '189.90'
                              sku: SKU-2
                              interval: month
                              interval_count: 1
                internal:
                  summary: Assinatura de produtos internos
                  value:
                    data:
                      products:
                        - product_id: 55
                          name: Camiseta Branca
                          price: 189.9
                          sku: SKU-2
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '500':
          description: Erro ao listar os produtos disponíveis.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/products:
    post:
      operationId: addSubscriptionProducts
      tags: [Subscriptions]
      summary: Adicionar produtos
      description: |
        Adiciona um ou mais produtos a uma assinatura existente. Retorna o detalhe
        atualizado da assinatura.

        Funciona para os dois tipos de assinatura, e a forma de identificar o
        produto muda conforme o tipo — sempre use os identificadores devolvidos por
        [produtos disponíveis](/api-reference/subscriptions/produtos-disponiveis):

        - **Produtos internos da Appmax** → `product_id` (+ `quantity`). Nome,
          preço e SKU vêm do cadastro do produto; se enviados, são ignorados.
        - **Produtos da Shopify** → `shopify_product_id` **e**
          `shopify_variant_id` (+ `quantity`). O preço é lido da variante.

        ::: warning A assinatura precisa estar "parada"
        Alterar produtos só é permitido enquanto a assinatura está ativa, não
        pausada e sem cobrança em andamento. Veja os erros `409` e `422` abaixo.
        :::

        ::: warning Assinaturas sem produtos
        A gestão de produtos exige uma assinatura com produtos definidos: criada
        com `products` no [criar assinatura](/api-reference/subscriptions/criar-assinatura)
        ou originada de um checkout Shopify. Assinaturas criadas sem `products`
        (que herdam o valor do pedido) respondem `404` aqui.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddSubscriptionProductsRequest'
            examples:
              internal:
                summary: Produto interno da Appmax
                value:
                  products:
                    - product_id: 55
                      quantity: 1
              shopify:
                summary: Produto da Shopify
                value:
                  products:
                    - shopify_product_id: '123'
                      shopify_variant_id: '456'
                      quantity: 1
      responses:
        '200':
          description: Produtos adicionados. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: |
            Produto repetido na requisição, produto que já está na assinatura, ou
            assinatura com cobrança em andamento / dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                alreadyInSubscription:
                  value:
                    errors:
                      message: Produto já está na assinatura.
                duplicated:
                  value:
                    errors:
                      message: Produto duplicado na seleção.
                chargeInFlight:
                  value:
                    errors:
                      message: charge_in_flight
        '422':
          description: |
            Erro de validação, produto inválido para a assinatura, ou assinatura
            inativa/pausada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                validation:
                  summary: Campo obrigatório ausente
                  value:
                    errors:
                      message:
                        products:
                          - The products field is required.
                invalidProduct:
                  summary: Identificadores Shopify ausentes
                  value:
                    errors:
                      message: 'Produto inválido: shopify_product_id e shopify_variant_id são obrigatórios.'
                paused:
                  summary: Assinatura pausada
                  value:
                    errors:
                      message: status_paused
        '500':
          description: Erro ao adicionar produto à assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/products/{variantId}/quantity:
    patch:
      operationId: updateSubscriptionProductQuantity
      tags: [Subscriptions]
      summary: Alterar a quantidade de um produto
      description: |
        Altera a quantidade de um produto já presente na assinatura. Vale para os
        dois tipos de assinatura — o produto é identificado na URL pelo `variantId`,
        que é o `product_id` (produtos internos da Appmax) ou o `variant_id`
        (produtos da Shopify) mostrado no detalhe da assinatura. Retorna o detalhe
        atualizado.

        ::: warning A assinatura precisa estar "parada"
        Alterar produtos só é permitido enquanto a assinatura está ativa, não
        pausada e sem cobrança em andamento. Veja os erros `409` e `422` abaixo.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionVariantId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubscriptionProductQuantityRequest'
            examples:
              default:
                value:
                  quantity: 3
      responses:
        '200':
          description: Quantidade atualizada. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: |
            Assinatura com cobrança em andamento ou dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: charge_in_scheduler_window
        '422':
          description: |
            Erro de validação, produto ausente na assinatura ou assinatura
            inativa/pausada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                validation:
                  summary: Campo obrigatório ausente
                  value:
                    errors:
                      message:
                        quantity:
                          - The quantity field is required.
                paused:
                  summary: Assinatura pausada
                  value:
                    errors:
                      message: status_paused
        '500':
          description: Erro ao atualizar quantidade do produto na assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/subscriptions/{id}/products/{variantId}:
    delete:
      operationId: removeSubscriptionProduct
      tags: [Subscriptions]
      summary: Remover um produto
      description: |
        Remove um produto da assinatura. Vale para os dois tipos de assinatura — o
        produto é identificado na URL pelo `variantId`, que é o `product_id`
        (produtos internos da Appmax) ou o `variant_id` (produtos da Shopify)
        mostrado no detalhe da assinatura. Retorna o detalhe atualizado, sem o
        produto removido.

        A assinatura precisa manter pelo menos um produto — para encerrá-la por
        completo use [cancelar assinatura](/api-reference/subscriptions/cancelar-assinatura).

        ::: warning
        A rota é `DELETE /v1/subscriptions/{id}/products/{variantId}` — sem o
        sufixo `/quantity`. Alterar produtos só é permitido enquanto a assinatura
        está ativa, não pausada e sem cobrança em andamento.
        :::
      parameters:
        - $ref: '#/components/parameters/SubscriptionId'
        - $ref: '#/components/parameters/SubscriptionVariantId'
      responses:
        '200':
          description: Produto removido. Retorna o detalhe atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionDetailResponse'
        '404':
          description: Assinatura não encontrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Assinatura não encontrada.
        '409':
          description: |
            Assinatura com cobrança em andamento ou dentro da janela do agendador.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: charge_in_flight
        '422':
          description: |
            Último produto da assinatura, produto ausente na assinatura ou
            assinatura inativa/pausada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'
              examples:
                lastProduct:
                  value:
                    errors:
                      message: A assinatura deve manter ao menos um produto.
                paused:
                  value:
                    errors:
                      message: status_paused
        '500':
          description: Erro ao remover produto da assinatura.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageErrorEnvelope'

  /v1/products:
    get:
      operationId: listProducts
      tags: [Products]
      summary: Listar produtos
      description: |
        Lista os produtos do merchant com paginação, filtro por nome/status e
        ordenação. Retorna **20 itens por página**. Cada produto pertence a uma
        *company*, resolvida automaticamente a partir das credenciais.
      parameters:
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Página da listagem.
          example: 1
        - name: name
          in: query
          required: false
          schema:
            type: string
          description: Filtra produtos cujo nome contém o termo informado.
          example: camiseta
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [active, inactive, all]
            default: active
          description: |
            Sem valor ou `active`: apenas ativos. `inactive`: apenas inativos.
            `all`: todos.
          example: active
        - name: sort_by
          in: query
          required: false
          schema:
            type: string
            enum: [name, price, created_at]
            default: name
          description: Campo de ordenação.
          example: price
        - name: sort_dir
          in: query
          required: false
          schema:
            type: string
            enum: [asc, desc]
            default: asc
          description: Direção da ordenação.
          example: asc
      responses:
        '200':
          description: Lista de produtos retornada com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListProductsResponse'
              examples:
                default:
                  value:
                    data:
                      products:
                        - id: 10
                          sku: SKU-1
                          name: Camiseta Preta
                          price: 199.9
                          description: Camiseta 100% algodão
                          image: /uploads/a.png
                          inventory: 5
                          cost: 50.0
                          is_active: true
                          created_at: '2026-07-01 10:00:00'
                          updated_at: '2026-07-02 12:00:00'
                      pagination:
                        total: 1
                        per_page: 20
                        current_page: 1
                        last_page: 1
        '422':
          description: Parâmetros de consulta inválidos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message:
                        status:
                          - The selected status is invalid.
        '500':
          description: Erro ao listar os produtos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao listar os produtos.
    post:
      operationId: createProduct
      tags: [Products]
      summary: Criar um produto
      description: |
        Cria um novo produto para o merchant. Os campos `name` e `price` são
        obrigatórios. O produto é criado ativo (`is_active: true`).

        ::: tip Unicidade
        `sku` e `external_id` devem ser únicos por company. Se já existir um
        produto com o mesmo valor, a API retorna `422`.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProductRequest'
            examples:
              default:
                summary: Produto com todos os campos
                value:
                  name: Camiseta Preta
                  price: 199.9
                  sku: SKU-1
                  external_id: EXT-123
                  description: Camiseta 100% algodão
                  image: /uploads/a.png
                  inventory: 5
                  cost: 50.0
      responses:
        '201':
          description: Produto criado com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
              examples:
                default:
                  value:
                    data:
                      id: 10
                      sku: SKU-1
                      name: Camiseta Preta
                      price: 199.9
                      description: Camiseta 100% algodão
                      image: /uploads/a.png
                      inventory: 5
                      cost: 50.0
                      is_active: true
                      created_at: '2026-07-01 10:00:00'
                      updated_at: '2026-07-01 10:00:00'
        '422':
          description: |
            Erro de validação dos campos ou `sku`/`external_id` duplicado para a
            company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                validation:
                  summary: Campos obrigatórios ausentes
                  value:
                    errors:
                      message:
                        name:
                          - The name field is required.
                        price:
                          - The price field is required.
                duplicate:
                  summary: SKU ou external_id duplicado
                  value:
                    errors:
                      message: Já existe um produto com o mesmo SKU ou external_id.
        '500':
          description: Erro ao criar o produto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao criar o produto.

  /v1/products/{id}:
    get:
      operationId: getProduct
      tags: [Products]
      summary: Consultar um produto
      description: |
        Retorna um produto específico do merchant pelo identificador (`id`).
        Produtos de outra company são tratados como inexistentes (`404`).
      parameters:
        - $ref: '#/components/parameters/ProductId'
      responses:
        '200':
          description: Produto retornado com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
              examples:
                default:
                  value:
                    data:
                      id: 10
                      sku: SKU-1
                      name: Camiseta Preta
                      price: 199.9
                      description: Camiseta 100% algodão
                      image: /uploads/a.png
                      inventory: 5
                      cost: 50.0
                      is_active: true
                      created_at: '2026-07-01 10:00:00'
                      updated_at: '2026-07-02 12:00:00'
        '404':
          description: Produto inexistente ou de outra company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Produto não encontrado.
        '500':
          description: Erro ao buscar o produto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao buscar o produto.
    put:
      operationId: updateProduct
      tags: [Products]
      summary: Atualizar um produto
      description: |
        Atualiza os dados de um produto existente. Envie apenas os campos que
        deseja alterar; todos são opcionais e seguem as mesmas regras da criação.
      parameters:
        - $ref: '#/components/parameters/ProductId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateProductRequest'
            examples:
              default:
                summary: Atualização parcial
                value:
                  name: Camiseta Preta Slim
                  price: 219.9
      responses:
        '200':
          description: Produto atualizado com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
              examples:
                default:
                  value:
                    data:
                      id: 10
                      sku: SKU-1
                      name: Camiseta Preta Slim
                      price: 219.9
                      description: Camiseta 100% algodão
                      image: /uploads/a.png
                      inventory: 5
                      cost: 50.0
                      is_active: true
                      created_at: '2026-07-01 10:00:00'
                      updated_at: '2026-07-02 13:30:00'
        '404':
          description: Produto inexistente ou de outra company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Produto não encontrado.
        '422':
          description: Erro de validação ou `sku`/`external_id` duplicado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorEnvelope'
              examples:
                duplicate:
                  summary: SKU ou external_id duplicado
                  value:
                    errors:
                      message: Já existe um produto com o mesmo SKU ou external_id.
        '500':
          description: Erro ao atualizar o produto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao atualizar o produto.
    delete:
      operationId: deleteProduct
      tags: [Products]
      summary: Excluir um produto
      description: |
        Exclui um produto do merchant. A exclusão é feita por **soft delete**: o
        produto é marcado como inativo (`is_active: false`) e continua consultável
        com o filtro de status `inactive` ou `all`.

        ::: warning Bloqueio
        A exclusão é bloqueada (`409`) quando o produto está vinculado a uma
        assinatura.
        :::
      parameters:
        - $ref: '#/components/parameters/ProductId'
      responses:
        '200':
          description: Produto excluído (soft delete) com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
              examples:
                default:
                  value:
                    data:
                      id: 10
                      sku: SKU-1
                      name: Camiseta Preta
                      price: 199.9
                      description: Camiseta 100% algodão
                      image: /uploads/a.png
                      inventory: 5
                      cost: 50.0
                      is_active: false
                      created_at: '2026-07-01 10:00:00'
                      updated_at: '2026-07-02 14:00:00'
        '404':
          description: Produto inexistente ou de outra company.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Produto não encontrado.
        '409':
          description: Produto vinculado a uma assinatura não pode ser excluído.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Produto vinculado a uma assinatura não pode ser excluído.
        '500':
          description: Erro ao excluir o produto.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductErrorEnvelope'
              examples:
                default:
                  value:
                    errors:
                      message: Erro ao excluir o produto.

  /v1/payment-link:
    post:
      operationId: createPaymentLink
      tags: [Payment Links]
      summary: Criar um link de pagamento
      description: |
        Cria um novo link de pagamento. A resposta traz o `checkout_url` — a URL
        hospedada pela Appmax que você compartilha com o comprador para que ele
        finalize o pagamento.

        A requisição deve conter um token Bearer do **merchant**, obtido no
        fluxo de autenticação. Veja
        [Autenticação](/guides/autenticacao).

        ::: warning
        O campo `value` é informado em **centavos** e o mínimo aceito é `500`
        (R$ 5,00).
        :::

        O campo `fee_transfer_from_installment` define a partir de qual parcela
        o custo do parcelamento é repassado ao comprador. Com o valor `0`
        (default), o custo é absorvido pelo merchant em todas as parcelas.

        O campo `document` é opcional. Quando informado, deve conter um CPF ou
        CNPJ válido e o merchant deve possuir vínculo com a empresa do
        documento informado.

        ::: warning Comportamento padrão do documento
        Quando já existe um link de pagamento criado e vinculado a uma empresa,
        a última empresa utilizada na criação de um link passa a ser o padrão.
        Portanto, ao criar um novo link **sem informar o campo `document`**, o
        sistema continuará criando o link vinculado à última empresa utilizada,
        mesmo que uma nova empresa tenha sido cadastrada posteriormente. Para
        garantir que o link seja vinculado a uma empresa específica, informe o
        `document` correspondente no payload.
        :::
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentLinkRequest'
      responses:
        '201':
          description: Link de pagamento criado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                        description: ID do link de pagamento criado.
                        example: 244975
                      checkout_url:
                        type: string
                        description: URL de checkout a ser compartilhada com o comprador.
                        example: 'https://link_de_pagamento/pl/ID_DO_LINK'
        '400':
          description: Requisição inválida.
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Mensagem descrevendo o problema.
                        example: "O nome 'Nome douto' viola os termos de uso da plataforma Appmax."
        '401':
          description: Não autorizado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Motivo da não autorização.
                        example: Usuário sem permissão
        '403':
          description: Usuário bloqueado para criar links de pagamento.
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Motivo do bloqueio.
                        example: Usuário bloqueado para a criação de links de pagamento
        '404':
          description: Empresa do documento não encontrada ou não vinculada ao merchant.
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Mensagem de erro.
                        example: Você não possui vínculo com a empresa do documento informado.
        '422':
          description: Erro na validação dos dados ou erro de negócio ao processar a criação.
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: object
                    properties:
                      message:
                        type: string
                        description: Houve um erro ao criar seu link de pagamento. Por favor, tente novamente mais tarde.
                        example: Houve um erro ao criar seu link de pagamento. Por favor, tente novamente mais tarde.
              examples:
                camposObrigatorios:
                  summary: Campos obrigatórios ausentes ou inválidos
                  value:
                    errors:
                      message:
                        name:
                          - O campo name é obrigatório.
                        value:
                          - O campo value é obrigatório.
                        description:
                          - O campo description é obrigatório.
                        payments:
                          - O campo payments é obrigatório.
                valorMinimo:
                  summary: Valor abaixo do mínimo permitido
                  value:
                    errors:
                      message:
                        value:
                          - Valor mínimo de R$ 5,00
                metodoPagamentoInvalido:
                  summary: Método de pagamento não permitido
                  value:
                    errors:
                      message:
                        payments.0:
                          - 'Método de pagamento inválido. Os métodos de pagamento disponíveis são: creditcard,boleto,pix'
                parcelasObrigatorias:
                  summary: max_installments obrigatório quando credit card está presente
                  value:
                    errors:
                      message:
                        max_installments:
                          - O campo max installments é obrigatório.
                documentoInvalido:
                  summary: Documento em formato inválido
                  value:
                    errors:
                      message:
                        document:
                          - Documento inválido.

  /v1/payment-link/{payment_link_id}/orders:
    get:
      operationId: getPaymentLinkOrders
      tags: [Payment Links]
      summary: Consultar os dados de um link de pagamento
      description: |
        Retorna os pedidos gerados por um determinado link de pagamento, com o
        status de cada um. Use para acompanhar as conversões do link criado em
        [`POST /v1/payment-link`](/api-reference/payment-links/criar-link-pagamento).

        A resposta é paginada: `meta` traz os totais da consulta e `links` traz
        as URLs de navegação entre as páginas.
      parameters:
        - $ref: '#/components/parameters/PaymentLinkId'
      responses:
        '200':
          description: Link de pagamento encontrado com sucesso.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    description: Pedidos gerados pelo link de pagamento.
                    items:
                      type: object
                      properties:
                        order:
                          type: object
                          properties:
                            id:
                              type: integer
                              description: ID do pedido.
                              example: 1
                            status:
                              type: string
                              description: Status do pedido.
                              example: cancelado
                            total:
                              type: integer
                              description: Valor total do pedido em **centavos**.
                              example: 6000
                  meta:
                    type: object
                    description: Metadados da paginação.
                    properties:
                      total:
                        type: integer
                        description: Total de pedidos encontrados.
                        example: 2
                      page:
                        type: integer
                        description: Página atual.
                        example: 1
                      per_page:
                        type: integer
                        description: Quantidade de itens por página.
                        example: 20
                      total_pages:
                        type: integer
                        description: Total de páginas.
                        example: 1
                  links:
                    type: object
                    description: URLs de navegação da paginação.
                    properties:
                      self:
                        type: string
                        example: 'https://api.appmax.com.br/v1/payment-link/1/orders?page=1'
                      next:
                        type: [string, 'null']
                        example: null
                      prev:
                        type: [string, 'null']
                        example: null
                      last:
                        type: string
                        example: 'https://api.appmax.com.br/v1/payment-link/1/orders?page=1'
              examples:
                default:
                  value:
                    data:
                      - order:
                          id: 1
                          status: cancelado
                          total: 6000
                      - order:
                          id: 2
                          status: pendente
                          total: 6000
                    meta:
                      total: 2
                      page: 1
                      per_page: 20
                      total_pages: 1
                    links:
                      self: 'https://api.appmax.com.br/v1/payment-link/1/orders?page=1'
                      next: null
                      prev: null
                      last: 'https://api.appmax.com.br/v1/payment-link/1/orders?page=1'
        '401':
          description: Erro de autenticação.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Token Bearer do **merchant** obtido via `POST /oauth2/token` usando as
        credenciais do merchant (não do app). Veja
        [Autenticação](/guides/autenticacao).
    secretHeader:
      type: apiKey
      in: header
      name: secret
      description: |
        Autenticação via header `secret` (API Gateway).

  parameters:
    SubscriptionId:
      name: id
      in: path
      required: true
      schema:
        type: integer
      description: Identificador da assinatura.
      example: 10
    SubscriptionVariantId:
      name: variantId
      in: path
      required: true
      schema:
        type: string
      description: |
        Identificador do produto dentro da assinatura, retornado no detalhe:

        - assinaturas de produtos Shopify → `variant_id` (o `shopify_variant_id`);
        - assinaturas de produtos internos da Appmax → `product_id`.
      example: '456'
    SubscriptionCycleIndex:
      name: cycleIndex
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
      description: |
        Número do ciclo de recorrência (1 = primeiro ciclo após a cobrança de
        origem). Precisa ser maior que `completed_cycles`.
      example: 3
    SubscriptionCycleIndexQuery:
      name: cycle_index
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
      description: |
        Ciclo a alterar. Omitido, vale o próximo ciclo da assinatura
        (`completed_cycles + 1`).
      example: 3
    SubscriptionCycleProductRef:
      name: productRef
      in: path
      required: true
      schema:
        type: string
      description: |
        Identificador do produto adicionado ao ciclo: `shopify_variant_id` em
        assinaturas da Shopify, `product_id` em assinaturas de produtos internos
        da Appmax.
      example: '987'
    ProductId:
      name: id
      in: path
      required: true
      schema:
        type: integer
      description: Identificador único do produto.
      example: 10
    RecipientHash:
      name: recipient_hash
      in: path
      required: true
      schema:
        type: string
      description: Hash do recebedor (retornado em `POST /v1/recipient`).
      example: d9bd0dae-3274-5e5a-939f-f50d867eb652
    PagarmeRecipientHash:
      name: recipient_hash
      in: path
      required: true
      schema:
        type: string
      description: |
        `id` do recebedor, retornado em
        [Criar recebedor (contrato PagarMe)](/api-reference/pagarme/criar-recebedor).
      example: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
    PaymentLinkId:
      name: payment_link_id
      in: path
      required: true
      schema:
        type: string
      description: ID do link de pagamento (retornado em `POST /v1/payment-link`).
      example: '2'

  responses:
    PagarmeForbidden:
      description: Token ausente ou inválido.
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: object
                properties:
                  message:
                    type: string
                    example: Missing Authentication Token
    PagarmeRecipientNotFound:
      description: Recebedor inexistente ou cadastrado por outro marketplace.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PagarmeMessageEnvelope'
          examples:
            default:
              value:
                data:
                  message: Recipient not found

  schemas:
    PagarmeMessageEnvelope:
      type: object
      description: Erro das rotas `/v1/p/`, com a descrição em `data.message`.
      properties:
        data:
          type: object
          properties:
            message:
              type: string
              description: Descrição do erro.

    PagarmeFieldErrors:
      type: object
      description: |
        Erro de formato das rotas `/v1/p/`: mapa `campo → [mensagens]` na raiz
        do corpo, sem envelope. A chave é o caminho do campo em notação de
        ponto (ex.: `register_information.document`).
      additionalProperties:
        type: array
        items:
          type: string

    PagarmePhoneNumber:
      type: object
      required: [ddd, number]
      properties:
        ddd:
          type: string
          description: DDD com 2 dígitos.
          example: '11'
        number:
          type: string
          description: Número com 8 ou 9 dígitos.
          example: '976187304'
        type:
          type: string
          description: Tipo do telefone.
          example: mobile

    PagarmeAddress:
      type: object
      required: [street, street_number, complementary, neighborhood, city, state, zip_code]
      properties:
        street:
          type: string
          maxLength: 255
          example: Av. Paulista
        street_number:
          type: string
          maxLength: 45
          example: '1000'
        complementary:
          type: string
          maxLength: 120
          example: Sala 10
        neighborhood:
          type: string
          maxLength: 120
          example: Bela Vista
        city:
          type: string
          maxLength: 80
          example: São Paulo
        state:
          type: string
          maxLength: 120
          example: SP
        zip_code:
          type: string
          description: CEP.
          example: '01310100'
        reference_point:
          type: string
          maxLength: 255
          description: Ponto de referência.
          example: Próximo ao metrô

    PagarmeManagingPartner:
      type: object
      description: |
        Sócio de recebedor PJ. O primeiro da lista é o representante legal e
        vira o titular do login na Appmax: `name`, `email`, `document` e
        `birthdate` são obrigatórios nele. Os demais sócios só são validados
        e ecoados na resposta do cadastro.
      properties:
        name:
          type: string
          description: Nome completo. Só letras, acentos e espaços.
          minLength: 3
          maxLength: 255
          example: Carlos Lima
        email:
          type: string
          format: email
          example: carlos@lojadocarlos.com.br
        document:
          type: string
          description: CPF, só dígitos.
          example: '52998224725'
        birthdate:
          type: string
          description: Data de nascimento em `dd/mm/aaaa` ou `aaaa-mm-dd`.
          example: 09/12/1980
        mother_name:
          type: string
          maxLength: 255
          example: Rosa Lima
        monthly_income:
          type: integer
          description: Renda mensal em centavos.
          example: 1000000
        professional_occupation:
          type: string
          maxLength: 255
          example: Empresário
        self_declared_legal_representative:
          type: boolean
          description: Se o sócio se declara representante legal.
          example: true
        phone_numbers:
          type: array
          maxItems: 5
          items:
            $ref: '#/components/schemas/PagarmePhoneNumber'
        address:
          $ref: '#/components/schemas/PagarmeAddress'

    PagarmeRegisterInformation:
      type: object
      description: |
        Dados cadastrais do recebedor. Os campos dependem de `type`:

        - `individual` (PF): `name`, `birthdate`, `monthly_income` e `address`
          são obrigatórios;
        - `corporation` (PJ): `company_name`, `trading_name`,
          `annual_revenue`, `main_address` e `managing_partners` são
          obrigatórios.
      required: [type, email, document, site_url, phone_numbers]
      properties:
        type:
          type: string
          enum: [individual, corporation]
          description: Tipo do recebedor.
          example: individual
        email:
          type: string
          format: email
          description: |
            E-mail do recebedor. Em PJ é o e-mail da empresa; o login usa o
            e-mail do representante legal (`managing_partners[0].email`).
          example: maria.souza@exemplo.com
        document:
          type: string
          description: CPF (`individual`) ou CNPJ (`corporation`), só dígitos.
          example: '35873882029'
        site_url:
          type: string
          maxLength: 100
          description: URL da loja ou página de vendas. Opcional na PagarMe, **obrigatório aqui**.
          example: https://lojadamaria.com.br
        phone_numbers:
          type: array
          minItems: 1
          description: |
            Telefones. Opcional na PagarMe, **obrigatório aqui**. Só o
            primeiro é gravado.
          items:
            $ref: '#/components/schemas/PagarmePhoneNumber'
        name:
          type: string
          description: "**PF.** Nome completo. Só letras, acentos e espaços."
          minLength: 3
          maxLength: 255
          example: Maria Souza
        birthdate:
          type: string
          description: "**PF.** Data de nascimento em `dd/mm/aaaa` ou `aaaa-mm-dd`."
          example: 12/10/1995
        monthly_income:
          type: integer
          description: "**PF.** Renda mensal em centavos."
          example: 500000
        mother_name:
          type: string
          maxLength: 255
          description: "**PF.** Nome da mãe."
          example: Ana Souza
        professional_occupation:
          type: string
          maxLength: 255
          description: "**PF.** Profissão."
          example: Vendedora
        address:
          allOf:
            - $ref: '#/components/schemas/PagarmeAddress'
          description: "**PF.** Endereço do recebedor."
        company_name:
          type: string
          minLength: 3
          maxLength: 255
          description: "**PJ.** Nome fantasia."
          example: Loja do Carlos
        trading_name:
          type: string
          minLength: 3
          maxLength: 255
          description: "**PJ.** Razão social."
          example: Carlos Comércio LTDA
        annual_revenue:
          type: integer
          description: "**PJ.** Faturamento anual em centavos."
          example: 120000000
        corporation_type:
          type: string
          maxLength: 255
          description: "**PJ.** Tipo de empresa."
          example: LTDA
        founding_date:
          type: string
          maxLength: 255
          description: "**PJ.** Data de fundação."
          example: '2015-03-20'
        main_address:
          allOf:
            - $ref: '#/components/schemas/PagarmeAddress'
          description: "**PJ.** Endereço da empresa."
        managing_partners:
          type: array
          minItems: 1
          maxItems: 20
          description: "**PJ.** Sócios. O primeiro é o representante legal."
          items:
            $ref: '#/components/schemas/PagarmeManagingPartner'

    PagarmeBankAccountInput:
      type: object
      required: [holder_name, holder_type, holder_document, bank, branch_number, account_number, account_check_digit, type]
      properties:
        holder_name:
          type: string
          maxLength: 255
          description: Nome do titular.
          example: Maria Souza
        holder_type:
          type: string
          enum: [individual, company]
          description: |
            `individual` para recebedor pessoa física, `company` para pessoa
            jurídica.
          example: individual
        holder_document:
          type: string
          description: CPF ou CNPJ do titular. Precisa ser o documento do próprio recebedor.
          example: '35873882029'
        bank:
          type: string
          description: |
            Código COMPE do banco. Precisa estar entre os
            [bancos homologados](/guides/bancos-homologados).
          example: '001'
        branch_number:
          type: string
          description: Agência, só dígitos (até 40).
          example: '1234'
        branch_check_digit:
          type: string
          description: Dígito da agência, se houver (até 4 dígitos).
          example: '5'
        account_number:
          type: string
          description: Número da conta, só dígitos (até 13).
          example: '123456'
        account_check_digit:
          type: string
          description: Dígito da conta (até 4 dígitos).
          example: '7'
        type:
          type: string
          enum: [checking, savings]
          description: "`checking` (corrente) ou `savings` (poupança)."
          example: checking
        metadata:
          type: object
          description: Dados livres.

    PagarmeCreateRecipientRequest:
      type: object
      required: [register_information, default_bank_account]
      properties:
        code:
          type: string
          maxLength: 100
          description: Referência do recebedor no seu sistema.
          example: erp-maria-001
        metadata:
          type: object
          description: Dados livres.
        register_information:
          $ref: '#/components/schemas/PagarmeRegisterInformation'
        default_bank_account:
          $ref: '#/components/schemas/PagarmeBankAccountInput'
        transfer_settings:
          type: object
          description: |
            Aceito só com `transfer_enabled: false`. `true` retorna `422`.
          properties:
            transfer_enabled:
              type: boolean
              example: false
        automatic_anticipation_settings:
          type: object
          description: |
            Aceito só com `enabled: false`. `true` retorna `422`.
          properties:
            enabled:
              type: boolean
              example: false

    PagarmeUpdateRecipientRequest:
      type: object
      description: |
        Mesmo formato do cadastro. Só os campos editáveis são gravados; os
        demais são ignorados.
      properties:
        register_information:
          type: object
          required: [type]
          properties:
            type:
              type: string
              enum: [individual, corporation]
              description: Tipo com que o recebedor foi cadastrado.
            name:
              type: string
              description: "**PF.** Nome do recebedor."
            birthdate:
              type: string
              description: "**PF.** Data de nascimento (`dd/mm/aaaa` ou `aaaa-mm-dd`, maior de idade)."
            phone_numbers:
              type: array
              description: Só o primeiro item é lido; envie `ddd` e `number` juntos.
              items:
                $ref: '#/components/schemas/PagarmePhoneNumber'
            address:
              allOf:
                - $ref: '#/components/schemas/PagarmeAddress'
              description: "**PF.** Endereço. Envie só os campos que quer alterar."
            main_address:
              allOf:
                - $ref: '#/components/schemas/PagarmeAddress'
              description: "**PJ.** Endereço da empresa. Envie só os campos que quer alterar."
            managing_partners:
              type: array
              description: "**PJ.** Só `name` e `birthdate` do primeiro item (representante legal) são gravados."
              items:
                type: object
                properties:
                  name:
                    type: string
                  birthdate:
                    type: string

    PagarmeBankAccount:
      type: object
      description: Conta bancária padrão do recebedor.
      properties:
        id:
          type: string
          description: Identificador da conta.
          example: '8812'
        holder_name:
          type: string
          example: Maria Souza
        holder_type:
          type: string
          enum: [individual, company]
          example: individual
        holder_document:
          type: string
          example: '35873882029'
        bank:
          type: string
          example: '001'
        branch_number:
          type: string
          description: |
            Agência. Na consulta, vem com o dígito junto (se houver), sem
            `branch_check_digit`.
          example: '1234'
        branch_check_digit:
          type: string
          description: Dígito da agência. Só na resposta do cadastro.
          example: '5'
        account_number:
          type: string
          example: '123456'
        account_check_digit:
          type: string
          example: '7'
        type:
          type: string
          enum: [checking, savings]
          example: checking
        status:
          type: string
          description: Sempre `active`.
          example: active
        created_at:
          type: string
          format: date-time
          example: '2026-10-07T14:32:10Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-10-07T14:32:10Z'
        metadata:
          type: object
          description: Só na resposta do cadastro, ecoando a entrada.

    PagarmeRecipient:
      type: object
      description: Objeto `recipient` no formato da PagarMe v5.
      properties:
        id:
          type: string
          description: "`recipient_hash` do recebedor."
          example: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
        code:
          type: string
          description: Só na resposta do cadastro, ecoando a entrada.
          example: erp-maria-001
        name:
          type: string
          description: Nome da pessoa (PF) ou nome fantasia (PJ).
          example: Maria Souza
        email:
          type: string
          example: maria.souza@exemplo.com
        document:
          type: string
          description: CPF (PF) ou CNPJ (PJ).
          example: '35873882029'
        type:
          type: string
          enum: [individual, corporation]
          example: individual
        payment_mode:
          type: string
          description: Sempre `bank_transfer`.
          example: bank_transfer
        status:
          type: string
          enum: [registration, active, refused]
          description: |
            - `registration`: onboarding (KYC) em andamento;
            - `active`: onboarding aprovado;
            - `refused`: onboarding recusado.
          example: registration
        created_at:
          type: string
          format: date-time
          example: '2026-10-07T14:32:10Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-10-07T14:32:10Z'
        transfer_settings:
          type: object
          description: |
            Fixo: a Appmax não faz saque automático por esta API, e o saque é
            sempre sob demanda.
          properties:
            transfer_enabled:
              type: boolean
              example: false
            transfer_interval:
              type: string
              nullable: true
              example: null
            transfer_day:
              type: integer
              example: 0
        default_bank_account:
          allOf:
            - $ref: '#/components/schemas/PagarmeBankAccount'
          nullable: true
        gateway_recipients:
          type: array
          description: Sempre vazio.
          items:
            type: object
        automatic_anticipation_settings:
          type: object
          description: "Fixo: antecipação automática não é configurada por esta API."
          properties:
            enabled:
              type: boolean
              example: false
            type:
              type: string
              example: full
            volume_percentage:
              type: integer
              example: 0
            delay:
              type: integer
              nullable: true
              example: null
        metadata:
          type: object
          description: Só na resposta do cadastro, ecoando a entrada.
        register_information:
          type: object
          description: |
            Dados cadastrais, com os mesmos campos da entrada. No cadastro, os
            campos não gravados são ecoados da entrada; na consulta, saem
            `null` ou são omitidos.

    PagarmeRecipientBalance:
      type: object
      properties:
        currency:
          type: string
          description: Moeda dos saldos (ISO 4217).
          example: BRL
        available_amount:
          type: integer
          description: Saldo disponível, em centavos.
          example: 152030
        waiting_funds_amount:
          type: integer
          description: Saldo a liberar, em centavos.
          example: 489900
        transferred_amount:
          type: integer
          description: Total líquido já transferido para a conta do recebedor, em centavos.
          example: 1250000
        recipient:
          type: object
          description: Resumo do recebedor.
          properties:
            id:
              type: string
              example: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
            name:
              type: string
              example: Maria Souza
            email:
              type: string
              example: maria.souza@exemplo.com
            document:
              type: string
              example: '35873882029'
            type:
              type: string
              enum: [individual, corporation]
              example: individual
            status:
              type: string
              enum: [registration, active, refused]
              example: active
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    PagarmeTransferSettingsInput:
      type: object
      required: [transfer_enabled]
      properties:
        transfer_enabled:
          type: boolean
          description: Liga (`true`) ou desliga (`false`) o saque automático.
          example: true
        transfer_interval:
          type: string
          description: Só `daily`, em qualquer capitalização.
          example: Daily
        transfer_day:
          type: integer
          description: Só `0`.
          example: 0

    PagarmeAnticipationSettingsInput:
      type: object
      required: [enabled]
      properties:
        enabled:
          type: boolean
          description: Liga (`true`) ou desliga (`false`) a antecipação automática.
          example: true
        type:
          type: string
          description: Só `full`.
          example: full
        volume_percentage:
          description: Só `100`, como número ou texto.
          oneOf:
            - type: integer
            - type: string
          example: '100'
        delay:
          type: integer
          nullable: true
          description: |
            Ausente, `null` ou igual à janela de liberação atual do recebedor.
            Não é configurável.
          example: null
        days:
          type: array
          description: Ausente ou `[]` (todo dia útil).
          items:
            type: integer
          example: []

    PagarmeRecipientSummary:
      type: object
      description: Resumo do recebedor na listagem.
      properties:
        id:
          type: string
          example: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
        name:
          type: string
          example: Maria Souza
        email:
          type: string
          example: maria.souza@exemplo.com
        document:
          type: string
          example: '35873882029'
        type:
          type: string
          enum: [individual, corporation]
          example: individual
        status:
          type: string
          enum: [registration, active, refused]
          example: active
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        transfer_settings:
          type: object
          properties:
            transfer_enabled:
              type: boolean
            transfer_interval:
              type: string
              nullable: true
            transfer_day:
              type: integer
        default_bank_account:
          allOf:
            - $ref: '#/components/schemas/PagarmeBankAccount'
          nullable: true

    PagarmeListRecipientsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PagarmeRecipientSummary'
        paging:
          type: object
          properties:
            total:
              type: integer
              description: Total de recebedores do marketplace.
              example: 1

    Product:
      type: object
      required: [sku, name, quantity]
      properties:
        sku:
          type: string
          description: SKU do produto.
          example: '9000010'
        name:
          type: string
          description: Nome do produto.
          example: Livro de receitas
        quantity:
          type: integer
          minimum: 1
          description: Quantidade do produto.
          example: 1
        unit_value:
          type: integer
          minimum: 0
          description: |
            Valor unitário do produto em **centavos**. Obrigatório quando
            `products_value` não é informado no pedido.
          example: 12300
        type:
          type: string
          enum: [physical, digital]
          default: physical
          description: Tipo do produto.
          example: digital

    SubscriptionStatus:
      type: string
      enum: [ACTIVE, PAUSED, CANCELLED, OVERDUE, SUSPENDED, FINISHED]
      description: |
        Status da assinatura:
        - `ACTIVE` — ativa, com cobranças recorrentes em dia.
        - `PAUSED` — pausada temporariamente.
        - `CANCELLED` — cancelada (não gera novas cobranças).
        - `OVERDUE` — com cobrança em atraso.
        - `SUSPENDED` — suspensa.
        - `FINISHED` — encerrada (atingiu o número máximo de ciclos).
      example: ACTIVE

    SubscriptionTag:
      type: [string, 'null']
      description: |
        Apelido da assinatura definido pelo lojista. Quando não há apelido
        definido, a API devolve o padrão `#assinatura{id}`.
      example: Clube do Whey

    SubscriptionListProduct:
      type: object
      properties:
        name:
          type: string
          description: Nome do produto.
          example: Camiseta Preta
        price:
          type: [number, 'null']
          description: Preço unitário registrado na assinatura.
          example: 199.9
        quantity:
          type: integer
          description: Quantidade do produto cobrada a cada ciclo.
          example: 1
        shopify_product_id:
          type: [string, 'null']
          description: |
            Identificador do produto na Shopify. Vem `null` em assinaturas de
            produtos internos da Appmax.
          example: null
        variant_id:
          type: [string, 'null']
          description: |
            Identificador da variante na Shopify. Vem `null` em assinaturas de
            produtos internos da Appmax.
          example: null
        product_id:
          type: [integer, 'null']
          description: |
            Identificador do produto interno da Appmax. Vem `null` em assinaturas
            de produtos Shopify.
          example: 55
        image_url:
          type: [string, 'null']
          description: Imagem do produto (da variante na Shopify ou do cadastro interno).
          example: null
        compare_at_price:
          type: [number, 'null']
          description: Preço "de" da variante na Shopify, quando disponível.
          example: null

    SubscriptionCustomer:
      type: object
      description: Dados do cliente vinculado à assinatura.
      properties:
        name:
          type: string
          description: Nome completo do cliente.
          example: Maria Silva
        email:
          type: string
          format: email
          description: E-mail do cliente.
          example: cliente@exemplo.com
        document:
          type: [string, 'null']
          description: CPF ou CNPJ do cliente, apenas dígitos.
          example: '12345678909'
        phone:
          type: [string, 'null']
          description: Telefone do cliente (DDD + número), apenas dígitos.
          example: '11999999999'
        address:
          type: object
          description: |
            Endereço de entrega usado nas cobranças recorrentes: o endereço
            próprio da assinatura ou, quando ela ainda não tem um, o do cadastro
            do cliente. Assinatura sem endereço em nenhuma das duas origens
            devolve o bloco com todos os campos `null`, exceto `country`.
          properties:
            country:
              type: [string, 'null']
              description: País no padrão ISO 3166-1 alpha-2.
              example: BR
            street:
              type: [string, 'null']
              description: Logradouro.
              example: Rua São Bento
            number:
              type: [string, 'null']
              description: Número.
              example: '111'
            complement:
              type: [string, 'null']
              description: Complemento.
              example: Bloco 7
            district:
              type: [string, 'null']
              description: Bairro.
              example: Centro
            city:
              type: [string, 'null']
              description: Cidade.
              example: São Paulo
            state:
              type: [string, 'null']
              description: UF, sigla de 2 letras.
              example: SP
            postcode:
              type: [string, 'null']
              description: CEP, apenas dígitos.
              example: '01010000'

    SubscriptionPayment:
      type: object
      description: Meio de pagamento usado nas cobranças recorrentes.
      properties:
        payment_info:
          type: object
          description: |
            Dados do cartão tokenizado. Em assinaturas de PIX todos os campos
            vêm `null`.
          properties:
            brand:
              type: [string, 'null']
              description: |
                Bandeira do cartão, como devolvida pelo adquirente (por exemplo
                `visa`, `mastercard`, `elo`, `amex`). Não é uma lista fechada.
              example: visa
            final:
              type: [string, 'null']
              description: Últimos dígitos do cartão.
              example: '1234'
            expiration:
              type: [string, 'null']
              description: Validade do cartão no formato `MM/AAAA`.
              example: 12/2029

    SubscriptionListItem:
      type: object
      properties:
        subscription_id:
          type: integer
          description: Identificador da assinatura. Use este valor nas demais rotas.
          example: 10
        tag:
          $ref: '#/components/schemas/SubscriptionTag'
        status:
          $ref: '#/components/schemas/SubscriptionStatus'
        products:
          type: array
          description: Produtos que compõem a assinatura.
          items:
            $ref: '#/components/schemas/SubscriptionListProduct'
        value:
          type: [number, 'null']
          description: Valor da assinatura.
          example: 199.9
        freight_value:
          type: [number, 'null']
          description: Valor do frete cobrado a cada ciclo.
          example: 19.9
        discount:
          type: [number, 'null']
          description: |
            Desconto da assinatura. Em reais quando `discount_type` é `nominal`,
            ou o percentual quando é `percent`. `null` quando a assinatura nunca
            teve desconto próprio definido.
          example: 10
        discount_type:
          type: [string, 'null']
          enum: [nominal, percent, null]
          description: |
            Como o `discount` é interpretado. `nominal` é um valor fixo em reais;
            `percent` incide sobre o valor dos produtos e é recalculado a cada
            cobrança.
          example: percent
        interval:
          type: [string, 'null']
          enum: [day, week, month, year, null]
          description: |
            Unidade da periodicidade:
            - `day` — dias.
            - `week` — semanas.
            - `month` — meses.
            - `year` — anos.

            Combine com `interval_count` para obter a cadência (por exemplo
            `month` + `2` = a cada dois meses).
          example: month
        interval_count:
          type: [integer, 'null']
          description: Quantidade de unidades entre cobranças.
          example: 1
        current_cycle:
          type: [integer, 'null']
          description: Ciclo em andamento (`completed_cycles` + 1).
          example: 3
        customer:
          oneOf:
            - $ref: '#/components/schemas/SubscriptionCustomer'
            - type: 'null'
        payment:
          allOf:
            - $ref: '#/components/schemas/SubscriptionPayment'
          description: |
            Na listagem, `payment_info.expiration` vem sempre `null` — a validade do
            cartão só é retornada no detalhe da assinatura.
        charges:
          type: array
          description: Histórico de cobranças.
          items:
            $ref: '#/components/schemas/SubscriptionCharge'
        skipped_cycles:
          type: array
          description: |
            Índices dos ciclos futuros marcados para pular. Vazio quando não há
            nenhum. Use o índice aqui para
            [desfazer o skip](/api-reference/subscriptions/desfazer-skip-ciclo).
          items:
            type: integer
          example: [3]
        cycle_changes:
          type: array
          description: |
            Ciclos cuja composição de produtos difere da composição permanente da
            assinatura, por conta de
            [alterações feitas naquele ciclo](/api-reference/subscriptions/adicionar-produtos-ciclo).
            Vazio quando nenhum ciclo foi alterado.

            É o estado atual, não um histórico: desfazer a alteração remove o ciclo
            desta lista.
          items:
            $ref: '#/components/schemas/SubscriptionCycleChange'
        original_order_id:
          type: [integer, 'null']
          description: |
            Pedido que deu origem à assinatura — a venda a partir da qual ela foi criada.
            Vem `null` quando a assinatura ainda não tem nenhuma cobrança com pedido
            associado.
          example: 126509440
        original_shopify_order_id:
          type: [string, 'null']
          description: |
            Mesmo pedido de origem, identificado na Shopify. Vem `null` quando a venda
            que originou a assinatura não veio de uma loja Shopify.
          example: '7217346216132'
        next_charge_at:
          type: [string, 'null']
          description: |
            Data da próxima cobrança, no formato `dd/mm/aaaa`. Já desconta os
            ciclos pulados — é sempre a data em que a cobrança vai de fato
            acontecer. Veja `skipped_cycles`.
          example: 15/08/2026
        created_at:
          type: [string, 'null']
          description: |
            Data/hora de criação da assinatura, no formato `aaaa-mm-dd hh:mm:ss` —
            o mesmo formato devolvido no detalhe.
          example: '2026-06-15 10:00:00'

    ListSubscriptionsResponse:
      type: object
      properties:
        data:
          type: object
          description: Envelope de dados da listagem.
          properties:
            subscriptions:
              type: array
              description: Assinaturas da página retornada.
              items:
                $ref: '#/components/schemas/SubscriptionListItem'
            pagination:
              type: object
              description: Dados de paginação da listagem.
              properties:
                total:
                  type: integer
                  description: Total de assinaturas encontradas para o e-mail informado.
                  example: 1
                per_page:
                  type: integer
                  description: Itens por página. Fixo em 10.
                  example: 10
                current_page:
                  type: integer
                  description: Página retornada.
                  example: 1
                last_page:
                  type: integer
                  description: Última página disponível.
                  example: 1

    CreateSubscriptionRequest:
      type: object
      required: [order_id]
      properties:
        order_id:
          type: integer
          minimum: 1
          description: Identificador do pedido de origem, que será transformado em assinatura.
          example: 12345
        interval:
          type: string
          enum: [day, week, month, year]
          default: month
          description: |
            Unidade da periodicidade (dias, semanas, meses ou anos). Combine com
            `interval_count` para obter a cadência — `month` + `2` = a cada dois meses,
            `day` + `45` = a cada 45 dias.

            Quando a combinação equivale a uma unidade maior, ela é gravada nessa
            unidade: `day` + `14` vira `week` + `2` e `month` + `12` vira `year` + `1`.
          example: month
        interval_count:
          type: integer
          minimum: 1
          default: 1
          description: Quantidade de intervalos entre cobranças.
          example: 1
        max_cycles:
          type: integer
          minimum: 1
          description: Número máximo de ciclos. Sem valor = ilimitado.
          example: 12
        next_charge_at:
          type: string
          format: date
          description: Data da próxima cobrança.
          example: '2026-08-15'
        fail_max_tries:
          type: integer
          minimum: 1
          default: 3
          description: Tentativas em caso de falha na cobrança.
          example: 3
        fail_interval_hours:
          type: integer
          minimum: 1
          default: 24
          description: Horas entre tentativas de cobrança.
          example: 24
        products:
          type: array
          description: |
            Produtos que compõem a assinatura. Informe **ou** `product_id` (produto
            interno da Appmax) **ou** o par `shopify_product_id` +
            `shopify_variant_id` (produto da Shopify).

            Obrigatório quando o pedido veio de um checkout Shopify — nesse caso
            só a forma Shopify é aceita.
          items:
            type: object
            properties:
              product_id:
                type: integer
                minimum: 1
                description: Identificador do produto interno da Appmax.
                example: 55
              shopify_product_id:
                type: string
                description: Identificador do produto na Shopify. Exigido junto de `shopify_variant_id`.
                example: '8888888'
              shopify_variant_id:
                type: string
                description: |
                  Identificador da variante na Shopify. O preço é lido da variante,
                  não do payload.
                example: '7777777'
              quantity:
                type: integer
                minimum: 1
                default: 1
                description: Quantidade do produto cobrada a cada ciclo.
                example: 1
        freight_value:
          type: number
          minimum: 0
          description: Valor do frete.
          example: 19.9
        discount:
          type: number
          minimum: 0
          description: |
            Desconto da assinatura. Em reais quando `discount_type` é `nominal`,
            ou o percentual (0 a 100) quando é `percent`. Omitido, a assinatura
            nasce sem desconto — o cupom do pedido de origem não é herdado.
          example: 10.0
        discount_type:
          type: string
          enum: [nominal, percent]
          default: nominal
          description: |
            Como o `discount` é interpretado. `nominal` é um valor fixo em reais;
            `percent` incide sobre o valor dos produtos e é recalculado a cada
            cobrança, acompanhando mudanças na composição da assinatura.
          example: percent

    CreateSubscriptionResponse:
      type: object
      description: |
        A criação devolve a assinatura recém-criada. Diferente dos demais
        endpoints, aqui o identificador vem em `id` e o `status` usa a forma
        interna em minúsculas (`active`, `canceled`, `overdue`, `suspended`,
        `finished`). Para o formato normalizado, consulte a assinatura pelo
        endpoint de detalhe.
      properties:
        data:
          type: object
          description: Assinatura criada.
          properties:
            id:
              type: integer
              description: Identificador da assinatura.
              example: 10
            uuid:
              type: string
              format: uuid
              description: |
                Identificador público da assinatura, usado na URL do painel do
                assinante (`panel_url`).
              example: 6f1c0f1e-6a1c-4a2b-9f0e-3d5c7a9b1234
            status:
              type: string
              enum: [active, canceled, overdue, suspended, finished]
              description: |
                Status interno da assinatura, em minúsculas:
                - `active` — ativa.
                - `canceled` — cancelada.
                - `overdue` — com cobrança em atraso.
                - `suspended` — suspensa.
                - `finished` — encerrada (atingiu `max_cycles`).

                Não inclui `paused`: uma assinatura pausada continua `active` aqui.
                O status normalizado (com `PAUSED`) vem no detalhe e na listagem.
              example: active
            subscription_status_id:
              type: integer
              enum: [1, 2, 3, 4, 5]
              description: |
                Identificador numérico do status, equivalente a `status`:
                - `1` — `active`
                - `2` — `canceled`
                - `3` — `overdue`
                - `4` — `suspended`
                - `5` — `finished`
              example: 1
            interval:
              type: string
              enum: [day, week, month, year]
              description: |
                Unidade da periodicidade:
                - `day` — dias.
                - `week` — semanas.
                - `month` — meses.
                - `year` — anos.
              example: month
            interval_count:
              type: integer
              description: Quantidade de unidades entre cobranças.
              example: 1
            interval_name:
              type: string
              description: |
                Nome da cadência em português, derivado de `interval` +
                `interval_count`. Não é uma lista fechada: combinações usuais
                devolvem `Semanal`, `Quinzenal`, `Mensal`, `Bimestral`,
                `Trimestral`, `Semestral` ou `Anual`; as demais caem em um rótulo
                genérico como `Mensal a cada 4 mês(es)`.
              example: Mensal
            charge_day:
              type: [integer, 'null']
              description: |
                Dia fixo de cobrança (1 a 31), quando definido. Vem `null` quando a
                assinatura segue a data de criação em vez de um dia fixo. Em dias 29 a
                31, os meses mais curtos cobram no último dia.
              example: 15
            fail_max_tries:
              type: [integer, 'null']
              description: Tentativas de cobrança antes de a assinatura sair de `active`.
              example: 3
            fail_interval_hours:
              type: [integer, 'null']
              description: Horas de espera entre tentativas de cobrança.
              example: 24
            max_cycles:
              type: [integer, 'null']
              description: Número máximo de ciclos (`null` = ilimitado).
              example: 12
            completed_cycles:
              type: [integer, 'null']
              description: Ciclos já cobrados. Começa em `0` na criação.
              example: 0
            current_cycle:
              type: [integer, 'null']
              description: Ciclo em andamento (`completed_cycles` + 1).
              example: 1
            next_charge_at:
              type: [string, 'null']
              description: |
                Data/hora da próxima cobrança, no formato `aaaa-mm-dd hh:mm:ss`.
              example: '2026-08-15 00:00:00'
            original_order_id:
              type: [integer, 'null']
              description: |
                Pedido que deu origem à assinatura — a venda a partir da qual ela foi
                criada.
              example: 126509440
            original_shopify_order_id:
              type: [string, 'null']
              description: |
                Mesmo pedido de origem, identificado na Shopify. Vem `null` quando a
                venda não veio de uma loja Shopify.
              example: '7217346216132'
            created_at:
              type: [string, 'null']
              description: |
                Data/hora de criação da assinatura, no formato `aaaa-mm-dd hh:mm:ss`.
              example: '2026-07-15 10:00:00'
            freight_value:
              type: [number, 'null']
              description: Valor do frete cobrado a cada ciclo.
              example: 19.9
            discount:
              type: [number, 'null']
              description: |
                Desconto da assinatura. Em reais quando `discount_type` é `nominal`,
                ou o percentual quando é `percent`. `null` quando a assinatura nunca
                teve desconto próprio definido.
              example: 10
            discount_type:
              type: [string, 'null']
              enum: [nominal, percent, null]
              description: |
                Como o `discount` é interpretado. `nominal` é um valor fixo em reais;
                `percent` incide sobre o valor dos produtos e é recalculado a cada
                cobrança.
              example: percent
            products:
              type: array
              description: Produtos que compõem a assinatura.
              items:
                $ref: '#/components/schemas/SubscriptionDetailProduct'
            customer:
              oneOf:
                - $ref: '#/components/schemas/SubscriptionCustomer'
                - type: 'null'
            payment:
              $ref: '#/components/schemas/SubscriptionPayment'
            charges:
              type: array
              description: |
                Cobranças da assinatura. Na criação traz apenas a cobrança de
                origem, com `cycle` igual a `0`.
              items:
                $ref: '#/components/schemas/SubscriptionCharge'
            panel_url:
              type: string
              format: uri
              description: URL do painel do assinante.
              example: https://assinaturas.appmax.com.br/painel/6f1c0f1e-6a1c-4a2b-9f0e-3d5c7a9b1234

    SubscriptionDetailProduct:
      type: object
      properties:
        name:
          type: string
          description: Nome do produto.
          example: Camiseta Preta
        shopify_product_id:
          type: [string, 'null']
          description: |
            Identificador do produto na Shopify. Vem `null` em assinaturas de
            produtos internos da Appmax.
          example: null
        variant_id:
          type: [string, 'null']
          description: |
            Identificador da variante na Shopify. Vem `null` em assinaturas de
            produtos internos da Appmax.
          example: null
        product_id:
          type: [integer, 'null']
          description: |
            Identificador do produto interno da Appmax. Vem `null` em assinaturas
            de produtos Shopify.
          example: 55
        sku:
          type: [string, 'null']
          description: SKU do produto.
          example: SKU-1
        quantity:
          type: [integer, 'null']
          description: Quantidade do produto cobrada a cada ciclo.
          example: 1
        price:
          type: [number, 'null']
          description: Preço unitário registrado na assinatura.
          example: 199.9
        image_url:
          type: [string, 'null']
          description: Imagem do produto (da variante na Shopify ou do cadastro interno).
          example: null
        compare_at_price:
          type: [number, 'null']
          description: Preço "de" da variante na Shopify, quando disponível.
          example: null

    SubscriptionCharge:
      type: object
      properties:
        cycle:
          type: integer
          description: |
            Ciclo de renovação a que a cobrança corresponde, começando em `1`. A
            cobrança de origem (o pedido que deu início à assinatura) não entra
            nesta lista — use `last_charge_at` para a data do pagamento inicial.
          example: 1
        order_id:
          type: [integer, 'null']
          description: Pedido gerado pela cobrança na Appmax.
          example: 12345
        shopify_order_id:
          type: [string, 'null']
          description: |
            Pedido correspondente na Shopify, quando houver. Vem `null` enquanto o
            pedido ainda não foi criado na loja.
          example: '6868591542526'
        charged_at:
          type: [string, 'null']
          description: Data da cobrança, no formato `dd/mm/aaaa`.
          example: 15/07/2026
        value:
          type: [number, 'null']
          description: Valor cobrado no ciclo, já com frete e desconto aplicados.
          example: 199.9
        status:
          type: [string, 'null']
          enum: [paid, waiting, overdue, canceled, null]
          description: |
            Status da cobrança:
            - `paid` — paga.
            - `waiting` — aguardando pagamento.
            - `overdue` — em atraso (falhou e ainda há tentativas).
            - `canceled` — cancelada.
          example: paid

    SubscriptionDetail:
      type: object
      properties:
        subscription_id:
          type: integer
          description: Identificador da assinatura.
          example: 10
        tag:
          $ref: '#/components/schemas/SubscriptionTag'
        status:
          $ref: '#/components/schemas/SubscriptionStatus'
        products:
          type: array
          description: Produtos que compõem a assinatura.
          items:
            $ref: '#/components/schemas/SubscriptionDetailProduct'
        value:
          type: [number, 'null']
          description: Valor da assinatura.
          example: 199.9
        freight_value:
          type: [number, 'null']
          description: Valor do frete cobrado a cada ciclo.
          example: 19.9
        discount:
          type: [number, 'null']
          description: |
            Desconto da assinatura. Em reais quando `discount_type` é `nominal`,
            ou o percentual quando é `percent`. `null` quando a assinatura nunca
            teve desconto próprio definido.
          example: 10
        discount_type:
          type: [string, 'null']
          enum: [nominal, percent, null]
          description: |
            Como o `discount` é interpretado. `nominal` é um valor fixo em reais;
            `percent` incide sobre o valor dos produtos e é recalculado a cada
            cobrança.
          example: percent
        interval:
          type: [string, 'null']
          enum: [day, week, month, year, null]
          description: |
            Unidade da periodicidade:
            - `day` — dias.
            - `week` — semanas.
            - `month` — meses.
            - `year` — anos.

            Combine com `interval_count` para obter a cadência (por exemplo
            `month` + `2` = a cada dois meses).
          example: month
        interval_count:
          type: [integer, 'null']
          description: Quantidade de unidades entre cobranças.
          example: 1
        current_cycle:
          type: [integer, 'null']
          description: Ciclo em andamento (`completed_cycles` + 1).
          example: 3
        completed_cycles:
          type: [integer, 'null']
          description: Ciclos já cobrados.
          example: 2
        customer:
          oneOf:
            - $ref: '#/components/schemas/SubscriptionCustomer'
            - type: 'null'
        payment:
          $ref: '#/components/schemas/SubscriptionPayment'
        max_cycles:
          type: [integer, 'null']
          description: Número máximo de ciclos (`null` = ilimitado).
          example: 12
        skipped_cycles:
          type: array
          description: |
            Índices dos ciclos futuros marcados para pular. Vazio quando não há
            nenhum. Use o índice aqui para
            [desfazer o skip](/api-reference/subscriptions/desfazer-skip-ciclo).
          items:
            type: integer
          example: [3]
        cycle_changes:
          type: array
          description: |
            Ciclos cuja composição de produtos difere da composição permanente da
            assinatura, por conta de
            [alterações feitas naquele ciclo](/api-reference/subscriptions/adicionar-produtos-ciclo).
            Vazio quando nenhum ciclo foi alterado.

            É o estado atual, não um histórico: desfazer a alteração remove o ciclo
            desta lista.
          items:
            $ref: '#/components/schemas/SubscriptionCycleChange'
        original_order_id:
          type: [integer, 'null']
          description: |
            Pedido que deu origem à assinatura — a venda a partir da qual ela foi criada.
            Vem `null` quando a assinatura ainda não tem nenhuma cobrança com pedido
            associado.
          example: 126509440
        original_shopify_order_id:
          type: [string, 'null']
          description: |
            Mesmo pedido de origem, identificado na Shopify. Vem `null` quando a venda
            que originou a assinatura não veio de uma loja Shopify.
          example: '7217346216132'
        next_charge_at:
          type: [string, 'null']
          description: |
            Data/hora da próxima cobrança, no formato `aaaa-mm-dd hh:mm:ss`. Já
            desconta os ciclos pulados — é sempre a data em que a cobrança vai
            de fato acontecer. Vem `null` quando não há próxima cobrança.
          example: '2026-08-15 00:00:00'
        last_charge_at:
          type: [string, 'null']
          description: Data da última cobrança, no formato `dd/mm/aaaa`.
          example: 15/07/2026
        charges:
          type: array
          description: Histórico de cobranças.
          items:
            $ref: '#/components/schemas/SubscriptionCharge'
        created_at:
          type: [string, 'null']
          description: |
            Data/hora de criação da assinatura, no formato `aaaa-mm-dd hh:mm:ss`.
          example: '2026-06-15 10:00:00'
        canceled_at:
          type: [string, 'null']
          description: |
            Data/hora do cancelamento, no formato `aaaa-mm-dd hh:mm:ss`. Vem `null`
            quando a assinatura não foi cancelada.
          example: null
        paused_from:
          type: [string, 'null']
          description: |
            Data/hora em que a pausa começou, no formato `aaaa-mm-dd hh:mm:ss`. Vem
            `null` quando a assinatura nunca foi pausada.
          example: null
        paused_until:
          type: [string, 'null']
          description: |
            Data/hora em que a pausa termina, no formato `aaaa-mm-dd hh:mm:ss`. Vem
            `null` em pausa por prazo indeterminado ou quando não há pausa.
          example: null
        pause_reason:
          type: [string, 'null']
          description: |
            Motivo informado na última pausa da assinatura. Permanece preenchido
            depois de reativar — serve como histórico, não como estado atual.
          example: Cliente vai viajar
        cancel_reason:
          type: [string, 'null']
          description: Motivo informado no cancelamento da assinatura.
          example: Achou caro

    SubscriptionDetailResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/SubscriptionDetail'

    UpdateSubscriptionFrequencyRequest:
      type: object
      required: [interval, interval_count]
      properties:
        interval:
          type: string
          enum: [day, week, month, year]
          description: |
            Nova unidade da periodicidade (dias, semanas, meses ou anos). Combine com
            `interval_count` para obter a cadência — `month` + `2` = a cada dois meses,
            `day` + `45` = a cada 45 dias.

            Quando a combinação equivale a uma unidade maior, ela é tratada nessa
            unidade: `day` + `14` é o mesmo que `week` + `2` e `month` + `12` é o mesmo
            que `year` + `1`.
          example: month
        interval_count:
          type: integer
          minimum: 1
          description: Quantidade de intervalos entre cobranças.
          example: 2

    AddSubscriptionCycleProductsRequest:
      allOf:
        - $ref: '#/components/schemas/AddSubscriptionProductsRequest'
        - type: object
          properties:
            cycle_index:
              type: integer
              minimum: 1
              description: |
                Ciclo que recebe os produtos. Omitido, vale o próximo ciclo da
                assinatura (`completed_cycles + 1`).
              example: 3
    UpdateSubscriptionCycleProductQuantityRequest:
      type: object
      required: [quantity]
      properties:
        cycle_index:
          type: integer
          minimum: 1
          description: |
            Ciclo a alterar. Omitido, vale o próximo ciclo da assinatura
            (`completed_cycles + 1`).
          example: 3
        quantity:
          type: integer
          minimum: 1
          description: Nova quantidade do produto naquele ciclo.
          example: 3
    SubscriptionCycleProductsResponse:
      type: object
      properties:
        data:
          type: object
          description: Composição do ciclo depois da alteração.
          properties:
            cycle_index:
              type: integer
              description: Ciclo alterado. Quando o ciclo não é informado, vem o próximo ciclo.
              example: 3
            products:
              type: array
              description: |
                Todos os produtos que aquele ciclo vai cobrar: os da assinatura
                mais os adicionados ao ciclo.
              items:
                type: object
                properties:
                  name:
                    type: string
                    example: Produto A
                  price:
                    type: number
                    format: float
                    example: 100
                  quantity:
                    type: integer
                    example: 1
                  variant_id:
                    type: string
                    description: Variante na Shopify. Ausente em assinaturas de produtos internos.
                    example: '456'
                  shopify_product_id:
                    type: string
                    description: Produto na Shopify. Ausente em assinaturas de produtos internos.
                    example: '123'
                  product_id:
                    type: integer
                    description: Produto interno da Appmax. Ausente em assinaturas da Shopify.
                    example: 55
                  sku:
                    type: [string, 'null']
                    example: SKU-A
            added_products:
              type: array
              description: |
                Produtos adicionados àquele ciclo. Vem vazio quando nada foi
                adicionado.
              items:
                type: object
                properties:
                  shopify_product_id:
                    type: string
                    example: '789'
                  variant_id:
                    type: string
                    example: '987'
                  product_id:
                    type: [integer, 'null']
                    description: |
                      Produto interno da Appmax. Em assinaturas da Shopify vem o
                      produto que a Appmax mantém para aquela variante.
                    example: 55
                  name:
                    type: string
                    example: Caneca
                  sku:
                    type: [string, 'null']
                    example: CAN-1
                  quantity:
                    type: integer
                    example: 1
                  price:
                    type: number
                    format: float
                    example: 30
            removed_products:
              type: array
              description: |
                Produtos da assinatura que **não** serão cobrados nem entregues
                naquele ciclo.
              items:
                type: object
                properties:
                  shopify_product_id:
                    type: string
                    example: '789'
                  variant_id:
                    type: string
                    example: '654'
                  product_id:
                    type: [integer, 'null']
                    description: |
                      Produto interno da Appmax. Em assinaturas da Shopify vem o
                      produto que a Appmax mantém para aquela variante.
                    example: 56
                  name:
                    type: string
                    example: Produto A
                  sku:
                    type: [string, 'null']
                    example: SKU-A
                  quantity:
                    type: integer
                    example: 1
                  price:
                    type: number
                    format: float
                    example: 40
            quantity_changes:
              type: array
              description: |
                Produtos cuja quantidade foi alterada só naquele ciclo.
              items:
                type: object
                properties:
                  reference:
                    type: string
                    description: |
                      `shopify_variant_id` em assinaturas da Shopify,
                      `product_id` nas de produtos internos.
                    example: '55'
                  quantity:
                    type: integer
                    description: Quantidade que passa a valer naquele ciclo.
                    example: 3
                  previous_quantity:
                    type: [integer, 'null']
                    description: |
                      Quantidade da composição permanente, para comparação. Vem
                      `null` quando o produto não está mais na composição atual.
                    example: 1
                  name:
                    type: [string, 'null']
                    example: Produto A
                  sku:
                    type: [string, 'null']
                    example: SKU-A
                  variant_id:
                    type: string
                    description: Variante na Shopify. Presente em assinaturas Shopify.
                    example: '654'
                  shopify_product_id:
                    type: [string, 'null']
                    description: Produto na Shopify. Presente em assinaturas Shopify.
                    example: '789'
                  product_id:
                    type: [integer, 'null']
                    description: |
                      Produto interno da Appmax. Em assinaturas da Shopify vem o
                      produto que a Appmax mantém para aquela variante.
                    example: 55
            value:
              type: [number, 'null']
              format: float
              description: Valor que aquele ciclo vai cobrar, já com as alterações aplicadas.
              example: 130
    SubscriptionCycleResponse:
      type: object
      description: Endereço de entrega da assinatura, já normalizado.
      properties:
        data:
          type: object
          description: Resultado da operação sobre o ciclo.
          properties:
            cycle_index:
              type: integer
              description: Ciclo afetado.
              example: 3
            status:
              type: string
              enum: [skipped, unbilled]
              description: |
                Situação do ciclo após a operação:
                - `skipped` — ciclo será pulado.
                - `unbilled` — ciclo voltou à fila de cobrança.
              example: skipped
            next_charge_at:
              type: [string, 'null']
              description: |
                Data da próxima cobrança que efetivamente será feita, já
                descontando os ciclos pulados. É o mesmo valor devolvido no
                detalhe e na listagem da assinatura. Vem `null` quando o ciclo
                pulado era o último (`max_cycles`) e a assinatura será encerrada.
              example: '2026-10-15 00:00:00'

    PauseSubscriptionRequest:
      type: object
      description: Todos os campos são opcionais.
      properties:
        paused_until:
          type: string
          format: date
          description: |
            Data até a qual a assinatura fica pausada. Sem valor, a pausa é por
            prazo indeterminado.
          example: '2026-09-01'
        reason:
          type: string
          maxLength: 1000
          description: Motivo da pausa.
          example: Cliente solicitou pausa temporária

    CancelSubscriptionRequest:
      type: object
      description: Todos os campos são opcionais.
      properties:
        reason:
          type: string
          maxLength: 1000
          description: Motivo do cancelamento.
          example: Cliente não deseja mais o produto

    UpdateChargeDayRequest:
      type: object
      required: [charge_day]
      properties:
        charge_day:
          type: integer
          minimum: 1
          maximum: 31
          description: |
            Dia fixo de cobrança (1 a 31).
            Dias 29, 30 e 31 são aceitos: nos meses que não têm o dia escolhido a
            cobrança cai no último dia do mês, e o dia volta a valer no mês seguinte
            que o tiver. Uma assinatura no dia 31 cobra 31/01, 28/02, 31/03, 30/04.
          example: 15

    UpdateChargeDayResponse:
      type: object
      properties:
        data:
          type: object
          description: Resultado da alteração do dia de cobrança.
          properties:
            charge_day:
              type: integer
              description: Dia fixo de cobrança aplicado (1 a 31).
              example: 15
            next_charge_at:
              type: [string, 'null']
              description: Próxima data de cobrança recalculada.
              example: '2026-08-15 00:00:00'
            adjusted:
              type: boolean
              description: |
                `true` quando a data entregue não é a que o dia escolhido produziria
                (dia já passado no mês, ou data que ficaria colada na cobrança anterior).
              example: false
            adjusted_reason:
              type: [string, 'null']
              description: |
                Código do ajuste. `day_already_passed` quando o dia escolhido já passou
                neste mês; `too_close_to_last_charge` quando a data ficaria a menos de
                meio ciclo da última cobrança. `null` quando não houve ajuste.
              enum: [day_already_passed, too_close_to_last_charge, null]
              example: null
            adjusted_message:
              type: [string, 'null']
              description: Explicação do ajuste, pronta para exibição. `null` quando não houve ajuste.
              example: null

    SubscriptionCycleChange:
      type: object
      description: O que um ciclo específico tem de diferente da composição permanente.
      properties:
        cycle_index:
          type: integer
          description: Índice do ciclo, contado a partir de 1.
          example: 3
        status:
          type: string
          description: |
            Situação do ciclo: `PROCESSED` já foi cobrado, `UNBILLED` ainda vai ser.
          enum: [PROCESSED, UNBILLED]
          example: UNBILLED
        added:
          type: array
          description: Produtos adicionados só neste ciclo.
          items:
            $ref: '#/components/schemas/SubscriptionCycleProduct'
        removed:
          type: array
          description: Produtos da composição permanente que não entram neste ciclo.
          items:
            $ref: '#/components/schemas/SubscriptionCycleProduct'
        quantity_changes:
          type: array
          description: Produtos cuja quantidade foi alterada só neste ciclo.
          items:
            $ref: '#/components/schemas/SubscriptionCycleQuantityChange'

    SubscriptionCycleProduct:
      type: object
      properties:
        name:
          type: [string, 'null']
          example: Caneca Appmax
        sku:
          type: [string, 'null']
          example: CAN-1
        quantity:
          type: integer
          example: 1
        price:
          type: number
          description: Preço unitário.
          example: 30
        variant_id:
          type: string
          description: Variante na Shopify. Presente em assinaturas Shopify.
          example: '48304785031422'
        shopify_product_id:
          type: string
          description: Produto na Shopify. Presente em assinaturas Shopify.
          example: '9260400836862'
        product_id:
          type: [integer, 'null']
          description: |
            Produto interno da Appmax. Em assinaturas da Shopify vem o produto que
            a Appmax mantém para aquela variante.
          example: 42
        image_url:
          type: [string, 'null']
          description: Imagem do produto (da variante na Shopify ou do cadastro interno).
          example: null
        compare_at_price:
          type: [number, 'null']
          description: Preço "de" da variante na Shopify, quando disponível.
          example: null

    SubscriptionCycleQuantityChange:
      type: object
      properties:
        reference:
          type: string
          description: |
            Identificador do produto alterado: a variante na Shopify, ou o id do
            produto da Appmax em assinaturas criadas por API.
          example: '48304785031422'
        quantity:
          type: integer
          description: Quantidade que vale neste ciclo.
          example: 3
        previous_quantity:
          type: [integer, 'null']
          description: |
            Quantidade da composição permanente. Vem `null` quando o produto não
            está mais na composição atual.
          example: 2
        name:
          type: [string, 'null']
          example: Caneca Appmax
        sku:
          type: [string, 'null']
          example: CAN-1
        variant_id:
          type: string
          description: Variante na Shopify. Presente em assinaturas Shopify.
          example: '48304785031422'
        shopify_product_id:
          type: [string, 'null']
          description: Produto na Shopify. Presente em assinaturas Shopify.
          example: '9260400836862'
        product_id:
          type: [integer, 'null']
          description: |
            Produto interno da Appmax. Em assinaturas da Shopify vem o produto que
            a Appmax mantém para aquela variante.
          example: 42
        image_url:
          type: [string, 'null']
          description: Imagem do produto (da variante na Shopify ou do cadastro interno).
          example: null
        compare_at_price:
          type: [number, 'null']
          description: Preço "de" da variante na Shopify, quando disponível.
          example: null

    ForceChargeResponse:
      type: object
      properties:
        data:
          type: object
          description: Resultado da cobrança antecipada.
          properties:
            charge_id:
              type: integer
              description: Identificador da cobrança gerada.
              example: 41287
            order_id:
              type: [integer, 'null']
              description: Pedido gerado para o ciclo antecipado.
              example: 126509440
            cycle_index:
              type: integer
              description: Ciclo cobrado, contado a partir de 1.
              example: 7
            value:
              type: [number, 'null']
              description: Valor cobrado.
              example: 189.9
            currency:
              type: [string, 'null']
              description: Moeda da cobrança.
              example: BRL
            status:
              type: string
              description: Status da assinatura após a cobrança.
              example: active
            next_charge_at:
              type: [string, 'null']
              description: Próxima data de cobrança já considerando o ciclo consumido.
              example: '2026-10-20 00:00:00'

    SubscriptionAddress:
      type: object
      required: [postcode, street, number, district, city, state]
      properties:
        country:
          type: string
          minLength: 2
          maxLength: 2
          description: |
            País no padrão ISO 3166-1 alpha-2. Opcional — na ausência do campo,
            o endereço é tratado como brasileiro (`BR`).
          example: BR
        postcode:
          type: string
          maxLength: 20
          description: CEP. A pontuação enviada é removida na gravação.
          example: 01010-000
        street:
          type: string
          maxLength: 255
          description: Logradouro.
          example: Rua São Bento
        number:
          type: string
          maxLength: 56
          description: Número.
          example: '111'
        complement:
          type: string
          maxLength: 255
          description: Complemento.
          example: Bloco 7
        district:
          type: string
          maxLength: 255
          description: Bairro.
          example: Centro
        city:
          type: string
          maxLength: 255
          description: Cidade.
          example: São Paulo
        state:
          type: string
          maxLength: 255
          description: |
            Em endereço brasileiro, a UF com 2 letras (gravada em maiúsculas).
            Em endereço de outro país, o nome da região, com até 255 caracteres.
          example: SP

    SetCycleDiscountRequest:
      type: object
      required: [discount]
      properties:
        cycle_index:
          type: integer
          minimum: 1
          description: |
            Ciclo que recebe o desconto. Omitido, vale para o próximo ciclo da
            assinatura.
          example: 4
        discount:
          type: number
          minimum: 0
          description: |
            Valor do desconto do ciclo. Em reais quando `discount_type` é `nominal`,
            ou o percentual (0 a 100) quando é `percent`.
          example: 15
        discount_type:
          type: string
          enum: [nominal, percent]
          default: nominal
          description: |
            Como o `discount` é interpretado. No percentual, incide sobre os produtos
            daquele ciclo.
          example: percent

    CycleDiscountResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            cycle_index:
              type: integer
              description: Ciclo afetado.
              example: 4
            discount:
              type: [number, 'null']
              description: |
                Desconto do ciclo. `null` quando o ciclo não tem desconto próprio e
                segue o desconto da assinatura.
              example: 15
            discount_type:
              type: [string, 'null']
              enum: [nominal, percent, null]
              description: Como o `discount` do ciclo é interpretado.
              example: percent

    UpdateSubscriptionDiscountRequest:
      type: object
      required: [discount]
      properties:
        discount:
          type: number
          minimum: 0
          description: |
            Valor do desconto. Em reais quando `discount_type` é `nominal`, ou o
            percentual (0 a 100) quando é `percent`. Envie `0` para remover o
            desconto.
          example: 10
        discount_type:
          type: string
          enum: [nominal, percent]
          default: nominal
          description: |
            Como o `discount` é interpretado. `nominal` é um valor fixo em reais;
            `percent` incide sobre o valor dos produtos e é recalculado a cada
            cobrança.
          example: percent

    UpdateSubscriptionTagRequest:
      type: object
      required: [tag]
      properties:
        tag:
          type: [string, 'null']
          maxLength: 255
          description: |
            Apelido da assinatura. Vazio ou `null` remove o apelido e faz a API
            voltar a devolver o padrão `#assinatura{id}`.
          example: Clube do Whey

    UpdateSubscriptionPaymentMethodRequest:
      type: object
      required: [payment_data]
      properties:
        payment_data:
          type: object
          required: [token]
          properties:
            token:
              type: string
              minLength: 32
              maxLength: 32
              description: |
                Token do cartão, com 32 caracteres, devolvido pelo
                [Appmax JS](/guides/appmax-js) no navegador do assinante.
                Não envie dados de cartão neste endpoint.
              example: add5cac283934dd887c0c51360bf6822

    SubscriptionAddressRequest:
      type: object
      required: [address]
      properties:
        address:
          $ref: '#/components/schemas/SubscriptionAddress'

    SubscriptionAddressResource:
      type: object
      properties:
        country:
          type: [string, 'null']
          description: País no padrão ISO 3166-1 alpha-2.
          example: BR
        state:
          type: [string, 'null']
          description: |
            UF em maiúsculas nos endereços brasileiros; nome da região nos
            demais países.
          example: SP
        city:
          type: [string, 'null']
          description: Cidade.
          example: São Paulo
        district:
          type: [string, 'null']
          description: Bairro.
          example: Centro
        street:
          type: [string, 'null']
          description: Logradouro.
          example: Rua São Bento
        number:
          type: [string, 'null']
          description: Número.
          example: '111'
        complement:
          type: [string, 'null']
          description: Complemento.
          example: Bloco 7
        postcode:
          type: [string, 'null']
          description: CEP, sem pontuação.
          example: '01010000'

    SubscriptionAddressResponse:
      type: object
      properties:
        data:
          type: object
          description: Resultado da gravação do endereço.
          properties:
            address:
              $ref: '#/components/schemas/SubscriptionAddressResource'

    GetSubscriptionAddressResponse:
      type: object
      properties:
        data:
          type: object
          description: Endereço em vigor na assinatura.
          properties:
            address:
              description: |
                Endereço próprio da assinatura ou, na falta dele, o do cadastro
                do cliente. Vem `null` quando nenhuma das duas origens tem
                endereço utilizável.
              oneOf:
                - $ref: '#/components/schemas/SubscriptionAddressResource'
                - type: 'null'

    SubscriptionAvailableShopifyProduct:
      type: object
      description: Item retornado para assinaturas de produtos Shopify.
      properties:
        shopify_product_id:
          type: string
          description: Identificador do produto na Shopify.
          example: '123'
        title:
          type: string
          description: Nome do produto na Shopify.
          example: Camiseta Branca
        variants:
          type: array
          description: Variantes compatíveis com a periodicidade da assinatura.
          items:
            type: object
            properties:
              shopify_variant_id:
                type: string
                description: Use este valor no corpo de "Adicionar produtos".
                example: '456'
              title:
                type: string
                description: Nome da variante na Shopify.
                example: Mensal
              price:
                type: [string, 'null']
                description: Preço da variante na Shopify, devolvido como texto.
                example: '189.90'
              sku:
                type: [string, 'null']
                description: SKU da variante.
                example: SKU-2
              interval:
                type: string
                enum: [day, week, month, year]
                description: |
                  Unidade da periodicidade da variante:
                  - `day` — dias.
                  - `week` — semanas.
                  - `month` — meses.
                  - `year` — anos.
                example: month
              interval_count:
                type: integer
                description: Quantidade de unidades entre cobranças.
                example: 1

    SubscriptionAvailableInternalProduct:
      type: object
      description: Item retornado para assinaturas de produtos internos da Appmax.
      properties:
        product_id:
          type: integer
          description: Use este valor no corpo de "Adicionar produtos".
          example: 55
        name:
          type: string
          description: Nome do produto.
          example: Camiseta Branca
        price:
          type: [number, 'null']
          description: Preço cadastrado do produto.
          example: 189.9
        sku:
          type: [string, 'null']
          description: SKU do produto.
          example: SKU-2

    SubscriptionAvailableProductsResponse:
      type: object
      properties:
        data:
          type: object
          description: Envelope de dados dos produtos disponíveis.
          properties:
            products:
              type: array
              description: |
                Produtos que podem ser adicionados à assinatura. O formato de cada
                item depende do tipo da assinatura: produtos Shopify ou produtos
                internos da Appmax.
              items:
                oneOf:
                  - $ref: '#/components/schemas/SubscriptionAvailableShopifyProduct'
                  - $ref: '#/components/schemas/SubscriptionAvailableInternalProduct'

    AddSubscriptionProductsRequest:
      type: object
      required: [products]
      properties:
        products:
          type: array
          minItems: 1
          description: Lista de produtos a adicionar.
          items:
            type: object
            description: |
              Identifique o produto conforme o tipo da assinatura: `product_id`
              (produto interno da Appmax) ou `shopify_product_id` +
              `shopify_variant_id` (produto da Shopify). Use `qty` ou `quantity`
              para a quantidade.
            properties:
              product_id:
                type: integer
                minimum: 1
                description: Produto interno da Appmax. Usado em assinaturas de produtos internos.
                example: 55
              shopify_product_id:
                type: string
                description: Produto na Shopify. Exigido junto de `shopify_variant_id`.
                example: '123'
              shopify_variant_id:
                type: string
                description: Variante na Shopify. Exigido junto de `shopify_product_id`.
                example: '456'
              name:
                type: string
                description: |
                  Opcional e apenas informativo — nome, preço e SKU são resolvidos
                  do cadastro do produto (interno) ou da variante (Shopify).
                example: Camiseta Branca
              price:
                type: number
                description: Opcional e apenas informativo. Veja `name`.
                example: 189.9
              sku:
                type: string
                description: Opcional e apenas informativo. Veja `name`.
                example: SKU-2
              qty:
                type: integer
                minimum: 1
                description: Quantidade (alias de `quantity`).
                example: 1
              quantity:
                type: integer
                minimum: 1
                description: Quantidade do produto cobrada a cada ciclo.
                example: 1

    UpdateSubscriptionProductQuantityRequest:
      type: object
      required: [quantity]
      properties:
        quantity:
          type: integer
          minimum: 1
          description: Nova quantidade do produto.
          example: 3

    MessageErrorEnvelope:
      type: object
      description: Formato padrão de erro da API com mensagem simples.
      properties:
        errors:
          type: object
          properties:
            message:
              type: string
              description: Descrição do erro.

    ProductResource:
      type: object
      description: Representação de um produto do catálogo retornada pela API.
      properties:
        id:
          type: integer
          description: Identificador único do produto.
          example: 10
        sku:
          type: [string, 'null']
          description: Código interno do produto (SKU). Único por company.
          example: SKU-1
        name:
          type: string
          description: Nome do produto.
          example: Camiseta Preta
        price:
          type: number
          description: Preço de venda.
          example: 199.9
        description:
          type: [string, 'null']
          description: Descrição do produto.
          example: Camiseta 100% algodão
        image:
          type: [string, 'null']
          description: Caminho ou URL da imagem.
          example: /uploads/a.png
        inventory:
          type: integer
          description: Quantidade em estoque.
          example: 5
        cost:
          type: [number, 'null']
          description: Custo do produto.
          example: 50.0
        is_active:
          type: boolean
          description: Indica se o produto está ativo. Produtos excluídos ficam `false`.
          example: true
        created_at:
          type: string
          description: Data/hora de criação (`YYYY-MM-DD HH:MM:SS`).
          example: '2026-07-01 10:00:00'
        updated_at:
          type: string
          description: Data/hora da última atualização (`YYYY-MM-DD HH:MM:SS`).
          example: '2026-07-02 12:00:00'

    CreateProductRequest:
      type: object
      required: [name, price]
      properties:
        name:
          type: string
          maxLength: 255
          description: Nome do produto.
          example: Camiseta Preta
        price:
          type: number
          minimum: 0
          description: Preço de venda. Maior ou igual a 0.
          example: 199.9
        sku:
          type: string
          maxLength: 100
          description: Código interno do produto. Único por company.
          example: SKU-1
        external_id:
          type: string
          maxLength: 255
          description: Identificador externo do produto. Único por company.
          example: EXT-123
        description:
          type: string
          description: Descrição livre do produto.
          example: Camiseta 100% algodão
        image:
          type: string
          description: Caminho ou URL da imagem.
          example: /uploads/a.png
        inventory:
          type: integer
          minimum: 0
          default: 0
          description: Quantidade em estoque. Maior ou igual a 0.
          example: 5
        cost:
          type: number
          minimum: 0
          description: Custo do produto. Maior ou igual a 0.
          example: 50.0

    UpdateProductRequest:
      type: object
      description: |
        Envie apenas os campos que deseja alterar. Todos são opcionais e seguem
        as mesmas regras da criação.
      properties:
        name:
          type: string
          maxLength: 255
          example: Camiseta Preta Slim
        price:
          type: number
          minimum: 0
          example: 219.9
        sku:
          type: string
          maxLength: 100
          description: Único por company.
          example: SKU-1
        external_id:
          type: string
          maxLength: 255
          description: Único por company.
          example: EXT-123
        description:
          type: string
          example: Camiseta 100% algodão
        image:
          type: string
          example: /uploads/a.png
        inventory:
          type: integer
          minimum: 0
          example: 5
        cost:
          type: number
          minimum: 0
          example: 50.0

    ProductResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/ProductResource'

    ListProductsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            products:
              type: array
              items:
                $ref: '#/components/schemas/ProductResource'
            pagination:
              type: object
              properties:
                total:
                  type: integer
                  description: Total de produtos que atendem ao filtro.
                  example: 1
                per_page:
                  type: integer
                  description: Itens por página. Fixo em 20.
                  example: 20
                current_page:
                  type: integer
                  description: Página atual.
                  example: 1
                last_page:
                  type: integer
                  description: Última página disponível.
                  example: 1

    ProductErrorEnvelope:
      type: object
      description: Formato padrão de erro da API V4 com mensagem simples.
      properties:
        errors:
          type: object
          properties:
            message:
              type: string
              description: Descrição do erro.

    CreateOrderRequest:
      type: object
      required: [customer_id, products]
      properties:
        customer_id:
          type: integer
          description: ID do cliente (obtido na criação do cliente).
          example: 29
        products:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Product'
          description: Lista de produtos do pedido.
        products_value:
          type: integer
          minimum: 0
          description: |
            Valor total dos produtos em **centavos**. Obrigatório quando
            `unit_value` não é informado nos produtos.
          example: 12300
        discount_value:
          type: integer
          minimum: 0
          description: Valor do desconto em **centavos**.
          example: 0
        shipping_value:
          type: integer
          minimum: 0
          description: Valor do frete em **centavos**.
          example: 3999

    Order:
      type: object
      properties:
        id:
          type: integer
          description: Identificador do pedido na Appmax.
          example: 1
        status:
          type: string
          description: Status do pedido. Veja [Status de pedidos](/guides/status-pedidos).
          example: pendente

    CreateOrderResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            order:
              $ref: '#/components/schemas/Order'

    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string

    ValidationErrorEnvelope:
      type: object
      properties:
        errors:
          type: object
          properties:
            message:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
              description: |
                Mapa `campo -> [mensagens de erro]` com a descrição do problema
                em cada campo inválido.

    OrderAmounts:
      type: object
      description: Detalhamento dos valores do pedido, todos em **centavos**.
      properties:
        sub_total:
          type: integer
          description: Subtotal dos produtos (em centavos).
          example: 4662
        shipping_value:
          type: integer
          description: Valor do frete (em centavos).
          example: 2738
        discount:
          type: integer
          description: Valor do desconto aplicado (em centavos).
          example: 0
        installment_fee:
          type: integer
          description: Taxa de parcelamento, quando aplicável (em centavos).
          example: 1516

    OrderDetail:
      type: object
      description: |
        Dados detalhados do pedido. Veja [Status de pedidos](/guides/status-pedidos)
        para o significado do campo `status`.
      properties:
        id:
          type: integer
          description: Identificador do pedido na Appmax.
          example: 3531
        status:
          type: string
          description: Status atual do pedido.
          example: estornado
        total_paid:
          type: integer
          description: Valor total pago em **centavos**.
          example: 8916
        amounts:
          $ref: '#/components/schemas/OrderAmounts'
        created_at:
          type: string
          description: Data/hora de criação do pedido (`YYYY-MM-DD HH:mm:ss`).
          example: '2025-02-13 14:09:48'
        updated_at:
          type: string
          description: Data/hora da última atualização do pedido.
          example: '2025-02-13 14:11:55'

    OrderCustomer:
      type: object
      description: Dados do cliente vinculado ao pedido.
      properties:
        id:
          type: integer
          description: ID do cliente.
          example: 2023
        name:
          type: string
          description: Nome completo do cliente.
          example: Junior Almeida
        email:
          type: string
          description: E-mail do cliente.
          example: junior.almeida@teste.com
        document_number:
          type: string
          description: CPF ou CNPJ do cliente (apenas dígitos).
          example: '19100000000'

    OrderPaymentCard:
      type: object
      description: Dados do cartão utilizado, quando o método for `creditcard`.
      properties:
        brand:
          type: string
          description: Bandeira do cartão.
          example: visa
        number:
          type: string
          description: Número do cartão mascarado.
          example: 400000****0010

    OrderPayment:
      type: object
      description: |
        Dados do pagamento. Pode estar vazio quando o pagamento ainda não foi
        processado.
      properties:
        method:
          type: string
          description: 'Método utilizado: `creditcard`, `pix`, `boleto` ou `apple_pay`.'
          example: creditcard
        installments:
          type: integer
          description: Número de parcelas (somente cartão de crédito).
          example: 12
        installments_amount:
          type: integer
          description: Valor de cada parcela em **centavos**.
          example: 743
        card:
          $ref: '#/components/schemas/OrderPaymentCard'
        pix:
          $ref: '#/components/schemas/OrderPaymentPix'
        paid_at:
          type: string
          description: Data/hora da confirmação do pagamento.
          example: '2025-02-13 14:10:20'

    OrderPaymentPix:
      type: object
      description: |
        Dados do pagamento PIX. Presente apenas quando `method` é `pix`.
      properties:
        end_to_end_id:
          type: string
          description: Identificador único da transação PIX no Banco Central (E2EID). Vazio enquanto o pagamento não for confirmado.
          example: 0F362453123GGF
        pay_reference:
          type: string
          description: Identificador da transação PIX no sistema (txid).
          example: 12150f98e957540ba46b297be99afce971c
        creation_date:
          type: string
          description: Data/hora de criação da cobrança PIX, formato `Y-m-d H:i:s`.
          example: '2026-02-11 09:23:29'
        expiration_date:
          type: string
          description: Data/hora de expiração da cobrança PIX, formato `Y-m-d H:i:s`.
          example: '2026-02-11 09:53:29'

    OrderRefund:
      type: object
      description: |
        Dados do reembolso. Pode estar vazio caso o pedido não tenha sido
        estornado.
      properties:
        refunded_at:
          type: string
          description: Data/hora do estorno.
          example: '2025-02-13 14:11:55'

    GetOrderResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            order:
              $ref: '#/components/schemas/OrderDetail'
            customer:
              $ref: '#/components/schemas/OrderCustomer'
            payment:
              $ref: '#/components/schemas/OrderPayment'
            refund:
              $ref: '#/components/schemas/OrderRefund'

    CustomerAddress:
      type: object
      description: Endereço do cliente.
      properties:
        postcode:
          type: string
          description: CEP (apenas dígitos).
          example: '91520270'
        street:
          type: string
          description: Logradouro.
          example: Rua Francisco Carneiro da Rocha
        number:
          type: string
          description: Número do endereço.
          example: '582'
        complement:
          type: string
          description: Complemento (opcional).
          example: Casa
        district:
          type: string
          description: Bairro.
          example: Moinhos de Ventos
        city:
          type: string
          description: Cidade.
          example: Porto Alegre
        state:
          type: string
          description: UF.
          example: RS

    CustomerTracking:
      type: object
      description: Dados de origem da visita (UTMs).
      additionalProperties:
        type: string
      example:
        utm_source: google
        utm_campaign: teste

    CreateOrUpdateCustomerRequest:
      type: object
      required: [first_name, last_name, email, phone, ip]
      properties:
        first_name:
          type: string
          description: Nome do cliente.
          example: Junior
        last_name:
          type: string
          description: Sobrenome do cliente.
          example: Almeida
        email:
          type: string
          description: E-mail válido do cliente.
          example: junior.almeida@email.com
        phone:
          type: string
          description: Telefone com DDD (máximo 11 caracteres).
          example: '51983655100'
        ip:
          type: string
          description: IP de origem do cliente (coletado via Appmax JS).
          example: 127.0.0.1
        document_number:
          type: string
          description: CPF ou CNPJ do cliente.
          example: '25226493029'
        address:
          $ref: '#/components/schemas/CustomerAddress'
        products:
          type: array
          items:
            $ref: '#/components/schemas/Product'
          description: Lista de produtos vinculados ao cliente.
        tracking:
          $ref: '#/components/schemas/CustomerTracking'
        cart_link:
          type: string
          description: |
            URL do carrinho abandonado. Envie este campo para acionar a
            [recuperação de vendas com IA](/guides/recuperacao-vendas-ia)
            (funcionalidade em fase beta).
          example: https://subdomain.domain.com/cart/123-345-678

    CreateCustomerResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            customer:
              type: object
              properties:
                id:
                  type: integer
                  description: ID do cliente criado/atualizado.
                  example: 1

    TrackingCodeRequest:
      type: object
      required: [order_id, shipping_tracking_code]
      properties:
        order_id:
          type: integer
          description: ID do pedido.
          example: 2
        shipping_tracking_code:
          type: string
          description: Código de rastreio do pedido.
          example: EEEASDASDAS1239A

    UpsellRequest:
      type: object
      required: [upsell_hash, products_value, products]
      properties:
        upsell_hash:
          type: string
          description: Hash do pedido para upsell (gerado no pagamento por cartão).
          example: '4000114202503117156088040208561001715608804'
        products_value:
          type: integer
          minimum: 0
          description: Valor total dos produtos do upsell (em **centavos**).
          example: 12300
        products:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Product'

    CreditCardData:
      type: object
      description: Dados do cartão. Use `token` (tokenização prévia) **ou** os dados completos do cartão.
      properties:
        token:
          type: string
          description: Token do cartão (obtido via tokenização).
          example: 422146c7523a46119d6073ea56193913
        number:
          type: string
          description: Número do cartão.
          example: '4444222222222222'
        cvv:
          type: string
          description: Código de segurança (máximo 4 caracteres).
          example: '123'
        expiration_month:
          type: string
          description: Mês de expiração (1 a 12).
          example: '1'
        expiration_year:
          type: string
          description: Ano de expiração (2 a 4 caracteres).
          example: '25'
        holder_document_number:
          type: string
          description: CPF ou CNPJ do titular do cartão.
          example: '19100000000'
        holder_name:
          type: string
          description: Nome do titular do cartão.
          example: Teste teste
        installments:
          type: integer
          minimum: 1
          description: 'Número de parcelas. O teto é o configurado para a loja — 12 por padrão, e maior quando ela tem parcelamento estendido habilitado. No Apple Pay o limite é fixo em 12.'
          example: 1
        soft_descriptor:
          type: string
          description: Texto exibido na fatura do cartão.
          example: MYSTORE

    Subscription:
      type: object
      description: |
        **Pendente de especificação.** Placeholder criado só para destravar a
        resolução de `$ref` no restante da spec — o formato real de
        `payment_data.subscription` ainda não foi documentado. Antes de usar
        em produção, substituir pelos campos reais (ex.: os mesmos de
        `CreateSubscriptionRequest`, sem `order_id`, ou outro formato a
        confirmar com o time responsável).

    CreditCardPaymentRequest:
      type: object
      required: [order_id, customer_id, payment_data]
      properties:
        order_id:
          type: integer
          description: ID do pedido.
          example: 12345
        customer_id:
          type: integer
          description: ID do cliente.
          example: 407
        payment_data:
          type: object
          required: [credit_card]
          properties:
            credit_card:
              $ref: '#/components/schemas/CreditCardData'
            subscription:
              $ref: '#/components/schemas/Subscription'

    PixData:
      type: object
      properties:
        document_number:
          type: string
          description: CPF ou CNPJ do pagador.
          example: '19100000000'
        expiration_date:
          type: string
          description: |
            Opcional. Data/hora de vencimento do Pix, formato `Y-m-d H:i:s`
            no fuso de Brasília (`America/Sao_Paulo`). Precisa ser uma data
            futura. Se omitido, o vencimento segue a configuração da loja.
          example: '2026-09-10 15:00:00'

    PixPaymentRequest:
      type: object
      required: [order_id]
      properties:
        order_id:
          type: integer
          description: ID do pedido.
          example: 113
        payment_data:
          type: object
          properties:
            pix:
              $ref: '#/components/schemas/PixData'
            subscription:
              $ref: '#/components/schemas/Subscription'

    BoletoData:
      type: object
      required: [document_number]
      properties:
        document_number:
          type: string
          description: CPF ou CNPJ do pagador.
          example: '19100000000'

    BoletoPaymentRequest:
      type: object
      required: [order_id, payment_data]
      properties:
        order_id:
          type: integer
          description: ID do pedido.
          example: 113
        payment_data:
          type: object
          required: [boleto]
          properties:
            boleto:
              $ref: '#/components/schemas/BoletoData'

    ApplePayToken:
      type: object
      description: Apple Pay Token devolvido pela PaymentSheet do Safari.
      properties:
        data:
          type: string
        signature:
          type: string
        header:
          type: object
          properties:
            publicKeyHash:
              type: string
            ephemeralPublicKey:
              type: string
            transactionId:
              type: string
        version:
          type: string
          example: EC_v1

    ApplePayMethod:
      type: object
      properties:
        displayName:
          type: string
          example: Visa 3714
        network:
          type: string
          example: Visa
        type:
          type: string
          example: credit

    ApplePayData:
      type: object
      properties:
        installments:
          type: integer
          minimum: 1
          maximum: 12
          description: 'Número de parcelas, de 1 a 12. O teto aqui é fixo — veja o aviso sobre parcelamento estendido na descrição do endpoint.'
          example: 3
        holder_document_number:
          type: string
          example: '22233344450'
        soft_descriptor:
          type: string
          example: EXEMPLOLOJA
        payment_data:
          $ref: '#/components/schemas/ApplePayToken'
        payment_method:
          $ref: '#/components/schemas/ApplePayMethod'
        transaction_identifier:
          type: string
          example: trx....wvu

    ApplePayPaymentRequest:
      type: object
      required: [order_id, customer_id, payment_data]
      properties:
        order_id:
          type: integer
          example: 123
        customer_id:
          type: integer
          example: 456
        payment_data:
          type: object
          required: [apple_pay]
          properties:
            apple_pay:
              $ref: '#/components/schemas/ApplePayData'
    GooglePayData:
      type: object
      required: [installments, holder_document_number, payment_data]
      properties:
        installments:
          type: integer
          minimum: 1
          maximum: 12
          description: 'Número de parcelas, de 1 a 12.'
          example: 2
        holder_document_number:
          type: string
          description: CPF ou CNPJ do portador.
          example: '51425014038'
        soft_descriptor:
          type: string
          maxLength: 13
          description: Texto exibido na fatura, até 13 caracteres.
          example: MINHALOJA
        payment_data:
          $ref: '#/components/schemas/GooglePayPaymentMethodData'
    GooglePayPaymentMethodData:
      type: object
      description: 'O `paymentMethodData` recebido no `onAuthorize` do Appmax JS, integral e sem alteração. Os campos já vêm preenchidos pelo Google.'
      required: [tokenizationData, info]
      properties:
        type:
          type: string
          example: CARD
        description:
          type: string
          example: 'Visa •••• 1234'
        tokenizationData:
          type: object
          required: [type, token]
          properties:
            type:
              type: string
              description: Sempre `PAYMENT_GATEWAY`.
              example: PAYMENT_GATEWAY
            token:
              type: string
              description: 'O token, como string. Não aplicar parse — o parse é feito pela Appmax.'
              example: '{"signature":"MEUCIQ...","protocolVersion":"ECv2","signedMessage":"{...}"}'
        info:
          type: object
          required: [cardNetwork]
          properties:
            cardNetwork:
              type: string
              description: Bandeira devolvida pelo Google.
              example: VISA
            cardDetails:
              type: string
              description: Quatro últimos dígitos, utilizados no comprovante.
              example: '1234'
            assuranceDetails:
              type: object
              description: Dados de verificação do Google. Repassar sem alteração.
              properties:
                accountVerified:
                  type: boolean
                  example: true
                cardHolderAuthenticated:
                  type: boolean
                  example: true
    GooglePayPaymentRequest:
      type: object
      required: [order_id, customer_id, payment_data]
      properties:
        order_id:
          type: integer
          description: Pedido a ser pago, retornado por `POST /v1/orders`.
          example: 3
        customer_id:
          type: integer
          description: Cliente, retornado por `POST /v1/customers`.
          example: 7
        payment_data:
          type: object
          required: [google_pay]
          properties:
            google_pay:
              $ref: '#/components/schemas/GooglePayData'

    TokenizeRequest:
      type: object
      required: [payment_data]
      properties:
        payment_data:
          type: object
          required: [credit_card]
          properties:
            credit_card:
              type: object
              required: [number, cvv, expiration_month, expiration_year, holder_name]
              properties:
                number:
                  type: string
                  description: Número do cartão de crédito.
                  example: '4444222222222222'
                cvv:
                  type: string
                  description: Código de segurança (máximo 4 caracteres).
                  example: '123'
                expiration_month:
                  type: string
                  description: Mês de expiração (1 a 12).
                  example: '12'
                expiration_year:
                  type: string
                  description: Ano de expiração (2 a 4 caracteres).
                  example: '28'
                holder_name:
                  type: string
                  description: Nome do titular do cartão.
                  example: John Doe

    InstallmentsRequest:
      type: object
      required: [installments, total_value]
      properties:
        installments:
          type: integer
          minimum: 1
          maximum: 12
          description: Número de parcelas desejado.
          example: 10
        total_value:
          type: integer
          minimum: 0
          description: Valor total do pedido em **centavos**.
          example: 10000
        settings:
          type: boolean
          description: Quando `true`, retorna as configurações de parcelamento do merchant.
          example: true

    RefundRequest:
      type: object
      required: [order_id, type]
      properties:
        order_id:
          type: integer
          description: ID do pedido.
          example: 1
        type:
          type: string
          enum: [total, partial]
          description: 'Tipo de reembolso. Padrão: `total`.'
          example: total
        value:
          type: integer
          minimum: 0
          description: Valor do reembolso em **centavos**. Obrigatório quando `type = partial`.
          example: 10000

    MerchantSessionRequest:
      type: object
      required: [store_url, external_id]
      properties:
        store_url:
          type: string
          description: >-
            Hostname do domínio do checkout em que o Apple Pay vai rodar —
            **sem** `https://` e **sem** caminho (ex.: `window.location.hostname`
            no browser). Enviar a URL completa faz a Apple recusar a sessão
            com "domainName must be a fully qualified domain name without
            scheme or path".
          example: checkout.minhaloja.com.br
        external_id:
          type: string
          description: UUID da loja gerado durante a instalação.
          example: 37bb0791-ee0b-457d-860c-186e32978bcd

    RecipientTriage:
      type: object
      required: [revenue, storeUrl]
      properties:
        revenue:
          type: integer
          description: Faturamento mensal online em reais (inteiro). `1000` = R$ 1.000,00.
          example: 10000
        storeUrl:
          type: string
          description: URL da página de vendas da loja.
          example: 'https://exemplo-loja.com.br'

    RecipientAccount:
      type: object
      required: [email, name, cpf, phone, dateOfBirth]
      properties:
        email:
          type: string
          description: E-mail do titular da conta.
          example: usuario@exemplo.com
        name:
          type: string
          description: Nome completo do titular.
          example: Nome do Usuário
        cpf:
          type: string
          description: CPF do titular (apenas dígitos).
          example: '00000000000'
        phone:
          type: string
          description: Telefone do titular.
          example: '11900000000'
        dateOfBirth:
          type: string
          description: Data de nascimento (YYYY-MM-DD).
          example: '1990-01-01'

    RecipientCompany:
      type: object
      required:
        [companyName, companyDocumentNumber, companyPostcode, companyAddress,
         companyAddressNumber, companyAddressState, companyAddressNeighborhood,
         companyCity]
      properties:
        companyName:
          type: string
          description: Razão social da empresa.
          example: Empresa Exemplo LTDA
        companyDocumentNumber:
          type: string
          description: CNPJ da empresa — apenas dígitos, 14 posições, sem pontuação.
          example: '00000000000000'
        companyPostcode:
          type: string
          description: CEP do endereço da empresa (apenas dígitos).
          example: '00000000'
        companyAddress:
          type: string
          description: Logradouro (rua, avenida) do endereço da empresa.
          example: Rua Exemplo
        companyAddressNumber:
          type: string
          description: Número do endereço.
          example: '100'
        companyAddressState:
          type: string
          description: Estado (UF) em duas letras.
          example: SP
        companyAddressNeighborhood:
          type: string
          description: Bairro.
          example: Bairro Exemplo
        companyCity:
          type: string
          description: Cidade.
          example: São Paulo
        companyAddressComplement:
          type: string
          description: Complemento do endereço (opcional).
          example: Complemento

    RecipientBankAccount:
      type: object
      description: |
        Conta bancária do recebedor, usada para os saques. Se o objeto for
        enviado, **todos os campos abaixo passam a ser obrigatórios**.
      required: [bank, agency, account, bankAccountType]
      properties:
        bank:
          type: integer
          description: |
            Código do banco (COMPE). Consulte a lista completa de instituições
            homologadas em [Bancos homologados](/guides/bancos-homologados).
          example: 104
        agency:
          type: string
          description: Agência da conta, com dígito quando houver.
          example: '1234-0'
        account:
          type: string
          description: Número da conta, com dígito verificador.
          example: '12345-2'
        bankAccountType:
          type: string
          description: |
            Tipo de conta: `CC` (conta corrente), `CD` (conta digital),
            `PG` (conta de pagamento) ou `PP` (conta poupança). Veja
            [Bancos homologados](/guides/bancos-homologados).
          enum: [CC, CD, PG, PP]
          example: CC

    RecipientConfig:
      type: object
      description: |
        Configurações aplicadas no cadastro do recebedor. Quando o CPF da conta
        já pertence a um usuário da Appmax, o recebedor é vinculado a esse
        usuário: o e-mail de acesso não é enviado e
        `enableAutomaticWithdraw: true` é recusado com `422`.
      properties:
        sendAccountAccess:
          type: boolean
          description: |
            Envia ao recebedor um e-mail para redefinir a senha e acessar a
            plataforma. Enviado como booleano (`true` ou `false`). Não tem efeito
            quando o CPF já pertence a um usuário da Appmax.
          default: false
          example: true
        enableAutomaticWithdraw:
          type: boolean
          description: |
            Ativa o saque automático do recebedor: saque de todo o saldo
            disponível e antecipação do saldo a liberar, todos os dias úteis às
            08:00, na conta de recebimento cadastrada (com chave PIX). Cada
            agendamento só é criado se a empresa do recebedor ainda não tiver um
            do mesmo tipo. Recusado com `422` quando o CPF já pertence a um
            usuário da Appmax.

            Uma vez ativado, o saque automático não pode ser desativado pela API
            nem pelo próprio recebedor. Caso seja necessário desativá-lo, entre
            em contato com a Appmax: a solicitação passa por uma análise
            individual antes de ser efetivada.
          default: false
          example: false

    CreateRecipientRequest:
      type: object
      required: [triage, account, company]
      properties:
        triage:
          $ref: '#/components/schemas/RecipientTriage'
        account:
          $ref: '#/components/schemas/RecipientAccount'
        company:
          $ref: '#/components/schemas/RecipientCompany'
        bankAccount:
          $ref: '#/components/schemas/RecipientBankAccount'
        config:
          $ref: '#/components/schemas/RecipientConfig'

    UpdateRecipientRequest:
      type: object
      description: |
        Todos os blocos e campos são opcionais — envie só o que deseja alterar.
        Campos fora desta lista são ignorados.
      properties:
        account:
          type: object
          description: Dados do titular da conta.
          properties:
            name:
              type: string
              description: Nome completo do titular. Apenas letras (com ou sem acento) e espaços.
              minLength: 3
              maxLength: 255
              example: Nome do Usuário
            phone:
              type: string
              description: |
                Telefone do titular com DDD (10 ou 11 caracteres). Caracteres não
                numéricos são removidos antes de salvar. Não pode estar em uso
                por outro usuário.
              minLength: 10
              maxLength: 11
              example: '11900000000'
            dateOfBirth:
              type: string
              format: date
              description: Data de nascimento (YYYY-MM-DD). O titular precisa ter 18 anos ou mais.
              example: '1990-01-01'
        company:
          type: object
          description: Endereço da empresa.
          properties:
            companyPostcode:
              type: string
              description: CEP do endereço da empresa (apenas dígitos).
              example: '00000000'
            companyAddress:
              type: string
              description: Logradouro (rua, avenida) do endereço da empresa.
              maxLength: 255
              example: Rua Exemplo
            companyAddressNumber:
              type: string
              description: Número do endereço.
              maxLength: 45
              example: '100'
            companyAddressState:
              type: string
              description: Estado (UF) em duas letras.
              maxLength: 120
              example: SP
            companyAddressComplement:
              type: string
              description: Complemento do endereço.
              maxLength: 120
              example: Complemento
            companyAddressNeighborhood:
              type: string
              description: Bairro.
              maxLength: 120
              example: Bairro Exemplo
            companyCity:
              type: string
              description: Cidade.
              maxLength: 80
              example: São Paulo
        bankAccount:
          type: object
          description: |
            Altera a conta bancária **principal** já cadastrada do recebedor.
            Diferente do cadastro, os subcampos são independentes — envie apenas
            os que deseja alterar. **Não pode ser enviado** para recebedores
            lojistas (usuário que já possui lojas na Appmax) — nesse caso a
            requisição retorna `422`.
          properties:
            bank:
              type: string
              description: |
                Código do banco (COMPE), numérico. Precisa ser um banco
                homologado — veja [Bancos homologados](/guides/bancos-homologados).
              example: '104'
            agency:
              type: string
              description: Agência da conta, com dígito quando houver.
              maxLength: 45
              example: '1234-0'
            account:
              type: string
              description: Número da conta, com dígito verificador.
              maxLength: 45
              example: '12345-2'
            bankAccountType:
              type: string
              description: |
                Tipo de conta: `CC` (conta corrente), `CD` (conta digital),
                `PG` (conta de pagamento) ou `PP` (conta poupança).
              enum: [CC, CD, PG, PP]
              example: CC

    CreateRecipientFlexibleRequest:
      type: object
      description: |
        O endpoint realiza o cadastro de um recebedor flexível, 
        recebendo dados de triagem, identificação da conta, 
        informações cadastrais da empresa e dados bancários para vinculação da conta de recebimento.
      required: [triage, account, company]
      properties:
        triage:
          $ref: '#/components/schemas/RecipientTriage'
        account:
          type: object
          description: |
            Dados do titular. Nesta rota o envio é parcial — `name` e `cpf` são
            sempre obrigatórios, e `dateOfBirth` é obrigatório quando o recebedor
            é pessoa física (CPF em `company.companyDocumentNumber`).
          required: [name, cpf]
          properties:
            name:
              type: string
              description: Nome completo do titular.
              example: 'Fulano de Tal'
            cpf:
              type: string
              description: CPF do titular (apenas dígitos).
              example: '11122233344'
            dateOfBirth:
              type: string
              format: date
              pattern: AAAA-MM-DD
              description: |
                Data de nascimento do titular, no formato `AAAA-MM-DD`.
                **Obrigatória para recebedor pessoa física** (CPF em
                `company.companyDocumentNumber`); opcional para pessoa jurídica
                (CNPJ). Precisa ser uma data válida.
              example: '1995-04-20'
        company:
          type: object
          required:
            [companyName, companyDocumentNumber, companyPostcode, companyAddress,
             companyAddressNumber, companyAddressState, companyAddressComplement,
             companyAddressNeighborhood, companyCity]
          properties:
            companyName:
              type: string
              description: Razão social da empresa.
              example: 'Empresa de Testes LTDA'
            companyDocumentNumber:
              type: string
              description: CPF ou CNPJ da empresa — apenas dígitos.
              example: '00000000000191'
            companyPostcode:
              type: string
              description: CEP do endereço da empresa (apenas dígitos).
              example: '00000000'
            companyAddress:
              type: string
              description: Logradouro (rua, avenida) do endereço da empresa.
              example: 'Rua dos Testes'
            companyAddressNumber:
              type: string
              description: Número do endereço.
              example: '123'
            companyAddressState:
              type: string
              description: Estado (UF) em duas letras.
              example: SP
            companyAddressComplement:
              type: string
              description: Complemento do endereço — opcional nesta rota.
              example: 'Bloco A'
            companyAddressNeighborhood:
              type: string
              description: Bairro.
              example: 'Bairro Fictício'
            companyCity:
              type: string
              description: Cidade.
              example: 'Cidade Exemplo'
        bankAccount:
          type: object
          description: |
            Conta bancária do recebedor. Se enviado, todos os campos abaixo
            são obrigatórios. Envie `bankAccount` ou `pixKey` — nunca os dois
            ausentes.
          required: [bank, agency, account, bankAccountType]
          properties:
            bank:
              type: integer
              description: |
                Código do banco (COMPE). Consulte a lista completa em
                [Bancos homologados](/guides/bancos-homologados).
              example: 104
            agency:
              type: string
              description: Agência da conta, com dígito quando houver.
              example: '0001'
            account:
              type: string
              description: Número da conta, com dígito verificador.
              example: '12345-6'
            bankAccountType:
              type: string
              description: Tipo de conta bancária.
              oneOf:
                - const: CC
                  title: Conta Corrente
                - const: CD
                  title: Conta Digital
                - const: PG
                  title: Conta de Pagamento
                - const: PP
                  title: Conta Poupança
              example: CC
        config:
          $ref: '#/components/schemas/RecipientConfig'
        pixKey:
          type: string
          description: |
            Chave Pix do recebedor (CPF, CNPJ, e-mail, telefone ou chave
            aleatória). Obrigatório quando `bankAccount` não é enviado.
          example: 'chave-pix-exemplo'

    FacematchLinkRequest:
      type: object
      required: [phone]
      properties:
        phone:
          type: string
          description: 'Telefone que recebe o SMS (formato com DDD: `11999999999`).'
          example: '11999999999'

    SplitItem:
      type: object
      required: [amount, recipient_hash]
      properties:
        amount:
          type: integer
          minimum: 0
          description: Valor destinado ao recebedor em **centavos**.
          example: 1000
        recipient_hash:
          type: string
          description: Hash do recebedor previamente aprovado.
          example: d9bd0dae-3274-5e5a-939f-f50d867eb652
        refund_charge_percentage:
          type: integer
          minimum: 0
          maximum: 100
          description: |
            Porcentagem que o recebedor deve ser cobrado sobre o valor líquido em caso de estorno do pedido. (0 - 100).
          example: 0

    CreateSplitOrderRequest:
      type: object
      required: [split]
      properties:
        split:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/SplitItem'

    WithdrawRequestBody:
      type: object
      required: [value]
      properties:
        value:
          type: integer
          minimum: 1
          description: Valor do saque em **centavos** (inteiro positivo).
          example: 1000

    WithdrawRequestBankAccount:
      type: object
      description: Conta bancária vinculada à solicitação de saque, no formato retornado pela consulta (não confundir com `RecipientBankAccount`, usado no cadastro do recebedor).
      properties:
        id:
          type: integer
          example: 9
        bank:
          type: string
          description: Código do banco (COMPE), como string.
          example: '66'
        agency:
          type: string
          example: '512'
        account:
          type: string
          example: '9260373'
        name:
          type: string
          nullable: true
        type:
          type: string
          description: 'Tipo da conta, ex: `national_pj`.'
          example: national_pj
        profile:
          type: string
          description: 'Perfil da conta: `CC` (conta corrente), `CD`, `PG` ou `PP`.'
          example: CC
        register_number:
          type: string
          nullable: true
        pix_key:
          type: string
          nullable: true
          example: test@test.com
        bank_account_type:
          type: string
          nullable: true

    GetWithdrawRequestResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            withdraw_request_id:
              type: integer
              example: 69
            status:
              type: string
              description: 'Status traduzido, ex: `pending`, `approved`, `refused`. Veja a lista completa em [Status do split de pagamentos](/guides/split-status).'
              example: pending
            net_value:
              type: integer
              description: Valor líquido do saque, em centavos.
              example: 984
            gross_value:
              type: integer
              description: Valor bruto do saque, em centavos.
              example: 1000
            withdraw_tax:
              type: integer
              description: Taxa de saque aplicada, em centavos.
              example: 0
            currency:
              type: string
              example: BRL
            created_at:
              type: string
              nullable: true
              description: 'Data de criação, formato `Y-m-d H:i:s`.'
              example: '2025-12-30 11:08:04'
            source_type:
              type: string
              description: 'Origem do solicitante: `recipient` ou `user`.'
              example: recipient
            source_id:
              type: string
              description: Hash do recebedor ou ID do usuário solicitante.
              example: 4471b2d1-5e30-5f4d-a64b-c18d72c82a79
            bank_account:
              $ref: '#/components/schemas/WithdrawRequestBankAccount'

    RecipientPaginationMeta:
      type: object
      properties:
        page:
          type: integer
          description: Página atual.
          example: 1
        pages:
          type: integer
          description: Total de páginas. É `0` quando não há resultados.
          example: 1
        total:
          type: integer
          description: Total de itens encontrados.
          example: 2
        limit:
          type: integer
          description: Quantidade máxima de itens por página.
          example: 20

    ListRecipientsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              recipient_hash:
                type: string
                description: Hash do recebedor, usado nas demais rotas de split.
                example: 0a0c3269-b5fa-543b-b9ad-1cf27d0944c1
              status:
                type: string
                description: |
                  Status do onboarding do recebedor. Veja
                  [Status do split de pagamentos](/guides/split-status#status-do-recebedor-recipient).
                enum:
                  - Onboarding completed
                  - Onboarding on verification
                  - Awaiting face match completion
                  - Onboarding rejected
                example: Onboarding completed
              account:
                type: object
                description: Dados da conta (pessoa) do recebedor.
                properties:
                  name:
                    type: string
                    example: Maria Souza
                  email:
                    type: string
                    example: maria.souza@exemplo.com
                  cpf:
                    type: string
                    description: CPF, sem máscara.
                    example: '35873882029'
                  phone:
                    type: string
                    example: '11976187304'
              company:
                type: object
                description: Dados da empresa do recebedor.
                properties:
                  name:
                    type: string
                    example: Souza Comércio LTDA
                  document_number:
                    type: string
                    description: CNPJ, sem máscara. Para empresas de pessoa física, é o CPF.
                    example: '97761238000171'
              created_at:
                type: string
                nullable: true
                description: 'Data de cadastro do recebedor, formato `Y-m-d H:i:s`.'
                example: '2026-10-01 14:32:10'
        meta:
          $ref: '#/components/schemas/RecipientPaginationMeta'

    ListRecipientOrdersResponse:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              order_id:
                type: integer
                description: ID do pedido na Appmax.
                example: 500
              total:
                type: integer
                description: Valor total do pedido, em centavos.
                example: 15000
              liquid_total:
                type: integer
                description: Valor líquido do pedido (total menos as taxas da Appmax), em centavos.
                example: 14230
              recipient_amount:
                type: integer
                description: |
                  Valor destinado ao recebedor no pedido, em centavos. É a soma de
                  todos os splits do recebedor nesse pedido.
                example: 4035
        meta:
          $ref: '#/components/schemas/RecipientPaginationMeta'

    CreatePaymentLinkRequest:
      type: object
      required: [name, value, description, payments, max_installments, fee_transfer_from_installment]
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 255
          description: Nome do link de pagamento.
          example: Teste Name
        value:
          type: integer
          minimum: 500
          description: Valor do link em **centavos**. Mínimo `500`.
          example: 1551
        description:
          type: string
          maxLength: 1000
          description: Descrição do link de pagamento.
          example: Description payment link
        product_type:
          type: string
          enum: [physical, digital]
          default: physical
          description: Tipo do produto.
          example: physical
        document:
          type: string
          nullable: true
          description: |
            CPF ou CNPJ vinculado ao link de pagamento. Quando informado,
            deve ser um documento válido e o merchant precisa possuir
            vínculo com a empresa deste documento.
          example: '19100000000'
        payments:
          type: array
          description: Métodos de pagamento habilitados no link.
          items:
            type: string
            enum: [creditcard, boleto, pix]
          example: [creditcard, boleto, pix]
        max_installments:
          type: integer
          minimum: 1
          maximum: 12
          default: 12
          description: Número máximo de parcelas.
          example: 12
        fee_transfer_from_installment:
          type: integer
          minimum: 0
          maximum: 12
          default: 0
          description: |
            Parcela a partir da qual o custo do parcelamento é
            absorvido/repassado. Com `0`, o custo é absorvido pelo merchant.
          example: 0
        allow_multiple_sales:
            type: boolean
            nullable: true
            default: true
            description: |
              Indica se o link de pagamento permite alterar a 
              quantidade de produtos a ser vendidos no carrinho. Quando
              `true`, permite editar a quantidade; quando
              `false`, é desativado. Por padrão,
              o valor é `true`; caso o dado não seja enviado, o link permanecerá
              com a opção ativa.
            example: true