agent-payment-policy
v0.15.1
Published
Pre-purchase seller evidence, policy authorization, and receipt primitives for AI agents buying APIs through x402 or MPP
Maintainers
Readme
agent-payment-policy
Credential-free policy and evidence primitives for machine buyers that use x402 or MPP.
Use it when an AI agent needs to turn a private purchase need into a bounded payment plan, reject incompatible x402 or MPP offers, authorize one exact request separately from execution, and produce a public-safe payment receipt. It is a buyer policy layer, not a wallet, facilitator, or seller proxy.
The package separates five concerns:
- A private machine need becomes an immutable intent digest.
- External offers are filtered by exact request, value, capital, ownership, and expiry constraints.
- A separate Ed25519 policy identity authorizes one exact plan.
- A second short-lived authorization can bind the complete signing action, including every call in a batch, after runtime payment terms are known.
- A public-safe receipt binds settlement and output evidence without retaining query values, JSON request bodies, or response bodies.
It also exposes a wallet-free control-coverage gate for delegated signers. A settlement adapter can declare where each of fourteen required controls is enforced, then reject the profile unless every pre-signature and post-settlement control is covered by the provider, the independent buyer, or both. The result uses explicit dispositions rather than a security score.
It contains no wallet executor, payment signer, facilitator credential, seller key, RPC key, live project identity, or default wallet path. It cannot move funds.
Version 0.13.0 adds an optional buyer-owned acceptance-schema boundary. The
buyer can inspect a local JSON Schema, bind its canonical SHA-256 digest into an
intent, prepare an exact validator before wallet access, and require the actual
paid output to satisfy that same schema when creating a receipt. The schema is
not copied into the intent, plan, authorization, receipt, or network request.
Contracts without schemaDigest retain the prior required-field behavior.
Version 0.13.1 adds a neutral installable agent skill for applying that boundary to x402, MPP, HTTP 402, paid API, and paid MCP workflows. The skill complements wallet and payment-execution tools. It does not authorize spend or move funds.
Version 0.14.1 adds the public residual as three verbs: decide, bind, and classify. A foreign agent can inspect a request, plan an offer, verify a signed authorization, accept a local paid body, and classify caller-verified receipt facts without importing Pilot buyer code, loading a wallet, or calling a facilitator. Official clients can still pay a SameDayDesk 402 without this package. This release is CLI-additive on 0.14.x; a buyer pin on 0.14.0 keeps working.
Decide, bind, and classify
Speak the package as three verbs. Public residual may inspect, plan, verify, accept a local body, and classify caller-verified facts. It may not load a wallet, sign a payment, call a facilitator, or read Pilot identity. Durable Ed25519 policy signing stays library-only. There is no durable private-key CLI flag.
| Verb | Meaning | CLI | Library |
| --- | --- | --- | --- |
| decide | Is this request constructible, and is there a viable offer? | construct-request, construct-request-schema, plan-check, inspect-url, inspect-json-request, output-schema-check, offer-coherence-check, listing-identity-check, response-contract-check | constructRequest, createPlan, normalizeRequest, inspectOutputSchema, evaluateOfferCoherence, evaluateListingIdentity, evaluateResponseContract |
| bind | Lock one finished request or verify a signed plan without a private key | construct-request (when request_constructible), verify-authorization, verify-execution, service-deployment-verify | constructRequest, verifyAuthorization, verifyExecutionAuthorization, verifyServiceDeploymentStatement |
| classify | Accept a local paid body or classify already verified settlement facts | output-accept, receipt-completeness-check | inspectOutputSchema → prepareOutputValidator → validateOutput, evaluateReceiptCompleteness |
construct-request takes offline JSON and returns request_constructible or
not_constructible. It reuses normalizeRequest. Bare path templates, empty or
placeholder query values, a missing finished example, credential-shaped query
keys, and a non-read_only effect are refused. Bare /extract is
not_constructible. A finished example such as
https://agents.samedaydesk.com/extract?url=https://example.com binds. Missing
purchase evidence stays observable; requirePurchaseEvidence remains opt-in.
{
"method": "GET",
"url": "/extract",
"example": "https://agents.samedaydesk.com/extract?url=https://example.com",
"effect": "read_only",
"requirePurchaseEvidence": false
}plan-check wraps createPlan with no key. Over-cap, stale, and self-pay
offers are no_viable_offer.
verify-authorization takes an envelope, a public PEM, and a plan file.
A tampered plan fails. A matching envelope verifies. verify-execution is the
optional second check after the plan authorization is independently verified.
output-accept inspects a local schema, prepares the validator, and validates a
paid body. Stdout is hash-only. Exit 0 accepts and exit 1 rejects. The body
is never echoed.
receipt-completeness-check still exits 0 by default, including for
partial. Opt in with --fail-on conflict so CI can halt on conflict
without treating missing optional evidence as failure.
Version 0.15.0 packs two credential-free adoption examples, asserts that the
default CLI and those examples neither sign a payment nor send one, repairs the
skill output-contract field names (maxResponseBytes, an explicit
requiredFields list, and parsed JSON), and isolates nested npm pack from CI
--dry-run. It includes all additive decide, bind, and classify behavior from
0.14.1; 0.14.1 does not include the packaged examples.
Version 0.15.1 keeps a nullable object property in the buyer contract when its
parent requires that property, without falsely promoting the nullable object's
branch-only nested fields to unconditional required paths. This covers both
JSON Schema type: ["object", "null"] and OpenAPI-style nullable: true. A
nullable root exposes no guaranteed property paths. The exact prepared schema
still validates the selected runtime branch, so a non-null object must satisfy
its own nested requirements while null remains valid when permitted.
Try it
npm install agent-payment-policy
npx agent-payment-policy demo
npx agent-payment-policy inspect-url 'https://example.com/data?asset=ETH'
npx agent-payment-policy inspect-json-request 'https://example.com/analyze' ./request.json
npx agent-payment-policy output-schema-check ./output-schema.json data.value,data.sourceThe demo generates an ephemeral policy key and produces a plan plus a verified authorization. It performs no network request and no payment. Running the CLI with no command prints usage and does not generate keys, sign, or pay.
Five-minute local adoption
Pack this repository and run the two credential-free examples. Version 0.15.0 ships them; version 0.14.1 does not. They use mock x402 and MPP offers plus a frozen policy-authorization fixture. They do not fetch, load a wallet, sign a payment, or send a payment.
npm pack --ignore-scripts
npm install ./agent-payment-policy-0.15.1.tgz
node node_modules/agent-payment-policy/examples/mock-x402-mpp-preflight.mjs
node node_modules/agent-payment-policy/examples/verify-policy-receipt.mjsThe preflight example filters mock offers and selects one plan. The verification
example checks a committed policy authorization, binds a public-safe receipt,
and classifies synthetic completeness observations. That completeness report is
not chain or wallet evidence. See examples/README.md.
The five-minute no-pay path is unchanged: inspect-url, output-schema-check,
demo, and receipt-completeness-check. Additive composition verbs:
npx agent-payment-policy construct-request ./request.json
npx agent-payment-policy plan-check ./plan-input.json
npx agent-payment-policy verify-authorization ./envelope.json ./public.pem ./plan.json
npx agent-payment-policy output-accept ./output-schema.json sha256:... ./paid-body.json
npx agent-payment-policy receipt-completeness-check ./observation.json --fail-on conflictInstall the agent skill
npx skills add epistemedeus/agent-payment-policy@agent-payment-policyThe skill teaches an agent to define a buyer-owned JSON Schema, bind its exact
digest through intent and authorization, and validate the settled body before
accepting delivery. It is also included in the npm package at
skills/agent-payment-policy/SKILL.md.
Bind the output you are willing to buy
import { readFile } from "node:fs/promises";
import {
createIntent,
createReceipt,
inspectOutputSchema,
prepareOutputValidator,
} from "agent-payment-policy";
const schema = JSON.parse(await readFile("./output-schema.json", "utf8"));
const inspected = inspectOutputSchema({
schema,
requiredFields: ["data.value", "data.source"],
});
const intent = createIntent({
// other private need, economics, and policy fields
output: {
mediaType: "application/json",
requiredFields: ["data.value", "data.source"],
maxResponseBytes: 100_000,
schemaDigest: inspected.schemaDigest,
},
});
// Compile before wallet or signing work.
const outputSchemaValidator = prepareOutputValidator({
schema,
contract: intent.output,
});
const receipt = createReceipt({
plan,
authorization,
amountAtomic,
transactionReference,
response,
outputSchemaValidator,
});inspectOutputSchema accepts only the same bounded, self-contained response
schema subset used by the pre-purchase contract evaluator. It also compiles the
schema with strict JSON Schema 2020-12 plus standard formats. The prepared
validator is capability-like and bound to one digest; a different or missing
validator fails closed when the intent includes schemaDigest.
Classify receipt completeness
Provider receipts, chain receipts, and buyer balance evidence frequently carry different parts of the settlement fact. Normalize already verified facts into controlled match states and classify them without retaining raw headers, signatures, paid bodies, credentials, or wallet secrets:
import { evaluateReceiptCompleteness, SCHEMAS } from "agent-payment-policy";
const report = evaluateReceiptCompleteness({
schemaVersion: SCHEMAS.receiptCompletenessObservation,
protocol: "x402",
receipt: {
present: true,
success: "confirmed",
transactionReference: "match",
amount: "missing",
network: "match",
asset: "match",
recipient: "match",
payer: "match"
},
transaction: {
checked: true,
success: "confirmed",
transactionReference: "match",
amount: "not_checked",
network: "match",
asset: "match",
recipient: "match",
payer: "match"
},
balance: {
checked: true,
delta: "match",
asset: "match",
payer: "match"
},
outputValidation: "passed"
});The report distinguishes reconciled, partial, conflict, and
insufficient; lists proven and missing dimensions; and says whether
transaction or balance evidence supplemented the provider receipt. The caller
must verify every input fact first. This evaluator does not parse a raw receipt,
verify a transaction or signature, authorize payment, access a wallet, or prove
independent demand.
For a local file:
npx agent-payment-policy receipt-completeness-check ./observation.json
npx agent-payment-policy receipt-completeness-check ./observation.json --fail-on conflict
npx agent-payment-policy receipt-completeness-schemaGate a delegated signer
import {
assertControlCoverage,
createControlCoverage,
PAYMENT_CONTROL_DIMENSIONS,
} from "agent-payment-policy";
const coverage = createControlCoverage({
profileId: "tempo-pathusd",
provider: "your-signer",
network: "eip155:42431",
protocol: "mpp-tempo-charge",
independentVerified: [...PAYMENT_CONTROL_DIMENSIONS],
});
assertControlCoverage(coverage, {
profileId: "tempo-pathusd",
provider: "your-signer",
network: "eip155:42431",
protocol: "mpp-tempo-charge",
});Call the assertion before account access, wallet loading, signing, or payment. The report is evidence about enforcement placement, not proof that a declared control is implemented correctly. Bind profiles in reviewed code, test each control against drift, and retain the underlying acceptance evidence.
operation answers which RPC or signing method may run. execution_shape
separately answers whether the complete action, including batch cardinality and
ordering, is fixed. Do not credit method allowlisting as execution-shape
coverage.
Bind the exact execution batch
Provider policies often constrain a signing method, contract, function, and arguments without constraining how many calls appear in a batched transaction. Use a second short-lived execution authorization after the transaction has been built and independently reviewed:
import {
authorizeExecution,
verifyExecutionAuthorization,
} from "agent-payment-policy";
const executionEnvelope = authorizeExecution({
authorization, // output of verifyAuthorization
method: "eth_signTransaction",
network: "eip155:42431",
action: unsignedTransaction,
}, { privateKey: policyPrivateKey, kid: "policy-execution-1" });
const executionAuthorization = verifyExecutionAuthorization(executionEnvelope, {
publicKey: policyPublicKey,
authorization,
method: "eth_signTransaction",
network: "eip155:42431",
action: unsignedTransaction,
});Changing the method, network, any call, call order, call count, fee field, nonce, validity field, calldata byte, or other JSON field invalidates the binding. The authorization records only a canonical digest and byte count, not the action body. This is an exact transport and signer-boundary lock. The trusted buyer still must independently determine that the original action is safe and matches the payment challenge.
Safety boundary
normalizeRequest rejects non-HTTPS URLs, user information, nonstandard HTTPS
ports, local hostnames, literal private/reserved IPs, and credential-like query
keys. A network adapter must resolve DNS and pass every result to
assertResolvedPublicAddresses immediately before connecting. It must also pin
the resolved address, reject cross-origin redirects, enforce time and response
size limits, and repeat the check after every allowed redirect.
The package hashes the full private request URL for exact binding while exposing
only method, origin, path, and query-key names in plans and receipts. For JSON
POST requests, it canonicalizes the private object and additionally exposes only
the media type, byte count, and SHA-256 body digest. The request body itself is
not retained. An executor must reconstruct the canonical bytes and match the
signed request binding before sending or paying. See
docs/threat-model.md and
docs/data-handling.md.
Response-contract reports keep both top-level requiredFields and recursively
guaranteed requiredPaths. A schema that requires only data does not promise
data.attributes, even when that nested property is described. Buyer policy can
therefore require exact useful paths without retaining the seller schema or
example. Reports carrying recursive paths use the immutable
agent-payment-policy.response-contract-report.v2 identifier.
Purchase evidence
A payment challenge says how to pay. It does not by itself say whether the exact operation is read-only, which JSON paths the seller declares as guaranteed, how response replay is bound, or which runtime receipts the buyer must validate.
Version 0.12.0 adds a small credential-free contract for that gap:
import {
createPurchaseEvidenceManifest,
purchaseEvidenceLink,
selectPurchaseEvidenceLink,
verifyPurchaseEvidenceManifest,
} from "agent-payment-policy";
const manifest = createPurchaseEvidenceManifest({
service: { origin: "https://seller.example", version: "1.0.0" },
protocols: ["x402", "mpp"],
evidence: { deployment: "https://seller.example/.well-known/deployment.json" },
operations: [{
method: "GET",
path: "/data",
effect: "read_only",
output: {
mediaType: "application/json",
schemaDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
requiredPaths: ["data", "data.value"],
declaration: "seller_declared",
},
replay: { requestBinding: ["method", "canonical_url"] },
receipt: {
x402: "PAYMENT-RESPONSE",
mpp: "Payment-Receipt",
runtimeValidationRequired: true,
},
}],
boundary: { claims: "seller_declared_until_independently_verified" },
});
const link = purchaseEvidenceLink(
"https://seller.example/.well-known/agent-payment-evidence.json",
);
const manifestUrl = selectPurchaseEvidenceLink(
link,
"https://seller.example/data",
);
const binding = verifyPurchaseEvidenceManifest(manifest, {
target: "https://seller.example/data",
method: "GET",
requiredPaths: ["data.value"],
});The Link header carries both the registered describedby relation and the
package's exact extension relation. Generic API documentation links remain
unrelated and are ignored. The target must be same-origin, the manifest digest
is deterministic, and selection is by exact method and path.
The package performs no fetch. A network adapter still needs HTTPS, DNS and SSRF controls, redirect rejection, byte and time limits, and a fresh read before wallet access. The manifest is seller-declared planning evidence. It is not permission to spend and does not replace live payment terms, output validation, receipts, settlement, or independent trust evidence.
Status
This is a reference implementation under active design review. It does not claim adoption, adaptive supplier selection, wallet security, or transaction execution. A separately reviewed adapter can use the verified authorization as one input to its own signing policy. A control-coverage report does not make a provider trustworthy by declaration; the integration remains responsible for proving every claimed enforcement path.
Standardize wallet policy observations
Version 0.5.0 added a provider-neutral observation format for the exact wallet
policy problem this project encountered on both Tempo and Solana. Provider
policies denied useful wrong-action cases while still signing a transaction
that duplicated an approved action. The format therefore treats operation
and execution_shape as separate controls.
Create a safe offline draft matrix:
agent-payment-policy wallet-policy-init \
provider-lab "Example Wallet" eip155:8453 x402 \
> wallet-policy-observations.jsonReplace each not_tested row with the high-level result from your bounded
provider test. Keep only the standardized case, allowed, denied, or
error, the denial class, and an optional short safe code. Do not place API
keys, wallet IDs, signatures, transaction bodies, raw provider messages, or
other evidence payloads in the file.
Evaluate it locally with no network or wallet access:
agent-payment-policy wallet-policy-check wallet-policy-observations.jsonThe result is conformant, partial, or unsafe, with no opaque numerical
score. Only actual=denied plus denialClass=policy receives provider-native
credit. SDK validation errors and generic provider failures remain
inconclusive. The required duplicate_approved_action case must be explicitly
policy-denied for exact execution shape to pass.
Print the portable JSON Schemas:
agent-payment-policy wallet-policy-schemaThe core exports are createWalletPolicyObservationDraft,
evaluateWalletPolicyObservations, normalizeWalletPolicyObservations,
WALLET_POLICY_OBSERVATION_CASES, walletPolicyObservationInputSchema, and
walletPolicyObservationOutputSchema.
These APIs evaluate caller-supplied observations. They do not execute or prove the provider tests, access a wallet, verify a signature, sign, broadcast, or settle a payment.
Standardize stateful budget observations
Version 0.6.0 adds a separate seven-case format for wallet policies that depend on prior or concurrent requests. It does not fold stateful behavior into exact- action conformance because they answer different questions.
Create and evaluate a safe offline draft:
agent-payment-policy stateful-policy-init \
provider-budget-lab "Example Wallet" eip155:8453 x402 \
> stateful-policy-observations.json
agent-payment-policy stateful-policy-check stateful-policy-observations.json
agent-payment-policy stateful-policy-schemaThe required cases are one request inside the cap, a sequential request that
exceeds the cumulative cap, signed-but-unbroadcast accounting, unrecognized
calldata, concurrent oversubscription, and a missing counter reference. An
optional case records application-side serialization separately. A local mutex,
queue, or rate limiter can be useful but never receives provider-native credit.
The output is conformant, partial, or unsafe and has no opaque score.
The core exports are createStatefulWalletPolicyObservationDraft,
evaluateStatefulWalletPolicyObservations,
normalizeStatefulWalletPolicyObservations,
STATEFUL_WALLET_POLICY_OBSERVATION_CASES,
statefulWalletPolicyObservationInputSchema, and
statefulWalletPolicyObservationOutputSchema.
Compare catalog metadata with the live unsigned offer
Version 0.7.0 adds the missing boundary between machine discovery and value authorization. A registry can advertise a stale route, protocol, amount, network, asset, recipient, or expiry even when the seller's current unsigned x402 or MPP challenge is correct.
Create a secret-free observation file with one catalog record and one live unsigned runtime offer, then evaluate it locally:
agent-payment-policy offer-coherence-check offer-observation.json
agent-payment-policy offer-coherence-schemaThe runtime record must include a complete request, protocol, atomic amount,
network, asset, recipient, and unexpired absolute deadline. An x402 adapter can
derive that deadline from the challenge observation time plus
maxTimeoutSeconds; MPP normally supplies it directly. Catalog economics may
be missing. Missing catalog fields remain unknown, explicit disagreements
are drifted, and only complete agreement is coherent.
The evaluator returns eligible_for_separate_value_and_policy_authorization
only after every dimension agrees. It does not authorize the purchase. Feed the
coherent runtime offer into createPlan, then use the existing independent plan
and execution authorization layers.
Full query values and JSON request-body values remain digest-bound and absent
from the report. The evaluator makes no network request, accepts no credential,
accesses no wallet, signs nothing, and sends no payment. The core exports are
evaluateOfferCoherence, offerCoherenceInputSchema, and
offerCoherenceOutputSchema.
Version 0.8.0 adds a second credential-free boundary for catalog listing identity. A buyer or seller can supply one canonical origin, exact route, optional settlement identity, an optional list of catalogs checked, and up to 100 catalog records:
agent-payment-policy listing-identity-check listing-observation.json
agent-payment-policy listing-identity-schemaThe evaluator classifies each catalog as canonical, duplicate_records,
alias_only, alias_collision, or route_absent. A non-canonical record is
an alias candidate only when it shares the exact route and caller-supplied
settlement identity. That still does not prove hostname ownership. Conflicts
return review_required; only a canonical result advances to a separate live
runtime-offer preflight. Declaring a checked catalog in sources also lets an
empty response produce an explicit route_absent result instead of looking
like a catalog that was never checked.
Query values and settlement-identity values are absent from the report. The
evaluator makes no network request, accepts no credential, accesses no wallet,
signs nothing, sends no payment, and never treats catalog duplication as
demand. The core exports are evaluateListingIdentity,
listingIdentityInputSchema, and listingIdentityOutputSchema.
Version 0.9.0 adds a signed service-deployment statement for an ambiguity that catalog records and matching well-known files cannot resolve on their own. A seller can bind one canonical HTTPS origin, explicitly authorized alias origins, exact HTTP method and path pairs, and exact x402 or MPP settlement identities in a short-lived Ed25519 JWS:
import {
createServiceDeploymentStatement,
signServiceDeploymentStatement,
verifyServiceDeploymentStatement,
} from "agent-payment-policy";
const statement = createServiceDeploymentStatement({
canonicalOrigin: "https://seller.example",
deployments: [{
origin: "https://seller.example",
routes: [{ method: "GET", path: "/data" }],
settlement: [{
protocol: "x402",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
recipient: "0x2222222222222222222222222222222222222222",
decimals: 6,
}],
}, {
origin: "https://edge.example",
routes: [{ method: "GET", path: "/data" }],
settlement: [{
protocol: "x402",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
recipient: "0x2222222222222222222222222222222222222222",
decimals: 6,
}],
}],
}, { ttlMs: 86_400_000 });
const envelope = signServiceDeploymentStatement(statement, {
privateKey,
kid: "seller-2026-08",
});
const verification = verifyServiceDeploymentStatement(envelope, {
publicKey,
request: { method: "GET", url: "https://edge.example/data?asset=ETH" },
runtimeOffer: {
protocol: "x402",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
recipient: "0x2222222222222222222222222222222222222222",
decimals: 6,
},
});Verification fails closed on a lookalike or unlisted origin, a different HTTP method or path, an expired statement, a different verification key, and any protocol, network, asset, recipient, or decimals mismatch. The result excludes query values and explicitly reports that it did not authorize, sign, or send a payment. The caller must obtain the verification key through a separate trusted identity channel. A valid signature proves control of that key, not DNS or domain control.
Private-key signing is intentionally library-only. The CLI can verify an envelope without accepting private keys:
agent-payment-policy service-deployment-verify envelope.json public-key.pem observation.json
agent-payment-policy service-deployment-schemaVersion 0.10.0 adds a credential-free response-contract readiness boundary for
machine buyers. It classifies one seller-declared successful JSON response as
admissible, partial, absent, or invalid before value authorization:
agent-payment-policy response-contract-check response-observation.json
agent-payment-policy response-contract-schemaAn admissible declaration is a bounded, self-contained top-level object schema with typed required fields and no unsupported composition or external-reference keywords. An optional example is checked only for structural consistency with that limited subset. The report retains the schema digest and top-level required field names, but not the schema, example, or query values. It makes no network request and accesses no credential or wallet.
This is pre-purchase evidence, not runtime attestation. Seller declarations are
unauthenticated unless the integrating caller supplies a separate trust layer.
The paid response must still be validated against the independently authorized
output contract after settlement. Complex JSON Schema composition remains
partial instead of receiving invented coverage.
