402The402Machine

AGENT PURCHASE GUIDE / COMPLETE FLOW

Discover.
Negotiate. Save.

Inspect the catalogue, create one purchase, pay its Lightning invoice, and save the delivered capability 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.

GATE / MERCHANT PAYWALL

Request-bound access.
Direct merchant settlement.

GATE protects a fixed external API route. Agents create an intent, pay the merchant invoice, submit payer proof, then verify the signed receipt locally.

BODY='{"city":"Madrid"}'
IDEMPOTENCY_KEY="gate-$(cat /proc/sys/kernel/random/uuid)"

curl --silent --show-error --dump-header gate.headers \
  --output gate-quote.json \
  --request POST https://the402machine.com/api/gate/intents \
  --header "Authorization: Bearer $GATE_PROJECT_KEY" \
  --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --header 'X-Gate-Project: gate_project_…' \
  --header 'X-Gate-Route: weather' \
  --header 'X-Gate-Method: POST' \
  --header 'X-Gate-Path: /v1/weather' \
  --header 'Content-Type: application/json' \
  --data-binary "$BODY"

INTENT_ID="$(jq -r .intentId gate-quote.json)"
# After the wallet supplies the 32-byte preimage:
curl --fail-with-body --silent --show-error \
  --request POST "https://the402machine.com/api/gate/intents/$INTENT_ID/prove" \
  --header "Authorization: Bearer ***" \
  --header 'X-Gate-Project: gate_project_…' \
  --header 'Content-Type: application/json' \
  --data "$(jq -cn --arg preimage "$PREIMAGE_HEX" '{preimage:$preimage}')"

The first response is HTTP 402. Payment goes to the project's stored Lightning Address. Do not send a recipient address from the client. For LUD-21 projects, poll GET /api/gate/intents/{intentId} with the same project capability instead of submitting a payer preimage.

Credit rule: quotes, polling, expiry and invalid proofs consume zero credits. Only a persisted AUTHORIZED result consumes one monthly or prepaid authorization.

GATE does not claim x402 compatibility. It uses HTTP 402, payer proof and signed Ed25519 receipts.

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 every response and capability field. The source client shows the complete flow.

Open OpenAPIReference client ↗