Sub-system 18: agent payments over the Machine Payments Protocol
Sub-system 18: agent payments over the Machine Payments Protocol
Section titled “Sub-system 18: agent payments over the Machine Payments Protocol”Status: implemented 2026-09-21. Binding decision: master D59.
1. Goal
Section titled “1. Goal”Give agents one resource they can buy per request without an account. The
transaction runs over the Machine Payments Protocol (MPP,
draft-httpauth-payment-01) with the stripe charge method.
2. Scope
Section titled “2. Scope”- In scope:
GET /api/v1/spotlights/{day}/metricson the apex, priced at 100 cents EUR per request; the 402 challenge; credential verification; settlement through Stripe; a receipt header; OpenAPI discovery throughx-payment-info; documentation in/pricing.md,/developers.md,/agents.mdand/llms.txt. - Out of scope: sessions (
intent=session), other payment methods, an MCP tool for the paid resource, refunds of MPP charges.
3. Flow
Section titled “3. Flow”- The agent requests the resource with no credential.
- The apex answers 402,
Cache-Control: no-store, andWWW-Authenticate: Payment id, realm, method="stripe", intent="charge", request, expires, description.requestis the JCS-serialised, base64url JSON{amount, currency, description, externalId, methodDetails:{networkId,paymentMethodTypes:["card"]}}. The challenge is valid for 5 minutes. - The agent obtains a Stripe Shared Payment Token and retries with
Authorization: Payment <base64url JSON {challenge, payload:{spt}}>. - The apex verifies the credential shape and binding: realm, method, intent,
requestfor the same resource, and expiry. It reads the metrics row. A missing day answers 404 before any charge. - The apex calls
POST /internal/agent-payments/settleon apps/api over the service binding with theX-NYL-Internaldigest. The api confirms a PaymentIntent withpayment_method_data.shared_payment_granted_tokenon Stripe API version2026-04-22.preview, idempotency keympp:<challenge id>, and stores one row inagent_payments. - The apex answers 200 with the metrics and
Payment-Receipt({status:"success", method:"stripe", timestamp, reference}).
A replayed credential finds the stored operation on the api, verifies its resource,
amount, currency and payment-token fingerprint, and returns the stored receipt.
Before calling Stripe, the api persists an attempt in agent_payment_attempts.
Uncertain results return 503 and require the same credential and idempotency key.
Known pending PaymentIntents are retrieved, not recreated. After 23 hours an
unknown PaymentIntent requires operator reconciliation, before Stripe may prune
its idempotency key. Expired challenges can reconcile an existing attempt but
cannot start a new payment.
4. Data
Section titled “4. Data”agent_payments(challenge_id PK, resource, amount_cents, currency, payment_intent_id, settled_at), migration 0049. Reads seek the primary key.
5. Security
Section titled “5. Security”- The apex holds no Stripe secret. Only apps/api charges.
- The settle route accepts the internal host only and a constant-time digest compare of the shared secret.
- The credential must echo the exact
requestthe apex minted for that resource, so an agent cannot pay for one day and read another. - The Stripe SDK 18.5.0 typings do not carry
shared_payment_granted_token. The parameter is passed through one typed cast instripeCharge, pinned by a contract test.
6. Ceilings
Section titled “6. Ceilings”- One price, one resource. A second paid resource copies the route entry.
- The challenge id contains a random nonce and an HMAC binding the realm, resource request and expiry, using a domain-separated derivation of the existing handoff secret. Missing signing configuration fails closed. The apex stores no challenge; the api stores payment attempts before settlement.
7. ACP checkout
Section titled “7. ACP checkout”The same fixed EUR 1 report is available through ACP 2026-04-17 at
/checkout_sessions on the API and apex. Create/get/update/complete/cancel
persist in migration 0051. OAuth account:read identifies the owner (user and
grant); a buyer-approved Stripe SPT separately authorizes the charge. A revoked
or replaced grant cannot retrieve its earlier purchases.
All requests require API-Version; mutations also require Idempotency-Key. Server pricing and one settled day are authoritative. Checkout expires after 30 minutes; an atomic freeze prevents complete/cancel and update/complete races. Identical retries preserve payment identity. Unknown outcomes return 503; known payment failures or buyer action return requires_escalation, with no automatic 3DS redirect. Cart changes/cancellation are refused after payment starts. Fulfillment requires confirmed settlement and the owning bearer grant.
The custom com.nowyourlink.stripe.spt handler publishes config and instrument
schemas. API production config supplies the merchant account and agent profile;
preview fails closed without configuration. This does not implement a card vault,
ACP delegate-payment issuance, UCP, AP2 or x402.
8. Validation
Section titled “8. Validation”Focused protocol, ownership, replay, storage and payment-race tests cover this path. An opt-in test confirms a EUR 1 test-mode Stripe payment and credential replay, using the same settlement adapter:
NYL_STRIPE_TEST_ENV_FILE=/path/to/test.env pnpm --filter @nowyourlink/api exec vitest run --config vitest.sandbox.config.tsThe test rejects non-test keys before making requests. No live charge was made.