Direct L402 Integration

Here is the API integration.

Submit one public Lightning node key, pay one 500-sat Lightning invoice, and retrieve a structured, point-in-time Operational Intelligence report.

500 sats One node-bound report No subscription

Review copied code before use. BlueHorseShoe never asks for a wallet seed, node private key, LND admin macaroon or Nostr private key.

Inspect. Copy. Integrate.

Choose your integration language.

Every example creates the request ID once and reuses the same node-bound request for payment and retrieval. Each client retains the macaroon only in process memory, displays the invoice, and asks only for the payment preimage. Never generate a new request ID between the two calls.

CLI

curl

# STEP 1 - Create one request. Replace the public key and UUID first.
# Keep request.json unchanged until the report has been retrieved.
cat > request.json <<'JSON'
{
  "request_id": "bhs-REPLACE-WITH-UUID",
  "source_pub_key": "02REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
  "schema_version": "operational-summary-v2"
}
JSON

challenge_headers=$(curl --silent --show-error --proto '=https' --tlsv1.2 --max-redirs 0 \
  --request POST \
  --header 'Content-Type: application/json' \
  --data-binary @request.json \
  --dump-header - --output /dev/null \
  https://bluehorseshoe.site/BlueHorseShoe/api/v2/l402/challenge)

macaroon=$(printf '%s' "$challenge_headers" | sed -n 's/.*macaroon="\([^"]*\)".*/\1/p')
invoice=$(printf '%s' "$challenge_headers" | sed -n 's/.*invoice="\([^"]*\)".*/\1/p')
printf 'STEP 2 - Pay this invoice once within 15 minutes:\n%s\n' "$invoice"
printf 'STEP 3 - Payment preimage (hidden): '
stty -echo; IFS= read -r preimage; stty echo; printf '\n'
L402_AUTHORIZATION="L402 $macaroon:$preimage"
curl --proto '=https' --tlsv1.2 --max-redirs 0 \
  --request POST \
  --header 'Content-Type: application/json' \
  --header "Authorization: $L402_AUTHORIZATION" \
  --data-binary @request.json \
  https://bluehorseshoe.site/BlueHorseShoe/api/v2/operational-summary
unset challenge_headers macaroon invoice preimage L402_AUTHORIZATION
Python 3

Python

import getpass, json, re, urllib.error, urllib.request, uuid

print("STEP 1 - Request a 500-sat invoice.")
print("Replace source_pub_key with the 66-character public node key.")
base = "https://bluehorseshoe.site/BlueHorseShoe/api/v2"
request_id = f"bhs-{uuid.uuid4()}"
payload = json.dumps({
    "request_id": request_id,
    "source_pub_key": "02REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
    "schema_version": "operational-summary-v2",
}).encode()

request = urllib.request.Request(
    f"{base}/l402/challenge", payload,
    {"Content-Type": "application/json"}, method="POST")
try:
    urllib.request.urlopen(request)
except urllib.error.HTTPError as response:
    if response.code != 402:
        raise
    print("Request ID (keep this process open):", request_id)
    header = response.headers["WWW-Authenticate"]

macaroon = re.search(r'macaroon="([^"]+)"', header).group(1)
invoice = re.search(r'invoice="([^"]+)"', header).group(1)
print("STEP 2 - Pay this invoice once within 15 minutes:", invoice)
preimage = getpass.getpass("STEP 3 - Payment preimage (hidden): ").strip()
if not re.fullmatch(r"[0-9a-fA-F]{64}", preimage):
    raise ValueError("Preimage must be 64 hexadecimal characters")
authorization = f"L402 {macaroon}:{preimage}"
paid = urllib.request.Request(
    f"{base}/operational-summary", payload,
    {"Content-Type": "application/json", "Authorization": authorization},
    method="POST")
with urllib.request.urlopen(paid) as report:
    print("Report HTTP", report.status)
    print(report.read().decode())
Node.js 18+

JavaScript

import { randomUUID } from "node:crypto";
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

console.log("STEP 1 - Request a 500-sat invoice.");
console.log("Replace source_pub_key with the 66-character public node key.");
const base = "https://bluehorseshoe.site/BlueHorseShoe/api/v2";
const body = JSON.stringify({
  request_id: "bhs-" + randomUUID(),
  source_pub_key: "02REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
  schema_version: "operational-summary-v2"
});

const challenge = await fetch(base + "/l402/challenge", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body,
  redirect: "error"
});
if (challenge.status !== 402) throw new Error("Expected HTTP 402, received " + challenge.status);
console.log("Request ID (keep this):", JSON.parse(body).request_id);
const header = challenge.headers.get("www-authenticate");
const macaroon = header.match(/macaroon="([^"]+)"/)[1];
const invoice = header.match(/invoice="([^"]+)"/)[1];
console.log("STEP 2 - Pay this invoice once within 15 minutes:", invoice);

const terminal = createInterface({ input, output });
const preimage = await terminal.question("STEP 3 - Payment preimage: ");
terminal.close();
if (!/^[0-9a-fA-F]{64}$/.test(preimage)) throw new Error("Invalid payment preimage");
const authorization = `L402 :`;
const result = await fetch(base + "/operational-summary", {
  method: "POST",
  headers: { "Content-Type": "application/json", Authorization: authorization },
  body,
  redirect: "error"
});
console.log("Report HTTP", result.status);
console.log(await result.json());
Java 11+

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Scanner;
import java.util.UUID;
import java.util.regex.Pattern;

public class BlueHorseShoeApiExample {
  public static void main(String[] args) throws Exception {
    System.out.println("STEP 1 - Request a 500-sat invoice.");
    System.out.println("Replace publicKey with the 66-character public node key.");
    HttpClient client = HttpClient.newBuilder()
        .followRedirects(HttpClient.Redirect.NEVER)
        .build();
    String base = "https://bluehorseshoe.site/BlueHorseShoe/api/v2";
    String publicKey = "02REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS";
    if (!publicKey.matches("^(02|03)[0-9a-f]{64}$")) {
      throw new IllegalArgumentException("Replace publicKey before running this example.");
    }
    String requestId = "bhs-" + UUID.randomUUID();
    String body = "{\"request_id\":\"" + requestId
        + "\",\"source_pub_key\":\"" + publicKey
        + "\",\"schema_version\":\"operational-summary-v2\"}";

    HttpRequest challenge = HttpRequest.newBuilder(
            URI.create(base + "/l402/challenge"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();
    HttpResponse<String> quote = client.send(
        challenge, HttpResponse.BodyHandlers.ofString());
    System.out.println("Request ID (keep this): " + requestId);
    System.out.println("Challenge HTTP " + quote.statusCode());
    if (quote.statusCode() != 402) {
      throw new IllegalStateException("Expected HTTP 402. Response: " + quote.body());
    }
    String header = quote.headers().firstValue("WWW-Authenticate").orElseThrow();
    var macaroonMatch = Pattern.compile("macaroon=\\\"([^\\\"]+)\\\"").matcher(header);
    var invoiceMatch = Pattern.compile("invoice=\\\"([^\\\"]+)\\\"").matcher(header);
    if (!macaroonMatch.find() || !invoiceMatch.find()) throw new IllegalStateException("Invalid L402 challenge");
    String macaroon = macaroonMatch.group(1);
    System.out.println("STEP 2 - Pay this invoice once within 15 minutes: " + invoiceMatch.group(1));

    System.out.println("STEP 3 - Obtain the preimage from your wallet after payment.");
    System.out.print("Payment preimage: ");
    String preimage = new Scanner(System.in).nextLine().trim();
    if (!preimage.matches("[0-9a-fA-F]{64}")) throw new IllegalArgumentException("Invalid preimage");
    String authorization = "L402 " + macaroon + ":" + preimage;
    HttpRequest paid = HttpRequest.newBuilder(
            URI.create(base + "/operational-summary"))
        .header("Content-Type", "application/json")
        .header("Authorization", authorization)
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();
    HttpResponse<String> result = client.send(
        paid, HttpResponse.BodyHandlers.ofString());
    System.out.println("Report HTTP " + result.statusCode());
    System.out.println(result.body());
  }
}
PowerShell 7

PowerShell

Write-Host 'STEP 1 - Request a 500-sat invoice.'
Write-Host 'Replace source_pub_key with the 66-character public node key.'
$baseUri = 'https://bluehorseshoe.site/BlueHorseShoe/api/v2'
$request = @{
    request_id = "bhs-$([guid]::NewGuid().ToString('N'))"
    source_pub_key = '02REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS'
    schema_version = 'operational-summary-v2'
} | ConvertTo-Json -Compress

$challenge = Invoke-WebRequest `
    -Uri "$baseUri/l402/challenge" `
    -Method Post `
    -ContentType 'application/json' `
    -Body $request `
    -SkipHttpErrorCheck `
    -MaximumRedirection 0
$l402 = [string]$challenge.Headers['WWW-Authenticate']
$requestId = ($request | ConvertFrom-Json).request_id
Write-Host "Request ID (keep this): $requestId"
if ($challenge.StatusCode -ne 402) {
    throw "Expected HTTP 402; received $($challenge.StatusCode)."
}

if ($l402 -notmatch 'macaroon="([^"]+)".*invoice="([^"]+)"') { throw 'Invalid L402 challenge' }
$macaroon = $Matches[1]; $invoice = $Matches[2]
Write-Host "STEP 2 - Pay this invoice once within 15 minutes: $invoice"
$securePreimage = Read-Host 'STEP 3 - Payment preimage (hidden)' -AsSecureString
$pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePreimage)
try { $preimage = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) }
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) }
if ($preimage -notmatch '^[0-9a-fA-F]{64}$') { throw 'Invalid preimage' }
$authorization = "L402 :"
$report = Invoke-RestMethod `
    -Uri "$baseUri/operational-summary" `
    -Method Post `
    -ContentType 'application/json' `
    -Headers @{ Authorization = $authorization } `
    -Body $request `
    -MaximumRedirection 0
Write-Host 'Report retrieved successfully.'
$report | ConvertTo-Json -Depth 20
Payment boundary: The examples deliberately do not automate wallet payment. Confirm the 500-sat invoice and apply an explicit spending policy before allowing software or an agent to pay it. The examples retain the macaroon only in process memory and never display it. Never commit the macaroon, payment preimage, or assembled Authorization value to source control or logs.

Decision Support

Public evidence for a better-informed node review.

The response can bring several evidence streams together without accessing private node data.

Profile

Public indicators

Review public node and channel context with explicit evidence timestamps.

Routing

Routing observations

Inspect routing evidence and potential candidate channel opportunities.

Reachability

Channel observations

Review observed payment reachability strengths and weaknesses across public channels.

Action

Operator considerations

Receive findings for operator review—not automated instructions or guaranteed outcomes.

Canonical Flow

Built for applications, scripts and autonomous agents.

The user guide documents the platform-neutral HTTPS flow. The PowerShell helper automates the same workflow as an optional convenience.

  • Scoped request and schema
  • One payment per analysis
  • Retry-safe processing
  • Explicit section availability
  • Structured JSON response

Verify Downloads

Check the release package before use.

A SHA-256 checksum for the reviewed guide is supplied alongside the download.

Download SHA-256 Checksums