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

# Get retention offers

> Returns what this subscription can be offered for each cancellation reason, so you can build the cancellation flow in your own app.

Every cancellation reason is listed with the offer configured for it in the Kelviq dashboard retention settings: a discount, a pause, a link to support, or nothing. Each offer carries an eligibility verdict for this particular subscription. An ineligible offer is still returned with `ineligibleReason` set, so you can show it greyed out with an explanation rather than hide it.

When retention is switched off, `enabled` is `false` and every reason comes back with `offer: null`. The reason list is still returned so the same reason picker can be rendered either way.

To apply the offer the customer chooses, call [Accept a retention offer](/api-reference/subscriptions/accept-a-retention-offer). To record the cancellation instead, call [Cancel a subscription](/api-reference/subscriptions/cancel-a-subscription) with the same `cancellationFeedback`.

<Info>Click the base URL in the API playground and select the **Sandbox** host for test data or the **Production** host for live data. Use credentials from the same environment.</Info>


## OpenAPI

````yaml /api-reference/openapi.json get /subscriptions/{subscriptionId}/retention/
openapi: 3.0.0
info:
  title: kelviq API
  version: 1.0.0
  description: >-
    API for interacting with kelviq services, derived from Python SDK
    documentation.
servers:
  - url: https://sandboxapi.kelviq.com/api/v1
    description: Sandbox — Test payments
  - url: https://api.kelviq.com/api/v1
    description: Production — Live payments
security:
  - bearerAuth: []
tags:
  - name: Products
    description: Catalog products.
  - name: Product Settings
    description: Per-product settings (currency, VPN/Tor/proxy, product URL).
  - name: Product Files
    description: Product images and downloadable assets.
  - name: Features
    description: Catalog features that can be granted as plan entitlements.
  - name: Plans
    description: Catalog plans (CRUD, publish, versions, prices).
  - name: Plan Entitlements
    description: Feature entitlements attached to a plan.
  - name: Plan Files
    description: Files attached to plans, and signed download links.
  - name: Media
    description: Generate presigned S3 upload URLs for product/plan images and files.
  - name: Partner
    description: Partner integration APIs (organization provisioning, lookup).
  - name: Charges
    description: >-
      One-time payments charged immediately against a customer's default payment
      method.
  - name: Refunds
    description: Full and partial refunds for paid orders.
  - name: Transactions
    description: >-
      Charges, refunds, disputes, and other financial movements for the
      authenticated organization.
  - name: Payment Methods
    description: Saved customer payment methods (cards, etc.).
  - name: Orders
    description: Completed and resolved orders for the authenticated organization.
  - name: Order Events
    description: Timeline events for a specific order.
  - name: Invoices
    description: Invoices generated for orders and subscription billing cycles.
  - name: Discounts
    description: >-
      Discount codes (coupons) that can be applied to products and plans at
      checkout.
  - name: Webhook Endpoints
    description: >-
      Configure the destination URLs and event subscriptions that receive
      webhook deliveries.
  - name: Webhooks
    description: Delivery logs for webhook events sent to your configured endpoints.
  - name: Checkout Sessions
    description: Merchant-side view of checkout sessions created by customers.
  - name: Checkout Session Events
    description: Timeline events for a specific checkout session.
paths:
  /subscriptions/{subscriptionId}/retention/:
    get:
      tags:
        - Subscriptions
      summary: Get retention offers
      description: >-
        Returns what this subscription can be offered for each cancellation
        reason, so you can build the cancellation flow in your own app.


        Every cancellation reason is listed with the offer configured for it in
        the Kelviq dashboard retention settings: a discount, a pause, a link to
        support, or nothing. Each offer carries an eligibility verdict for this
        particular subscription. An ineligible offer is still returned with
        `ineligibleReason` set, so you can show it greyed out with an
        explanation rather than hide it.


        When retention is switched off, `enabled` is `false` and every reason
        comes back with `offer: null`. The reason list is still returned so the
        same reason picker can be rendered either way.


        To apply the offer the customer chooses, call [Accept a retention
        offer](/api-reference/subscriptions/accept-a-retention-offer). To record
        the cancellation instead, call [Cancel a
        subscription](/api-reference/subscriptions/cancel-a-subscription) with
        the same `cancellationFeedback`.
      operationId: getRetentionOffers
      parameters:
        - name: subscriptionId
          in: path
          required: true
          description: The unique identifier of the subscription.
          example: 78058918-9746-4280-9b9b-1bd5115eec6e
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: The retention configuration for this subscription.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetentionOffersResponse'
              example:
                enabled: true
                writtenFeedback:
                  enabled: true
                  question: What could we have done better?
                reasons:
                  - reason: TOO_EXPENSIVE
                    offer:
                      type: DISCOUNT
                      percentageOff: 20
                      billingCycles: 3
                      eligible: true
                      ineligibleReason: null
                  - reason: UNUSED
                    offer:
                      type: PAUSE
                      durations:
                        - months: 1
                          billingResumesOn: '2026-12-01'
                        - months: 3
                          billingResumesOn: '2027-02-01'
                      eligible: true
                      ineligibleReason: null
                  - reason: CUSTOMER_SERVICE
                    offer:
                      type: SUPPORT
                      url: https://cal.com/acme/15min
                      eligible: true
                      ineligibleReason: null
                  - reason: MISSING_FEATURES
                    offer: null
                  - reason: SWITCHED_SERVICE
                    offer: null
                  - reason: LOW_QUALITY
                    offer: null
                  - reason: TOO_COMPLEX
                    offer: null
                  - reason: OTHER
                    offer: null
        '400':
          description: >-
            The subscription was not found, is not active, or the offer could
            not be applied. `detail` explains why.
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
              example:
                detail: This offer has already been used on this subscription.
components:
  schemas:
    RetentionOffersResponse:
      type: object
      properties:
        enabled:
          type: boolean
          description: >-
            Whether retention offers are switched on for the organization. When
            `false`, every reason has `offer: null`.
        writtenFeedback:
          type: object
          description: >-
            Whether to ask the customer for a free-text comment, and the
            question to show. Pass the answer as `cancellationComment` when
            cancelling.
          properties:
            enabled:
              type: boolean
            question:
              type: string
              nullable: true
              example: What could we have done better?
        reasons:
          type: array
          description: One entry per cancellation reason, in a fixed order.
          items:
            $ref: '#/components/schemas/RetentionReason'
    RetentionReason:
      type: object
      properties:
        reason:
          type: string
          enum:
            - TOO_EXPENSIVE
            - MISSING_FEATURES
            - SWITCHED_SERVICE
            - UNUSED
            - CUSTOMER_SERVICE
            - LOW_QUALITY
            - TOO_COMPLEX
            - OTHER
          description: >-
            The cancellation reason. Use the same value as
            `cancellationFeedback` when accepting an offer or cancelling.
          example: TOO_EXPENSIVE
        offer:
          nullable: true
          description: The offer configured for this reason, or `null` when there is none.
          oneOf:
            - $ref: '#/components/schemas/RetentionDiscountOffer'
            - $ref: '#/components/schemas/RetentionPauseOffer'
            - $ref: '#/components/schemas/RetentionSupportOffer'
          discriminator:
            propertyName: type
            mapping:
              DISCOUNT: '#/components/schemas/RetentionDiscountOffer'
              PAUSE: '#/components/schemas/RetentionPauseOffer'
              SUPPORT: '#/components/schemas/RetentionSupportOffer'
    RetentionDiscountOffer:
      type: object
      required:
        - type
        - eligible
      properties:
        type:
          type: string
          enum:
            - DISCOUNT
        percentageOff:
          type: integer
          description: Percentage taken off each invoice while the discount lasts.
          example: 20
        billingCycles:
          type: integer
          description: How many billing cycles the discount applies to.
          example: 3
        eligible:
          type: boolean
          description: Whether this subscription can take the offer right now.
        ineligibleReason:
          type: string
          nullable: true
          enum:
            - MANUAL_BILLING
            - NOT_RECURRING
            - FREE_SUBSCRIPTION
            - SUBSCRIPTION_NOT_ACTIVE
            - NOT_ACTIVE
            - ALREADY_PAUSED
            - SCHEDULED_TO_CANCEL
            - ALREADY_TAKEN
            - NO_USABLE_DURATION
            - SHORTER_THAN_BILLING_PERIOD
            - NOT_CONFIGURED
          description: >-
            Why the offer cannot be taken, or `null` when `eligible` is `true`.
            `MANUAL_BILLING`, `NOT_RECURRING` and `FREE_SUBSCRIPTION` describe
            the subscription itself; `SUBSCRIPTION_NOT_ACTIVE`, `NOT_ACTIVE`,
            `ALREADY_PAUSED` and `SCHEDULED_TO_CANCEL` its current state;
            `ALREADY_TAKEN` means the organization's repeat policy blocks the
            offer; `NO_USABLE_DURATION` and `SHORTER_THAN_BILLING_PERIOD` mean
            every configured pause length is shorter than one billing period;
            `NOT_CONFIGURED` means the offer is missing its setup, such as a
            support URL.
    RetentionPauseOffer:
      type: object
      required:
        - type
        - eligible
      properties:
        type:
          type: string
          enum:
            - PAUSE
        durations:
          type: array
          description: >-
            Pause lengths the customer may choose. Only lengths that skip at
            least one renewal for this subscription are listed.
          items:
            type: object
            properties:
              months:
                type: integer
                example: 3
              billingResumesOn:
                type: string
                format: date
                description: >-
                  The date billing and access resume if this length is chosen.
                  Aligned to the end of a billing period.
                example: '2027-02-01'
        eligible:
          type: boolean
          description: Whether this subscription can take the offer right now.
        ineligibleReason:
          type: string
          nullable: true
          enum:
            - MANUAL_BILLING
            - NOT_RECURRING
            - FREE_SUBSCRIPTION
            - SUBSCRIPTION_NOT_ACTIVE
            - NOT_ACTIVE
            - ALREADY_PAUSED
            - SCHEDULED_TO_CANCEL
            - ALREADY_TAKEN
            - NO_USABLE_DURATION
            - SHORTER_THAN_BILLING_PERIOD
            - NOT_CONFIGURED
          description: >-
            Why the offer cannot be taken, or `null` when `eligible` is `true`.
            `MANUAL_BILLING`, `NOT_RECURRING` and `FREE_SUBSCRIPTION` describe
            the subscription itself; `SUBSCRIPTION_NOT_ACTIVE`, `NOT_ACTIVE`,
            `ALREADY_PAUSED` and `SCHEDULED_TO_CANCEL` its current state;
            `ALREADY_TAKEN` means the organization's repeat policy blocks the
            offer; `NO_USABLE_DURATION` and `SHORTER_THAN_BILLING_PERIOD` mean
            every configured pause length is shorter than one billing period;
            `NOT_CONFIGURED` means the offer is missing its setup, such as a
            support URL.
    RetentionSupportOffer:
      type: object
      required:
        - type
        - eligible
      properties:
        type:
          type: string
          enum:
            - SUPPORT
        url:
          type: string
          nullable: true
          description: >-
            Where to send the customer, such as a booking page or a support
            form.
          example: https://cal.com/acme/15min
        eligible:
          type: boolean
          description: Whether this subscription can take the offer right now.
        ineligibleReason:
          type: string
          nullable: true
          enum:
            - MANUAL_BILLING
            - NOT_RECURRING
            - FREE_SUBSCRIPTION
            - SUBSCRIPTION_NOT_ACTIVE
            - NOT_ACTIVE
            - ALREADY_PAUSED
            - SCHEDULED_TO_CANCEL
            - ALREADY_TAKEN
            - NO_USABLE_DURATION
            - SHORTER_THAN_BILLING_PERIOD
            - NOT_CONFIGURED
          description: >-
            Why the offer cannot be taken, or `null` when `eligible` is `true`.
            `MANUAL_BILLING`, `NOT_RECURRING` and `FREE_SUBSCRIPTION` describe
            the subscription itself; `SUBSCRIPTION_NOT_ACTIVE`, `NOT_ACTIVE`,
            `ALREADY_PAUSED` and `SCHEDULED_TO_CANCEL` its current state;
            `ALREADY_TAKEN` means the organization's repeat policy blocks the
            offer; `NO_USABLE_DURATION` and `SHORTER_THAN_BILLING_PERIOD` mean
            every configured pause length is shorter than one billing period;
            `NOT_CONFIGURED` means the offer is missing its setup, such as a
            support URL.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        The Server API Key obtained from the kelviq application. Pass as a
        Bearer token in the Authorization header. Example: 'Authorization:
        Bearer __YOUR_API_KEY__'

````

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