Skip to main content

API Monetization

This guide shows how to monetize your API endpoints using L402 payments.

Overview

L402 allows you to charge for API access at the endpoint level:

  • Per-endpoint pricing - Different prices for different resources
  • Path-based configuration - Protect paths with wildcards
  • Zero configuration for clients - Standard HTTP 402 flow
  • Works with any API - REST, GraphQL, WebSocket

Basic Setup

1. Enable L402

Add L402 configuration to your appsettings.json:

{
"L402": {
"Enabled": true,
"DefaultPriceSats": 100,
"ProtectedPaths": [
"/api/premium/*"
]
}
}

2. Register Middleware

In your ASP.NET Core application:

var builder = WebApplication.CreateBuilder(args);

// Add Lightning Enable services
builder.Services.AddLightningEnable(builder.Configuration);
builder.Services.AddL402Authentication();

var app = builder.Build();

// Add L402 middleware (before your API endpoints)
app.UseL402Authentication();

app.MapGet("/api/premium/data", () => new { message = "Premium content!" });

3. Test

# Without payment - returns 402
curl http://localhost:5096/api/premium/data

# With L402 credential - returns 200
curl http://localhost:5096/api/premium/data \
-H "Authorization: L402 <macaroon>:<preimage>"

Endpoint Pricing

Configure Different Prices

{
"L402": {
"DefaultPriceSats": 10,
"EndpointPricing": [
{
"PathPattern": "/api/ai/gpt4",
"PriceSats": 500,
"Description": "GPT-4 API call"
},
{
"PathPattern": "/api/ai/dalle",
"PriceSats": 1000,
"Description": "DALL-E image generation"
},
{
"PathPattern": "/api/data/*",
"PriceSats": 25,
"Description": "Data API access"
}
]
}
}

Path Matching

PatternMatches
/api/dataExact path only
/api/data/*Any path starting with /api/data/
/api/*/status/api/users/status, /api/orders/status

Protecting Existing APIs

Attribute-Based Protection

[ApiController]
[Route("api/[controller]")]
public class PremiumController : ControllerBase
{
[HttpGet("data")]
[RequiresL402(PriceSats = 100)]
public IActionResult GetData()
{
return Ok(new { data = "Premium content" });
}

[HttpPost("analyze")]
[RequiresL402(PriceSats = 500)]
public IActionResult Analyze([FromBody] AnalysisRequest request)
{
return Ok(new { result = "Analysis complete" });
}
}

Programmatic Protection

app.MapGet("/api/premium/data", async (HttpContext context, IL402Service l402) =>
{
// Check for valid L402 credential
if (!await l402.ValidateRequestAsync(context, priceSats: 100))
{
// Returns 402 with invoice
return Results.StatusCode(402);
}

return Results.Ok(new { data = "Premium content" });
});

Client Integration

JavaScript Client

class L402Client {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.credential = null;
}

async request(endpoint) {
const headers = {};
if (this.credential) {
headers['Authorization'] =
`L402 ${this.credential.macaroon}:${this.credential.preimage}`;
}

const response = await fetch(`${this.baseUrl}${endpoint}`, { headers });

if (response.status === 402) {
const challenge = await response.json();
return {
needsPayment: true,
invoice: challenge.l402.invoice,
amount: challenge.l402.amount_sats,
macaroon: challenge.l402.macaroon
};
}

return response.json();
}

setCredential(macaroon, preimage) {
this.credential = { macaroon, preimage };
}
}

// Usage
const client = new L402Client('https://api.example.com');

const result = await client.request('/api/premium/data');

if (result.needsPayment) {
// Display invoice to user
console.log(`Please pay ${result.amount} sats`);
console.log(`Invoice: ${result.invoice}`);

// After user pays and provides preimage
client.setCredential(result.macaroon, preimage);

// Retry
const data = await client.request('/api/premium/data');
console.log(data);
}

Python Client

import requests

class L402Client:
def __init__(self, base_url):
self.base_url = base_url
self.credential = None

def request(self, endpoint):
headers = {}
if self.credential:
headers['Authorization'] = f"L402 {self.credential['macaroon']}:{self.credential['preimage']}"

response = requests.get(f"{self.base_url}{endpoint}", headers=headers)

if response.status_code == 402:
data = response.json()
return {
'needs_payment': True,
'invoice': data['l402']['invoice'],
'amount': data['l402']['amount_sats'],
'macaroon': data['l402']['macaroon']
}

return response.json()

def set_credential(self, macaroon, preimage):
self.credential = {'macaroon': macaroon, 'preimage': preimage}

cURL Example

#!/bin/bash

# Step 1: Request protected endpoint
RESPONSE=$(curl -s -w "\n%{http_code}" \
https://api.example.com/api/premium/data)

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" == "402" ]; then
INVOICE=$(echo "$BODY" | jq -r '.l402.invoice')
MACAROON=$(echo "$BODY" | jq -r '.l402.macaroon')

echo "Pay this invoice: $INVOICE"
echo "Enter preimage after payment:"
read PREIMAGE

# Step 2: Retry with credential
curl https://api.example.com/api/premium/data \
-H "Authorization: L402 $MACAROON:$PREIMAGE"
fi

Advanced Configuration

Token Validity

Control how long tokens remain valid:

{
"L402": {
"DefaultTokenValiditySeconds": 3600,
"EndpointPricing": [
{
"PathPattern": "/api/one-time/*",
"PriceSats": 1000,
"TokenValiditySeconds": 60
}
]
}
}

Service Tiers

Create different service tiers:

{
"L402": {
"ServiceTiers": [
{
"Name": "basic",
"Paths": ["/api/basic/*"],
"PriceSats": 10,
"RateLimit": 100
},
{
"Name": "premium",
"Paths": ["/api/premium/*"],
"PriceSats": 100,
"RateLimit": 1000
},
{
"Name": "enterprise",
"Paths": ["/api/enterprise/*"],
"PriceSats": 500,
"RateLimit": 10000
}
]
}
}

Custom Caveats

Add custom restrictions to macaroons:

services.AddL402Authentication(options =>
{
options.AddCaveat = (context, caveats) =>
{
// Add IP restriction
caveats.Add($"ip = {context.Connection.RemoteIpAddress}");

// Add request limit
caveats.Add("requests = 100");

// Add custom data
caveats.Add($"user_tier = {GetUserTier(context)}");
};
});

Analytics

Track L402 Usage

services.AddL402Authentication(options =>
{
options.OnPaymentVerified = async (context, payment) =>
{
await _analytics.TrackAsync(new L402Event
{
PaymentHash = payment.PaymentHash,
Endpoint = context.Request.Path,
AmountSats = payment.AmountSats,
Timestamp = DateTime.UtcNow
});
};
});

View Analytics

Query payment analytics:

curl https://api.lightningenable.com/api/l402/analytics \
-H "X-API-Key: your-api-key"
{
"period": "last_30_days",
"totalPayments": 1523,
"totalSatsEarned": 152300,
"topEndpoints": [
{ "path": "/api/ai/gpt4", "payments": 892, "sats": 89200 },
{ "path": "/api/data/market", "payments": 631, "sats": 63100 }
]
}

Best Practices

Pricing Strategy

  1. Start low - Lower barrier to entry
  2. Value-based pricing - Charge more for expensive operations
  3. Free tier - Consider some free endpoints for discovery
  4. Consistent pricing - Similar endpoints should cost similar amounts

Error Handling

app.MapGet("/api/premium/data", async (HttpContext context, IL402Service l402) =>
{
try
{
var result = await l402.ValidateRequestAsync(context, priceSats: 100);

if (!result.IsValid)
{
// Log for analytics
_logger.LogInformation("L402 challenge issued for {Path}", context.Request.Path);
return result.ChallengeResponse;
}

return Results.Ok(new { data = "Premium content" });
}
catch (L402Exception ex)
{
_logger.LogError(ex, "L402 validation error");
return Results.Problem("Payment validation failed");
}
});

Testing

# Test with curl
curl -i https://api.example.com/api/premium/data

# Verify 402 response includes all required fields
# - macaroon
# - invoice
# - amount_sats
# - payment_hash

Next Steps