Skip to main content

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 formerror 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:

FieldTypePresent onDescription
errorstringall errorsShort error identifier (e.g., "Invalid API key", "Subscription required")
messagestringmost errorsHuman-readable description
action_requiredstring403 subscription/feature errorsWhat to do next: contact_support, subscribe, update_payment_method, renew_subscription, upgrade_plan
current_planstringsome 403 errorsYour current plan tier
required_planstring403 feature errorsPlan tier that includes the feature
subscription_statusstringsome 403 errorsCurrent Stripe subscription status
featurestring403 feature errorsThe 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": "..."
}
warning

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

CodeMeaningWhen Used
200 OKRequest succeededGET, PUT, POST (when returning data)
201 CreatedResource createdPOST when creating payments, refunds, proxies
204 No ContentSuccess, no response bodyDELETE operations

Client Error Codes

CodeMeaningCommon Causes
400 Bad RequestInvalid requestMissing required fields, invalid format, validation errors, duplicate order ID
401 UnauthorizedAuthentication failedMissing/invalid API key, invalid webhook signature
402 Payment RequiredL402 payment neededAccessing L402-protected endpoints without valid token
403 ForbiddenAccess deniedInactive account, subscription issues, feature not available
404 Not FoundResource not foundInvalid invoice ID, order ID, proxy ID
409 ConflictResource conflictRare — only raised by uncaught conflict exceptions. Duplicate order IDs return 400, not 409
429 Too Many RequestsRate limit or auth-failure throttleToo many requests in the window, or too many failed authentication attempts from your IP

Server Error Codes

CodeMeaningWhen Used
500 Internal Server ErrorServer errorUnexpected errors, includes correlationId
502 Bad GatewayUpstream errorPayment provider/Stripe API failures, target API unreachable
503 Service UnavailableTemporary outageMaintenance, circuit breaker open
504 Gateway TimeoutUpstream timeoutPayment 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:

StatusMessage summaryaction_required
past_duePayment is past dueupdate_payment_method
canceledSubscription canceledrenew_subscription
unpaidSubscription unpaidrenew_subscription
incompleteSetup incompleterenew_subscription
incomplete_expiredSetup expiredrenew_subscription
note

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:

featureEndpointsNotes
refunds/api/refundsRequires refunds enabled on your account
multi_currency/api/payments/*convert*Requires multi-currency enabled on your account
l402/api/l402/challengesRequires 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.

note

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 MessageCause
No Authorization header providedMissing Authorization header
Invalid authorization schemeUsing Basic/Bearer instead of L402
Invalid L402 formatMalformed macaroon:preimage format
Preimage does not match payment hashIncorrect preimage
L402 verification failedInvalid 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
}
info

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:

PolicyLimitWindowEndpoints
Global1001 minAll requests (per API key, or per IP when anonymous)
Read2001 minGET operations
Payment Create101 minPOST /api/payments, POST /api/refunds, /api/checkout/*
Write201 minMerchant self-service writes (e.g., key regeneration)
Checkout Create51 minStripe checkout session creation
Admin301 minInternal 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 TypeRetry?Strategy
400 Bad RequestNoFix request data
401 UnauthorizedNoFix credentials
402 Payment RequiredNoComplete payment
403 ForbiddenNoCheck subscription/features
404 Not FoundNoCheck resource ID
429 Rate LimitedYesWait retryAfter seconds (from the JSON body)
429 Auth ThrottleNoFix your API key first — retrying keeps the throttle engaged
500 Server ErrorYesExponential backoff
502 Bad GatewayYesExponential backoff
503 Service UnavailableYesWait, then retry
504 Gateway TimeoutYesRetry immediately

Next Steps