---
title: "Cancel the plan"
method: POST
path: "/billing/subscription/cancel"
tags: ["Billing"]
---

# Cancel the plan

`POST /billing/subscription/cancel`

## Request

- Base URL: `https://api.nippy.host`
- URL: `https://api.nippy.host/billing/subscription/cancel`
- Auth: `bearer` (a scheme the document does not define)

A paid subscription is cancelled at period end and keeps access until then. A free trial ends immediately: the account returns to Free at once, sites over the Free cap go offline, and no charge is made. The trial cannot be restarted.

## Response `200`

- APISubscriptionView
  - `plan` 'free' | 'basic' | 'pro' | 'max', required
  - `status` 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete' | 'incomplete_expired' | 'unpaid' | 'paused', required: Stripe-mirrored status (free tier is always `active`)
  - `seats` number, required: Seats billed this period (item quantity)
  - `interval` 'month' | 'year', required: Current billing cadence (monthly/annual)
  - `currency` 'gbp' | 'usd' | 'eur' | 'chf', required: Presentment currency of the paid subscription (locked at creation). Drives price display; cosmetic on the free tier.
  - `perSeatPence` number, required: Per-seat recurring charge in `currency` minor units at the subscription's cadence — what the account actually pays. Can differ from the catalog figure when the subscription is grandfathered on an older pricing version. 0 on the free tier.
  - `scheduledInterval` 'month' | 'year', nullable, required: A cadence queued to apply at the next renewal (annual→monthly), else null
  - `scheduledPlan` 'free' | 'basic' | 'pro' | 'max', nullable, required: A lower tier queued to apply at the next renewal (downgrade), else null
  - `currentPeriodEnd` string, date-time, nullable, required
  - `cancelAtPeriodEnd` boolean, required: Whether the subscription ends at period close
  - `trialModalPending` boolean, required: Whether the one-time upgrade offer is still unanswered on this account (ADR 0053; named for the trial it offered until ADR 0063). Only says it has not been answered — whether it is *due* (a free personal account with a site, viewed by its owner) is the client’s call.
  - `accountCreatedAt` string, date-time, required: When the account was created. The one-time upgrade offer reads it to tell a first session from a return visit (ADR 0053, amendment 1) — a fact from the server, because a browser-local flag fails on a second device.
  - `duplicateHold` boolean, required: True while the account is on a duplicate-account hold (ADR 0057): its live sites are offline and it cannot publish or start a plan until its owner answers the request at `/duplicate-accounts`.
  - `limits` APIPlanLimits, required
    - `sites` number, nullable, required: Max sites on the account (a paused site still holds its slot); null means unlimited
    - `storageBytes` number, nullable, required: Max total stored bytes; null means unlimited
    - `uploadBytes` number, nullable, required: Per-upload byte ceiling; null means unlimited
    - `previews` number, nullable, required: Max simultaneously active previews (pending + live); null means unlimited
  - `features` APIPlanFeatures, required
    - `passwordProtect` boolean, required
    - `inBrowserEditing` boolean, required
    - `showBanner` boolean, required
    - `canInviteMembers` boolean, required
    - `customDomains` boolean, required: Attach custom domains to sites
    - `analytics` boolean, required: Access the visitor-analytics dashboard
    - `optOutOfSearch` boolean, required: Control search-engine indexing of the site
    - `overridePageMeta` boolean, required: Override the page title / meta description
    - `overrideDownloads` boolean, required: Control the directory-listing download buttons
    - `qrCodes` boolean, required: Generate downloadable QR codes for a site's public URL
    - `formReplies` boolean, required: Read the contents of captured form replies. Capture, storage and the reply counts are ungated — every plan collects and nothing is dropped — so this flag decides only whether a reply can be read.
  - `usage` APIQuotaUsage, required
    - `sites` number, required: Sites holding a plan slot (every state except pending)
    - `liveSites` number, required
    - `storageBytes` number, required
    - `customDomains` number, required
    - `seats` number, required
    - `activePreviews` number, required: Previews occupying the active-preview cap (pending + live)
    - `billingSuspendedSites` number, required: Sites offline on a billing hold — more live sites than the plan allows. They come back when there is room.

## Other responses

- `403`: Only the account owner can manage billing

---

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