Subscription & Plan Enforcement
Lightning Enable enforces active subscriptions and plan-specific feature access on every API request. This page explains how plan tiers work, what happens when a subscription lapses, and how feature gating controls access to plan-specific functionality.
Plan Tiers
Lightning Enable has three plan tiers. Pricing is based on capabilities, never transaction volume. Platform integrations (Shopify Commerce) are included with any paid plan.
| Plan | Tier ID | Price | Stripe subscription |
|---|---|---|---|
| Free Producer Sandbox | free | $0 | Not required |
| Agentic Commerce | individual | $49/month | Required |
| Agentic Commerce — Business | l402 | Contact us | Required |
Agentic Commerce includes a 30-day free trial through self-serve checkout; neither paid plan offers annual billing. Free is the floor: an account with no plan tier set resolves to Free, so there is no paid default.
Agentic Commerce — Business is contact-only: it is not purchasable through /Checkout, /BitcoinCheckout, POST /api/stripe/create-checkout-session, or POST /api/bitcoin/create-subscription — a request naming it on any of those surfaces is rejected with a 400. To subscribe, email support@lightningenable.com. Existing Business subscribers keep everything shown in this page's tables (feature entitlements, limits, renewal) unaffected.
Retired tier ids
As of September 2026, three tiers exist. The pilot, standalone (also spelled standaloneapi), and standard (Kentico Commerce) tiers were removed after a production snapshot confirmed no account was on any of them.
Their spellings still resolve, so an account created before the change and an old checkout link both keep working:
| Retired id | Resolves to |
|---|---|
standard, kenticocommerce, kentico-commerce, kentico | individual |
standalone, standaloneapi, standalone-api | individual |
pilot | free |
Retired paid ids resolve to Individual rather than Free so an existing subscriber is never silently downgraded.
Resolving is not the same as selecting. You cannot start anything new on a retired tier — POST /api/stripe/create-checkout-session and POST /api/bitcoin/create-subscription reject a retired id with a 400. The admin endpoint PUT /api/admin/merchants/{id} still accepts one and rewrites it to the live tier it maps to; a tier id that is neither live nor retired is rejected with a 400 that names the accepted values.
Feature Comparison
Every plan includes core API access. Higher tiers unlock additional capabilities.
| Feature | Free Producer Sandbox | Agentic Commerce | Agentic Commerce — Business |
|---|---|---|---|
| Full REST API | Yes | Yes | Yes |
| Lightning Network payments | Yes | Yes | Yes |
| Multi-currency (USD, EUR, GBP, BTC) | No | Yes | Yes |
| Analytics | No | Yes | Yes |
| Priority support | No | Yes | Yes |
| Max environments | 1 | 2 | 2 |
| Max webhook endpoints | 1 | 5 | 5 |
| Platform integrations (Shopify) | No | Yes | Yes |
| L402 protocol (server-side) | Capped | Yes | Yes |
| Pay-per-request monetization | Capped | Yes | Yes |
| MCP AI agent integration | Yes | Yes | Yes |
| White-glove onboarding | No | No | Yes |
| Custom branding | No | No | No |
Free is capped rather than unlimited: 3 L402 endpoints, 200 challenges per month, 1,000 sats maximum per challenge, and 1 proxy configuration. The endpoint cap counts distinct resource paths for the life of the account; the challenge cap resets monthly.
Checking Your Plan
Use the merchant settings endpoint to see your current plan and features:
curl https://api.lightningenable.com/api/merchant/me \
-H "X-API-Key: YOUR_API_KEY"
The response includes your planTier, subscriptionStatus, and a features object with all feature flags.
Free Trial
Lightning Enable offers a 30-day free trial on self-serve Agentic Commerce checkout, giving you full API access before your first payment.
How It Works
- Eligible plan: Agentic Commerce ($49/mo), via self-serve checkout
- Duration: 30 days from the date of subscription
- Card required: A valid payment method must be provided at signup. You will not be charged during the trial period.
- Full access: Trial merchants have complete API access, identical to a paid subscription. The
subscriptionStatuswill showtrialing. - Auto-converts: At the end of the 30-day trial, the subscription automatically converts to a paid plan. Your card on file will be charged at the plan's regular rate.
- Cancel anytime: You can cancel before the trial ends to avoid being charged. Use the Stripe customer portal to manage your subscription.
Agentic Commerce — Business is contact-only and does not go through this self-serve trial flow. Email support@lightningenable.com and any trial terms will be arranged directly.
Abuse Prevention
To maintain fair access, Lightning Enable enforces one free trial per email address. If a customer has previously used a trial (on any plan), subsequent subscriptions will skip the trial period and begin billing immediately.
Trial Eligibility by Plan
| Plan | Trial Eligible |
|---|---|
| Free Producer Sandbox ($0) | No -- Free is the floor, not a trial |
| Agentic Commerce ($49/mo) | Yes, via self-serve checkout |
| Agentic Commerce -- Business (contact us) | Arranged directly on contact, not via self-serve checkout |
Subscription Lifecycle
Valid Subscription States
The subscription enforcement middleware checks every authenticated API request. Only two statuses grant access:
| Status | Meaning | API Access |
|---|---|---|
active | Subscription is current and paid | Allowed |
trialing | In free trial period | Allowed |
past_due | Payment failed, awaiting retry | Blocked |
canceled | Subscription was canceled | Blocked |
unpaid | Payment not received | Blocked |
incomplete | Initial payment not completed | Blocked |
incomplete_expired | Initial payment window expired | Blocked |
Subscription Validation Flow
The middleware performs these checks in order for every authenticated request:
- Path exemption -- Certain paths skip subscription checks entirely (Stripe endpoints, webhooks, health checks, Swagger).
- Account active check -- If the merchant account is deactivated (
isActive = false), the request is immediately blocked with a403. - Free carve-out -- An account whose tier resolves to
freeskips the subscription checks entirely, because Free requires no Stripe subscription. Capacity is enforced elsewhere, by the Free caps on the L402 and proxy endpoints. Two guards narrow this carve-out (see Free carve-out guards below). - L402 fast-lane trial carve-out -- An account created by the L402 Fast Lane also skips the subscription checks for the length of its trial (see L402 fast-lane trial below).
- Stripe subscription required -- Every account that took no carve-out must have a valid
StripeSubscriptionId. Accounts without one receive a403withaction_required: "subscribe". - Subscription status check -- The status must be
activeortrialing. Any other status returns a403with a status-specific message. - Billing period validation -- If
CurrentPeriodEndis set and has passed, the request is blocked even if the status field still showsactive. This catches expired subscriptions before the Stripe webhook updates the status. - Feature gating -- Plan-specific features are checked against the requested endpoint (see Feature Gating below). This step runs on every path that reached it, carve-outs included, so a Free account cannot reach a paid-only endpoint just because the subscription check was skipped.
Request
│
├─ Exempt path? ──── Yes ──→ Allow
│
├─ No MerchantId? ── Yes ──→ Allow (unauthenticated)
│
├─ Account inactive? ────── → 403 "Account inactive"
│
├─ Tier resolves to free
│ (and may take a carve-out)? ─ Yes ──→ Feature gate ──→ Allow
│
├─ L402 fast-lane trial,
│ still within CurrentPeriodEnd? ─ Yes ─→ Feature gate ──→ Allow
│
├─ No Stripe sub? ─────────→ 403 "Subscription required"
│
├─ Status not active/trialing? → 403 "Subscription not active"
│
├─ CurrentPeriodEnd passed? ──→ 403 "Subscription period expired"
│
├─ Feature not available? ────→ 403 "Feature not available"
│
└─ All checks pass ─────────→ Allow (features set in context)
Free carve-out guards
The Free carve-out grants API access with no Stripe subscription, so two states are deliberately excluded from it:
- An unrecognized tier id. A tier value that is neither live nor retired — an operator typo, say — does not take the carve-out. It falls through to the subscription checks, which fail closed. Reading an unrecognized value as Free would hand it permanent unpaid access.
- A blank tier that carries Stripe billing state. A blank tier normally means "never set", and Free is the right reading. A blank tier on a row that also holds a
stripeCustomerIdorstripeSubscriptionIdis a data anomaly, not a Free account, so it faces the subscription checks. Otherwise a canceled subscription would regain full access by way of the carve-out.
A blank tier with no billing state is the ordinary case and is admitted as Free.
L402 fast-lane trial
An account created through the L402 Fast Lane has no Stripe subscription — the 100-sat Lightning payment is the proof of intent — so it needs a carve-out of its own. It applies only when all four of these hold:
- the stored tier is exactly
individual subscriptionStatusistrialing- both
stripeSubscriptionIdandstripeCustomerIdare empty currentPeriodEndis in the future
The first condition matches the stored value, not the resolved one. That is deliberate and narrower than it looks: an account stored under a retired id resolves to individual everywhere else but does not take this carve-out, because the background job that ends the trial filters on the stored string in SQL and could not find it either. Matching wider here than the job can find would turn a bounded trial into permanent unpaid access.
The trial ends by way of a background job that downgrades the account to Free once trialEnd passes with no Stripe conversion. That job additionally requires the account to be marked as fast-lane-originated and not to have billing deferred, so an account with deferred billing stays on Individual rather than being downgraded. Adding billing mid-trial bills immediately and does not start a second trial.
Subscription Expiration
What Happens When a Subscription Expires
When a subscription expires or is canceled, API requests return 403 Forbidden with a JSON body describing the issue and what action to take.
Canceled subscription:
{
"error": "Subscription not active",
"message": "Your subscription has been canceled. Please subscribe again to continue using the service.",
"subscription_status": "canceled",
"action_required": "renew_subscription"
}
Past due payment:
{
"error": "Subscription not active",
"message": "Your subscription payment is past due. Please update your payment method to continue using the service.",
"subscription_status": "past_due",
"action_required": "update_payment_method"
}
Billing period expired (webhook delay protection):
{
"error": "Subscription period expired",
"message": "Your subscription billing period has expired. Please renew your subscription to continue using the service.",
"subscription_status": "active",
"current_period_end": "2026-01-15T00:00:00.0000000Z",
"action_required": "renew_subscription"
}
Even if the subscription status still reads active, the middleware checks whether CurrentPeriodEnd has passed. This provides a safety net for cases where Stripe webhook delivery is delayed, ensuring expired subscriptions are caught in near-real-time.
Grace Period Behavior
Lightning Enable relies on Stripe's built-in retry and grace period logic:
- Stripe retries failed payments automatically according to your Stripe account's Smart Retries settings (typically 3-4 attempts over several days).
- During retries, the subscription status transitions to
past_due. API access is blocked during this period. - If all retries fail, Stripe marks the subscription as
canceledorunpaiddepending on your Stripe settings. - There is no additional grace period built into Lightning Enable beyond what Stripe provides. The moment the subscription status leaves
activeortrialing, API access is blocked.
To restore access after a lapsed subscription:
- Update your payment method via the Stripe customer portal.
- Or subscribe again at lightningenable.com.
Exempt Paths
These paths are never subject to subscription enforcement, even for expired accounts:
| Path | Reason |
|---|---|
/api/stripe/create-checkout-session | Must be accessible to (re)subscribe |
/api/stripe/customer-portal | Must be accessible to manage billing |
/api/stripe/subscription | Must be accessible to check status |
/api/stripe/pricing | Public pricing information |
/api/webhooks/stripe | Incoming Stripe webhooks |
/api/webhooks/opennode | Incoming OpenNode webhooks |
/api/webhooks/strike | Incoming Strike webhooks |
/api/l402/pricing | L402 demo pricing (uses L402 token auth) |
/api/l402/status | L402 demo status (uses L402 token auth) |
/api/l402/demo | L402 demo endpoint (uses L402 token auth) |
/api/l402/premium-data | L402 demo premium data (uses L402 token auth) |
/api/l402/content | L402-protected premium guides (uses L402 token auth) |
/api/manifests | Public manifest registry |
/l402/test | L402 public test endpoint (1-sat ping) |
/health | Health check |
/swagger | API documentation |
Only the specific L402 demo paths listed above are exempt. The L402 producer API (/api/l402/challenges and /api/l402/challenges/verify) is subscription-enforced and feature-gated — it requires an active Agentic Commerce subscription with L402Enabled.
This ensures merchants can always manage their subscription and billing even when their API access is blocked.
Evaluating without a subscription
Two paths give you API access with no Stripe subscription. Both are self-serve; neither needs an administrator.
| Path | Tier | Duration | Limits |
|---|---|---|---|
| Free Producer Sandbox | free | Indefinite | 3 endpoints, 200 challenges/month, 1,000 sats max per challenge, 1 proxy |
| L402 Fast Lane | individual | 30 days | None — a full Individual trial |
The admin-created pilot tier that used to serve this purpose was removed in September 2026. An account still stored as pilot resolves to free and takes the Free carve-out, so it keeps working at Free capacity.
Feature Gating
Beyond subscription status, the middleware enforces plan-specific feature access on certain endpoints.
Gated Features
Each gate reads a per-account flag, not the plan table. Plan changes set those flags, but an operator can also set them individually.
| Feature | Gated Endpoint | Account flag | required_plan | action_required |
|---|---|---|---|---|
refunds | /api/refunds/* | refundsEnabled | null | contact_support |
multi_currency | /api/payments/*/convert | multiCurrencyEnabled | individual | upgrade_plan |
l402 | /api/l402/challenges* | l402Enabled | individual | upgrade_plan |
If a merchant attempts to access a gated endpoint without the required feature flag, they receive:
{
"error": "Feature not available",
"message": "Refund processing is not enabled for your account. Please contact support.",
"feature": "refunds",
"current_plan": "free",
"required_plan": null,
"action_required": "contact_support"
}
No plan sets refundsEnabled — it is an operator-granted per-account flag. The refunds 403 therefore sends required_plan: null and action_required: "contact_support", because upgrading would not turn the feature on. Contact support@lightningenable.com instead.
feature, not on the tier nameKey your error handling on the feature field. current_plan is the normalized tier: it is always one of free, individual, or l402 for an account whose tier we recognize, even when the stored value is a retired spelling. There are two exceptions: an unrecognized tier value is echoed back raw so an operator can see what needs fixing, and a blank tier (no plan on file) is reported as null.
Feature Flags in Context
When a request passes all subscription and feature checks, the middleware populates MerchantFeatures in the request context. Controllers can use these flags for fine-grained access control:
| Feature Flag | Type | Description |
|---|---|---|
RefundsEnabled | boolean | Can process refunds |
MultiCurrencyEnabled | boolean | Can use multi-currency conversion |
MaxWebhookEndpoints | int | Maximum webhook endpoints allowed |
AnalyticsEnabled | boolean | Access to analytics |
PrioritySupport | boolean | Priority support access |
CustomBrandingEnabled | boolean | Custom branding on checkout |
L402 Feature Gating
L402 server-side features (creating proxies, configuring endpoint pricing, the producer API) are available on every live tier. Access is controlled by the l402Enabled flag on the merchant account, which the plan sets to true on all three. What differs is capacity, not availability: Free is capped, both paid plans are not.
Check your L402 status:
curl https://api.lightningenable.com/api/merchant/l402-status \
-H "X-API-Key: YOUR_API_KEY"
| Plan | L402 Server-Side | Price |
|---|---|---|
| Free Producer Sandbox | Yes — capped at 3 endpoints, 200 challenges/mo, 1,000 sats per challenge | $0 |
| Agentic Commerce | Yes — uncapped | $49/mo |
| Agentic Commerce — Business | Yes — uncapped | Contact us |
The MCP server's L402 client tools (access_l402_resource, pay_l402_challenge) are free for everyone. No subscription is needed to pay L402 invoices -- only to create L402-protected endpoints.
Handling Subscription Errors in Your Integration
Detecting Subscription Issues
All subscription-related errors return HTTP 403 with an action_required field. Use this field to determine the appropriate response:
action_required | Meaning | Recommended Action |
|---|---|---|
contact_support | Account deactivated, or a feature that no plan grants (refunds) | Contact support@lightningenable.com. Do not offer an upgrade |
subscribe | No active subscription | Redirect to subscription page |
update_payment_method | Payment failed | Redirect to Stripe customer portal |
renew_subscription | Subscription expired or canceled | Redirect to subscription page |
upgrade_plan | Feature requires a different plan | Show upgrade options for required_plan |
Branch on action_required, not on error. contact_support covers two different causes, and error tells you which: "Account inactive" versus "Feature not available".
Example Error Handler
async function callLightningEnableApi(endpoint) {
const response = await fetch(`https://api.lightningenable.com${endpoint}`, {
headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY }
});
if (response.status === 403) {
const error = await response.json();
switch (error.action_required) {
case 'subscribe':
case 'renew_subscription':
console.error('Subscription issue:', error.message);
// Redirect user to subscription page
break;
case 'update_payment_method':
console.error('Payment issue:', error.message);
// Redirect user to Stripe customer portal
break;
case 'upgrade_plan':
// required_plan is null when no plan grants the feature, so guard it.
if (error.required_plan) {
console.error(`Feature "${error.feature}" requires the ${error.required_plan} plan`);
// Show upgrade options
} else {
console.error(`Feature "${error.feature}" is not available on any plan:`, error.message);
// Point the user at support, not at checkout
}
break;
case 'contact_support':
// Two causes: a deactivated account, or a feature no plan grants
// (refunds). error.feature is present only for the second.
if (error.feature) {
console.error(`Feature "${error.feature}" must be enabled by support:`, error.message);
} else {
console.error('Account issue:', error.message);
}
break;
}
throw new Error(error.message);
}
return response.json();
}
Next Steps
- Product Overview -- Compare plan features
- Error Code Reference -- All API error codes
- Merchant Settings -- Check subscription status via API
- FAQ -- Common questions about plans and pricing