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());
}
}
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.