Subscription API

Manage the authenticated user's recurring subscription: cancel auto-renewal (keeping the period already paid for) and open a one-shot Creem billing portal for card and invoice management. These endpoints take no request body and return flat, bespoke JSON shapes — they are not wrapped in the standard {success, payload, meta, errors, description} envelope. Both require authentication.

Cancelling never refunds and never cuts access early. Cancellation disables auto-renewal both at Creem (scheduled — billing stops at period end) and locally. The user keeps full access until access_until, after which the subscription simply lapses.

Cancel Subscription

POST /v1/subscription/cancel Requires Auth

Cancel Subscription

Cancel Subscription

Disable auto-renewal on the authenticated user's active subscription. No request body is required. The user keeps access until the end of the period they already paid for; no refund is issued. This call is idempotent — cancelling an already-cancelled subscription succeeds and echoes back the current expiry with "already_cancelled": true.

Example request:
curl -X POST https://api.proxyhat.com/v1/subscription/cancel \
  -H "Authorization: Bearer __API_KEY__" \
  -H "Accept: application/json"
import requests

response = requests.post(
    "https://api.proxyhat.com/v1/subscription/cancel",
    headers={
        "Authorization": "Bearer __API_KEY__",
        "Accept": "application/json",
    },
)

data = response.json()
if data["success"]:
    print(f"Access until: {data[\"access_until\"]}")
else:
    print(f"Cancel failed ({data[\"code\"]}): {data[\"message\"]}")
const response = await fetch("https://api.proxyhat.com/v1/subscription/cancel", {
  method: "POST",
  headers: {
    "Authorization": "Bearer __API_KEY__",
    "Accept": "application/json",
  },
});

const data = await response.json();
if (data.success) {
  console.log(`Access until: ${data.access_until}`);
} else {
  console.log(`Cancel failed (${data.code}): ${data.message}`);
}
req, _ := http.NewRequest("POST", "https://api.proxyhat.com/v1/subscription/cancel", nil)
req.Header.Set("Authorization", "Bearer __API_KEY__")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
if data["success"] == true {
    fmt.Printf("Access until: %v\n", data["access_until"])
} else {
    fmt.Printf("Cancel failed (%v): %v\n", data["code"], data["message"])
}

When the subscription was already cancelled on a prior call, the response additionally carries "already_cancelled": true and no second call is made to Creem:

Response 200
{
  "success": true,
  "already_cancelled": true,
  "access_until": "2026-08-15T09:30:00+00:00"
}

Error responses

If the account has no active subscription (or no linked Creem subscription), the endpoint returns 422:

Response 422
{
  "success": false,
  "code": "NO_ACTIVE_SUBSCRIPTION",
  "message": "You have no active subscription to cancel."
}

If the cancellation request to the billing provider fails, the endpoint returns 500 and nothing is changed — the call is safe to retry:

Response 500
{
  "success": false,
  "code": "CANCEL_FAILED",
  "message": "Could not cancel subscription. Please try again or contact support."
}

Billing Portal

One-shot, single-use URL. The returned portal_url is a fresh Creem Customer Portal session for browser redirect — the user can update their payment card, view billing history, and manage the subscription there. Do not cache it; generate a new one each time you need to send the user to the portal.
POST /v1/subscription/billing-portal Requires Auth

Open Billing Portal

Open Billing Portal

Generate a one-shot Creem Customer Portal session URL for the authenticated user and return it for a browser redirect. No request body is required. Requires an existing billing customer on the account (created the first time the user pays); accounts that have never been billed cannot open the portal.

Example request:
curl -X POST https://api.proxyhat.com/v1/subscription/billing-portal \
  -H "Authorization: Bearer __API_KEY__" \
  -H "Accept: application/json"
import requests

response = requests.post(
    "https://api.proxyhat.com/v1/subscription/billing-portal",
    headers={
        "Authorization": "Bearer __API_KEY__",
        "Accept": "application/json",
    },
)

data = response.json()
if data["success"]:
    # Redirect the user's browser to this URL.
    print(data["portal_url"])
else:
    print(f"Portal unavailable ({data[\"code\"]}): {data[\"message\"]}")
const response = await fetch("https://api.proxyhat.com/v1/subscription/billing-portal", {
  method: "POST",
  headers: {
    "Authorization": "Bearer __API_KEY__",
    "Accept": "application/json",
  },
});

const data = await response.json();
if (data.success) {
  // Redirect the user's browser to this URL.
  window.location.href = data.portal_url;
} else {
  console.log(`Portal unavailable (${data.code}): ${data.message}`);
}
req, _ := http.NewRequest("POST", "https://api.proxyhat.com/v1/subscription/billing-portal", nil)
req.Header.Set("Authorization", "Bearer __API_KEY__")
req.Header.Set("Accept", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
if data["success"] == true {
    fmt.Printf("Portal URL: %v\n", data["portal_url"])
} else {
    fmt.Printf("Portal unavailable (%v): %v\n", data["code"], data["message"])
}

Error responses

If the account has no billing customer (it has never been charged), the endpoint returns 422:

Response 422
{
  "success": false,
  "code": "NO_BILLING_PORTAL",
  "message": "Billing portal is not available for this account."
}

If the billing provider fails to create the portal session, the endpoint returns 500; retry or contact support:

Response 500
{
  "success": false,
  "code": "PORTAL_CREATION_FAILED",
  "message": "Could not open the billing portal. Please try again or contact support."
}