Documentation Index
Fetch the complete documentation index at: https://docs.flexprice.io/llms.txt
Use this file to discover all available pages before exploring further.
Overview
A trial is a window where customers are on the paid plan schedule but billing has not begun yet in the recurring sense described below.
Where trial_period_days comes from
- Pass
trial_period_days on POST /v1/subscriptions to override the plan for that subscription only.
- Or omit it. Every recurring fixed price on the linked plan contributes the same
trial_period_days (otherwise create fails validation).
Two invoices developers care about
- Trial start (
SUBSCRIPTION_TRIAL_START): $0, finalized at subscribe time with the same line-item shape as real advance billing for trial_start → trial_end, but all amounts forced to 0 (preview + integrations). collection_method controls SUCCEEDED vs PENDING here; details in Trial-start invoice.
- Trial end (
SUBSCRIPTION_TRIAL_END): first payable billing window trial_end → trial_end + billing_period. payment_behavior drives failure handling here; details in What happens at trial end.
Typical reasons to use trials
- Product-led growth and self-serve upgrade paths
- Freemium-style evaluation before committing
- Time-boxed enterprise or partner pilots
How It Works
Trial subscriptions move through three statuses:
trialing -> (trial_end reached) -> incomplete -> (invoice paid / zero amount) -> active
| Status | What it means | Billing |
|---|
trialing | Trial window is active | SUBSCRIPTION_TRIAL_START: $0 finalized invoice for trial_start → trial_end (preview line items); subscription stays trialing |
incomplete | Trial ended; first paid invoice issued | Awaiting SUBSCRIPTION_TRIAL_END payment (unless amount is $0) |
active | Trial converted or non-trial start | Normal billing cycle |
Period fields while trialing
current_period_start = trial_start
current_period_end = trial_end
After trial_end, the billing anchor moves to trial_end. The first paid period is full-length: [trial_end, trial_end + billing_period]. There is no shortened first billing period after a trial.
Automation: trial-end processing is handled by Flexprice workflows. No manual cron or API is required for the trial_end transition itself.
Trial-start invoice
Billing reason (API string): SUBSCRIPTION_TRIAL_START.
Applies only when the subscription is created as non-draft trialing (trial window > 0 days).
What you get on create:
- Invoice period:
period_start = trial_start (same as start_date), period_end = trial_end, matching current_period_* while subscription_status is trialing.
- Amounts:
subtotal, total, amount_due, and each line item amount are 0 after compute. Quantity, price linkage, and display metadata stay, so customers see what will charge later.
- Subscription status: Remains
trialing. SUBSCRIPTION_TRIAL_START does not gate activation. Moving to active still depends on SUBSCRIPTION_TRIAL_END (or a non-trial create path).
collection_method and payment_behavior
Use these fields deliberately: they clarify how trial start differs from trial end.
| Topic | Role |
|---|
payment_behavior | Applies when generating / paying SUBSCRIPTION_TRIAL_END and broader subscription invoice payment flows (default_active, allow_incomplete, …). See What happens at trial end. |
collection_method | After finalize, $0 trial-start invoices are SUCCEEDED under charge_automatically or left PENDING under send_invoice so outbound invoice sync can capture a card without active. |
collection_method | Trial-start invoice payment_status (typical) |
|---|
charge_automatically | SUCCEEDED at $0 |
send_invoice | PENDING until host / PSP completes the synced $0 flow |
On POST /v1/subscriptions, latest_invoice is this document when subscription_status is trialing.
Configuring a Trial Period
- Define
trial_period_days on the plan’s recurring FIXED prices or
- Override on
POST /v1/subscriptions.
There is no trial_enabled flag. If trial_period_days > 0 after resolve (inherit vs override), the subscription enters trialing; 0 at subscription level disables trial even if the plan carries trial days.
Step 1: Set trial_period_days on the price
Constraints:
- Only
billing_cadence: RECURRING + price_type: FIXED combinations accept trial_period_days. Usage-only or unsupported tier geometries fail validation upstream.
- Omitted ⇒
0. That price contributes no inherited trial.
curl -X POST https://api.flexprice.io/v1/prices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 4900,
"currency": "USD",
"billing_cadence": "RECURRING",
"billing_period": "MONTHLY",
"billing_period_count": 1,
"price_type": "FIXED",
"trial_period_days": 14
}'
Response (abbreviated):
{
"id": "price_01abc123",
"billing_cadence": "RECURRING",
"price_type": "FIXED",
"trial_period_days": 14
}
Step 2: Create a subscription (trial inherited automatically)
When all recurring fixed prices on a plan share the same trial_period_days, that value is inherited by the subscription automatically. No trial fields are required in the subscription request.
curl -X POST https://api.flexprice.io/v1/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_01xyz",
"plan_id": "plan_01abc",
"currency": "USD",
"billing_cadence": "RECURRING",
"billing_period": "MONTHLY",
"billing_period_count": 1,
"start_date": "2025-05-01T00:00:00Z"
}'
Response (abbreviated; real payloads also include plan, customer, line items with full pricing metadata, and other subscription fields):
{
"id": "sub_01def456",
"subscription_status": "trialing",
"customer_id": "cust_01xyz",
"plan_id": "plan_01abc",
"trial_start": "2025-05-01T00:00:00Z",
"trial_end": "2025-05-15T00:00:00Z",
"current_period_start": "2025-05-01T00:00:00Z",
"current_period_end": "2025-05-15T00:00:00Z",
"collection_method": "charge_automatically",
"latest_invoice": {
"id": "inv_trial_start_abc",
"billing_reason": "SUBSCRIPTION_TRIAL_START",
"invoice_type": "SUBSCRIPTION",
"invoice_status": "FINALIZED",
"payment_status": "SUCCEEDED",
"currency": "USD",
"period_start": "2025-05-01T00:00:00Z",
"period_end": "2025-05-15T00:00:00Z",
"subtotal": "0",
"total": "0",
"amount_due": "0",
"amount_remaining": "0",
"line_items": [
{
"display_name": "Pro · monthly (trial preview)",
"amount": "0",
"quantity": "1"
}
]
}
}
Notes on the abbreviated response:
subscription_status: "trialing" is the canonical lifecycle enum; the payload may also expose a separate status field (SubscriptionResponse).
latest_invoice.billing_reason is SUBSCRIPTION_TRIAL_START on this $0 opening invoice. It stays visible / finalized, not SKIPPED, unlike typical zero-amount SUBSCRIPTION_CREATE invoices.
- If you use
send_invoice instead of charge_automatically, payment_status on that same invoice will usually be PENDING until PSP / outbound sync settles the $0 path.
Overriding trial days at subscription creation
Pass trial_period_days in the subscription request to override the plan-level value.
curl -X POST https://api.flexprice.io/v1/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_01xyz",
"plan_id": "plan_01abc",
"currency": "USD",
"billing_cadence": "RECURRING",
"billing_period": "MONTHLY",
"billing_period_count": 1,
"start_date": "2025-05-01T00:00:00Z",
"trial_period_days": 0
}'
| Value | Effect |
|---|
| Omitted | Inherits trial_period_days from plan prices |
0 | Disables trial; subscription starts as active immediately |
> 0 | Overrides plan-level value with this number of days |
To shorten or lengthen the plan-level trial, pass a positive integer:
curl -X POST https://api.flexprice.io/v1/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_01xyz",
"plan_id": "plan_01abc",
"currency": "USD",
"billing_cadence": "RECURRING",
"billing_period": "MONTHLY",
"billing_period_count": 1,
"start_date": "2025-05-01T00:00:00Z",
"trial_period_days": 30
}'
What Happens at Trial End
Trial end is detected automatically. When trial_end <= now:
- Subscription status changes to
incomplete.
- Billing anchor resets to
trial_end.
- A
SUBSCRIPTION_TRIAL_END invoice is created covering [trial_end, trial_end + billing_period].
- If the invoice amount is zero: subscription immediately converts to
active. No payment event needed.
- If the invoice amount is non-zero: subscription stays
incomplete until the invoice is paid.
An incomplete subscription does not auto-expire. It remains incomplete until the trial-end invoice is paid or the subscription is cancelled.
payment_behavior at trial end:
| Value | Behavior if payment fails |
|---|
default_active (default) | Subscription activates regardless of payment result |
allow_incomplete | Subscription stays incomplete if payment fails |
error_if_incomplete | Subscription stays incomplete if payment fails; wallet fallback is not attempted |
default_incomplete | Subscription stays incomplete; only valid with send_invoice collection method |
Credit grants held pending activation are applied automatically when the subscription becomes active.
API Reference
Price fields
| Field | Type | Required | Description |
|---|
trial_period_days | integer | No | Trial length inherited by subscriptions linking this recurring fixed price. Default 0 (no trial). Only valid for billing_cadence: RECURRING + price_type: FIXED. Must be >= 0. |
Subscription create fields
| Field | Type | Required | Description |
|---|
trial_period_days | integer | No | Override plan-level trial length in days. 0 disables trial. Omit to inherit from plan prices (all recurring fixed plan prices must share the same value). Must be >= 0. |
payment_behavior | enum | No | SUBSCRIPTION_TRIAL_END and broader subscription payment semantics when attempts fail (default_active, allow_incomplete, error_if_incomplete, default_incomplete). API normalizes unspecified creates toward default_active (not all combinations are valid with every collection_method). |
collection_method | enum | No | charge_automatically vs send_invoice. For SUBSCRIPTION_TRIAL_START, controls whether the $0 invoice is succeeded immediately or left pending for outbound invoicing/card capture. |
| Field | Type | Description |
|---|
subscription_status | string | trialing while the trial window is open. Prefer this for branching; status may also exist on SubscriptionResponse. |
trial_start | timestamp | Trial start (= start_date on create). |
trial_end | timestamp | When paid billing anchors; first SUBSCRIPTION_TRIAL_END period starts here. |
current_period_start | timestamp | During trial = trial_start. |
current_period_end | timestamp | During trial = trial_end. |
latest_invoice | object | On non-draft trialing creates, populated with SUBSCRIPTION_TRIAL_START. |
latest_invoice.billing_reason | string | SUBSCRIPTION_TRIAL_START, the $0 trial preview invoice. |
Webhooks & Events
Trial → paid conversion:
subscription.activated when subscription_status flips trialing → active.
- Raised after
SUBSCRIPTION_TRIAL_END settles (paid) or auto-completes at amount_due = 0 (no PSP event required).
SUBSCRIPTION_TRIAL_START emits no subscription.activated. subscription_status remains trialing until SUBSCRIPTION_TRIAL_END finishes, even if the opening invoice shows payment_status: SUCCEEDED.
There is no dedicated subscription.trialing, subscription.trial_ended, or subscription.trial_ending webhook. Use subscription.activated (and your own trial clock if needed) to detect conversion.
Example payload shape (truncated; your handler may receive more fields):
{
"subscription": {
"id": "sub_01def456",
"status": "active",
"trial_start": "2025-05-01T00:00:00Z",
"trial_end": "2025-05-15T00:00:00Z",
"current_period_start": "2025-05-15T00:00:00Z",
"current_period_end": "2025-06-15T00:00:00Z"
}
}
For information on configuring webhook endpoints, see Webhooks.
Edge Cases
| Scenario | Behavior |
|---|
trial_period_days = 0 at subscription level | Trial disabled even if plan price has trial_period_days > 0 |
Plan prices have mismatched trial_period_days | Subscription creation fails: "all recurring fixed plan prices must have the same trial_period_days" |
| Cancellation during trial | Standard cancellation applies. Trial-end processing skips subscriptions not in trialing status. |
Paused subscription when trial_end is reached | Trial-end processing is skipped. Runs when the subscription is unpaused. |
| Zero-amount trial-end invoice | Subscription auto-activates immediately without waiting for a payment event. |
Trial-start $0 invoice | Always created for trialing creates; SUBSCRIPTION_TRIAL_START stays visible (not SKIPPED) unlike other $0 subscription invoices |
| Inherited (child) subscriptions | Trial status cascades from parent. Children are not processed independently. |
| Re-trialing | Not supported. Each subscription has one trial window. To extend a trial, cancel and recreate the subscription. |
Best Practices
- Payment ahead of trial end. You can onboard trialing subscriptions without storing a card, but
SUBSCRIPTION_TRIAL_END still expects a collectible path whenever amount_due > 0.
send_invoice + outbound PSP sync. Keeps SUBSCRIPTION_TRIAL_START in PENDING until the hosted $0 handshake finishes. Prefer this pattern when the gateway captures cards solely behind invoice artifacts.
- Pin
payment_behavior in production. SUBSCRIPTION_TRIAL_END obeys it when autopay retries fail (allow_incomplete vs default_active, etc.).
- Split entitlements cleanly. Fire
subscription.activated once SUBSCRIPTION_TRIAL_END succeeded (or $0 short-circuit). SUBSCRIPTION_TRIAL_START leaves subscription_status at trialing even when payment_status reads SUCCEEDED.
- Cheap staging loops. Truly
$0 plan prices accelerate trialing → active because SUBSCRIPTION_TRIAL_END can finalize with nothing to capture.
- Single trial episode. No built-in replay; cancel + recreate is the sanctioned reset.
Configure webhook destinations in Webhooks (events: Webhooks & Events above).