---
title: "Change Plan"
method: POST
path: "/v1/billing/change-plan"
tags: ["billing"]
---

# Change Plan

`POST /v1/billing/change-plan`

## Request

- Base URL: `https://api.elaan.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.elaan.io/v1/billing/change-plan`
- Auth: none declared

The paid→paid path (Pro↔Business, monthly↔annual): Paddle swaps the
existing subscription's price with immediate proration — never a second
checkout. 202 because the local plan flips only when the resulting
`subscription.updated` webhook lands; the console polls, like checkout.

## Headers

- `authorization` string, nullable

## Request body

- ChangePlanRequest: Move an existing paid subscription to this tier/period. The fields *are* the domain enums, so an unknown value is a clean 422 listing the permitted ones — the same way the other modules type their enum fields.
  - `plan` 'free' | 'pro' | 'business' | 'scale', required: A subscription tier. The natural identity of a plan is its slug; the price lives in Paddle (per price id), and the *limits* live in this module's `PLAN_LIMITS` so a pricing change is a one-line edit, not a data migration.
  - `period` 'monthly' | 'annual', required: How often a paid tier renews. Mirrors the pricing page's monthly/annual toggle; each (plan, period) pair maps to one Paddle price id.

## Response `202`

Successful Response

- object

## Other responses

- `409`: No paid subscription to change, or already on that tier
- `422`: Unknown plan/period, or no price configured for it

---

[API](https://skmtc.dev/elaan/apis/elaan-api.md) · [All operations](https://skmtc.dev/elaan/apis/elaan-api/llms.txt) · [OpenAPI document](https://skmtc.dev/elaan/apis/elaan-api/revisions/c96a7e6399aa?raw)
