L402 API
L402 (formerly LSAT) enables pay-per-request API access using Lightning Network payments.
Overview
L402 is a protocol for HTTP 402 Payment Required responses that enables:
- Pay-per-request API access without subscriptions
- Anonymous access - no accounts or credit cards needed
- Instant micropayments via Lightning Network
- Cryptographic verification using macaroons and preimages
Lightning Enable speaks two schemes on the same 402: classic L402 (macaroon + preimage) and the IETF Payment scheme (MPP, Machine Payments Protocol). One invoice backs both, so any client can pick the one it implements. See Payment (MPP) credentials.
Endpoints
Get L402 Pricing
Get pricing information for L402-protected endpoints.
GET /api/l402/pricing
Response
{
"defaultPriceSats": 10,
"serviceName": "lightning-enable",
"endpoints": [
{
"pathPattern": "/api/premium/*",
"priceSats": 100,
"description": "Premium API access"
},
{
"pathPattern": "/l402/proxy/*",
"priceSats": "varies",
"description": "Proxy pricing set per-proxy"
}
],
"tokenValiditySeconds": 3600
}
Check L402 Status
Check if a request has valid L402 authentication.
GET /api/l402/status
Headers
| Header | Required | Description |
|---|---|---|
Authorization | No | L402 <macaroon>:<preimage> |
Response (Authenticated)
{
"authenticated": true,
"paymentHash": "abc123...",
"expiresAt": "2024-12-29T13:00:00Z",
"remainingRequests": null
}
Response (Not Authenticated)
{
"authenticated": false,
"message": "L402 credential required"
}
L402 Protected Proxy
Access proxied APIs with L402 payment.
* /l402/proxy/{proxyId}/{path}
Without L402 Credential
Returns 402 Payment Required:
{
"error": "Payment Required",
"message": "Pay the Lightning invoice to access this resource",
"l402": {
"macaroon": "AgELbGlnaHRuaW5nLWVuYWJsZQJCMDAwMDAwMD...",
"invoice": "lnbc100n1pnxyz...",
"amount_sats": 10,
"payment_hash": "abc123def456...",
"expires_at": "2024-12-29T13:00:00Z"
}
}
With Valid L402 Credential
curl https://api.lightningenable.com/l402/proxy/{proxyId}/endpoint \
-H "Authorization: L402 AgEL...:abc123..."
Returns the proxied API response.
L402 Authentication Flow
Step 1: Request Protected Resource
curl https://api.lightningenable.com/l402/proxy/my-api/data
Step 2: Receive 402 Challenge
HTTP/1.1 402 Payment Required
WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc..."
{
"error": "Payment Required",
"l402": {
"macaroon": "AgEL...",
"invoice": "lnbc100n1p...",
"amount_sats": 10,
"payment_hash": "abc123..."
}
}
Step 3: Pay Lightning Invoice
Pay the invoice using any Lightning wallet. You'll receive a preimage (proof of payment).
Step 4: Access with Credential
Combine macaroon and preimage:
curl https://api.lightningenable.com/l402/proxy/my-api/data \
-H "Authorization: L402 AgEL...:abc123def456789..."
Step 5: Receive Response
HTTP/1.1 200 OK
{
"data": "Your requested content"
}
Credential Format
The L402 credential consists of two parts:
Authorization: L402 <macaroon>:<preimage>
| Component | Format | Description |
|---|---|---|
macaroon | Base64 | Bearer token with caveats |
preimage | Hex (64 chars) | 32-byte proof of payment |
Verification
The server verifies the credential in the following order:
- Preimage matches hash:
SHA256(preimage) == payment_hash - Macaroon signature: HMAC-SHA256 verification ensures the token was not tampered with
- All caveats satisfied:
expires(not expired),path(matches request path),merchant_id(matches request merchant),amount_sats(matches endpoint price)
Token Reuse
L402 tokens can be reused until they expire, but only for the same endpoint, merchant, and price tier they were issued for. The path, merchant_id, and amount_sats caveats are checked on every request, so a token cannot be reused across different contexts.
// Save credential after first payment
const credential = `${macaroon}:${preimage}`;
localStorage.setItem('l402_credential', credential);
// Reuse for subsequent requests to the SAME endpoint
const savedCredential = localStorage.getItem('l402_credential');
fetch(url, {
headers: { 'Authorization': `L402 ${savedCredential}` }
});
Default token validity: 1 hour (configurable per endpoint)
When caching credentials, key them by the full endpoint path (not just the host) since tokens are path-bound. A token issued for /l402/proxy/api-a/data will be rejected if used against /l402/proxy/api-b/data.
Macaroon Structure
Macaroons are cryptographic bearer tokens signed with HMAC-SHA256. Each macaroon contains an identifier, a set of caveats, and a signature. Lightning Enable embeds security caveats at issuance time that bind the token to a specific context, preventing reuse across endpoints, merchants, or price tiers.
{
"identifier": "lightning-enable:payment_hash:timestamp",
"caveats": [
"services = lightning-enable:0",
"path = /l402/proxy/my-api/data",
"merchant_id = 42",
"charge_id = abc123-def456",
"amount_sats = 100",
"expires = 1704067200"
],
"signature": "hmac-sha256"
}
Caveat Types
Every macaroon issued by Lightning Enable includes the following caveats. During verification, all caveats must be satisfied for the token to be accepted. An unknown or unsatisfied caveat causes verification to fail.
| Caveat | Example | Description |
|---|---|---|
services | services = lightning-enable:0 | Service identifier and tier. |
path | path = /l402/proxy/my-api/data | Binds the token to the API path it was issued for. A token issued for /api/premium/v1 cannot be used against /api/premium/v2. Wildcard paths (e.g., /l402/proxy/my-api/*) allow access to any sub-path under the prefix. |
merchant_id | merchant_id = 42 | Binds the token to the issuing merchant. Prevents cross-tenant token reuse -- a token issued by Merchant A cannot be replayed against Merchant B's endpoints. Both directions are enforced: if the request has a merchant context, the token must contain a matching merchant_id caveat, and vice versa. |
charge_id | charge_id = abc123-def456 | The OpenNode charge ID associated with the payment. |
amount_sats | amount_sats = 100 | Binds the token to the price at issuance. Prevents a token purchased at a lower price (e.g., 10 sats for a demo endpoint) from being reused against a higher-priced endpoint (e.g., 100 sats for premium data) that happens to share a wildcard path pattern. |
expires | expires = 1704067200 | Unix timestamp after which the token is no longer valid. Default validity is 1 hour (configurable per endpoint). |
Caveat Verification
When a client presents an L402 credential, Lightning Enable performs the following checks in order:
- Preimage verification --
SHA256(preimage) == payment_hash(proves payment was made) - Macaroon signature -- HMAC-SHA256 verification (proves the token was not tampered with)
- Caveat satisfaction -- each caveat is evaluated against the current request context:
expires: the current time must be before the expiration timestamppath: the request path must match the bound path (exact or wildcard prefix)merchant_id: the request's merchant context must match the bound merchant IDamount_sats: the endpoint's current price must match the bound amount- Any unrecognized caveat causes the verification to fail (closed-world assumption)
If any check fails, the server returns an appropriate error (401 or 403) with a description of the failure.
Error Responses
402 Payment Required
{
"error": "Payment Required",
"message": "Pay the Lightning invoice to access this resource",
"l402": {
"macaroon": "...",
"invoice": "...",
"amount_sats": 10
}
}
401 Invalid Credential
{
"error": "Unauthorized",
"message": "Invalid L402 credential",
"details": "Preimage does not match payment hash"
}
403 Token Expired
{
"error": "Forbidden",
"message": "L402 token has expired",
"details": "Token expired at 2024-12-29T12:00:00Z"
}
403 Path Not Allowed
{
"error": "Forbidden",
"message": "Token not valid for this path",
"allowed": "/l402/proxy/api-a/*",
"requested": "/l402/proxy/api-b/data"
}
Payment (MPP) credentials
Alongside L402, every 402 also advertises the HTTP Payment authentication scheme, the format shared by draft-httpauth-payment-00 (the core scheme) and draft-lightning-charge-00 (the Lightning charge method). This is usually called MPP (Machine Payments Protocol). Three credential profiles are accepted, and all three are proofs of the same invoice:
| Profile | Authorization header | Reusable? | Best for |
|---|---|---|---|
| Classic L402 | L402 <macaroon>:<preimage> | Yes, until the macaroon expires | Existing L402 clients, LND lnget, Aperture-style tooling |
Legacy Payment (auth-params) | Payment method="lightning", preimage="<hex>" | Yes, until the challenge expires | Clients that only want to send a preimage |
Modern Payment (bearer token) | Payment <base64url(JSON)> | No. Single use | Clients built on the current drafts (mppx, l402-requests, the Lightning Enable MCP server v1.24+) |
You never opt in per merchant. The server emits both schemes for every L402-gated resource, and a client uses whichever one it implements.
The 402 challenge
A single 402 carries one WWW-Authenticate header per scheme. The Payment header is a superset: it holds the modern parameters and the legacy invoice / amount / currency parameters in the same value, and both drafts require clients to ignore parameters they don't know.
HTTP/1.1 402 Payment Required
WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc1u1p..."
WWW-Authenticate: Payment id="k9Q3...", realm="lightning-enable", method="lightning", intent="charge", request="eyJhbW91bnQiOiIxMDAiLC...", expires="2026-09-11T18:30:00Z", invoice="lnbc1u1p...", amount="100", currency="sat"
Cache-Control: no-store
| Parameter | Meaning |
|---|---|
id | Server-computed binding over every other parameter. Verification recomputes it from the fields you echo back, so any edit to the challenge invalidates the credential. |
realm | Always lightning-enable on the hosted API. |
method / intent | Always lightning / charge. |
request | base64url of a JCS-canonical JSON object: {"amount":"100","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"<hex>"}}. Echo it byte for byte. |
expires | RFC 3339 UTC. Never later than the BOLT11 invoice expiry. A modern credential must be redeemed before this instant. |
digest | Present only on proxied POST / PUT / PATCH requests with a body: the RFC 9530 Content-Digest of that body, bound into id. Resend the identical body when you redeem. |
invoice, amount, currency | Legacy parameters for clients that predate the drafts. Same invoice as request.methodDetails.invoice. |
Send Accept-Payment: lightning/charge;q=0 on the initial request if you want the Payment header suppressed and only the L402 header returned.
Redeem with a modern credential
Pay the invoice, then build the credential the drafts describe: a JSON object with the challenge you received (echoed exactly, including request) and a payload holding the preimage as 64 lowercase hex characters. base64url-encode it and send it as a bearer token.
{
"challenge": {
"id": "k9Q3...",
"realm": "lightning-enable",
"method": "lightning",
"intent": "charge",
"request": "eyJhbW91bnQiOiIxMDAiLC...",
"expires": "2026-09-11T18:30:00Z"
},
"payload": { "preimage": "7f8a9b2c...e9f0a" }
}
GET /l402/proxy/abc123/api/data HTTP/1.1
Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOiJrOVEz...
On success the response includes a receipt you can store or forward:
HTTP/1.1 200 OK
Payment-Receipt: eyJjaGFsbGVuZ2VJZCI6Ims5UTMuLi4iLCJtZXRob2QiOiJsaWdodG5pbmciLC...
Cache-Control: private
The receipt decodes to {"challengeId":"k9Q3...","method":"lightning","reference":"<payment hash>","status":"success","timestamp":"2026-09-11T18:12:04Z"}. The reference is the payment hash, never the preimage.
Single use. A modern credential is consumed atomically the first time it verifies. A second request with the same token gets a fresh 402. If the server accepted the credential but failed to deliver the upstream response, the consumption is released so you can retry with the same token. Redeem before expires. Classic L402 and legacy Payment credentials keep their reuse-until-expiry behaviour; nothing about them changed.
Redeem with a legacy Payment credential
Authorization: Payment method="lightning", preimage="7f8a9b2c...e9f0a"
No macaroon and no challenge echo. The server matches the preimage to a challenge it minted for that resource and price. Reusable until the challenge expires.
Modern-path errors
Failures on the modern path return 402 with application/problem+json, a fresh challenge in WWW-Authenticate, and Cache-Control: no-store. The type is https://paymentauth.org/problems/<slug>:
type slug | Meaning |
|---|---|
invalid-challenge | The echoed challenge doesn't decode, has a currency other than sat, or is missing its expiry or payment hash. |
verification-failed | The preimage doesn't hash to the challenge's payment hash, the challenge belongs to another merchant, the credential is past expires, or it was already redeemed (single use). The detail member says which. |
payment-insufficient | The challenge amount doesn't match the resource's price. |
method-unsupported | Modern credentials are switched off on this server. Fall back to L402. |
A malformed bearer token is rejected outright. It is never reinterpreted as a legacy credential.
Verify credentials as a producer
If you mint challenges with the Producer API, verify each profile with the matching endpoint:
- Classic L402 and legacy
Paymentcredentials:POST /api/l402/challenges/verify. - Modern bearer credentials:
POST /api/l402/challenges/verify-credential, which consumes the token and returns a ready-to-servePayment-Receiptvalue.
L402 Producer API
The Producer API lets merchants create L402 challenges programmatically — enabling agent-to-agent commerce where your AI agent charges other agents for access to resources.
The Producer API requires a Lightning Enable subscription — Agentic Commerce ($49/mo) or Agentic Commerce — Business (contact us). Consumer tools (paying for APIs, accessing L402 resources) are free — no subscription needed.
This is the supply side of agentic commerce. Your agent creates L402 challenges; other agents pay them. See the L402 Producer API guide for the full walkthrough.
Create L402 Challenge
Create a Lightning invoice + macaroon challenge for a resource.
POST /api/l402/challenges
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Merchant API key |
Idempotency-Key | No | Retry-safe key, max 200 characters. The same key with the same resource and price returns the same challenge for the life of that invoice; a different resource or price under the same key is a 409 while that invoice is still live, and a fresh mint once it has expired. See Idempotency. |
X-Idempotency-Key | No | The spelling this API shipped with. Identical behaviour; Idempotency-Key wins if you send both. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
resource | string | Yes | Resource identifier (URL, service name, or description) |
priceSats | long | Yes | Price in satoshis (minimum 1) |
description | string | No | Description shown on the Lightning invoice |
idempotencyKey | string | No | Same meaning as the Idempotency-Key header, for clients that can't set headers |
Example
curl -X POST https://api.lightningenable.com/api/l402/challenges \
-H "X-API-Key: YOUR_MERCHANT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "/api/weather/forecast",
"priceSats": 50,
"description": "7-day weather forecast"
}'
Response (200 OK)
{
"invoice": "lnbc500n1p3xyza...",
"macaroon": "AgELbGlnaHRuaW5n...",
"paymentHash": "abc123def456...",
"expiresAt": "2026-03-13T14:30:00Z",
"resource": "/api/weather/forecast",
"priceSats": 50
}
Error Responses
Returned as application/problem+json with a stable type URI. See Error format for the full body and the complete code list.
| Status | Description |
|---|---|
| 400 | Missing required field, invalid price, blank or over-long idempotency key, or no payment provider key on your account |
| 401 | Missing or invalid API key |
| 402 | A plan cap was reached (price, monthly volume, or distinct endpoints) |
| 403 | L402 not enabled on your plan |
| 409 | The idempotency key was already used for a different resource or price, or the endpoint was retired |
| 503 | The challenge could not be durably recorded, so none was issued — nothing was invoiced |
List Your Challenges
List the challenges you have minted, with payment status. Scoped to the account behind your API key.
GET /api/l402/challenges?status=paid&since=2026-09-01T00:00:00Z&limit=50&offset=0
| Query parameter | Default | Description |
|---|---|---|
status | none | paid, unpaid, or expired |
since | none | ISO 8601 lower bound on createdAt |
limit | 50 | Clamped to 1..200 |
offset | 0 | Clamped to at least 0 |
Returns { challenges, total, limit, offset, status, since }; the unpaged total is also in the X-Total-Count header. Each challenge carries paymentHash, resource, amountSats, status, createdAt, paidAt, expiresAt, and idempotencyKey — never a macaroon or a preimage.
curl "https://api.lightningenable.com/api/l402/challenges?status=paid&limit=25" \
-H "X-API-Key: YOUR_MERCHANT_API_KEY"
Get One Challenge
GET /api/l402/challenges/{paymentHash}
Returns the same object as one element of the list. A payment hash belonging to another account returns 404, not 403.
Verify L402 Token
Verify an L402 token (macaroon + preimage) to confirm payment.
POST /api/l402/challenges/verify
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Merchant API key |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
macaroon | string | Yes | Base64-encoded macaroon |
preimage | string | Yes | Hex-encoded preimage (64 characters) |
Example
curl -X POST https://api.lightningenable.com/api/l402/challenges/verify \
-H "X-API-Key: YOUR_MERCHANT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"macaroon": "AgELbGlnaHRuaW5n...",
"preimage": "7f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a"
}'
Response (200 OK — valid)
{
"valid": true,
"resource": "/api/weather/forecast",
"merchantId": 42,
"amountSats": 50,
"paymentHash": "abc123def456..."
}
Response (200 OK — invalid)
{
"valid": false,
"error": "Preimage does not match payment hash"
}
The first successful verification of a credential records the underlying challenge as paid — it starts showing as status: "paid" in the listing above, and fires the l402.challenge.paid webhook to your callback URL if you have one configured. Later verifications of the same credential do not re-fire it. See Payment webhooks.
Verify Payment Credential (single-use)
Verify a modern Payment bearer credential (the base64url JSON token described in Payment (MPP) credentials). Verifying consumes the credential: a second call with the same token returns valid: false.
POST /api/l402/challenges/verify-credential
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Merchant API key |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
credential | string | Yes | The token exactly as received, with or without the leading Payment scheme word |
resource | string | No | Reject unless the challenge was minted for this resource |
amountSats | integer | No | Reject unless the challenge was minted for this price |
Example
curl -X POST https://api.lightningenable.com/api/l402/challenges/verify-credential \
-H "X-API-Key: YOUR_MERCHANT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"credential": "<the base64url token from the Authorization header>",
"resource": "/api/weather/forecast",
"amountSats": 50
}'
Response (200 OK — valid)
{
"valid": true,
"consumed": true,
"resource": "/api/weather/forecast",
"merchantId": 42,
"amountSats": 50,
"paymentHash": "abc123def456...",
"receipt": "eyJjaGFsbGVuZ2VJZCI6Ims5UTMuLi4iLC..."
}
Serve receipt back to the payer as the Payment-Receipt response header on the resource you gate.
Response (200 OK — invalid)
{
"valid": false,
"consumed": false,
"error": "Preimage does not match the challenge payment hash."
}
400 mpp_not_supported means modern credentials are switched off on the server. Like /verify, the first successful call marks the challenge paid and fires l402.challenge.paid.
Code Examples
JavaScript L402 Client
class L402Client {
constructor() {
this.credentials = new Map();
}
async request(url, options = {}) {
const credential = this.credentials.get(this.getHost(url));
const headers = {
...options.headers,
...(credential && { 'Authorization': `L402 ${credential}` })
};
const response = await fetch(url, { ...options, headers });
if (response.status === 402) {
return this.handlePaymentRequired(url, response, options);
}
return response;
}
async handlePaymentRequired(url, response, options) {
const { l402 } = await response.json();
// Pay invoice and get preimage
const preimage = await this.payInvoice(l402.invoice);
// Store credential
const credential = `${l402.macaroon}:${preimage}`;
this.credentials.set(this.getHost(url), credential);
// Retry request
return this.request(url, options);
}
async payInvoice(invoice) {
// Integrate with your Lightning wallet
// Return the preimage after payment
throw new Error('Implement payInvoice()');
}
getHost(url) {
return new URL(url).host;
}
}
// Usage
const client = new L402Client();
const response = await client.request('https://api.example.com/l402/proxy/my-api/data');
Python L402 Client
import hashlib
import requests
class L402Client:
def __init__(self, pay_invoice_callback):
self.credentials = {}
self.pay_invoice = pay_invoice_callback
def request(self, url, **kwargs):
credential = self.credentials.get(self._get_host(url))
if credential:
kwargs.setdefault('headers', {})
kwargs['headers']['Authorization'] = f'L402 {credential}'
response = requests.request('GET', url, **kwargs)
if response.status_code == 402:
return self._handle_payment_required(url, response, kwargs)
return response
def _handle_payment_required(self, url, response, kwargs):
data = response.json()
l402 = data['l402']
# Pay invoice
preimage = self.pay_invoice(l402['invoice'])
# Verify preimage matches
payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
assert payment_hash == l402['payment_hash']
# Store and retry
self.credentials[self._get_host(url)] = f"{l402['macaroon']}:{preimage}"
return self.request(url, **kwargs)
def _get_host(self, url):
from urllib.parse import urlparse
return urlparse(url).netloc
cURL Workflow
#!/bin/bash
# Step 1: Get challenge
RESPONSE=$(curl -s https://api.example.com/l402/proxy/my-api/data)
HTTP_CODE=$(echo "$RESPONSE" | jq -r '.error // empty')
if [ "$HTTP_CODE" == "Payment Required" ]; then
MACAROON=$(echo "$RESPONSE" | jq -r '.l402.macaroon')
INVOICE=$(echo "$RESPONSE" | jq -r '.l402.invoice')
echo "Pay this invoice: $INVOICE"
echo "Enter preimage after payment:"
read PREIMAGE
# Step 2: Access with credential
curl https://api.example.com/l402/proxy/my-api/data \
-H "Authorization: L402 $MACAROON:$PREIMAGE"
fi
Wallet Integration
WebLN (Browser)
async function payL402Invoice(invoice) {
if (!window.webln) {
throw new Error('WebLN not available');
}
await window.webln.enable();
const { preimage } = await window.webln.sendPayment(invoice);
return preimage;
}
LND REST API
async function payWithLND(invoice) {
const response = await fetch(`${LND_REST_URL}/v1/channels/transactions`, {
method: 'POST',
headers: { 'Grpc-Metadata-macaroon': ADMIN_MACAROON },
body: JSON.stringify({ payment_request: invoice })
});
const { payment_preimage } = await response.json();
return Buffer.from(payment_preimage, 'base64').toString('hex');
}
Core Lightning
# Pay and get preimage
lightning-cli pay $INVOICE
PREIMAGE=$(lightning-cli listpays bolt11=$INVOICE | jq -r '.pays[0].preimage')
Best Practices
Store Credentials
Cache L402 credentials for token lifetime:
const CREDENTIAL_KEY = 'l402_credentials';
function saveCredential(host, credential, expiresAt) {
const credentials = JSON.parse(localStorage.getItem(CREDENTIAL_KEY) || '{}');
credentials[host] = { credential, expiresAt };
localStorage.setItem(CREDENTIAL_KEY, JSON.stringify(credentials));
}
function getCredential(host) {
const credentials = JSON.parse(localStorage.getItem(CREDENTIAL_KEY) || '{}');
const data = credentials[host];
if (data && new Date(data.expiresAt) > new Date()) {
return data.credential;
}
return null;
}
Handle Expired Tokens
async function request(url) {
const response = await fetch(url, {
headers: { 'Authorization': `L402 ${getCredential(url)}` }
});
if (response.status === 403) {
// Token expired, clear and get new one
clearCredential(url);
return request(url);
}
return response;
}
Budget Limits
Set spending limits:
class BudgetedL402Client extends L402Client {
constructor(maxSatsPerHour) {
super();
this.maxSats = maxSatsPerHour;
this.spent = 0;
this.resetTime = Date.now() + 3600000;
}
async handlePaymentRequired(url, response, options) {
const { l402 } = await response.json();
if (Date.now() > this.resetTime) {
this.spent = 0;
this.resetTime = Date.now() + 3600000;
}
if (this.spent + l402.amount_sats > this.maxSats) {
throw new Error('Budget exceeded');
}
this.spent += l402.amount_sats;
return super.handlePaymentRequired(url, response, options);
}
}
Next Steps
- Proxy Configuration - Set up L402 proxies
- How It Works - Technical deep dive
- API Monetization - Protect your APIs