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

# Cancel Subscription

> Cancel a subscription with the secret key. at_period_end=true (default, also when omitted, null or empty) schedules the end at current_period_end — the customer keeps the paid period and you can undo it with resume; at_period_end=false ends it now, voids unpaid Stripe invoices, no refund. Returns the Subscription. Errors: 400 non-boolean, 409 already canceled, 502 Stripe refused, 503 Stripe unreachable

## Overview

Stops the renewals of an `active` or `past_due` subscription. By default the subscription ends at the end of the period the customer already paid for; pass `at_period_end: false` to end it immediately — this also works on a subscription already scheduled to end.

| | `at_period_end: true` (default) | `at_period_end: false` |
| - | - | - |
| Further charges | None after `current_period_end` | None, from now — unpaid Stripe invoices of the subscription are voided, so no retry can charge the customer |
| `status` right after the call | Unchanged (`active` or `past_due`), with `cancel_at_period_end: true` | `canceled` |
| Can be undone | Yes, with [Resume Subscription](/api/endpoints/resume-subscription) until the period ends | No |
| `cancel_requested_by` | `api` | unchanged |
| `subscription_canceled` webhook | When the period ends | Right away |
| Refund of the current period | — | None (no automatic refund) |

<Tip>
  Customers can cancel by themselves (at period end only) from the `management_url` of their subscription — you don't need to build a cancellation flow. See [Let customers manage their subscription](/guides/subscriptions#let-customers-manage-their-subscription).
</Tip>

## Authentication

This endpoint requires your **Secret API Key** (server-side only). See [Authentication](/api/authentication) for details.

```bash theme={null}
Authorization: Bearer sk_live_xxxxxxxxxxxx
```

## Path Parameters

<ParamField path="subscription_id" type="string" required>
  The Payviox subscription ID (`psub_...`).
</ParamField>

## Body Parameters

<ParamField body="at_period_end" type="boolean" default="true">
  `true` to end the subscription at `current_period_end`, `false` to end it now. Omitted, `null` or `""` mean `true`; any other non-boolean value returns `400`.
</ParamField>

## Response

The updated [Subscription object](/api/endpoints/get-subscription#the-subscription-object). Calling it again with `at_period_end: true` on a subscription already scheduled to end returns `200` without changing anything (and keeps its `cancel_requested_by`). You can still end immediately a subscription the customer scheduled to cancel.

## Example Requests

<CodeGroup>
  ```bash cURL (at period end) theme={null}
  curl https://api.payviox.com/subscriptions/psub_8fKx2LmQp4ZrT7vW1nYc3aBd/cancel \
    -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
    -X POST
  ```

  ```bash cURL (immediately) theme={null}
  curl https://api.payviox.com/subscriptions/psub_8fKx2LmQp4ZrT7vW1nYc3aBd/cancel \
    -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -X POST \
    -d '{ "at_period_end": false }'
  ```

  ```javascript Node.js theme={null}
  async function cancelSubscription(subscriptionId, { now = false } = {}) {
    const response = await fetch(
      `https://api.payviox.com/subscriptions/${subscriptionId}/cancel`,
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.PAYVIOX_SECRET_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ at_period_end: !now }),
      }
    );

    if (response.status === 503) {
      throw new Error('Stripe unreachable, retry later'); // safe to retry
    }
    if (!response.ok) {
      const { error, message } = await response.json();
      throw new Error(`${error}: ${message}`);
    }
    return response.json(); // Subscription
  }
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      'https://api.payviox.com/subscriptions/psub_8fKx2LmQp4ZrT7vW1nYc3aBd/cancel',
      headers={'Authorization': f"Bearer {os.environ['PAYVIOX_SECRET_KEY']}"},
      json={'at_period_end': True}
  )

  subscription = response.json()
  print(subscription['cancel_at_period_end'])  # True
  ```
</CodeGroup>

## Example Response

<ResponseExample>
  ```json Scheduled (200) theme={null}
  {
    "id": "psub_8fKx2LmQp4ZrT7vW1nYc3aBd",
    "status": "active",
    "amount": 999,
    "unit_amount": 1029,
    "currency": "USD",
    "interval": "month",
    "interval_count": 1,
    "interval_label": "every month",
    "customer": "customer_abc123",
    "customer_email": "john@example.com",
    "order_id": "order_12345",
    "items": [
      { "name": "Pro plan", "quantity": 1, "price": 999 }
    ],
    "metadata": { "user_id": "usr_123" },
    "payment_method": "credit",
    "provider": "stripe",
    "provider_subscription_id": "sub_1QxYz2AbCdEfGhIj",
    "cycles_count": 2,
    "current_period_end": "2026-12-13T10:30:00+00:00",
    "cancel_at_period_end": true,
    "canceled_at": null,
    "canceled_by": null,
    "cancel_requested_by": "api",
    "cancellation_reason": "requested_by_merchant",
    "initial_session_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "management_url": "https://secure.payviox.com/subscription/k3Jd9QwL2xVb7NmP4tRz8YcF1sHg6UaE5oWi0qXn3BvM7ZpD",
    "created_at": "2026-10-13T10:30:00+00:00"
  }
  ```

  ```json Already Canceled (409) theme={null}
  {
    "error": "subscription_update_failed",
    "message": "This subscription is already canceled."
  }
  ```

  ```json Provider Unreachable (503) theme={null}
  {
    "error": "subscription_update_failed",
    "message": "The payment provider is unreachable. Try again in a moment."
  }
  ```

  ```json Invalid Body (400) theme={null}
  {
    "at_period_end": ["at_period_end must be a boolean."]
  }
  ```
</ResponseExample>

## Error Responses

<AccordionGroup>
  <Accordion title="400 -- Invalid at_period_end">
    `at_period_end` is neither a boolean nor empty (for example `"maybe"`). Omitted, `null` or `""` mean `true`.
  </Accordion>

  <Accordion title="401 -- Unauthorized">
    The API key is missing or invalid. This endpoint only accepts your **Secret API Key**.
  </Accordion>

  <Accordion title="404 -- Subscription not found">
    No subscription with this ID belongs to your business (`subscription_not_found`).
  </Accordion>

  <Accordion title="409 -- Already canceled">
    The subscription has already ended — nothing to do. A `409` with `The subscription changed meanwhile. Try again.` means it was modified during your request: retry.
  </Accordion>

  <Accordion title="502 -- Provider refused">
    Stripe refused the change. Nothing was changed on Payviox's side: contact [support@payviox.com](mailto:support@payviox.com).
  </Accordion>

  <Accordion title="503 -- Provider unreachable">
    Stripe couldn't be reached, or Stripe isn't configured on your account anymore. Nothing was changed: retry later.
  </Accordion>
</AccordionGroup>


## OpenAPI

````yaml POST /subscriptions/{subscription_id}/cancel
openapi: 3.0.3
info:
  title: Payviox API
  description: Payment processing API
  version: 1.0.0
servers:
  - url: https://api.payviox.com
security:
  - bearerAuth: []
paths:
  /subscriptions/{subscription_id}/cancel:
    post:
      tags:
        - Subscriptions
      summary: Cancel Subscription
      description: >-
        Cancel a subscription at the end of the current period (default) or
        immediately. Requires your Secret API Key.
      operationId: cancelSubscription
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
          description: Payviox subscription ID (`psub_...`)
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelSubscriptionRequest'
            example:
              at_period_end: true
      responses:
        '200':
          description: >-
            Subscription updated (or already scheduled to cancel); returns the
            subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '400':
          description: >-
            `at_period_end` is not a boolean (omitted, `null` or `""` mean
            `true`)
        '401':
          description: Unauthorized (missing or invalid Secret API Key)
        '404':
          description: No subscription with this ID on your account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The subscription is already canceled, or changed during the request
            (retry)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: subscription_update_failed
                message: This subscription is already canceled.
        '502':
          description: Stripe refused the change — contact support
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Stripe unreachable or not configured — retry later
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    CancelSubscriptionRequest:
      type: object
      properties:
        at_period_end:
          type: boolean
          default: true
          description: >-
            `true` (default, also when omitted, `null` or empty): the
            subscription ends at `current_period_end`, the customer keeps the
            period already paid, and you can undo it with resume. `false`: ends
            now and voids its unpaid Stripe invoices, so no retry can charge the
            customer; no automatic refund.
    Subscription:
      type: object
      required:
        - id
        - status
        - amount
        - unit_amount
        - currency
        - interval
        - interval_count
        - interval_label
        - order_id
        - cycles_count
        - cancel_at_period_end
        - initial_session_id
        - created_at
      properties:
        id:
          type: string
          description: Payviox subscription ID (`psub_` prefix)
        status:
          type: string
          enum:
            - active
            - past_due
            - canceled
          description: >-
            `active`: paid up, renews automatically (also while a cancellation
            is scheduled, see `cancel_at_period_end`). `past_due`: the last
            renewal charge failed and Stripe is retrying it. `canceled`: ended,
            no further charges.
        amount:
          type: integer
          description: Your price per period in cents — the `amount` of the signup session
        unit_amount:
          type: integer
          description: >-
            Amount charged to the customer at each renewal, in cents. Includes
            the fees added for the customer at checkout, but not one-time add-on
            fees (such as fraud screening) charged on the first payment only, so
            it can be lower than the first charge.
        currency:
          type: string
          description: Always `USD`
        interval:
          type: string
          enum:
            - day
            - week
            - month
            - year
        interval_count:
          type: integer
        interval_label:
          type: string
          description: Human-readable period, e.g. `every month`, `every 3 months`
        customer:
          type: string
          nullable: true
          description: Customer identifier from the signup session
        customer_email:
          type: string
          nullable: true
          description: Customer email, from the signup session or entered at checkout
        order_id:
          type: string
          description: '`order_id` of the signup session. Renewals use `{order_id}-{cycle}`'
        items:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Items of the signup session (one period)
        metadata:
          type: object
          additionalProperties: true
          description: Metadata of the signup session
        payment_method:
          type: string
          enum:
            - credit
            - apple_pay
            - google_pay
          description: Method the customer subscribed with (read with `provider`)
        provider:
          type: string
          description: Provider that charges the renewals (`stripe`)
        provider_subscription_id:
          type: string
          nullable: true
          description: >-
            ID of the subscription on your Stripe account (`sub_...`). `null`
            until Payviox has created it, usually within a minute of the first
            payment.
        cycles_count:
          type: integer
          description: >-
            Billing cycles started so far, signup included (1 = signup only). A
            renewal whose charge failed counts too.
        current_period_end:
          type: string
          format: date-time
          nullable: true
          description: >-
            End of the current billing period: date of the next renewal charge,
            or the date a scheduled cancellation takes effect
        cancel_at_period_end:
          type: boolean
          description: >-
            `true` when the subscription is scheduled to end at
            `current_period_end`
        canceled_at:
          type: string
          format: date-time
          nullable: true
          description: When the subscription ended (`null` while not canceled)
        canceled_by:
          type: string
          nullable: true
          enum:
            - api
            - manager
            - admin
            - payer
            - provider
            - system
            - null
          description: >-
            Who ended it: `api` (your backend), `manager` (your dashboard),
            `admin` (Payviox support), `payer` (the customer, from the
            management link), `provider` (on Stripe, e.g. after failed retries
            or a cancellation in the Stripe dashboard), `system` (Payviox, e.g.
            signup payment refunded or `setup_failed`). `null` until the
            subscription is canceled.
        cancel_requested_by:
          type: string
          nullable: true
          enum:
            - payer
            - api
            - manager
            - admin
            - system
            - null
          description: >-
            Who scheduled the end-of-period cancellation: `payer` (the
            customer), `api`, `manager` (your dashboard), `admin` (Payviox
            support) or `system`. `null` when no cancellation is scheduled, or
            when it was scheduled from your Stripe dashboard. Only the same side
            can resume it: you (`api`, `manager`, `null`), the customer
            (`payer`) or Payviox (`admin`, `system`).
        cancellation_reason:
          type: string
          nullable: true
          description: >-
            Set as soon as a cancellation is requested or scheduled:
            `requested_by_merchant`, `requested_by_customer`,
            `initial_payment_refunded`, `admin` (canceled by Payviox support),
            `setup_failed:<code>`, or a Stripe reason such as `payment_failed`,
            `cancellation_requested`, `provider` or `provider_deleted`. Treat
            unknown values as informational.
        initial_session_id:
          type: string
          description: Session ID of the signup payment
        management_url:
          type: string
          nullable: true
          description: >-
            Customer self-service page (view, cancel at period end, undo). Share
            it with your customer, e.g. in your confirmation email.
        created_at:
          type: string
          format: date-time
      example:
        id: psub_8fKx2LmQp4ZrT7vW1nYc3aBd
        status: active
        amount: 999
        unit_amount: 1029
        currency: USD
        interval: month
        interval_count: 1
        interval_label: every month
        customer: customer_abc123
        customer_email: john@example.com
        order_id: order_12345
        items:
          - name: Pro plan
            quantity: 1
            price: 999
        metadata:
          user_id: usr_123
        payment_method: credit
        provider: stripe
        provider_subscription_id: sub_1QxYz2AbCdEfGhIj
        cycles_count: 2
        current_period_end: '2026-12-13T10:30:00+00:00'
        cancel_at_period_end: false
        canceled_at: null
        canceled_by: null
        cancel_requested_by: null
        cancellation_reason: null
        initial_session_id: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
        management_url: >-
          https://secure.payviox.com/subscription/k3Jd9QwL2xVb7NmP4tRz8YcF1sHg6UaE5oWi0qXn3BvM7ZpD
        created_at: '2026-10-13T10:30:00+00:00'
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable explanation of what to fix
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````

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