internal

Cancel subscription

Changed on

In-app cancel modal endpoint. Sets Stripe cancel_at to the current period end (via Stripe's MaxPeriodEnd sentinel), stashes the canonical Scanopy reason in subscription metadata, returns the period end so the modal can render the retention disclosure.

post/api/billing/cancel

Request

  • Base URL
    https://app.scanopy.net
  • URL
    https://app.scanopy.net/api/billing/cancel
  • Auth
    • API key in header Authorization
    • or API key in cookie session_id

Request body

commentstring nullable

Free-text detail the customer added to their cancellation reason.

reason_code'too_expensive' | 'missing_features' | 'switched_service' | 'unused' | 'customer_service' | 'low_quality' | 'too_complex' | 'other' required

Cancellation reason captured in SubscriptionCancelled / CancellationInitiated events. Mirrors the values surfaced in the in-app cancel flow (Phase 5).

save_offer_redeemed'pause' | 'discount' | 'downgrade'

Save-offer choices presented during in-app cancellation (Phase 5).

save_offer_shownSaveOffer[]

Whether the retention discount was offered during this flow.

Response

Cancellation initiated

errorstring nullable

Not sent on a successful response. Failure messages arrive in an ApiErrorResponse.

successboolean required

Always true on a successful response.

Example response

{
  "meta": {
    "api_version": 1,
    "server_version": "0.19.4"
  }
}

Changes