> ## Documentation Index
> Fetch the complete documentation index at: https://docs.z2pay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Simular a troca de plano

> A mesma conta da troca, sem efeito nenhum — para mostrar o valor antes de o cliente confirmar.

`POST /subscriptions/:id/change-plan/preview`

Faz parte do recurso [Assinaturas](/pt-BR/subscriptions) — o conceito e os dez estados estão lá.

Roda a **mesma conta** de [trocar o plano](/pt-BR/subscriptions/change-plan), sem efeito nenhum: nada é cobrado, agendado ou alterado. O corpo é idêntico ao da troca real.

Existe para uma coisa: a tela onde o cliente escolhe o plano poder mostrar o valor **antes** do botão de confirmar.

## O que volta

`netAmount` é o que sairia de fatura agora, em centavos — o mesmo número que a troca real cobraria. Numa troca que seria agendada para a virada, ele é zero.

`lines` traz as duas pernas do rateio separadas: o crédito pelo que resta do plano atual e o débito do novo. **Cada valor é positivo** — o sinal está em `kind`, que vale `credit` ou `debit`. Some-as com o sinal e você chega no `netAmount`.

`effectiveAt` e `effectiveDate` dizem quando a troca valeria: agora, ou na virada do ciclo. É por aí que a sua tela decide entre *"você será cobrado hoje"* e *"a mudança entra em DD/MM"*.

`isDowngrade` diz se o destino é mais barato que o plano atual.

<Note>
  **A simulação não pode divergir da execução.** As duas usam o mesmo cálculo internamente — se o valor mostrado aqui não fosse o cobrado depois, a tela viraria mentira.
</Note>

## Recusa pelos mesmos motivos da troca real

Destino incompatível, assinatura em estado que não permite, rebaixamento pedido como imediato: tudo responde `409` aqui também, com o mesmo motivo.

É o principal ganho de chamar a simulação: o erro aparece na hora de **escolher** o plano, não na de confirmar.

<Note>
  **É `POST` porque leva corpo, não porque muda algo.** A rota é leitura pura, sem efeito colateral — chamá-la duas vezes com o mesmo corpo devolve o mesmo resultado, e ela não aceita `Idempotency-Key` por não precisar.
</Note>


## OpenAPI

````yaml openapi/billing.json POST /subscriptions/{id}/change-plan/preview
openapi: 3.0.3
info:
  title: Z2Pay Billing API
  version: 1.0.0
  description: Cobrança recorrente da Z2Pay — planos, assinaturas e faturas
servers:
  - url: https://api.sandbox.z2pay.com/v1
    description: Sandbox
  - url: https://api.z2pay.com/v1
    description: Produção
security: []
tags:
  - name: Invoices
    description: Faturas emitidas pelas assinaturas
  - name: Plans
    description: Planos de cobrança e versões de preço
  - name: Subscriptions
    description: Assinaturas recorrentes
paths:
  /subscriptions/{id}/change-plan/preview:
    post:
      tags:
        - Subscriptions
      summary: Simular a troca de plano
      description: >-
        Faz a mesma conta da troca, sem efeito nenhum: nada é cobrado, agendado
        ou alterado.


        Devolve quando a troca valeria, quanto sairia de fatura agora
        (`netAmount`, em centavos) e as duas pernas da proporção em `lines` — o
        crédito pelo que resta do plano atual e o débito do novo, cada um com
        valor positivo e o sinal em `kind`.


        Recusa pelos mesmos motivos da troca real, então o erro aparece na hora
        de escolher o plano, não na de confirmar.
      operationId: SubscriptionController_previewPlanChange
      parameters:
        - name: id
          in: path
          required: true
          description: ID da assinatura
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                planId:
                  type: string
                  minLength: 1
                  description: >-
                    Plano de destino. Ausente ⇒ o destino é avulso e `items` é
                    obrigatório.
                items:
                  type: array
                  items:
                    oneOf:
                      - type: object
                        properties:
                          priceVersionId:
                            type: string
                            minLength: 1
                            description: >-
                              Versão de preço (Price) que dita unitAmount,
                              currency e recurrence.
                          quantity:
                            type: integer
                            minimum: 0
                            exclusiveMinimum: true
                            default: 1
                            description: Quantidade do item (default 1).
                        required:
                          - priceVersionId
                      - type: object
                        properties:
                          description:
                            type: string
                            minLength: 1
                            maxLength: 200
                            description: Descrição do item avulso (inline).
                          unitAmount:
                            type: integer
                            minimum: 0
                            exclusiveMinimum: true
                            description: Valor unitário por ciclo, em centavos.
                          quantity:
                            type: integer
                            minimum: 0
                            exclusiveMinimum: true
                            default: 1
                            description: Quantidade do item (default 1).
                        required:
                          - description
                          - unitAmount
                  description: >-
                    Itens do destino avulso (referência ou inline). Ignorado
                    quando há planId.
                itemOverrides:
                  type: object
                  additionalProperties:
                    type: integer
                    minimum: 0
                    exclusiveMinimum: true
                  description: Override de quantidade por componente do plano destino.
                effectiveAt:
                  type: string
                  enum:
                    - now
                    - period_end
                  description: >-
                    Quando a troca vale. Ausente ⇒ imediata para plano mais
                    caro, na virada para mais barato. Pedir `now` num plano mais
                    barato é recusado.
                prorationBehavior:
                  type: string
                  enum:
                    - create_prorations
                    - none
                  default: create_prorations
                  description: >-
                    `none` troca agora sem cobrar a diferença (cortesia). Só
                    vale com effectiveAt=now.
      responses:
        '200':
          description: Simulação da troca
        '400':
          description: >-
            Requisição inválida — algum parâmetro ou campo do corpo não passou
            na validação
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Identificador estável do erro. É por ele que você deve
                          ramificar, não pela mensagem.
                      message:
                        type: string
                        description: >-
                          Descrição legível. Pode mudar sem aviso e não deve ser
                          usada em condicional.
                      issues:
                        type: array
                        description: >-
                          Um item por campo rejeitado. Nunca vem vazio: se há
                          400 de validação, há pelo menos um.
                        items:
                          type: object
                          properties:
                            path:
                              type: string
                              description: >-
                                Campo que falhou. Vem vazio quando o erro é do
                                corpo como um todo.
                            message:
                              type: string
                              description: O que há de errado com esse campo.
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Validation failed
                  issues:
                    - path: status
                      message: >-
                        Status inválido. Valores aceitos: pending,
                        waiting_payment, paid, refused, canceled, refunded
                    - path: startDate
                      message: >-
                        Data deve ser ISO 8601 com timezone (ex.:
                        2026-06-24T00:00:00Z)
        '401':
          description: Chave de API ausente, malformada ou inválida
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Identificador estável do erro. É por ele que você deve
                          ramificar, não pela mensagem.
                      message:
                        type: string
                        description: >-
                          Descrição legível. Pode mudar sem aviso e não deve ser
                          usada em condicional.
              example:
                error:
                  code: UNAUTHORIZED
                  message: Invalid API key
        '404':
          description: Assinatura ou plano de destino não encontrado
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Identificador estável do erro. É por ele que você deve
                          ramificar, não pela mensagem.
                      message:
                        type: string
                        description: >-
                          Descrição legível. Pode mudar sem aviso e não deve ser
                          usada em condicional.
              example:
                error:
                  code: NOT_FOUND
                  message: Resource not found
        '409':
          description: A troca real seria recusada — o motivo vem no corpo do erro
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Identificador estável do erro. É por ele que você deve
                          ramificar, não pela mensagem.
                      message:
                        type: string
                        description: >-
                          Descrição legível. Pode mudar sem aviso e não deve ser
                          usada em condicional.
              example:
                error:
                  code: CONFLICT
                  message: No refundable payment found
      security:
        - apiKey: []
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: API Key da Credential (gerada no Backoffice)

````