DeveloperLoyalty & Churn APIs

Churn API

API documentation for 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

PropertyValue
Base URL{store}.myshopify.com/apps/subscribfy-api/v1/membership/churn
AuthenticationSubscribfy 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

MethodEndpointDescription
GET/offersList all cancellation reasons and offers
GET/{contract_id}/offersGet offers applied to a contract
POST/{contract_id}/offers/{offer_id}/activationApply 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

ParameterTypeRequiredDescription
keystringYesSubscribfy API key
cancellation_reasonstringNoFilter 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

AliasDisplay Label
technical_issuesI'm having technical problems
enough_itemsI have enough items
too_expensiveIt's too expensive
not_need_subscriptionI don't need a subscription
not_using_enoughI don't use it enough
not_found_productsI couldn't find the products I liked
order_issuesProblems with my order
use_another_serviceI'm using another service
otherOther

Offer Types

TypeDescriptionRules
discount_priceApply discount to subscriptiondiscount_type, discount_value
change_frequencyChange billing frequencyinterval_count, interval_name
add_store_creditsAdd store credits to customercredit_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

ParameterTypeRequiredDescription
contract_idintegerYesSubscription 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

ParameterTypeRequiredDescription
contract_idintegerYesSubscription contract ID
offer_idintegerYesOffer 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

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}`);
}
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']}\n";
    foreach ($reason['offers'] as $offer) {
        echo "  - {$offer['name']} ({$offer['type']})\n";
    }
}

// Apply offer to contract
$result = $churn->applyOffer(12345, 15);
if (isset($result['status']) && $result['status'] === 'Active') {
    echo "Offer applied successfully!";
}
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

StatusErrorCause
403Only one offer is allowed per contractContract already has an active offer
404Not FoundContract or offer does not exist
422Validation ErrorInvalid 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

On this page