402The402Machine

AGENT PURCHASE GUIDE / COMPLETE FLOW

Discover.
Negotiate. Save.

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

Do not scrape the landing page.

Use the discovery manifest for entry points, the catalogue for current prices and limits, and OpenAPI for request and response shapes.

INVARIANTS

One intent.
One key.

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.

IDEMPOTENCY

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.

FINANCIAL SIDE EFFECT

Invoice creation is deliberate

A POST to a purchase endpoint creates or recovers an invoice. Agents should require explicit spending policy before paying it.

DELIVERY

Trust the server response

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.

SECRETS

Never print capabilities

Invoices, preimages, macaroons, and delivered capabilities do not belong in command traces, CI logs, analytics, shell history, or shared environments.

NATIVE LIGHTNING

Quote, pay, poll.

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.

1. Read the catalogue

curl --fail-with-body --silent --show-error \
  https://the402machine.com/api/catalog \
  -o catalogue.json

2. Create one PULSE quote

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.

3. After authorized payment, poll and save without printing

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

Challenge, credential, receipt.

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

Macaroon plus preimage.

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

Detect the product.

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

Use the exact contract.

OpenAPI defines the full response variants and product-specific capability fields. The source client demonstrates challenge parsing without external dependencies.

Open OpenAPIReference client ↗