Spoolis docs
Verify a receipt
The Outcome Receipt is the public, portable economic artifact. Verify its authenticity with no Spoolis account or API call, and pin the trust set when verification must work offline.
Verification example
In the canonical recipe, Spoolis verifies 100 enrichment records at $1.00 per accepted record, accepts 98, rejects 2 with reasons, and signs an Outcome Receipt for $98.00 earned. Verify that receipt before the payment stack acts on the earned amount.
import { verifyOutcomeReceipt } from '@spoolis/receipt-verifier'
const keys = await fetch('https://spoolis.com/.well-known/spoolis-keys.json').then(r => r.json())
const expectedEnvironment = 'production'
const trustSet = keys.receipts[expectedEnvironment]
if (!trustSet) throw new Error('No receipt key is published for this environment')
const result = await verifyOutcomeReceipt(receipt, { trustSet, environment: expectedEnvironment })
console.log(result.valid)
console.log(receipt.amounts.earned)Sandbox and demo receipts use expectedEnvironment = 'demo'. Install with npm install @spoolis/receipt-verifier. The package has zero runtime dependencies and uses WebCrypto. The thin alias package @spoolis/outcome exposes the same verification as verifyOutcome, and the CLI accepts spoolis outcome verify receipt.json.
Verify from Python
The same verifier ships for Python 3.10+ as spoolis-receipt-verifier on PyPI, with the same trust-set contract and reason codes as the npm package.
pip install spoolis-receipt-verifierfrom spoolis_receipt_verifier import verify_outcome_receipt
result = verify_outcome_receipt(receipt, trust_set=trust_set)
print(result["valid"], result["reasons"])Verify a hosted receipt
For a hosted receipt at /r/<id>, fetch its signed JSON twin at /r/<id>/receipt.json. The JSON response links to the published receipt keys with the https://spoolis.com/rels/receipt-keys relation and to the receipt schema with describedby. The keys are available at /.well-known/spoolis-keys.json.
import { verifyOutcomeReceipt } from '@spoolis/receipt-verifier'
const receiptUrl = 'https://spoolis.com/r/<id>/receipt.json'
const receipt = await fetch(receiptUrl).then(response => response.json())
const keys = await fetch('https://spoolis.com/.well-known/spoolis-keys.json').then(response => response.json())
const expectedEnvironment = 'production'
const trustSet = keys.receipts[expectedEnvironment]
if (!trustSet) throw new Error('No receipt key is published for this environment')
const result = await verifyOutcomeReceipt(receipt, {
trustSet,
environment: expectedEnvironment,
})
if (!result.valid) throw new Error('Receipt verification failed')The hosted page also states the receipt's proof boundary: the signature and hashes bind the recorded agreement, evidence references, condition methods, and unit calculation. They do not establish that every supplied outside-world fact was independently true, that AI assisted interpretation is mathematically proven, or that a human or external judge was correct.
Pin trust material for offline use
Outcome Receipts use schema spoolis/outcome-receipt@1. Receipt keys are published at https://spoolis.com/.well-known/spoolis-keys.json. Save and review the receipt entries your application trusts, then pass that array as trustSet. Read Trust and key rotation for key statuses, validity windows, and the change feed. The JSON Schema is published at https://spoolis.com/schema/outcome-receipt-v1.schema.json.
Require the outcome your consumer needs
requireOutcome() first verifies the receipt, then applies a consumer policy for minimum result, agreement hash, issuer, age, accepted units, earned amount, or fully satisfied evidence requirements. requireOutcome() ships in @spoolis/receipt-verifier 0.1.3 and later, and the CLI flag in @spoolis/cli 0.1.5 and later.
spoolis verify receipt.json --require '{"minimum_status":"pass","evidence_requirements":"satisfied"}'import { requireOutcome } from '@spoolis/receipt-verifier'
const required = await requireOutcome(receipt, {
minimum_status: 'pass',
evidence_requirements: 'satisfied',
}, { trustSet, environment: 'production' })
if (!required.ok) throw new Error(required.reasons.join(', '))A partial batch has result: "partial", not pass, so minimum_status: "pass" refuses an 8 of 10 job even though value was earned. A consumer that should act on partial batches gates on the units and the money instead: set minimum_status: "partial" or omit it, then require min_accepted_units or min_earned. The receipt's earned amount already excludes rejected and uncertain units.
Three more keys cover common consumer rules. required_conditions lists condition ids that must each appear in the receipt with a passing result; a failure reads required_condition_failed:<id> and a condition the receipt never evaluated reads required_condition_missing:<id>. max_uncertain_units caps units.uncertain, with 0 as the no-uncertain gate. min_accepted_ratio requires units.accepted / units.total to reach a decimal between 0 and 1. The unit keys report units_absent on a receipt without units. The policy deliberately stops there: no boolean expressions, no cross-receipt logic, no predicates over evidence content. Richer rules belong in your own code, reading the receipt fields directly.
The offline policy does not pretend to know whether a later receipt superseded this one. Check the hosted receipt status when current correction or revocation state matters.
Offline signatures and optional online status
Offline signature verification proves authenticity without Spoolis uptime. A valid Ed25519 signature proves the receipt is genuine and unaltered. It does not prove every underlying check is reproducible or that the evidence reflects real-world truth. The status check is optional and only adds correction, revocation, or advisory state. A consumer about to take an irreversible action on a receipt should check status first, or accept supersession risk knowingly: offline verification proves the receipt is authentic, the status check proves it is current.
curl -sS https://spoolis.com/api/v1/receipts/ocr_0123456789abcdef01234567/status{"receipt_id":"ocr_0123456789abcdef01234567","state":"current","status":"active","advisories":[],"checked_at":"2026-08-17T12:00:00.000Z"}status- The receipt's disposition:
active,corrected, orrevoked. state- The coarse currentness view:
current,superseded, orunknown.
For known receipts, both endpoints now return both fields. /api/v1/receipts/{receipt_id}/status is the canonical endpoint. /api/receipts/{id}/status remains for consumers of the published verifier.
GET /api/v1/receipts/{receipt_id}/status returns current, superseded, or unknown. A superseded response includes superseding_reason and includes superseded_by when a replacement receipt exists. A well-formed ID with no known row returns unknown with HTTP 200. Receipt IDs are content-derived 96-bit values rather than sequential identifiers. The response contains no party identity, email, or private amount data.
No Spoolis account is required for offline verification or the public status endpoint. A valid result does not claim that payment was authorized, captured, or settled.
Your judge, this receipt
A receipt's conditions may have been judged by an external evaluator your system owns. The receipt names that evaluator and its binding, and Bring your own judge covers the pattern end to end.
Authenticity is not pay-once enforcement
A Spoolis Outcome Receipt proves the agreed outcome happened and that the receipt is authentic. It does not by itself prove the receipt has not already been used to release payment.
Any system that releases money on a receipt must enforce pay-once itself. It must record each receipt id it has acted on and refuse to act on the same id twice.
Spoolis offers an optional online consumed-status check and an optional hard execute_by rejection for consumers that want them. Offline signature verification remains sufficient for authenticity. Spoolis does not prevent double-spend on the payer's behalf.