Skip to main content

Churn API

Manage subscription cancellation offers programmatically. Retrieve and apply retention offers to reduce churn.

Updated over a month ago

Churn API

Manage subscription cancellation offers programmatically. Retrieve available offers, view applied offers on contracts, and activate retention offers to reduce churn.


Overview

The Churn API enables custom retention flows by providing access to cancellation reasons and associated offers. Configure offers in Shopify Admin > Apps > Subscribfy > Settings > Churn Offers.


Endpoint

Property

Value

Base URL

{store}.myshopify.com/apps/subscribfy-api/v1/membership/churn

Authentication

Subscribfy API Key (via key parameter)


Authentication

All requests require your Subscribfy API key. Include it as a query parameter:

?key=your_subscribfy_api_key

Generate your API key in Subscribfy > Settings > API.


Available Endpoints

Method

Endpoint

Description

GET

/offers

List all cancellation reasons and offers

GET

/{contract_id}/offers

Get offers applied to a contract

POST

/{contract_id}/offers/{offer_id}/activation

Apply an offer to a contract


List Offers

Retrieve all cancellation reasons and their associated retention offers.

Request

GET /apps/subscribfy-api/v1/membership/churn/offers?key={api_key}

Query Parameters

Parameter

Type

Required

Description

key

string

Yes

Subscribfy API key

cancellation_reason

string

No

Filter by reason alias (see Cancellation Reasons)

Example Request

curl "https://your-store.myshopify.com/apps/subscribfy-api/v1/membership/churn/offers?key=your_api_key&cancellation_reason=too_expensive"

Response

[   {     "id": 6,     "title": "It's too expensive",     "description": null,     "alias": "too_expensive",     "offers": [       {         "id": 15,         "name": "Discount Subscription Price",         "description": "Discount the price of the subscription",         "type": "discount_price",         "rules": {           "discount_type": "percentage",           "discount_value": 20         }       },       {         "id": 16,         "name": "Change Subscription Frequency",         "description": "Change how often the subscription is billed",         "type": "change_frequency",         "rules": {           "interval_count": 2,           "interval_name": "month"         }       }     ]   } ]


Cancellation Reasons

Alias

Display Label

technical_issues

I'm having technical problems

enough_items

I have enough items

too_expensive

It's too expensive

not_need_subscription

I don't need a subscription

not_using_enough

I don't use it enough

not_found_products

I couldn't find the products I liked

order_issues

Problems with my order

use_another_service

I'm using another service

other

Other


Offer Types

Type

Description

Rules

discount_price

Apply discount to subscription

discount_type, discount_value

change_frequency

Change billing frequency

interval_count, interval_name

add_store_credits

Add store credits to customer

credit_amount


Get Contract Offers

Retrieve all offers that have been applied to a specific subscription contract, including their current status.

Request

GET /apps/subscribfy-api/v1/membership/churn/{contract_id}/offers?key={api_key}

Path Parameters

Parameter

Type

Required

Description

contract_id

integer

Yes

Subscription contract ID

Getting Contract ID

The contract ID is available from the customer metafield:

##{{ customer.metafields.exison.customer_subscription1.scid }}

Response

[   {     "offer": {       "id": 15,       "name": "Discount Subscription Price",       "description": "Discount the price of the subscription",       "type": "discount_price",       "rules": {         "discount_type": "percentage",         "discount_value": 20       }     },     "reward": {       "gid": "gid://shopify/SubscriptionManualDiscount/039597e2-c43d-4698-a2ee-36fd3e3fb076",       "title": "Cancellation Offer",       "target_type": "LINE_ITEM",       "recurring_cycle_limit": null,       "usage_count": 0,       "applies_on_each_item": false,       "discount_type": "percentage",       "value": 20,       "deleted_at": null     },     "status": "Active",     "deleted_at": null   },   {     "offer": {       "id": 16,       "name": "Change Subscription Frequency",       "description": "Change how often the subscription is billed",       "type": "change_frequency",       "rules": {         "interval_count": 1,         "interval_name": "year"       }     },     "reward": {       "interval_count": 1,       "interval_name": "YEAR",       "next_billing_date": "2026-10-01T10:00:00.000000Z"     },     "status": "Cancelled",     "deleted_at": "2025-10-20T10:11:28.000000Z"   } ]


Response Schema by Offer Type

Discount Price Offer

{   "offer": {     "type": "discount_price",     "rules": {       "discount_type": "percentage" | "fixed_amount",       "discount_value": float     }   },   "reward": {     "gid": "Shopify_Discount_GID",     "title": "Cancellation Offer",     "target_type": "LINE_ITEM" | "SHIPPING_LINE",     "recurring_cycle_limit": int | null,     "usage_count": int,     "applies_on_each_item": boolean,     "discount_type": "percentage" | "fixed_amount",     "value": float,     "deleted_at": datetime | null   },   "status": "Active" | "Cancelled",   "deleted_at": datetime | null }

Change Frequency Offer

{   "offer": {     "type": "change_frequency",     "rules": {       "interval_count": int,       "interval_name": "year" | "month" | "week" | "day"     }   },   "reward": {     "interval_count": int,     "interval_name": "YEAR" | "MONTH" | "WEEK" | "DAY",     "next_billing_date": datetime   },   "status": "Active" | "Cancelled",   "deleted_at": datetime | null }


Apply Offer

Activate a retention offer on a subscription contract. Only one offer can be active per contract at a time.

Request

POST /apps/subscribfy-api/v1/membership/churn/{contract_id}/offers/{offer_id}/activation?key={api_key}

Path Parameters

Parameter

Type

Required

Description

contract_id

integer

Yes

Subscription contract ID

offer_id

integer

Yes

Offer ID from /offers endpoint

Success Response

{   "offer": {     "id": 16,     "name": "Change Subscription Frequency",     "description": "Change how often the subscription is billed",     "type": "change_frequency",     "rules": {       "interval_count": 1,       "interval_name": "year"     }   },   "reward": {     "interval_count": 1,     "interval_name": "YEAR",     "next_billing_date": "2026-10-01T10:00:00.000000Z"   },   "status": "Active",   "deleted_at": null }

Error Response

HTTP 403 Forbidden  {   "message": "Only one offer is allowed per contract." }


Code Examples

JavaScript

class ChurnAPI {     constructor(store, apiKey) {         this.baseUrl = `https://${store}/apps/subscribfy-api/v1/membership/churn`;         this.apiKey = apiKey;     }      async getOffers(cancellationReason = null) {         const params = new URLSearchParams({ key: this.apiKey });         if (cancellationReason) {             params.append('cancellation_reason', cancellationReason);         }         const response = await fetch(`${this.baseUrl}/offers?${params}`);         return response.json();     }      async getContractOffers(contractId) {         const response = await fetch(             `${this.baseUrl}/${contractId}/offers?key=${this.apiKey}`         );         return response.json();     }      async applyOffer(contractId, offerId) {         const response = await fetch(             `${this.baseUrl}/${contractId}/offers/${offerId}/activation?key=${this.apiKey}`,             { method: 'POST' }         );         return response.json();     } }  // Usage const churn = new ChurnAPI('your-store.myshopify.com', 'your_api_key');  // Get offers for "too expensive" reason const offers = await churn.getOffers('too_expensive'); console.log(`Found ${offers.length} cancellation reasons`);  // Check existing offers on a contract const contractOffers = await churn.getContractOffers(12345); const activeOffer = contractOffers.find(o => o.status === 'Active');  // Apply a discount offer if no active offer exists if (!activeOffer && offers[0]?.offers[0]) {     const result = await churn.applyOffer(12345, offers[0].offers[0].id);     console.log(`Applied offer: ${result.offer.name}`); }

PHP

class ChurnAPI {     private $store;     private $apiKey;      public function __construct($store, $apiKey) {         $this->store = $store;         $this->apiKey = $apiKey;     }      private function request($endpoint, $method = 'GET') {         $url = "https://{$this->store}/apps/subscribfy-api/v1/membership/churn{$endpoint}";         $url .= (strpos($url, '?') === false ? '?' : '&') . "key={$this->apiKey}";          $ch = curl_init();         curl_setopt_array($ch, [             CURLOPT_URL => $url,             CURLOPT_RETURNTRANSFER => true,             CURLOPT_CUSTOMREQUEST => $method         ]);         $response = curl_exec($ch);         curl_close($ch);         return json_decode($response, true);     }      public function getOffers($cancellationReason = null) {         $endpoint = '/offers';         if ($cancellationReason) {             $endpoint .= "?cancellation_reason={$cancellationReason}";         }         return $this->request($endpoint);     }      public function getContractOffers($contractId) {         return $this->request("/{$contractId}/offers");     }      public function applyOffer($contractId, $offerId) {         return $this->request("/{$contractId}/offers/{$offerId}/activation", 'POST');     } }  // Usage $churn = new ChurnAPI('your-store.myshopify.com', 'your_api_key');  // Get all offers $offers = $churn->getOffers(); foreach ($offers as $reason) {     echo "Reason: {$reason['title']}
";     foreach ($reason['offers'] as $offer) {         echo "  - {$offer['name']} ({$offer['type']})
";     } }  // Apply offer to contract $result = $churn->applyOffer(12345, 15); if (isset($result['status']) && $result['status'] === 'Active') {     echo "Offer applied successfully!
"; }

Python

import requests  class ChurnAPI:     def __init__(self, store, api_key):         self.base_url = f'https://{store}/apps/subscribfy-api/v1/membership/churn'         self.api_key = api_key      def get_offers(self, cancellation_reason=None):         params = {'key': self.api_key}         if cancellation_reason:             params['cancellation_reason'] = cancellation_reason         response = requests.get(f'{self.base_url}/offers', params=params)         return response.json()      def get_contract_offers(self, contract_id):         response = requests.get(             f'{self.base_url}/{contract_id}/offers',             params={'key': self.api_key}         )         return response.json()      def apply_offer(self, contract_id, offer_id):         response = requests.post(             f'{self.base_url}/{contract_id}/offers/{offer_id}/activation',             params={'key': self.api_key}         )         return response.json()  # Usage churn = ChurnAPI('your-store.myshopify.com', 'your_api_key')  # Get offers for customers who find it too expensive offers = churn.get_offers('too_expensive') for reason in offers:     print(f"Reason: {reason['title']}")     for offer in reason['offers']:         print(f"  - {offer['name']}: {offer['type']}")  # Apply first available offer if offers and offers[0]['offers']:     contract_id = 12345     offer_id = offers[0]['offers'][0]['id']     result = churn.apply_offer(contract_id, offer_id)     print(f"Result: {result['status']}")


Liquid Integration

Access contract information in Shopify themes:

{% assign subscription = customer.metafields.exison.customer_subscription1 %} {% if subscription %}   <p>Contract ID: ##{{ subscription.scid }}</p>   <p>Status: ##{{ subscription.status }}</p> {% endif %}


Error Responses

Status

Error

Cause

403

Only one offer is allowed per contract

Contract already has an active offer

404

Not Found

Contract or offer does not exist

422

Validation Error

Invalid cancellation_reason parameter


Offer Lifecycle

  • Active - Offer is currently applied to the subscription

  • Cancelled - Offer was removed (contract cancelled or offer revoked)

  • 24-hour grace period - After cancellation, offers remain active for 24 hours before being fully revoked


Best Practices

  • Match reason to offer - Show relevant offers based on the cancellation reason

  • Check existing offers - Verify no active offer exists before applying a new one

  • Handle errors gracefully - Display user-friendly messages for API errors

  • Track offer usage - Monitor which offers are most effective at retention


Questions? Contact support@subscribfy.com

Did this answer your question?