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

# Upgrade a paid plan

> Start an idempotent paid plan upgrade for an existing subscription.

Starts an upgrade for a customer with an active Stripe-backed monthly subscription. Send `customerId` and the target `planId`, plus a client-generated `Idempotency-Key` header. The target must be an active paid plan with a synced Stripe Price and a **higher monthly price** than the customer's current plan.

The Stripe connection must have a stored signed webhook endpoint that is enabled and correctly configured in Stripe. Ledger returns `502 stripe_not_configured` when the setup is incomplete, or `502 stripe_webhook_unhealthy` when the endpoint cannot be verified. Both stop before changing the subscription or creating an invoice.

The response reports an `upgradeId`, a status (`applied`, `pending_payment`, `processing`, or `closed`), and a nullable `paymentUrl`. If `paymentUrl` is present, send the customer to Stripe's hosted invoice page. Ledger applies the new plan and its credits after Stripe confirms payment; `processing` means the result is still being reconciled. A verified upgrade starts a new full-price monthly billing cycle. Ledger expires the previous plan's unspent allowance and grants the new plan's allowance; separately purchased credit-pack credits remain available. If payment fails or expires, the existing plan and allowance remain in place.

Reuse the same idempotency key when retrying this intended upgrade. Reusing it for a different target returns `409 idempotency_key_mismatch`; starting another upgrade while one is pending returns `409 upgrade_pending`. An ineligible subscription, unsynced or non-higher target, or incompatible Stripe billing state returns `400`. If Stripe reconciliation needs another attempt (`503`) or a Stripe request fails (`502`), retry with the **same** key rather than starting a second upgrade.

See [Billing and Checkout](https://docs.integrateledger.com/billing.md) for payment timing and [Customers](https://docs.integrateledger.com/customers.md) for plan attachment. The API key selects the environment; live upgrades require live activation and Ledger's live-charge gate.


## OpenAPI

````yaml openapi.json POST /v1/billing/upgrade
openapi: 3.1.0
info:
  title: Ledger API
  version: 0.1.0
servers:
  - url: https://api.integrateledger.com
    description: Hosted Ledger API
security: []
paths:
  /v1/billing/upgrade:
    post:
      tags:
        - Billing
      summary: Upgrade a paid plan
      operationId: upgradePaidPlan
      parameters:
        - schema:
            type: string
            minLength: 1
            maxLength: 255
          required: true
          name: idempotency-key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpgradeInput'
      responses:
        '200':
          description: Current upgrade outcome
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpgradeResponse'
        '400':
          description: Invalid or ineligible upgrade
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpgradeError'
        '401':
          description: Invalid Ledger API key
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '404':
          description: Unknown customer or target plan
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '409':
          description: Idempotency key mismatch or pending upgrade
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '502':
          description: Stripe request failed or signed webhook endpoint unhealthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                required:
                  - error
        '503':
          description: Upgrade requires Stripe reconciliation or payment action
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  upgradeId:
                    type: integer
                required:
                  - error
      security:
        - ApiKey: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl --request POST
            'https://api.integrateledger.com/v1/billing/upgrade' \
              -H "Authorization: Bearer $LEDGER_API_KEY" \
              -H 'Idempotency-Key: upgrade_123' \
              -H 'Content-Type: application/json' \
              --data '{"customerId":"user_123","planId":"pro"}'
        - lang: python
          label: Python SDK (source only)
          source: >-
            import os

            from ledger_sdk import Ledger


            ledger = Ledger(api_key=os.environ["LEDGER_API_KEY"])

            ledger.upgrade_paid_plan(customer_id="user_123", plan_id="pro",
            idempotency_key="upgrade_123")
        - lang: typescript
          label: TypeScript SDK (source only)
          source: >-
            import { Ledger } from "@ledger/sdk";


            const ledger = new Ledger({ apiKey: process.env.LEDGER_API_KEY ?? ""
            });

            await ledger.upgradePaidPlan({ idempotencyKey: "upgrade_123", body:
            { customerId: "user_123", planId: "pro" } });
components:
  schemas:
    UpgradeInput:
      type: object
      properties:
        customerId:
          type: string
          minLength: 1
          maxLength: 255
        planId:
          type: string
          minLength: 1
          maxLength: 255
      required:
        - customerId
        - planId
      additionalProperties: false
    UpgradeResponse:
      type: object
      properties:
        upgradeId:
          type: integer
        status:
          type: string
          enum:
            - applied
            - pending_payment
            - processing
            - closed
        paymentUrl:
          type: string
          nullable: true
          format: uri
      required:
        - upgradeId
        - status
        - paymentUrl
    UpgradeError:
      type: object
      properties:
        error:
          type: string
      required:
        - error
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.