Skip to main content

Billing

Manage subscriptions, preview plan changes, view payment history, and track usage. Plans and coupon validation endpoints are public — all subscription management endpoints require a valid session (JWT cookie or Bearer token).


List Plans​

GET https://api.indreelforge.com/api/plans

Returns all active plans ordered by tier. No authentication required.

curl https://api.indreelforge.com/api/plans

Response​

{
"plans": [
{
"id": 1,
"name": "Free",
"slug": "free",
"description": "Get started with IndReelForge",
"price_monthly": "0.00",
"price_annual": "0.00",
"annual_total": "0.00",
"upload_limit": 10,
"profile_limit": 2,
"seat_limit": 1,
"has_whitelabel": false,
"has_scheduling": false,
"has_analytics": false,
"has_priority_support": false,
"ffmpeg_minutes_monthly": 10,
"features": ["10 uploads / month", "2 connected profiles"],
"addons": [],
"sort_order": 0
},
{
"id": 2,
"name": "Basic",
"slug": "basic",
"description": "For individuals and creators",
"price_monthly": "19.00",
"price_annual": "15.83",
"annual_total": "190.00",
"upload_limit": 100,
"profile_limit": 5,
"seat_limit": 1,
"has_whitelabel": false,
"has_scheduling": true,
"has_analytics": true,
"has_priority_support": false,
"ffmpeg_minutes_monthly": 60,
"features": ["100 uploads / month", "5 connected profiles", "Scheduling", "Analytics"],
"addons": [
{ "slug": "profiles_5", "label": "+5 profiles", "price_monthly": "9.00" }
],
"sort_order": 1
}
]
}

upload_limit is the string "unlimited" for plans with no cap.


Get Plan by Slug​

GET https://api.indreelforge.com/api/plans/:slug

Returns a single active plan. No authentication required.

ParameterTypeRequiredDescription
slugstring (path)YesPlan identifier: free, basic, professional, advanced, or business
curl https://api.indreelforge.com/api/plans/professional

Response​

{
"plan": {
"id": 3,
"name": "Professional",
"slug": "professional",
"description": "For growing teams",
"price_monthly": "49.00",
"price_annual": "40.83",
"annual_total": "490.00",
"upload_limit": 500,
"profile_limit": 15,
"seat_limit": 3,
"has_whitelabel": false,
"has_scheduling": true,
"has_analytics": true,
"has_priority_support": true,
"ffmpeg_minutes_monthly": 300,
"features": ["500 uploads / month", "15 connected profiles", "3 team seats", "Priority support"],
"addons": []
}
}

Returns 404 if the slug does not match an active plan.


Get Subscription​

GET https://api.indreelforge.com/api/billing/subscription

Returns the current subscription state for the authenticated user, including plan limits, billing status, and saved payment method details. Free users receive a synthesised subscription object rather than null, so the response shape is consistent across all plan tiers.

Query ParameterTypeRequiredDescription
skip_payment_method1 or trueNoSkip the Paddle payment-method lookup. Useful when polling for subscription activation and the card info is not needed.
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
'https://api.indreelforge.com/api/billing/subscription'

Response — paid user​

{
"subscription": {
"id": "sub_01abc123",
"plan_slug": "professional",
"plan_name": "Professional",
"billing_cycle": "monthly",
"status": "active",
"current_period_start": "2026-04-01T00:00:00.000Z",
"current_period_end": "2026-05-01T00:00:00.000Z",
"cancel_at_period_end": false,
"cancelled_at": null,
"pending_plan_change": null,
"pending_billing_cycle": null,
"scheduled_change_at": null,
"price": "49.00",
"annual_total": "490.00",
"upload_limit": 500,
"profile_limit": 15,
"seat_limit": 3,
"ffmpeg_minutes_monthly": 300,
"has_scheduling": true,
"has_analytics": true,
"has_whitelabel": false,
"is_managed": false
},
"payment_method": {
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2027
},
"is_free": false
}

Response — free user​

{
"subscription": {
"id": null,
"plan_slug": "free",
"plan_name": "Free",
"billing_cycle": "monthly",
"status": "free",
"current_period_start": null,
"current_period_end": null,
"cancel_at_period_end": false,
"cancelled_at": null,
"pending_plan_change": null,
"pending_billing_cycle": null,
"scheduled_change_at": null,
"price": 0,
"annual_total": 0,
"upload_limit": 10,
"profile_limit": 2,
"seat_limit": 1,
"ffmpeg_minutes_monthly": 0,
"has_scheduling": false,
"has_analytics": false,
"has_whitelabel": false,
"is_managed": false
},
"payment_method": null,
"is_free": true
}

subscription.status values: free, active, trialing, past_due, pending_downgrade, cancelled, expired.
payment_method is null when no card is saved or when skip_payment_method=1 is passed.
is_managed: true indicates an admin-granted plan that has no underlying Paddle subscription — payment-related UI actions should be hidden.


Change Plan​

POST https://api.indreelforge.com/api/billing/change-plan

Schedules a plan change or billing-cycle switch. Requires an active Paddle subscription; free users must go through the checkout flow. Requires 2FA to be verified on the current session.

Both upgrades and downgrades take effect at the end of the current billing period — the user keeps their current plan until then.

Passing planSlug: "free" cancels the subscription at the end of the current period (equivalent to calling POST /cancel).

Body ParameterTypeRequiredDescription
planSlugstringYesTarget plan: free, basic, professional, advanced, or business
billingCyclestringNomonthly (default) or annual
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"planSlug": "advanced", "billingCycle": "annual"}' \
-X POST https://api.indreelforge.com/api/billing/change-plan

Response​

{
"success": true,
"effective": "next_billing_cycle",
"scheduled_at": "2026-05-01T00:00:00.000Z",
"message": "Your plan will upgrade to Advanced at the start of your next billing cycle (5/1/2026). You keep your current plan until then."
}

Error responses​

StatuscodeDescription
400NO_SUBSCRIPTIONNo active subscription exists; use the checkout flow
400—Already on this plan and billing cycle
400NO_PADDLE_PRICEPaddle price ID not configured for this plan/cycle
402PAST_DUELast payment failed; update payment method first
403PADDLE_FORBIDDENPlan change must be completed via Paddle customer portal; portal_url included in the response
409BILLING_LOCK_TIMEOUTA concurrent billing operation is in progress

Preview Plan Change​

POST https://api.indreelforge.com/api/billing/preview-change

Returns a cost preview for switching to a different plan or billing cycle. Uses Paddle's proration API when available, falling back to a local price comparison for users without an active subscription.

No 2FA required — this is a read-only operation.

Body ParameterTypeRequiredDescription
planSlugstringYesTarget plan slug
billingCyclestringNomonthly (default) or annual
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"planSlug": "business", "billingCycle": "monthly"}' \
-X POST https://api.indreelforge.com/api/billing/preview-change

Response​

{
"current_plan": "professional",
"new_plan": "business",
"current_price": 49,
"new_price": 99,
"is_upgrade": true,
"is_downgrade": false,
"effective": "next_billing_cycle",
"prorated_immediate_charge": null,
"next_billed_amount": 99.00,
"next_billed_at": "2026-05-01T00:00:00.000Z",
"used_paddle_preview": true,
"message": "This upgrade takes effect at the start of your next billing cycle. You keep your current plan until then."
}

prorated_immediate_charge and next_billed_amount are null when Paddle preview is unavailable (free users or Paddle misconfigured).
used_paddle_preview: true means the amounts are authoritative from Paddle; false means they are local estimates.


Cancel Subscription​

POST https://api.indreelforge.com/api/billing/cancel

Schedules the subscription to cancel at the end of the current billing period. The user keeps full access until then. Requires 2FA.

Body ParameterTypeRequiredDescription
reasonstringNoCancellation reason: too_expensive, missing_features, switching_competitor, not_using, technical_issues, temporary_pause, or other
other_textstringNoFree-text explanation (max 500 chars); only recorded when reason is other
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"reason": "too_expensive"}' \
-X POST https://api.indreelforge.com/api/billing/cancel

Response​

{
"success": true,
"message": "Your subscription will end on 5/1/2026. You will keep access until then.",
"ends_at": "2026-05-01T00:00:00.000Z"
}

Returns 400 if there is no active subscription to cancel, or 409 with BILLING_LOCK_TIMEOUT if a concurrent billing operation is in progress.


Reactivate Subscription​

POST https://api.indreelforge.com/api/billing/reactivate

Reverses a scheduled cancellation. Only valid when cancel_at_period_end is true on the current subscription. Requires 2FA.

No request body is needed.

curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-X POST https://api.indreelforge.com/api/billing/reactivate

Response​

{
"success": true,
"message": "Your subscription has been reactivated. It will continue to renew on the next billing date."
}

Returns 400 if no scheduled cancellation is found, or 409 with BILLING_LOCK_TIMEOUT if a concurrent billing operation is in progress.


Payment History​

GET https://api.indreelforge.com/api/billing/payments

Returns a paginated list of payments for the authenticated user.

Query ParameterTypeRequiredDescription
pageintegerNoPage number, default 1
limitintegerNoResults per page, default 20, max 100
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
'https://api.indreelforge.com/api/billing/payments?page=1&limit=20'

Response​

{
"payments": [
{
"id": "pay_01abc",
"plan_slug": "professional",
"plan_name": "Professional",
"amount": "49.00",
"currency": "USD",
"billing_cycle": "monthly",
"provider": "paddle",
"status": "completed",
"provider_invoice_url": "https://invoice.paddle.com/inv/...",
"provider_invoice_pdf": null,
"created_at": "2026-04-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 6
}
}

Fetch Invoice​

GET https://api.indreelforge.com/api/billing/payments/:id/invoice

Returns a URL for the hosted invoice or PDF for a specific payment. If a cached URL exists it is returned immediately; otherwise the URL is fetched from Paddle, cached in the database, and then returned.

ParameterTypeRequiredDescription
idstring (path)YesPayment record ID
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
https://api.indreelforge.com/api/billing/payments/pay_01abc/invoice

Response​

{
"url": "https://invoice.paddle.com/inv/live_abc123..."
}

Returns 404 if the payment is not found, belongs to another user, or Paddle has not yet generated an invoice for the transaction.


Upcoming Charge​

GET https://api.indreelforge.com/api/billing/upcoming

Returns the next expected charge synthesised from the current subscription period end date. Currency is derived from the user's most recent completed payment to correctly reflect non-USD billing.

curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
https://api.indreelforge.com/api/billing/upcoming

Response — active subscription​

{
"upcoming": {
"amount_due": 49.00,
"currency": "USD",
"period_start": null,
"period_end": 1746057600,
"next_payment_attempt": 1746057600
}
}

period_end and next_payment_attempt are Unix timestamps. Returns { "upcoming": null } when the user has no active or past-due subscription.


Usage Metrics​

GET https://api.indreelforge.com/api/billing/usage

Returns the current billing-period usage against plan limits. Profile limits include any active add-on slots purchased on top of the base plan.

curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
https://api.indreelforge.com/api/billing/usage

Response​

{
"uploads": {
"used": 42,
"limit": 500,
"resets_at": "2026-05-01T00:00:00.000Z"
},
"profiles": {
"used": 8,
"limit": 20,
"base_limit": 15,
"addon_slots": 5
},
"api_keys": {
"used": 2
},
"ffmpeg": {
"used": 37,
"limit": 300,
"resets_at": "2026-05-01T00:00:00.000Z"
},
"features": {
"has_scheduling": true,
"has_analytics": true,
"has_whitelabel": false
},
"billing_cycle": {
"start": "2026-04-01T00:00:00.000Z",
"end": "2026-05-01T00:00:00.000Z"
}
}

uploads.limit is null for plans with unlimited uploads. billing_cycle.start is null for free users. resets_at reflects the subscription current_period_end for paid users and the monthly reset timestamp for free users.


Validate Coupon​

POST https://api.indreelforge.com/api/coupons/validate

Validates a coupon code before checkout. Checks expiry, usage caps, plan restrictions, per-user limits, and first-time-customer eligibility. The coupon code is always re-validated server-side at checkout; this endpoint exists for pre-checkout UX only. Requires authentication.

Body ParameterTypeRequiredDescription
codestringYesCoupon code (case-insensitive)
planSlugstringNoIntended plan slug. When provided, plan-restricted coupons are validated against it
curl \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"code": "LAUNCH50", "planSlug": "professional"}' \
-X POST https://api.indreelforge.com/api/coupons/validate

Response — valid coupon​

{
"valid": true,
"id": "cpn_01abc",
"discount_type": "percent",
"discount_value": 50,
"influencer_name": null
}

discount_type is either percent or fixed. The coupon code is intentionally not echoed back in the response.

Response — invalid coupon​

{
"valid": false,
"error": "Invalid, disabled, expired, or fully redeemed coupon",
"code": "INVALID"
}
code valueReason
INVALIDCode not found, disabled, expired, or max uses reached
PLAN_MISMATCHCoupon is restricted to specific plans and the provided planSlug is not among them
PER_USER_LIMITThe authenticated user has already redeemed this coupon the maximum number of times
NOT_FIRST_TIMECoupon is for first-time customers only and the user has a prior completed payment