Error Reference
This guide documents the error responses returned by the Lightning Enable API, organized by domain. Use this reference to implement robust error handling in your integration.
Error Response Format
Lightning Enable errors do not carry a machine-readable code field. Dispatch on the HTTP status code first, then on the error string if you need finer granularity.
Most errors return one of two shapes:
Short form — a single error field:
{
"error": "Invoice not found"
}
Long form — error plus a human-readable message:
{
"error": "Invalid API key",
"message": "The provided API key is invalid or inactive"
}
Some 403 Forbidden responses from subscription/feature enforcement add snake_case context fields alongside error and message:
| Field | Type | Present on | Description |
|---|---|---|---|
error | string | all errors | Short error identifier (e.g., "Invalid API key", "Subscription required") |
message | string | most errors | Human-readable description |
action_required | string | 403 subscription/feature errors | What to do next: contact_support, subscribe, update_payment_method, renew_subscription, upgrade_plan |
current_plan | string | some 403 errors | Your current plan tier |
required_plan | string | 403 feature errors | Plan tier that includes the feature |
subscription_status | string | some 403 errors | Current Stripe subscription status |
feature | string | 403 feature errors | The gated feature identifier |
Every error response also includes an X-Correlation-Id response header that you can use when contacting support. See Request Headers for details.
Validation Errors (Model Binding)
When a request body fails model binding or data-annotation validation (missing required fields, wrong types, out-of-range values), ASP.NET Core returns the standard RFC 9457 validation problem shape — not the {error} shape above:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Amount": [
"The field Amount must be between 0.00000001 and 21000000."
],
"OrderId": [
"The OrderId field is required."
]
},
"traceId": "00-a1b2c3d4e5f6...-01"
}
Check the Content-Type: validation problems are returned as application/problem+json, with per-field messages in the errors dictionary. Business-logic failures (e.g., duplicate order ID) use the {error} JSON shape instead.
Unhandled Exceptions
Unhandled server errors return a correlation ID for support inquiries:
{
"error": "An error occurred processing your request.",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-01-09T12:00:00Z"
}
The correlationId field in the JSON body matches the X-Correlation-Id response header. Include this value in any support requests to help us locate your request in our logs.
Error Sanitization (Production vs. Development)
In production, error responses for unhandled exceptions are sanitized to prevent leaking internal details. You will only see a generic error message, a correlation ID, and a timestamp.
In development, additional debugging fields are included:
{
"error": "An error occurred processing your request.",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-01-09T12:00:00Z",
"detail": "Specific exception message",
"exceptionType": "System.InvalidOperationException",
"stackTrace": "..."
}
The detail, exceptionType, and stackTrace fields are never included in production responses. Do not write code that depends on these fields in production.
HTTP Status Codes
Success Codes
| Code | Meaning | When Used |
|---|---|---|
200 OK | Request succeeded | GET, PUT, POST (when returning data) |
201 Created | Resource created | POST when creating payments, refunds, proxies |
204 No Content | Success, no response body | DELETE operations |
Client Error Codes
| Code | Meaning | Common Causes |
|---|---|---|
400 Bad Request | Invalid request | Missing required fields, invalid format, validation errors, duplicate order ID |
401 Unauthorized | Authentication failed | Missing/invalid API key, invalid webhook signature |
402 Payment Required | L402 payment needed | Accessing L402-protected endpoints without valid token |
403 Forbidden | Access denied | Inactive account, subscription issues, feature not available |
404 Not Found | Resource not found | Invalid invoice ID, order ID, proxy ID |
409 Conflict | Resource conflict | Rare — only raised by uncaught conflict exceptions. Duplicate order IDs return 400, not 409 |
429 Too Many Requests | Rate limit or auth-failure throttle | Too many requests in the window, or too many failed authentication attempts from your IP |
Server Error Codes
| Code | Meaning | When Used |
|---|---|---|
500 Internal Server Error | Server error | Unexpected errors, includes correlationId |
502 Bad Gateway | Upstream error | Payment provider/Stripe API failures, target API unreachable |
503 Service Unavailable | Temporary outage | Maintenance, circuit breaker open |
504 Gateway Timeout | Upstream timeout | Payment provider/target API timeout |
Authentication Errors
Missing API Key
HTTP Status: 401 Unauthorized
{
"error": "API key required",
"message": "Please provide API key in X-API-Key header"
}
Cause: Request missing the X-API-Key header.
Solution: Include your API key in every request:
curl -H "X-API-Key: YOUR_API_KEY" https://api.lightningenable.com/api/payments
Invalid API Key
HTTP Status: 401 Unauthorized
{
"error": "Invalid API key",
"message": "The provided API key is invalid or inactive"
}
Cause: API key doesn't match any active merchant account.
Solution:
- Verify you're using the correct API key (view it at Dashboard → Settings)
- Check if your account is active
- Contact support if you've lost your API key
Too Many Failed Authentication Attempts
HTTP Status: 429 Too Many Requests
Header: Retry-After: <seconds>
{
"error": "Too many failed authentication attempts",
"message": "Slow down and try again in 42 seconds"
}
Cause: More than 20 failed authentication attempts from your IP within a 60-second window. This is a per-IP throttle, separate from the general rate limiter.
Solution: Fix your API key — do not retry with the same key. Retrying failed authentications only keeps the throttle engaged. See Failed-Authentication Throttling.
Server Configuration Error
HTTP Status: 500 Internal Server Error
{
"error": "Server configuration error"
}
Cause: Server configuration issue on the Lightning Enable side.
Solution: Contact support@lightningenable.com with your correlation ID.
Subscription Errors
Account Inactive
HTTP Status: 403 Forbidden
{
"error": "Account inactive",
"message": "Your account is inactive. Please contact support.",
"action_required": "contact_support"
}
Cause: Merchant account has been deactivated.
Solution: Contact support@lightningenable.com to reactivate.
Subscription Required
HTTP Status: 403 Forbidden
{
"error": "Subscription required",
"message": "Your plan tier requires an active subscription. Please subscribe to continue using the service.",
"current_plan": "individual",
"action_required": "subscribe"
}
Cause: Paid-tier account without a valid Stripe subscription.
Solution: Subscribe at lightningenable.com.
Subscription Not Active
HTTP Status: 403 Forbidden
{
"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"
}
Possible Status Values:
| Status | Message summary | action_required |
|---|---|---|
past_due | Payment is past due | update_payment_method |
canceled | Subscription canceled | renew_subscription |
unpaid | Subscription unpaid | renew_subscription |
incomplete | Setup incomplete | renew_subscription |
incomplete_expired | Setup expired | renew_subscription |
action_required is update_payment_method only for past_due; every other inactive status returns renew_subscription. The message text varies per status.
Subscription Period Expired
HTTP Status: 403 Forbidden
{
"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"
}
Cause: The subscription's CurrentPeriodEnd has passed even though the status may still show active. This typically occurs when a Stripe webhook is delayed. The middleware catches expired billing periods as a safety net.
Solution: Renew your subscription or wait for Stripe to process the renewal. If your payment method is valid, this should resolve automatically once the Stripe webhook updates the billing period.
Feature Not Available
HTTP Status: 403 Forbidden
{
"error": "Feature not available",
"message": "L402 Agentic Commerce is not enabled for your plan. Upgrade to an Agentic Commerce plan to use the L402 producer API.",
"feature": "l402",
"current_plan": "standard",
"required_plan": "individual",
"action_required": "upgrade_plan"
}
Gated features:
feature | Endpoints | Notes |
|---|---|---|
refunds | /api/refunds | Requires refunds enabled on your account |
multi_currency | /api/payments/*convert* | Requires multi-currency enabled on your account |
l402 | /api/l402/challenges | Requires an Agentic Commerce plan (Individual or Business) |
See Subscription & Plan Enforcement for full details on plan tiers and feature gating.
Payment Errors
Payment Not Found
HTTP Status: 404 Not Found
{
"error": "Invoice 12345 not found for merchant 42"
}
Cause: Invoice ID doesn't exist or doesn't belong to your merchant account. Looking up a payment by order ID returns the same shape with an order-specific message ("Invoice not found for OrderId ORDER-123").
Solution: Verify the invoice ID is correct and belongs to your account.
Invalid Payment Request
HTTP Status: 400 Bad Request
Business-rule failures return the {error} shape with the specific reason:
{
"error": "Merchant 42 not found or inactive"
}
Field-level validation failures (amount out of range, missing order ID, malformed URL) return the validation problem shape instead.
Duplicate Order
HTTP Status: 400 Bad Request
{
"error": "Invoice already exists for OrderId ORDER-123"
}
Cause: A payment with this order ID already exists for your merchant account.
Solution: Use unique order IDs for each payment request. To safely retry a payment creation without risking duplicates, send an X-Idempotency-Key header — see Request Headers.
Duplicate orders return 400 Bad Request, not 409 Conflict. Match on the HTTP status plus the "Invoice already exists" prefix of the error string if you need to detect this case programmatically.
Refund Errors
Invalid Refund Request
HTTP Status: 400 Bad Request
{
"error": "Cannot refund invoice in status 'pending'. Only paid, underpaid, processing, or partially-refunded payments can be refunded."
}
Refundable Statuses: Only invoices with status paid, underpaid, processing, or refunded (for partial refunds) can be refunded.
Refund Not Found
HTTP Status: 404 Not Found
{
"error": "Refund 67890 not found for merchant 42"
}
Invoice Not Found (for refund)
HTTP Status: 400 Bad Request (on refund creation) or 404 Not Found (when listing refunds by invoice)
{
"error": "Invoice 12345 not found for merchant 42"
}
L402 Protocol Errors
Payment Required (402)
HTTP Status: 402 Payment Required
Headers:
WWW-Authenticate: L402 macaroon="AgEB...", invoice="lnbc..."
X-L402-Error: No Authorization header provided
Body:
{
"error": "Payment Required",
"message": "Pay the Lightning invoice to access this API",
"proxy": {
"id": "my-api-1234",
"name": "My API",
"description": "AI services monetized with Lightning"
},
"l402": {
"macaroon": "AgEBYXBpLmxpZ2h0bmluZ2VuYWJsZS5jb20...",
"invoice": "lnbc100n1pj...",
"amount_sats": 100,
"payment_hash": "abc123...",
"expires_at": "2026-01-09T13:00:00Z"
},
"instructions": {
"step1": "Pay the Lightning invoice using any Lightning wallet",
"step2": "Copy the preimage (proof of payment) from your wallet",
"step3": "Include in request: Authorization: L402 <macaroon>:<preimage> (or Authorization: Payment method=\"lightning\", preimage=\"<preimage>\")"
}
}
Invalid L402 Credential
HTTP Status: 402 Payment Required
Header: X-L402-Error: Invalid L402 format. Expected: L402 <macaroon>:<preimage>
Common L402 Errors:
| Error Message | Cause |
|---|---|
| No Authorization header provided | Missing Authorization header |
| Invalid authorization scheme | Using Basic/Bearer instead of L402 |
| Invalid L402 format | Malformed macaroon:preimage format |
| Preimage does not match payment hash | Incorrect preimage |
| L402 verification failed | Invalid or expired macaroon |
Proxy Not Found
HTTP Status: 404 Not Found
{
"error": "Proxy not found",
"message": "No active proxy configuration found for ID: invalid-proxy"
}
Proxy Unavailable
HTTP Status: 404 Not Found
{
"error": "Proxy unavailable",
"message": "This API proxy is currently unavailable"
}
Cause: The merchant account owning the proxy is inactive.
L402 Proxy Gateway Errors
Bad Gateway (502):
{
"error": "Bad Gateway",
"message": "Unable to connect to the target API",
"proxy_id": "my-api-1234",
"details": "Connection refused"
}
Gateway Timeout (504):
{
"error": "Gateway Timeout",
"message": "The target API did not respond in time",
"proxy_id": "my-api-1234"
}
Webhook Errors
Invalid Webhook Payload
HTTP Status: 400 Bad Request
{
"error": "Invalid JSON",
"details": "Invalid webhook payload format"
}
Invalid Payload Structure
HTTP Status: 400 Bad Request
{
"error": "Invalid payload"
}
Invoice Not Found (Webhook)
HTTP Status: 404 Not Found
{
"error": "Invoice not found"
}
Cause: Webhook received for unknown charge ID (provider charge ID not matched).
Invalid Webhook Signature
HTTP Status: 401 Unauthorized
{
"error": "Invalid signature"
}
Cause: Payment provider webhook signature verification failed.
Solution: Ensure your payment provider API key and webhook secret are correctly configured.
Webhook Signature Required (Production)
HTTP Status: 401 Unauthorized
{
"error": "Webhook signature verification required in production"
}
Cause: In production, all webhooks must include verifiable signatures.
Invalid Stripe Signature
HTTP Status: 400 Bad Request
{
"error": "Invalid signature"
}
Cause: Stripe webhook signature verification failed.
Rate Limiting Errors
Rate Limit Exceeded
HTTP Status: 429 Too Many Requests
{
"error": "Too many requests",
"message": "Rate limit exceeded. Please try again later.",
"retryAfter": 60
}
The general rate limiter does not emit X-RateLimit-* or Retry-After headers. The wait time is in the JSON body's retryAfter field (seconds). Only the auth-failure throttle sets a Retry-After header.
Rate Limits by Policy:
| Policy | Limit | Window | Endpoints |
|---|---|---|---|
| Global | 100 | 1 min | All requests (per API key, or per IP when anonymous) |
| Read | 200 | 1 min | GET operations |
| Payment Create | 10 | 1 min | POST /api/payments, POST /api/refunds, /api/checkout/* |
| Write | 20 | 1 min | Merchant self-service writes (e.g., key regeneration) |
| Checkout Create | 5 | 1 min | Stripe checkout session creation |
| Admin | 30 | 1 min | Internal admin endpoints |
Solution: Wait for the number of seconds in the body's retryAfter field, then retry. See Rate Limiting for backoff strategies.
Auth-Failure Throttle (Distinct 429)
A separate 429 — see Too Many Failed Authentication Attempts under Authentication Errors above. Distinguish the two by the error string: "Too many requests" (rate limiter, wait and retry) vs. "Too many failed authentication attempts" (auth throttle, fix your key).
Payment Provider Integration Errors
These errors arise when Lightning Enable cannot communicate with the configured payment provider (Strike or OpenNode).
Payment Provider API Error
HTTP Status: 502 Bad Gateway
{
"error": "Payment provider API error",
"details": "Invalid API key"
}
Common Errors:
- Invalid API key
- Insufficient balance
- Invalid charge request
- Rate limited by provider
Payment Provider Timeout
HTTP Status: 504 Gateway Timeout
{
"error": "Payment provider request timed out"
}
Solution: Retry the request. Provider requests have a 30-second timeout.
Payment Provider Circuit Breaker Open
When the payment provider experiences multiple consecutive failures, the circuit breaker opens:
HTTP Status: 503 Service Unavailable
The circuit breaker:
- Opens after 5 consecutive failures
- Stays open for 30 seconds
- Automatically tests recovery in half-open state
Strike API Errors
Errors specific to merchants using Strike as their payment provider.
Strike Authentication Failure
HTTP Status: 502 Bad Gateway
{
"error": "Payment provider API error",
"details": "Strike authentication failed: 401"
}
Cause: Invalid or expired Strike API key.
Solution: Verify your Strike API key via the admin API or contact support. Strike API keys are configured per-merchant via strikeApiKey.
Strike Rate Limited
HTTP Status: 429 Too Many Requests (from Strike, surfaced as 502)
{
"error": "Payment provider API error",
"details": "Strike rate limit exceeded"
}
Solution: Lightning Enable automatically retries with exponential backoff (3 retries). If this error persists, reduce request frequency.
Strike Webhook Entity Not Found
HTTP Status: 404 Not Found
{
"error": "Invoice not found"
}
Cause: Strike webhooks carry only an entityId. Lightning Enable fetches the full payment details from Strike after receipt. This error means the entity could not be found on follow-up fetch.
Note: Strike webhooks are "thin" — the controller must fetch full payment details from the Strike API after receiving a webhook event.
OpenNode Integration Errors
Errors specific to merchants using OpenNode as their payment provider.
OpenNode API Error
HTTP Status: 502 Bad Gateway
{
"error": "Payment provider API error",
"details": "Invalid API key"
}
Common OpenNode Errors:
- Invalid API key
- Insufficient balance
- Invalid charge request
- Rate limited by OpenNode
OpenNode Timeout
HTTP Status: 504 Gateway Timeout
{
"error": "Payment provider request timed out"
}
Solution: Retry the request. OpenNode has a 30-second timeout.
OpenNode Circuit Breaker Open
When OpenNode experiences multiple consecutive failures, the circuit breaker opens:
HTTP Status: 503 Service Unavailable
The circuit breaker:
- Opens after 5 consecutive failures
- Stays open for 30 seconds
- Automatically tests recovery in half-open state
Stripe Integration Errors
Checkout Session Errors
HTTP Status: 400 Bad Request
{
"error": "Failed to create checkout session. Please try again."
}
Common Causes:
- Invalid email format
- Missing required fields
- Stripe API error
Subscription Result Errors
Missing Session ID:
{
"error": "session_id is required"
}
Session Not Found:
{
"error": "Checkout session not found"
}
Payment Not Completed:
{
"error": "Payment not completed",
"paymentStatus": "unpaid"
}
Customer Not Found:
{
"error": "Customer not found for this session"
}
Merchant Not Found:
{
"error": "Merchant account not found. Please wait a moment and try again."
}
Customer Portal Errors
HTTP Status: 401 Unauthorized
{
"error": "API key authentication required"
}
HTTP Status: 400 Bad Request
{
"error": "No Stripe customer ID associated with this account"
}
Proxy Management Errors
Invalid Target URL
HTTP Status: 400 Bad Request
{
"error": "Invalid target URL. Must be a valid HTTP or HTTPS URL."
}
Invalid Path Pattern
HTTP Status: 400 Bad Request
{
"error": "Invalid path pattern. Must start with '/' and be a valid glob pattern."
}
Proxy Not Found
HTTP Status: 404 Not Found
{
"error": "Proxy not found"
}
Endpoint Pricing Not Found
HTTP Status: 404 Not Found
{
"error": "Endpoint pricing not found"
}
Merchant Settings Errors
Authentication Required
HTTP Status: 401 Unauthorized
{
"error": "Authentication required"
}
Merchant Not Found
HTTP Status: 404 Not Found
{
"error": "Merchant not found"
}
Invalid Payment Provider Key
HTTP Status: 400 Bad Request
{
"error": "Payment provider API key is required"
}
Invalid Webhook URL
HTTP Status: 400 Bad Request
{
"error": "Invalid webhook URL format"
}
Error Handling Best Practices
1. Always Check HTTP Status First
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new ApiError(response.status, error);
}
return response.json();
2. Dispatch on Status, Then the Error String
There is no machine-readable code field. Branch on the HTTP status first; use the error string (and context fields like action_required) when you need to distinguish causes within a status:
class ApiError extends Error {
constructor(status, data) {
super(data.message || data.error);
this.status = status;
this.error = data.error;
this.retryAfter = data.retryAfter;
this.actionRequired = data.action_required;
}
}
function handleError(err) {
switch (err.status) {
case 401:
return showConfigurationError('Check your API key');
case 403:
// action_required tells you what to do: subscribe,
// update_payment_method, renew_subscription, upgrade_plan...
return handleSubscriptionIssue(err.actionRequired);
case 429:
if (err.error === 'Too many failed authentication attempts') {
// Auth throttle — fix the key, don't retry
return showConfigurationError('Check your API key');
}
// General rate limit — wait retryAfter seconds from the body
return scheduleRetry(err.retryAfter || 60);
case 400:
if (err.error?.startsWith('Invoice already exists')) {
return handleDuplicateOrder(err);
}
return logAndAlert(err);
default:
return logAndAlert(err);
}
}
3. Implement Retry Logic
async function withRetry(fn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (!isRetryable(error) || attempt === maxRetries) {
throw error;
}
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(r => setTimeout(r, delay));
}
}
}
function isRetryable(error) {
// Retry server errors and general rate limits
if (error.status >= 500) return true;
if (error.status === 429
&& error.error !== 'Too many failed authentication attempts') {
return true;
}
return false;
}
4. Log Errors with Context
function logError(error, context) {
console.error({
timestamp: new Date().toISOString(),
status: error.status,
error: error.error,
message: error.message,
correlationId: error.correlationId,
context: {
endpoint: context.endpoint,
orderId: context.orderId,
merchantId: context.merchantId
}
});
}
5. User-Friendly Messages
Map the HTTP status (plus the error string where needed) to your own user-facing copy:
function getUserMessage(err) {
switch (err.status) {
case 401: return 'Authentication failed. Please check your settings.';
case 402: return 'Payment required to access this resource.';
case 403: return 'Please check your subscription status.';
case 404: return 'Not found. It may have expired.';
case 429: return 'Too many requests. Please wait a moment.';
case 502:
case 503:
case 504: return 'Payment service temporarily unavailable.';
default: return 'An error occurred. Please try again.';
}
}
Retry Strategy Summary
| Error Type | Retry? | Strategy |
|---|---|---|
| 400 Bad Request | No | Fix request data |
| 401 Unauthorized | No | Fix credentials |
| 402 Payment Required | No | Complete payment |
| 403 Forbidden | No | Check subscription/features |
| 404 Not Found | No | Check resource ID |
| 429 Rate Limited | Yes | Wait retryAfter seconds (from the JSON body) |
| 429 Auth Throttle | No | Fix your API key first — retrying keeps the throttle engaged |
| 500 Server Error | Yes | Exponential backoff |
| 502 Bad Gateway | Yes | Exponential backoff |
| 503 Service Unavailable | Yes | Wait, then retry |
| 504 Gateway Timeout | Yes | Retry immediately |
Next Steps
- Rate Limiting - Detailed rate limit information
- Authentication - API key setup and security
- Webhooks - Webhook configuration and signatures
- Payments API - Payment creation and status