Reuse after uncertainty
If a response is lost, repeat the same request with the same key. Do not generate a second key merely because the client timed out.
AGENT PURCHASE GUIDE / COMPLETE FLOW
The402Machine is designed for both humans and software agents. Inspect the catalogue, create one intended purchase, pay its Lightning invoice, and write the delivered capability to a private file without printing it.
START WITH CONTRACTS
Use the discovery manifest for entry points, the catalogue for current prices and limits, and OpenAPI for request and response shapes.
INVARIANTS
Create a new Idempotency-Key for each purchase intent. Retries for that intent must preserve product, plan, endpoint, headers, and body. For agent credentials, preserve the exact request body bytes, including JSON whitespace and key order.
If a response is lost, repeat the same request with the same key. Do not generate a second key merely because the client timed out.
A POST to a purchase endpoint creates or recovers an invoice. Agents should require explicit spending policy before paying it.
A wallet success screen is not delivery. Native clients poll the order. Payment Auth and L402 clients accept only the server's credential-bearing HTTP 200 response.
Invoices, preimages, macaroons, and delivered capabilities do not belong in command traces, CI logs, analytics, shell history, or shared environments.
NATIVE LIGHTNING
This is the simplest agent flow. It returns an ordinary JSON quote with HTTP 402. After payment, poll the order until the one-time delivery appears.
curl --fail-with-body --silent --show-error \ https://the402machine.com/api/catalog \ -o catalogue.json
BODY='{"planId":"spark"}'
IDEMPOTENCY_KEY="pulse-$(cat /proc/sys/kernel/random/uuid)"
umask 077
curl --silent --show-error --dump-header quote.headers \
--output quote.json \
--request POST https://the402machine.com/api/payments/pulse \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--data-binary "$BODY"
Expected HTTP 402: the JSON contains orderId, product, planId, amountSats, bolt11, network, and expiresAt. Treat bolt11 as invoice material and do not log the full response.
set +x
ORDER_ID="$(node -e 'const q=require("./quote.json"); process.stdout.write(q.orderId)')"
while :; do
STATUS="$(curl --silent --output delivery.tmp \
--write-out '%{http_code}' \
"https://the402machine.com/api/payments/$ORDER_ID")"
[ "$STATUS" = 200 ] && break
[ "$STATUS" = 402 ] || { rm -f delivery.tmp; exit 1; }
sleep 3
done
mv delivery.tmp capability.json
chmod 600 capability.json
Do not pipe capability.json through jq in a logged session. Parse it inside the consuming process or inspect only non-secret fields.
PAYMENT AUTHENTICATION
Request the draft HTTP Payment Authentication adapter with X-Payment-Protocol: payment. The first response is a Lightning charge challenge.
BODY='{"planId":"spark"}'
IDEMPOTENCY_KEY="payment-$(cat /proc/sys/kernel/random/uuid)"
umask 077
curl --silent --show-error --dump-header payment.headers \
--output payment-challenge.json \
--request POST https://the402machine.com/api/payments/pulse \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'X-Payment-Protocol: payment' \
--data-binary "$BODY"
Expected HTTP 402: WWW-Authenticate: Payment plus a small JSON body describing the order, amount, and expiry. Pay the invoice only under an explicit agent spending policy.
Build Authorization: Payment <credential> from the returned challenge and payment preimage. Repeat the same method, URL, Idempotency-Key, protocol header, and byte-identical body. A successful HTTP 200 includes Payment-Receipt and the delivered resource.
umask 077 cat > payment-auth.conf <<'EOF' header = "Authorization: Payment replace-with-derived-credential" EOF chmod 600 payment-auth.conf set +x curl --silent --show-error --dump-header payment-result.headers \ --output capability.json \ --request POST https://the402machine.com/api/payments/pulse \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ --header 'X-Payment-Protocol: payment' \ --config payment-auth.conf \ --data-binary "$BODY" chmod 600 capability.json
The placeholder credential is inert. The dependency-free repository client writes the invoice to invoice.json and the final delivery to capability.json with mode 0600; it prints only filenames and the non-secret product discriminator. Never pass a live credential as a command-line argument where the process list can expose it.
L402
The L402 adapter uses a request-bound binary macaroon and the Lightning preimage. It is a compatibility path, not Coinbase x402.
BODY='{"planId":"spark"}'
IDEMPOTENCY_KEY="l402-$(cat /proc/sys/kernel/random/uuid)"
umask 077
curl --silent --show-error --dump-header l402.headers \
--output l402-challenge.json \
--request POST https://the402machine.com/api/payments/pulse \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'X-Payment-Protocol: l402' \
--data-binary "$BODY"
Expected HTTP 402: WWW-Authenticate: L402 with macaroon and invoice parameters. After payment, repeat the exact request with Authorization: L402 <macaroon>:<preimage>.
umask 077 cat > l402-auth.conf <<'EOF' header = "Authorization: L402 replace-with-macaroon-and-preimage" EOF chmod 600 l402-auth.conf set +x curl --silent --show-error --output capability.json \ --request POST https://the402machine.com/api/payments/pulse \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $IDEMPOTENCY_KEY" \ --header 'X-Payment-Protocol: l402' \ --config l402-auth.conf \ --data-binary "$BODY" chmod 600 capability.json
DELIVERY DISPATCH
All successful purchase flows return {"settled":true,"resource":{...}}. Dispatch on resource.product, then keep the complete resource object private.
node <<'NODE'
const fs = require('node:fs');
const result = JSON.parse(fs.readFileSync('capability.json', 'utf8'));
if (!result.settled || !result.resource) process.exit(1);
switch (result.resource.product) {
case 'catch':
// ownerToken + ingestToken + publicId
break;
case 'whisper':
// readToken + publicId; the AES key remains client-side
break;
case 'pulse':
// ownerToken + pingToken + publicId
break;
default:
throw new Error('unknown delivered product');
}
NODE
For WHISPER, upload only AES-256-GCM ciphertext. The encryption key never goes to the server and must be combined with the returned read capability locally.
NEXT STEPS
OpenAPI defines the full response variants and product-specific capability fields. The source client demonstrates challenge parsing without external dependencies.