Subscriptions API
The Subscriptions API lets you create and manage customer subscriptions and subscription checkout flows. It provides endpoints to generate checkout links, list and retrieve subscriptions, and cancel subscriptions.
Supported subscription flows include:
- Create subscription (checkout link):
POST /v1/subscriptions— returns a hosted checkout URL to complete payment and create the subscription. - List subscriptions:
GET /v1/subscriptions— paginated list for the caller (customer or developer). - Retrieve subscription by ID:
GET /v1/subscriptions/{subscription_id}— get subscription details. - List by plan (public key):
GET /v1/subscriptions/{key_plan}— subscriptions associated to a given public plan key. - Cancel subscription:
DELETE /v1/subscriptions/{subscription_id}— cancel an existing subscription.
Base URL (development): http://127.0.0.1:8000
Authentication: the Subscriptions API supports API Key authentication using HTTP Basic Auth where the API key is the username and the password is empty. Example test key: sk_test_51O62xYzAbcDef123 (Base64 for sk_test_51O62xYzAbcDef123: is c2tfdGVzdF81MU82MnhZekFiY0RlZjEyMzo=).
Create a subscription (checkout link)¶
Endpoint
POST /v1/subscriptions
Creates a local pending subscription and returns a hosted checkout URL. The client (frontend) should redirect the customer to this URL to complete payment. The subscription activation is asynchronous — webhooks will notify your system when the subscription becomes active.
Important: Use the test key sk_test_51O62xYzAbcDef123 for local development. Do not expose live keys in client-side code.
Request fields
| Parameter | Description | Type | Required |
|---|---|---|---|
plan_id | Local price/plan identifier used to create the subscription | string | true |
customer_id | Existing customer identifier (if omitted, a guest/one-off flow may be created) | string | conditional |
quantity | Number of units for recurring billing | integer | false |
trial_days | Trial period in days (optional) | integer | false |
return_url | URL where the user will be redirected after checkout (required) | string | true |
cURL (Basic Auth - shorthand)
curl -X POST 'http://127.0.0.1:8000/v1/subscriptions' \
-u "sk_test_51O62xYzAbcDef123:" \
-H 'Content-Type: application/json' \
-d '{
"plan_id": "price_1AbCdEfG",
"customer_id": "cus_1234567890",
"quantity": 1,
"return_url": "https://example.com/subscription-result"
}'
cURL (Manual Authorization header)
curl -X POST 'http://127.0.0.1:8000/v1/subscriptions' \
-H 'Authorization: Basic c2tfdGVzdF81MU82MnhZekFiY0RlZjEyMzo=' \
-H 'Content-Type: application/json' \
-d '{"plan_id":"price_1AbCdEfG","customer_id":"cus_1234567890","return_url":"https://example.com/subscription-result"}'
Python (requests)
import requests
from requests.auth import HTTPBasicAuth
BASE = 'http://127.0.0.1:8000'
API_KEY = 'sk_test_51O62xYzAbcDef123'
payload = {
'plan_id': 'price_1AbCdEfG',
'customer_id': 'cus_1234567890',
'quantity': 1,
'return_url': 'https://example.com/subscription-result'
}
resp = requests.post(f"{BASE}/v1/subscriptions", auth=HTTPBasicAuth(API_KEY, ''), json=payload)
print(resp.status_code)
print(resp.json())
JavaScript (fetch, Node.js or modern browsers)
const apiKey = 'sk_test_51O62xYzAbcDef123';
const auth = 'Basic ' + Buffer.from(`${apiKey}:`).toString('base64');
fetch('http://127.0.0.1:8000/v1/subscriptions', {
method: 'POST',
headers: {
'Authorization': auth,
'Content-Type': 'application/json'
},
body: JSON.stringify({
plan_id: 'price_1AbCdEfG',
customer_id: 'cus_1234567890',
quantity: 1,
return_url: 'https://example.com/subscription-result'
})
})
.then(r => r.json())
.then(console.log)
.catch(console.error)
Angular HttpClient (TypeScript)
import { HttpClient, HttpHeaders } from '@angular/common/http';
const apiKey = 'sk_test_51O62xYzAbcDef123';
const headers = new HttpHeaders({
'Authorization': `Basic ${btoa(apiKey + ':')}`,
'Content-Type': 'application/json'
});
const payload = {
plan_id: 'price_1AbCdEfG',
customer_id: 'cus_1234567890',
return_url: 'https://example.com/subscription-result'
};
this.http.post('http://127.0.0.1:8000/v1/subscriptions', payload, { headers })
.subscribe(console.log, console.error);
Postman
- Method: POST
- URL:
http://127.0.0.1:8000/v1/subscriptions - Authorization: Basic Auth — Username:
sk_test_51O62xYzAbcDef123, Password: (leave empty) - Body: raw JSON with
plan_id,customer_id,return_url.
Successful Response (201 Created)
{
"code": 201,
"title": "Subscription created",
"content": "Checkout URL generated. Redirect the customer to complete payment.",
"data": {
"subscription_id": "sub_abc21d23",
"checkout_url": "https://console.4geeks.io/checkout/?data=eyJ...",
"test": true
}
}
Note: The
checkout_urlis a one-time URL for the checkout session. Store the returnedsubscription_idto reconcile webhooks and callbacks.
Retrieve a subscription¶
Endpoint
GET /v1/subscriptions/{subscription_id}
Retrieve a subscription by its ID. The response includes plan details, status, next billing date, customer information and trial data when applicable.
Path parameter
| Parameter | Description | Type | Required |
|---|---|---|---|
subscription_id | Subscription identifier (e.g., sub_abc21d23) | string | true |
Request example
curl -X GET 'http://127.0.0.1:8000/v1/subscriptions/sub_abc21d23' \
-u "sk_test_51O62xYzAbcDef123:" \
-H 'Accept: application/json'
Response (200 OK)
{
"subscription_id": "sub_abc21d23",
"plan_id": "price_1AbCdEfG",
"customer_id": "cus_1234567890",
"status": "active",
"current_period_start": "2025-12-01T00:00:00Z",
"current_period_end": "2026-01-01T00:00:00Z",
"next_billing_date": "2026-01-01",
"trial_end": null,
"created_at": "2025-12-01T12:00:00Z",
"test": true
}
Error (404 Not Found)
List subscriptions¶
Endpoint
GET /v1/subscriptions
Returns a paginated list of subscriptions for the caller. When called with a customer token (or scoped key) it returns that customer’s subscriptions; when called with a developer key it returns subscriptions for that developer’s plans.
Query parameters
| Parameter | Description | Type | Required |
|---|---|---|---|
page | Page number (default: 1) | integer | false |
page_size | Items per page (default: 10) | integer | false |
status | Filter by status (active, canceled, trialing) | string | false |
plan_id | Filter by plan id | string | false |
test | Filter by test mode (true/false) | boolean | false |
Request example
curl -X GET 'http://127.0.0.1:8000/v1/subscriptions?page=1&page_size=10' \
-u "sk_test_51O62xYzAbcDef123:" \
-H 'Accept: application/json'
Response (200 OK)
{
"count": 12,
"current_page": 1,
"total_pages": 2,
"results": [
{
"subscription_id": "sub_abc21d23",
"plan_id": "price_1AbCdEfG",
"customer_id": "cus_1234567890",
"status": "active",
"next_billing_date": "2026-01-01",
"test": true
}
]
}
List subscriptions by public plan key¶
Endpoint
GET /v1/subscriptions/{key_plan}
Return subscriptions associated with a public plan key. This endpoint is useful to list subscribers for a given public plan.
Path parameter
| Parameter | Description | Type | Required |
|---|---|---|---|
key_plan | Public plan key (e.g., plan_abcdef) | string | true |
Request example
curl -X GET 'http://127.0.0.1:8000/v1/subscriptions/plan_abcdef' \
-u "sk_test_51O62xYzAbcDef123:" \
-H 'Accept: application/json'
Response (200 OK)
[
{
"subscription_id": "sub_abc21d23",
"customer_id": "cus_1234567890",
"status": "active",
"next_billing_date": "2026-01-01",
"test": true
}
]
Cancel subscription¶
Endpoint
DELETE /v1/subscriptions/{subscription_id}
Cancels a subscription. The endpoint sets the local PlanSubscription.active field to false and attempts to cancel external billing where applicable. Canceling may also trigger webhooks and notification emails.
Request example
curl -X DELETE 'http://127.0.0.1:8000/v1/subscriptions/sub_abc21d23' \
-u "sk_test_51O62xYzAbcDef123:" \
-H 'Accept: application/json'
Response (200 OK)
{
"code": 200,
"title": "Subscription cancelled",
"content": "The subscription has been successfully cancelled.",
"subscription_id": "sub_abc21d23"
}
Errors
| Status | Reason | Description |
|---|---|---|
| 400 | Bad Request | Invalid or malformed request (e.g., invalid plan_id). |
| 401 | Unauthorized | Missing or invalid API key. |
| 403 | Forbidden | Caller lacks permission to cancel the target subscription. |
| 404 | Not Found | Subscription not found. |
| 409 | Conflict | Cancellation cannot be completed (e.g., billing reconciliation conflict). |
| 500 | Server Error | Internal error while processing cancellation. |
Error Handling (common)¶
400 Bad Request
401 Unauthorized
{
"error": {"code": 401, "type": "unauthorized", "message": "Invalid API key or missing Authorization header"}
}
403 Forbidden
404 Not Found
500 Internal Server Error
Security Best Practices¶
- Use HTTPS in production. The examples above use the local dev URL for convenience.
- Store API keys in environment variables or a secret manager (do not hardcode in code or commit them).
- Use test keys for development and live keys for production only.
- Rotate API keys regularly and remove unused keys.
- Validate
return_urlhost and scheme to prevent open redirect vulnerabilities. - Rely on webhooks to confirm subscription activation — do not assume success only from the checkout redirect.
Troubleshooting¶
Problem: I get 401 Unauthorized - Verify you are sending Authorization: Basic <base64(key:)> or using curl -u 'sk_test_...:'. - Confirm the API key hasn’t been rotated.
Problem: Checkout URL returns error or is blank - Ensure return_url is valid and accessible. - Check server logs and webhook processing for errors.
Problem: Webhook events not arriving - Verify webhook endpoint is publicly reachable and uses HTTPS in production. - Confirm the webhook signing secret and signature verification on your side.
FAQ¶
Q: What is returned after creating a subscription?
A: A subscription_id and a checkout_url (hosted checkout). The checkout URL must be visited to complete payment and activation.
Q: Are subscriptions activated synchronously?
A: No. Subscriptions are activated asynchronously. Use webhooks to detect when the subscription becomes active.
Q: Can I use API keys in frontend code?
A: Do not embed live API keys in client-side code. For web integrations, generate the hosted checkout server-side and return the checkout URL to the client.
If you want, I can also:
- Add a Postman collection JSON for these endpoints (importable)
- Add examples of webhook payloads and signature verification
- Add a short
subscriptions.mdsummary card for the docs index
Which would you like next?