openapi: 3.0.3
info:
  title: BXCodex Gateway API
  version: 1.0.0
  description: |
    API oficial da BXCodex Gateway.

    A V1 oferece pagamentos PIX, clientes, saldo, chave PIX para saques,
    saques, API Keys, webhooks e health check.

    Valores monetários são enviados em centavos de BRL.
    Exemplo: 10000 = R$ 100,00.

    A taxa da BXCodex é configurada internamente por usuário e não pode ser
    informada ou alterada pelo consumidor da API durante a criação de um pagamento.

servers:
  - url: https://bxcodex.com/v1
    description: Produção

tags:
  - name: API Keys
    description: Gerenciamento das chaves de API.
  - name: Customers
    description: Clientes finais das lojas integradas.
  - name: Payments
    description: Pagamentos PIX.
  - name: Balance
    description: Saldo do usuário da BXCodex.
  - name: PIX Key
    description: Chave PIX utilizada para receber saques.
  - name: Withdrawals
    description: Solicitações de saque.
  - name: Health
    description: Status da API.

security:
  - bearerAuth: []

paths:

  /api-keys:
    post:
      tags: [API Keys]
      summary: Criar uma API Key
      description: |
        Cria uma nova API Key para o usuário autenticado no painel da BXCodex.
        A chave secreta completa deve ser exibida somente no momento da criação.
        A autenticação desta operação é feita pelo painel da BXCodex, e não por
        uma API Key previamente criada.
      security: []
      x-bxcodex-auth: dashboard
      operationId: createApiKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
      responses:
        '201':
          description: API Key criada com sucesso.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreated'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

    get:
      tags: [API Keys]
      summary: Listar API Keys
      operationId: listApiKeys
      responses:
        '200':
          description: Lista de API Keys.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api-keys/{key_id}:
    delete:
      tags: [API Keys]
      summary: Revogar uma API Key
      operationId: revokeApiKey
      parameters:
        - $ref: '#/components/parameters/KeyId'
      responses:
        '204':
          description: API Key revogada com sucesso.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /customers:
    post:
      tags: [Customers]
      summary: Criar cliente
      operationId: createCustomer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCustomerRequest'
      responses:
        '201':
          description: Cliente criado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      tags: [Customers]
      summary: Listar clientes
      operationId: listCustomers
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: email
          in: query
          schema:
            type: string
            format: email
        - name: document
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Lista paginada de clientes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /customers/{customer_id}:
    get:
      tags: [Customers]
      summary: Consultar cliente
      operationId: getCustomer
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      responses:
        '200':
          description: Cliente encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      tags: [Customers]
      summary: Atualizar cliente
      operationId: updateCustomer
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateCustomerRequest'
      responses:
        '200':
          description: Cliente atualizado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Customer'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      tags: [Customers]
      summary: Excluir cliente
      operationId: deleteCustomer
      parameters:
        - $ref: '#/components/parameters/CustomerId'
      responses:
        '204':
          description: Cliente excluído.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /payments:
    post:
      tags: [Payments]
      summary: Criar pagamento PIX
      description: |
        Cria um pagamento PIX para o cliente.

        O valor deve ser informado em centavos de BRL.
        A taxa da BXCodex é aplicada internamente conforme a configuração
        comercial do usuário e não deve ser enviada nesta requisição.

        Recomenda-se o uso do header Idempotency-Key para evitar a criação
        duplicada de pagamentos.
      operationId: createPayment
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
      responses:
        '201':
          description: Pagamento PIX criado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      tags: [Payments]
      summary: Listar pagamentos
      operationId: listPayments
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/PaymentStatus'
        - name: customer_id
          in: query
          schema:
            type: string
        - name: created_from
          in: query
          schema:
            type: string
            format: date-time
        - name: created_to
          in: query
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Lista paginada de pagamentos.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /payments/{payment_id}:
    get:
      tags: [Payments]
      summary: Consultar pagamento
      operationId: getPayment
      parameters:
        - $ref: '#/components/parameters/PaymentId'
      responses:
        '200':
          description: Pagamento encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /payments/{payment_id}/cancel:
    post:
      tags: [Payments]
      summary: Cancelar pagamento pendente
      description: |
        Cancela um pagamento que ainda está pendente.
        Esta operação não representa estorno ou refund de um pagamento já aprovado.
      operationId: cancelPayment
      parameters:
        - $ref: '#/components/parameters/PaymentId'
      responses:
        '200':
          description: Pagamento cancelado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Pagamento não pode ser cancelado no estado atual.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /balance:
    get:
      tags: [Balance]
      summary: Consultar saldo
      operationId: getBalance
      responses:
        '200':
          description: Saldo atual do usuário.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /pix-key:
    post:
      tags: [PIX Key]
      summary: Cadastrar chave PIX
      description: |
        Cadastra a chave PIX utilizada para os saques do usuário.
        Cada usuário possui uma única chave PIX cadastrada.
      operationId: createPixKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertPixKeyRequest'
      responses:
        '201':
          description: Chave PIX cadastrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixKey'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Usuário já possui uma chave PIX cadastrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    get:
      tags: [PIX Key]
      summary: Consultar chave PIX
      operationId: getPixKey
      responses:
        '200':
          description: Chave PIX cadastrada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    put:
      tags: [PIX Key]
      summary: Alterar chave PIX
      description: |
        Substitui a chave PIX cadastrada. Recomenda-se exigir confirmação
        adicional pelo painel da BXCodex antes de efetivar a alteração.
      operationId: updatePixKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertPixKeyRequest'
      responses:
        '200':
          description: Chave PIX atualizada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PixKey'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /withdrawals:
    post:
      tags: [Withdrawals]
      summary: Solicitar saque
      description: |
        Solicita o saque de um valor do saldo disponível para a única chave
        PIX cadastrada pelo usuário.

        O valor deve ser informado em centavos de BRL.
      operationId: createWithdrawal
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWithdrawalRequest'
      responses:
        '201':
          description: Saque criado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Withdrawal'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Saldo insuficiente, chave PIX ausente ou operação duplicada.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    get:
      tags: [Withdrawals]
      summary: Listar saques
      operationId: listWithdrawals
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/WithdrawalStatus'
      responses:
        '200':
          description: Lista paginada de saques.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /withdrawals/{withdrawal_id}:
    get:
      tags: [Withdrawals]
      summary: Consultar saque
      operationId: getWithdrawal
      parameters:
        - $ref: '#/components/parameters/WithdrawalId'
      responses:
        '200':
          description: Saque encontrado.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Withdrawal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /health:
    get:
      tags: [Health]
      summary: Verificar disponibilidade da API
      security: []
      operationId: healthCheck
      responses:
        '200':
          description: API operacional.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'

components:

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Secret Key / API Key
      description: |
        Informe sua Chave Privada (Secret Key) gerada no painel BXCodex:
        Authorization: Bearer <SUA_CHAVE_PRIVADA>
        Exemplo: 106fcdfc00b0fa6f3530df50068c20fbf956f790

  parameters:
    KeyId:
      name: key_id
      in: path
      required: true
      schema:
        type: string
      description: Identificador da API Key.

    CustomerId:
      name: customer_id
      in: path
      required: true
      schema:
        type: string
      description: Identificador do cliente.

    PaymentId:
      name: payment_id
      in: path
      required: true
      schema:
        type: string
      description: Identificador do pagamento.

    WithdrawalId:
      name: withdrawal_id
      in: path
      required: true
      schema:
        type: string
      description: Identificador do saque.

    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: |
        Chave única utilizada para garantir idempotência da operação.
        Recomendada e, para operações financeiras, idealmente obrigatória
        na implementação do backend.

  responses:
    BadRequest:
      description: Requisição inválida.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    Unauthorized:
      description: API Key ausente, inválida ou revogada.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    NotFound:
      description: Recurso não encontrado.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    Conflict:
      description: Conflito com o estado atual ou recurso existente.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:

    CreateApiKeyRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          example: Loja Principal

    ApiKeyCreated:
      type: object
      required:
        - id
        - name
        - key
        - created_at
      properties:
        id:
          type: string
          example: key_123456
        name:
          type: string
          example: Loja Principal
        key:
          type: string
          description: Chave secreta / código privado de acesso. Exibida integralmente somente na criação.
          example: 106fcdfc00b0fa6f3530df50068c20fbf956f790
        created_at:
          type: string
          format: date-time

    ApiKey:
      type: object
      required:
        - id
        - name
        - prefix
        - status
        - created_at
      properties:
        id:
          type: string
          example: key_123456
        name:
          type: string
          example: Loja Principal
        prefix:
          type: string
          description: Primeiros dígitos da chave para identificação visual.
          example: 106fcdfc
        status:
          type: string
          enum: [active, revoked]
          example: active
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
          nullable: true

    ApiKeyListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ApiKey'

    CreateCustomerRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 150
          example: João da Silva
        email:
          type: string
          format: email
          example: joao@email.com
        document:
          type: string
          description: CPF ou CNPJ do cliente.
          example: '12345678900'

    UpdateCustomerRequest:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 150
        email:
          type: string
          format: email
        document:
          type: string

    Customer:
      type: object
      required:
        - id
        - name
        - created_at
        - updated_at
      properties:
        id:
          type: string
          example: cus_123456
        name:
          type: string
          example: João da Silva
        email:
          type: string
          format: email
          nullable: true
          example: joao@email.com
        document:
          type: string
          nullable: true
          example: '12345678900'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CustomerListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Customer'
        pagination:
          $ref: '#/components/schemas/Pagination'

    CreatePaymentRequest:
      type: object
      required:
        - amount
        - description
        - customer_id
      properties:
        amount:
          type: integer
          minimum: 1
          description: Valor em centavos de BRL. 10000 = R$ 100,00.
          example: 10000
        description:
          type: string
          minLength: 1
          maxLength: 255
          example: Pedido #123
        customer_id:
          type: string
          example: cus_123456
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: Data/hora de expiração do PIX.

    Payment:
      type: object
      required:
        - id
        - status
        - amount
        - currency
        - payment_method
        - customer_id
        - pix
        - created_at
      properties:
        id:
          type: string
          example: pay_123456
        status:
          $ref: '#/components/schemas/PaymentStatus'
        amount:
          type: integer
          example: 10000
        currency:
          type: string
          enum: [BRL]
          example: BRL
        payment_method:
          type: string
          enum: [pix]
          example: pix
        description:
          type: string
          example: Pedido #123
        customer_id:
          type: string
          example: cus_123456
        fee_percentage:
          type: number
          format: double
          description: Percentual da taxa BXCodex aplicado ao pagamento.
          example: 5.0
        fee_amount:
          type: integer
          description: Taxa BXCodex em centavos.
          example: 500
        net_amount:
          type: integer
          description: Valor líquido destinado ao saldo do usuário, em centavos.
          example: 9500
        pix:
          $ref: '#/components/schemas/PixPaymentData'
        created_at:
          type: string
          format: date-time
        approved_at:
          type: string
          format: date-time
          nullable: true
        cancelled_at:
          type: string
          format: date-time
          nullable: true

    PixPaymentData:
      type: object
      required:
        - qr_code
        - copy_paste
      properties:
        qr_code:
          type: string
          description: Conteúdo utilizado para renderizar o QR Code PIX.
          example: https://bxcodex.com/qr/pay_123456
        copy_paste:
          type: string
          description: Código PIX copia e cola.
          example: '00020101021226880014br.gov.bcb.pix...'

    PaymentListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Payment'
        pagination:
          $ref: '#/components/schemas/Pagination'

    PaymentStatus:
      type: string
      enum:
        - pending
        - approved
        - failed
        - cancelled
      example: pending

    Balance:
      type: object
      required:
        - available
        - pending
        - currency
      properties:
        available:
          type: integer
          description: Saldo disponível para saque, em centavos.
          example: 95000
        pending:
          type: integer
          description: Saldo pendente, em centavos.
          example: 20000
        currency:
          type: string
          enum: [BRL]
          example: BRL

    UpsertPixKeyRequest:
      type: object
      required:
        - type
        - key
      properties:
        type:
          $ref: '#/components/schemas/PixKeyType'
        key:
          type: string
          minLength: 1
          maxLength: 255
          example: '12345678900'

    PixKey:
      type: object
      required:
        - type
        - key
        - created_at
        - updated_at
      properties:
        type:
          $ref: '#/components/schemas/PixKeyType'
        key:
          type: string
          description: Chave PIX. A implementação pode mascarar parte do valor em respostas.
          example: '12345678900'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    PixKeyType:
      type: string
      enum:
        - cpf
        - cnpj
        - email
        - phone
        - random
      example: cpf

    CreateWithdrawalRequest:
      type: object
      required:
        - amount
      properties:
        amount:
          type: integer
          minimum: 1
          description: Valor do saque em centavos de BRL.
          example: 50000

    Withdrawal:
      type: object
      required:
        - id
        - amount
        - status
        - pix_key_type
        - created_at
      properties:
        id:
          type: string
          example: wd_123456
        amount:
          type: integer
          description: Valor do saque em centavos.
          example: 50000
        status:
          $ref: '#/components/schemas/WithdrawalStatus'
        pix_key_type:
          $ref: '#/components/schemas/PixKeyType'
        pix_key:
          type: string
          description: Chave utilizada no saque. Recomenda-se mascarar este campo nas respostas.
          example: '***78900'
        created_at:
          type: string
          format: date-time
        processing_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        failed_at:
          type: string
          format: date-time
          nullable: true
        failure_reason:
          type: string
          nullable: true

    WithdrawalListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Withdrawal'
        pagination:
          $ref: '#/components/schemas/Pagination'

    WithdrawalStatus:
      type: string
      enum:
        - pending
        - processing
        - completed
        - failed
        - cancelled
      example: pending

    Pagination:
      type: object
      required:
        - page
        - limit
        - total
        - total_pages
      properties:
        page:
          type: integer
          example: 1
        limit:
          type: integer
          example: 20
        total:
          type: integer
          example: 100
        total_pages:
          type: integer
          example: 5

    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: invalid_request
            message:
              type: string
              example: O campo amount é obrigatório.
            details:
              type: object
              additionalProperties: true

    HealthResponse:
      type: object
      required:
        - status
        - service
        - version
      properties:
        status:
          type: string
          enum: [ok]
          example: ok
        service:
          type: string
          example: bxcodex-gateway
        version:
          type: string
          example: 1.0.0

    PaymentWebhookEvent:
      type: object
      required:
        - id
        - event
        - created_at
        - data
      properties:
        id:
          type: string
          example: evt_123456
        event:
          type: string
          enum:
            - payment.created
            - payment.approved
            - payment.failed
            - payment.cancelled
          example: payment.approved
        created_at:
          type: string
          format: date-time
        data:
          $ref: '#/components/schemas/Payment'

    WithdrawalWebhookEvent:
      type: object
      required:
        - id
        - event
        - created_at
        - data
      properties:
        id:
          type: string
          example: evt_789012
        event:
          type: string
          enum:
            - withdrawal.created
            - withdrawal.processing
            - withdrawal.completed
            - withdrawal.failed
          example: withdrawal.completed
        created_at:
          type: string
          format: date-time
        data:
          $ref: '#/components/schemas/Withdrawal'


x-bxcodex-webhooks:
  description: |
    Webhooks enviados pela BXCodex para a URL configurada pelo usuário.
    A URL de destino é cadastrada/configurada no painel da BXCodex.

    O endpoint receptor deve responder com HTTP 2xx para confirmar o recebimento.
    Eventos com falha de entrega podem ser reenviados conforme a política de retry
    implementada pela BXCodex.

    Recomenda-se assinar o corpo do webhook com um segredo compartilhado e enviar
    a assinatura em um header, por exemplo X-BXCodex-Signature.

  events:
    payment.created:
      payload:
        $ref: '#/components/schemas/PaymentWebhookEvent'
    payment.approved:
      payload:
        $ref: '#/components/schemas/PaymentWebhookEvent'
    payment.failed:
      payload:
        $ref: '#/components/schemas/PaymentWebhookEvent'
    payment.cancelled:
      payload:
        $ref: '#/components/schemas/PaymentWebhookEvent'
    withdrawal.created:
      payload:
        $ref: '#/components/schemas/WithdrawalWebhookEvent'
    withdrawal.processing:
      payload:
        $ref: '#/components/schemas/WithdrawalWebhookEvent'
    withdrawal.completed:
      payload:
        $ref: '#/components/schemas/WithdrawalWebhookEvent'
    withdrawal.failed:
      payload:
        $ref: '#/components/schemas/WithdrawalWebhookEvent'
