Create Subscription
Create a recurring payment subscription
POST
/
api
/
v2
/
subscriptions
Create Subscription
curl --request POST \
--url https://api.example.com/api/v2/subscriptionsimport requests
url = "https://api.example.com/api/v2/subscriptions"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/api/v2/subscriptions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v2/subscriptions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v2/subscriptions"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v2/subscriptions")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v2/subscriptions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCreate Subscription
Create a new subscription for recurring payments. Subscriptions are typically created after a customer completes checkout via a payment link withpayment_type: "subscription".
Authentication
Authorization: Bearer glm_test_YOUR_API_KEY
Request Body
{
"customer_id": "cus_xyz789",
"product_id": 12345,
"amount_cents": 9900,
"currency": "USD",
"interval": "monthly",
"interval_count": 1,
"trial_period_days": 7,
"payment_method_id": "pm_abc123",
"metadata": {
"tier": "premium",
"source": "website"
}
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | Customer ID |
product_id | integer | No | Associated product ID |
merchant_product_id | string | No | Your product reference ID |
amount_cents | integer | Yes | Recurring charge amount in cents |
currency | string | Yes | ISO 4217 currency code (e.g., “USD”) |
interval | enum | Yes | daily, weekly, monthly, or yearly |
interval_count | integer | No | Number of intervals (default: 1) |
trial_period_days | integer | No | Free trial duration in days |
payment_method_id | string | Yes | ID of stored payment method |
metadata | object | No | Custom key-value pairs |
Response
{
"id": "sub_abc123xyz",
"customer_id": "cus_xyz789",
"status": "trialing",
"product_id": 12345,
"merchant_product_id": "MEMBERSHIP-PREMIUM",
"amount_cents": 9900,
"currency": "USD",
"interval": "monthly",
"interval_count": 1,
"trial_period_days": 7,
"trial_end": "2025-10-25T14:30:00Z",
"current_period_start": "2025-10-18T14:30:00Z",
"current_period_end": "2025-11-18T14:30:00Z",
"next_billing_date": "2025-10-25T14:30:00Z",
"cancel_at_period_end": false,
"payment_method": {
"type": "hsa_fsa_card",
"last4": "1111",
"brand": "visa"
},
"metadata": {
"tier": "premium",
"source": "website"
},
"created_at": "2025-10-18T14:30:00Z",
"updated_at": "2025-10-18T14:30:00Z"
}
Examples
Monthly Subscription with Trial
curl -X POST https://api.withgale.com/api/v2/subscriptions \
-H "Authorization: Bearer glm_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xyz789",
"product_id": 12345,
"amount_cents": 9900,
"currency": "USD",
"interval": "monthly",
"trial_period_days": 14,
"payment_method_id": "pm_abc123"
}'
Annual Subscription
curl -X POST https://api.withgale.com/api/v2/subscriptions \
-H "Authorization: Bearer glm_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xyz789",
"product_id": 12346,
"amount_cents": 99900,
"currency": "USD",
"interval": "yearly",
"payment_method_id": "pm_abc123"
}'
Bi-weekly Subscription
curl -X POST https://api.withgale.com/api/v2/subscriptions \
-H "Authorization: Bearer glm_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_xyz789",
"merchant_product_id": "COACHING-BIWEEKLY",
"amount_cents": 4900,
"currency": "USD",
"interval": "weekly",
"interval_count": 2,
"payment_method_id": "pm_abc123"
}'
With JavaScript
const createSubscription = async (customerId, planDetails) => {
const response = await fetch('https://api.withgale.com/api/v2/subscriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GALE_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_id: customerId,
product_id: planDetails.productId,
amount_cents: planDetails.price * 100,
currency: 'USD',
interval: planDetails.interval,
trial_period_days: planDetails.trialDays,
payment_method_id: planDetails.paymentMethodId,
metadata: {
tier: planDetails.tier,
source: 'web_app'
}
})
});
if (!response.ok) {
throw new Error('Failed to create subscription');
}
const subscription = await response.json();
console.log(`Subscription created: ${subscription.id}`);
console.log(`Status: ${subscription.status}`);
console.log(`Next billing: ${subscription.next_billing_date}`);
return subscription;
};
// Usage
const subscription = await createSubscription('cus_xyz789', {
productId: 12345,
price: 99,
interval: 'monthly',
trialDays: 7,
paymentMethodId: 'pm_abc123',
tier: 'premium'
});
Use Cases
Membership Tiers
// Create subscription based on selected tier
const membershipTiers = {
basic: {
productId: 12345,
amount_cents: 4900,
features: 'basic_access'
},
premium: {
productId: 12346,
amount_cents: 9900,
features: 'unlimited_access,priority_support'
},
elite: {
productId: 12347,
amount_cents: 19900,
features: 'unlimited_access,priority_support,1on1_coaching'
}
};
const subscribeMember = async (customerId, tier, paymentMethodId) => {
const plan = membershipTiers[tier];
return await createSubscription({
customer_id: customerId,
product_id: plan.productId,
amount_cents: plan.amount_cents,
currency: 'USD',
interval: 'monthly',
trial_period_days: 14,
payment_method_id: paymentMethodId,
metadata: {
tier: tier,
features: plan.features
}
});
};
Post-Checkout Subscription Creation
// Create subscription after payment link is paid
app.post('/webhooks/gale', async (req, res) => {
const event = req.body;
if (event.type === 'payment_link.paid') {
const { payment_link, customer, payment_method } = event.data;
// Check if this was a subscription payment link
if (payment_link.payment_type === 'subscription') {
const subscription = await createSubscription({
customer_id: customer.id,
product_id: payment_link.product_id,
amount_cents: payment_link.amount_cents,
currency: payment_link.currency,
interval: payment_link.subscription.interval,
interval_count: payment_link.subscription.interval_count,
trial_period_days: payment_link.subscription.trial_period_days,
payment_method_id: payment_method.id
});
console.log(`Subscription ${subscription.id} created for customer ${customer.email}`);
}
}
res.status(200).send('OK');
});
Upgrade/Downgrade Flow
// Cancel old subscription and create new one
const changeSubscriptionTier = async (customerId, oldSubId, newTier) => {
// Cancel existing subscription at period end
await cancelSubscription(oldSubId, {
cancel_at_period_end: true
});
// Create new subscription
const newSub = await subscribeMember(
customerId,
newTier,
paymentMethodId
);
return {
oldSubscription: oldSubId,
newSubscription: newSub.id,
effectiveDate: newSub.current_period_start
};
};
Webhooks
Subscription creation triggers the following webhook event:{
"type": "subscription.created",
"data": {
"id": "sub_abc123xyz",
"customer_id": "cus_xyz789",
"status": "trialing",
"amount_cents": 9900,
"interval": "monthly",
"trial_end": "2025-10-25T14:30:00Z"
}
}
Errors
| Status Code | Error Code | Description |
|---|---|---|
| 400 | invalid_request | Missing or invalid parameters |
| 400 | invalid_payment_method | Payment method not valid |
| 401 | unauthorized | Invalid or missing API key |
| 404 | customer_not_found | Customer ID not found |
| 422 | validation_error | Field validation failed |
| 429 | rate_limit_exceeded | Too many requests |
{
"error": {
"code": "validation_error",
"message": "Invalid subscription parameters",
"details": [
{
"field": "amount_cents",
"message": "Amount must be a positive integer"
}
]
}
}
Best Practices
Offer Trial Periods
Use trials to reduce friction for new subscribers
Store Payment Method
Collect and verify payment method before trial ends
Handle Webhooks
Listen for subscription events for lifecycle management
Use Metadata
Track features and tiers in metadata
Important Notes
Payment method required. Even with a trial period, a valid payment method must be provided and will be verified during subscription creation.
HSA/FSA subscriptions. Ensure recurring charges are for eligible products/services. Some HSA/FSA administrators restrict recurring charges.
Related Endpoints
- Get Subscription - GET /api/v2/subscriptions/
- List Subscriptions - GET /api/v2/subscriptions
- Update Subscription - PUT /api/v2/subscriptions/
- Cancel Subscription - POST /api/v2/subscriptions//cancel
Related Resources
⌘I
Create Subscription
curl --request POST \
--url https://api.example.com/api/v2/subscriptionsimport requests
url = "https://api.example.com/api/v2/subscriptions"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/api/v2/subscriptions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v2/subscriptions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v2/subscriptions"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v2/subscriptions")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v2/subscriptions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body