L402Requests 0.7.1

dotnet add package L402Requests --version 0.7.1
                    
NuGet\Install-Package L402Requests -Version 0.7.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="L402Requests" Version="0.7.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="L402Requests" Version="0.7.1" />
                    
Directory.Packages.props
<PackageReference Include="L402Requests" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add L402Requests --version 0.7.1
                    
#r "nuget: L402Requests, 0.7.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package L402Requests@0.7.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=L402Requests&version=0.7.1
                    
Install as a Cake Addin
#tool nuget:?package=L402Requests&version=0.7.1
                    
Install as a Cake Tool

L402-Requests (.NET)

Discord

Auto-paying L402 HTTP client for .NET. APIs behind Lightning paywalls just work.

L402Requests wraps HttpClient and automatically handles HTTP 402 responses by paying Lightning invoices and retrying with L402 credentials. It's a drop-in HTTP client where any API behind an L402 paywall "just works."

Install

dotnet add package L402Requests

Quick Start

using L402Requests;

using var client = new L402HttpClient();
var response = await client.GetAsync("https://api.example.com/paid-resource");
Console.WriteLine(await response.Content.ReadAsStringAsync());

That's it. The library detects your wallet from environment variables, pays the Lightning invoice when it gets a 402 response, and retries with L402 credentials.

Wallet Configuration

Set environment variables for your preferred wallet. The library auto-detects in this order:

Priority Wallet Environment Variables Preimage Support
1 LND LND_REST_HOST, LND_MACAROON_HEX Yes
2 NWC NWC_CONNECTION_STRING Yes (CoinOS, CLINK)
3 Strike STRIKE_API_KEY Yes
4 OpenNode OPENNODE_API_KEY No — cannot be used for L402

Recommended: Strike (full preimage support, no infrastructure required).

export STRIKE_API_KEY="your-strike-api-key"

LND

export LND_REST_HOST="https://localhost:8080"
export LND_MACAROON_HEX="your-admin-macaroon-hex"
export LND_TLS_CERT_PATH="/path/to/tls.cert"  # optional

NWC (Nostr Wallet Connect)

export NWC_CONNECTION_STRING="nostr+walletconnect://pubkey?relay=wss://relay&secret=hex"

OpenNode (not usable for L402)

OpenNode does not return payment preimages, and L402 needs the preimage to build the Authorization header — so an OpenNode payment settles and still buys no access.

Behaviour change in 0.7.0. Every 402 now throws UnsupportedWalletException before any funds move. Previously the withdrawal was submitted first and only then failed with PaymentFailedException — you paid and got nothing.

This can break existing error handling. UnsupportedWalletException is not a PaymentFailedException subclass, so a catch (PaymentFailedException) that used to swallow this now misses it and the exception escapes. See Error Handling.

OpenNode is still auto-detected, so an OPENNODE_API_KEY-only setup keeps resolving to a wallet that refuses every request. Switch to Strike, LND, or a compatible NWC wallet (CoinOS, CLINK, Alby Hub).

Budget Controls

Safety first — budgets are enabled by default to prevent accidental overspending:

using var client = new L402HttpClient(new L402Options
{
    MaxSatsPerRequest = 500,     // Max per single payment (default: 1000)
    MaxSatsPerHour = 5000,       // Hourly rolling limit (default: 10000)
    MaxSatsPerDay = 25000,       // Daily rolling limit (default: 50000)
    AllowedDomains = ["api.example.com"],  // Optional domain allowlist
});

If a payment would exceed any limit, BudgetExceededException is raised before the payment is attempted.

To disable budgets entirely:

using var client = new L402HttpClient(new L402Options { BudgetEnabled = false });

Explicit Wallet

using L402Requests;
using L402Requests.Wallets;

using var client = new L402HttpClient(new StrikeWallet("your-api-key"));
var response = await client.GetAsync("https://api.example.com/paid-resource");

DI / HttpClientFactory

// In Program.cs
builder.Services.AddL402HttpClient("myapi", options =>
{
    options.MaxSatsPerRequest = 500;
    options.MaxSatsPerHour = 5000;
    options.AllowedDomains = ["api.example.com"];
});

// In consuming class
public class MyService(IHttpClientFactory factory)
{
    public async Task<string> GetPaidData()
    {
        var client = factory.CreateClient("myapi");
        var response = await client.GetAsync("https://api.example.com/paid-resource");
        return await response.Content.ReadAsStringAsync();
    }
}

Spending Introspection

Track every payment made during a session:

using var client = new L402HttpClient();
await client.GetAsync("https://api.example.com/data");
await client.GetAsync("https://api.example.com/more-data");

Console.WriteLine($"Total: {client.SpendingLog.TotalSpent()} sats");
Console.WriteLine($"Last hour: {client.SpendingLog.SpentLastHour()} sats");
Console.WriteLine($"By domain: {string.Join(", ", client.SpendingLog.ByDomain())}");
Console.WriteLine(client.SpendingLog.ToJson());

Two-Step L402 Flows (Commerce)

Some servers intentionally use a two-step L402 flow where payment and claim are separate endpoints. This is common for physical goods — it separates payment from fulfillment and allows the claim URL to be shared with a gift recipient.

For example, the Lightning Enable Store returns a 402 on POST /checkout, and after payment you claim the order at POST /claim with the L402 credential.

In these cases, L402Requests pays the invoice automatically. Retrieve the credential (macaroon + preimage) from the spending log, then make the claim request:

// Store products cost ~48,000 sats incl. shipping — raise the hourly/daily
// caps too, or the default 10k/hour budget rejects the purchase.
using var client = new L402HttpClient(new L402Options
{
    MaxSatsPerRequest = 50000,
    MaxSatsPerHour = 50000,
    MaxSatsPerDay = 100000,
});
var checkout = await client.PostAsync(
    "https://store.lightningenable.com/api/store/checkout",
    JsonContent.Create(new { items = new[] { new { productId = 2, quantity = 1, size = "L", color = "Black" } } }));

// Payment was made — retrieve the credential from the spending log
var record = client.SpendingLog.Records[^1];
Console.WriteLine($"Paid {record.AmountSats} sats");

// Claim the order with the L402 credential. The order is identified by the
// macaroon — no shipping details in this request. The response's claimUrl is
// where the buyer enters their shipping address.
using var http = new HttpClient();
var claimRequest = new HttpRequestMessage(HttpMethod.Post, "https://store.lightningenable.com/api/store/claim")
{
    Content = JsonContent.Create(new { l402Credential = $"{record.Macaroon}:{record.Preimage}" }),
};
claimRequest.Headers.TryAddWithoutValidation("Authorization", $"L402 {record.Macaroon}:{record.Preimage}");
var claim = await http.SendAsync(claimRequest);
var claimBody = await claim.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"Share this with the recipient: {claimBody.GetProperty("claimUrl")}");

How It Works

  1. Your code makes an HTTP request via L402HttpClient
  2. If the server returns 200, the response is returned as-is
  3. If the server returns 402 with an L402 challenge:
    • The WWW-Authenticate: L402 macaroon="...", invoice="..." header is parsed
    • The BOLT11 invoice amount is checked against your budget — an invoice whose amount can't be read is refused, not paid
    • The invoice is paid via your configured Lightning wallet
    • The request is retried with Authorization: L402 {macaroon}:{preimage}
  4. Credentials are cached so subsequent requests to the same endpoint don't require re-payment

Error Handling

using L402Requests;

using var client = new L402HttpClient();
try
{
    var response = await client.GetAsync("https://api.example.com/paid-resource");
}
catch (BudgetExceededException e)
{
    Console.WriteLine($"Over budget: {e.LimitType} limit is {e.LimitSats} sats");
}
catch (InvoiceAmountUnknownException e)
{
    Console.WriteLine($"Refused unpriceable invoice: {e.Reason}");
}
catch (UnsupportedWalletException e)
{
    Console.WriteLine($"Wallet unusable for L402: {e.WalletReason}");
}
catch (PaymentFailedException e)
{
    Console.WriteLine($"Payment failed: {e.Reason}");
}
catch (NoWalletException)
{
    Console.WriteLine("No wallet configured");
}
Exception When Funds moved
BudgetExceededException Payment would exceed a budget limit No
InvoiceAmountUnknownException Invoice amount could not be determined, so it could not be checked against your budget No
UnsupportedWalletException Configured wallet cannot return preimages (OpenNode) No
PaymentFailedException Lightning payment failed Maybe
InvoiceExpiredException Invoice expired before payment No
NoWalletException No wallet env vars detected No
DomainNotAllowedException Domain not in AllowedDomains No
ChallengeParseException Malformed L402 challenge header No

All of them derive from L402Exception, so catch (L402Exception) handles every case in one block.

New in 0.7.0: InvoiceAmountUnknownException and UnsupportedWalletException. Both are precondition failures raised before a payment is attempted, so neither derives from PaymentFailedException — code that only catches PaymentFailedException will not catch them. See OpenNode for the case most likely to hit existing deployments.

Usage with AI Agents

L402Requests is the consumer-side complement to the Lightning Enable MCP Server. While the MCP server gives AI agents wallet tools, L402Requests lets your .NET code access paid APIs without any agent framework.

Part of Lightning Enable — infrastructure for agent commerce over Lightning. See the full ecosystem.

Semantic Kernel Tool

using L402Requests;
using Microsoft.SemanticKernel;

public class PaidApiPlugin
{
    private readonly L402HttpClient _client = new(new L402Options { MaxSatsPerRequest = 100 });

    [KernelFunction("fetch_paid_api")]
    [Description("Fetch data from an L402-protected API. Payment is handled automatically.")]
    public async Task<string> FetchPaidApiAsync(string url)
    {
        var response = await _client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
}

Standalone

using var client = new L402HttpClient();
var response = await client.GetAsync("https://api.example.com/premium-data");

What is L402?

L402 (formerly LSAT) is a protocol for monetizing APIs with Lightning Network micropayments. Instead of API keys or subscriptions, servers return HTTP 402 ("Payment Required") with a Lightning invoice. Once paid, the client receives a credential (macaroon + payment preimage) that grants access.

Learn more: docs.lightningenable.com

Example: MaximumSats API

MaximumSats provides paid Lightning Network APIs including AI DVM, WoT reports, Nostr analysis, and more. Use L402Requests to automatically pay for these endpoints:

using L402Requests;

using var client = new L402HttpClient();
var response = await client.GetAsync("https://maximumsats.com/api/dvm");
var data = await response.Content.ReadAsStringAsync();

Set your wallet via environment variable:

export STRIKE_API_KEY="your-strike-api-key"

The library automatically handles the L402 payment protocol — you just get the data.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.1 101 7/23/2026
0.7.0 96 7/17/2026
0.6.0 103 7/7/2026
0.5.0 110 7/3/2026
0.4.0 113 6/12/2026
0.3.0 128 3/21/2026
0.2.0 115 3/21/2026
0.1.1 125 3/16/2026
0.1.0 129 2/18/2026