Spoolis docs
Outcome Receipt specification
The Outcome Receipt is Spoolis's signed answer to: what happened, what passed, and what was earned? It is not payment authority, and it is signed before settlement.
Field reference
Most integrators care about these five fields
agreement: Which accepted Spool and agreement version this receipt covers.condition_results: What each agreed condition required and how it resolved.result: The overall pass, partial, fail, or uncertain outcome.units: How many units were submitted, accepted, and rejected.amounts.earned: The amount produced by the agreed economic rule.
The full field reference follows for completeness.
| Field | Meaning |
|---|---|
schema | The versioned identifier. Version 1 is spoolis/outcome-receipt@1. |
environment | production or demo. The account-free sandbox issues receipts in the demo environment. A verifier must require the environment it expects. |
id | A content-derived identifier. It is ocr_ plus the first 24 hex characters of the canonical unsigned body digest. |
agreement | The Spool ID, accepted agreement version, and SHA-256 agreement hash bound into the receipt. |
verification | Plan, compiler, evidence-policy, run, digest, and aggregation-policy identifiers used for this outcome. |
evidence_root | A SHA-256 digest binding the evidence set used by the run. |
declared_context | Optional caller-declared context recorded at issuance and never verified by Spoolis: declared_by is always caller, verified is always false, and gated_action names the consequential action the caller said the Outcome would gate (payment, workflow_step, publish, merge, delegation, other) with an optional gated_action_note. Consumers must not treat it as evidence. |
evidence_retention | Optional evidence-retention intent recorded at issuance. The policy is redact_after_days, redact_after_dispute_window, demo_ttl, or none. When present, retained_until records the intended boundary. Redaction keeps each evidence item's digests, so the receipt's evidence references still verify after the raw bytes are gone. Receipts issued before this field existed carry no retention statement. Bilateral Spool evidence is redacted after the dispute window closes plus the retention period, once the transaction is complete. |
condition_results | One row per condition. Each row records requirement, result, execution method, verifier, evidence references, optional provenance, optional confirmer, and optional reason. Each provenance entry names the source kind, capture method, observation time, and, when known, the submitting party (submitted_by) and any party it acted for (on_behalf_of). |
result | The aggregate pass, partial, fail, or uncertain result. |
units | Optional unitized outcome: total, accepted, rejected, earning rule, rejection summary, and unit-results digest. |
amounts | Decimal-string earned amount, optional committed amount, asset, and decimal precision. Earned is what the economic rule produced. Committed is the agreed maximum when present. |
payment_binding | Optional link between the receipt and the payment that funded the verification. Kind x402_payment_identifier carries the payment identifier the buyer supplied on the x402 request, so a consumer can join this receipt to that payment. |
actors | Payer and provider subject references, kinds, and optional alternate identifiers. |
acceptance_authority | Optional policy origin and author, plus version-pinned accepted and acknowledged parties listed separately. |
review | Optional review window copied from the agreement dispute policy: duration, end time, who may dispute, scope, and what happens on dispute. |
issued_at and execute_by | Issue time and an optional deadline for acting on the receipt. Passing execute_by does not invalidate the signature. |
nonce, series, and supersedes | Uniqueness, optional recurring-series context, and correction, review-decision, appeal-reversal, or dispute-resolution linkage. |
signing_key_id, signature, and algorithm | The SHA-256 ID of the Ed25519 public key, the base64 signature, and the fixed algorithm value Ed25519. |
evidence_retention records the intent that applied when the receipt was issued. redact_after_days covers production one-shot evidence, redact_after_dispute_window covers bilateral evidence after its dispute window and retention period, demo_ttl follows the demo expiry when that time is computable, and none declares no scheduled redaction. retained_until is the intended boundary recorded at issuance, not a guarantee of exact deletion timing. Redaction preserves per-item digests, so the receipt's evidence references still verify. Receipts issued before this field existed carry no retention statement. Bilateral Spool evidence is redacted after the dispute window closes plus the retention period, once the transaction is complete.
See the machine-readable JSON Schema for required properties, formats, and enum values.
Which policy governed this Outcome
The agreement block names the accepted policy instance: agreement.spool_id identifies the Spool, agreement.agreement_version identifies the projection version, and agreement.agreement_hash is the SHA-256 digest of the accepted policy projection. Issuance refuses to sign when that digest does not match the accepted agreement hash. When a hosted reusable policy was used, recipe.slug and recipe.hash identify it, while acceptance_authority records who authored the policy and who accepted it. A consumer pins agreement_hash in requireOutcome when it must act only under that accepted policy.
Condition results
requirement is mandatory or optional. result is pass, partial, fail, uncertain, not_evaluated, or not_applicable. Evidence provenance, when present, records source and capture facts. It is not a trust score. Missing provenance means unknown origin. Schema support for a method does not mean its executor is live. Use the single method-status table when choosing a request value.
| Method | Status | Meaning |
|---|---|---|
| Live today | ||
deterministic | Live | Fixed code and inputs produce a fixed evaluation. Durable evidence may support later reproduction; time-bound network observations may not. |
human_confirmed | Live | An authorized person supplied the confirmation. Spoolis recorded it and applied the agreed rule. |
ai_assisted | Live for claim_cited | Live for the claim_cited checker: model evaluation grounded in supplied evidence, with code-verified verbatim citations. Other AI-assisted checkers are not live yet. |
external_tool | Live when declared | Names the one external judge declared by the agreement and records external result provenance. |
What an Outcome Receipt proves
An Outcome Receipt is the signed result for the agreement hash identified in the receipt, issued under its named signing key. The evidence listed was committed to the receipt by hash. Each condition records its result and how it was decided: deterministic means checked by code, ai_assisted means AI assisted with every claim tied to a cited passage, human_confirmed means confirmed by a named party, and external_tool means decided by an external verifier. When units are present, the earned amount recomputes from the accepted units.
It does not prove that every supplied fact about the outside world was independently true, that AI assisted interpretation is mathematically proven, or that a human or external judge was correct.
Earned and committed amounts
The schema stores decimal strings in amounts.earned and optional amounts.committed. API views may also expose earned_amount_cents and committed_amount_cents. Those cent fields are projections, not Outcome Receipt v1 field names. Earned cannot exceed committed when committed is present.
The Outcome Receipt is a signed record of what was earned, consumable by any payment system that understands its schema and trusts its signing key. The receipt does not authorize payment. Spoolis does not become the wallet, payment provider, or settlement network.
Ed25519 signature model
Spoolis canonicalizes the unsigned receipt, derives the content-based receipt ID, and signs the canonical payload with Ed25519. The key ID is the SHA-256 digest of the DER-encoded public key. A valid signature proves that the receipt came from a trusted key and was not altered. It does not prove the evidence was true, make every check reproducible, authorize payment, or establish settlement. Pin keys from the published trust set. An environment with no configured signing key is omitted rather than given a substitute key.
A valid signature proves the receipt is authentic and unaltered. It does not prove the underlying evidence was true or authorize payment.
Receipt status
Offline signature verification proves authenticity without Spoolis uptime. The optional public endpoint GET /api/v1/receipts/{receipt_id}/status only adds correction, revocation, or advisory state after that offline check. It returns receipt_id, state, consumptions_recorded, checked_at, and optional superseding details. No account or API key is required.
Offline signature verification proves authenticity without Spoolis uptime. The optional status endpoint only adds correction, revocation, or advisory state.
current, superseded, and unknown are the machine-readable states. A well-formed ID with no known receipt returns unknown with HTTP 200. A superseded receipt may identify its replacement and reason. No payment is automatically reversed. Reconciliation remains the consumer's policy.
Consumers
Pass require_current with a status source when the consumer must not act on a superseded receipt. Pass require_no_open_disputes with the same online status source when an open dispute must block action. get_receipt_status is the MCP equivalent. Successors may identify review_decision or dispute_resolution as their reason.
Required evidence completeness
Each criterion may declare evidence_requirements, and the complete declaration is covered by agreement_hash. The receipt reports each requirement as present, missing, stale, unavailable, or provenance_unsatisfied, then reconciles those rows in evidence_completeness. When required evidence is not satisfied, the criterion defaults to uncertain.
The receipt moves from "how we judged what we were given" to "what the agreement required us to see: present, missing, stale, or conflicted."
Per-unit identity
A unitized agreement may declare an identity key with label or pseudonymous visibility. Each public unit result then carries identity.id, a SHA-256 digest derived from a secret random per-Spool salt and the unit value. Label visibility also publishes the original value. The salt is stored with the Spool and is never included in the public Outcome Receipt, which prevents outsiders from recomputing low-entropy identifiers or correlating them across Spools.
Schema versioning and compatibility
Every receipt names its schema version in the schema field. Version 1 is spoolis/outcome-receipt@1, published as machine-readable JSON Schema.
These are the compatibility commitments for version 1:
- Changes within version 1 are additive. We will not remove, rename, or retype an existing field, and we will not remove an existing enum value.
- New optional fields and new enum values may be added. The published JSON Schema is updated in the same release, so the current schema always validates current receipts.
- The schema rejects unknown fields. A consumer that pins a downloaded copy may see a newer receipt fail structural validation against that stale copy. Refresh the schema from the published URL before treating that failure as a defect.
- Signature verification never depends on the schema document. A signed receipt verifies offline against its canonical bytes forever, even after later schema releases.
- A breaking change gets a new identifier, such as
spoolis/outcome-receipt@2, published at its own schema URL. Version 1 receipts stay valid and verifiable; we will keep publishing the version 1 schema. - If a version is ever deprecated, we will publish a notice here at least 90 days before we stop issuing receipts under it. Already-issued receipts are never invalidated.
The same commitments that govern the URL-versioned API are on API versioning.
Trust and key rotation
A key marked active verifies normally. A retiring or retired key verifies with the key_retiring advisory. A revoked key fails with untrusted_signing_key.
Trust entries may include valid_from and valid_to. If a receipt's issued_at falls outside that window, the verifier adds key_outside_validity_window as an advisory without changing validity.
The pull-based change feed is the changes array in /.well-known/spoolis-keys.json. We publish every key status change in that document. A pinned consumer should re-read it on a schedule it chooses; the verifier never fetches it on its own. See the pinning recipe.
Specification status
The Outcome Receipt is designed to be portable. This page and the published JSON Schema are the complete specification: anyone can implement an independent verifier or consume receipts without a Spoolis account, and the reference verifier is MIT-licensed.
License: the specification text on this page is licensed under CC BY 4.0. The published JSON Schema and the reference verifier are MIT-licensed. Implementing the specification requires no permission from Spoolis.
Worked 100-record example
A buyer pays for 100 enrichment records at $1.00 per accepted record. Spoolis verifies the delivery. Ninety-eight records are accepted and two are rejected with reasons, so the signed Outcome Receipt records $98.00 earned from $100.00 committed. Spoolis determines earned value and signs the Outcome Receipt. A wallet, marketplace, payment system, or configured integration may verify and consume it, then act under its own authorization and settlement policy. The placeholders below identify run-specific values.
100 submitted · 98 accepted · 2 rejected · $98.00 earned
{
"schema": "spoolis/outcome-receipt@1",
"environment": "demo",
"id": "ocr_<content-derived digest>",
"agreement": { "spool_id": "spl_<demo id>", "agreement_version": 1, "agreement_hash": "<64 hex>" },
"verification": {
"plan_version": "vp_1", "plan_hash": "<64 hex>", "compiler_version": "compiler@1",
"evidence_policy_version": "ep_1", "run_id": "vr_<run id>", "run_digest": "<64 hex>",
"aggregation_policy": { "policy_id": "all_mandatory_pass", "policy_version": "1" }
},
"evidence_root": "<64 hex>",
"condition_results": [
{ "condition_id": "cond_<count>", "requirement": "mandatory", "result": "pass", "method": "deterministic", "verifier_id": "rows.count", "verifier_version": "1", "evidence_refs": ["evd_<count>"] },
{ "condition_id": "cond_<fields>", "requirement": "mandatory", "result": "partial", "method": "deterministic", "verifier_id": "nulls.absent", "verifier_version": "1", "evidence_refs": ["evd_<fields>"], "reason": "98 of 100 records passed." }
],
"result": "partial",
"units": { "total": 100, "accepted": 98, "rejected": 2, "earning_rule": { "type": "per_unit", "unit_amount": "1.00" }, "rejection_summary": [{ "reason": "nulls.absent", "count": 2 }], "unit_results_digest": "<64 hex>" },
"amounts": { "earned": "98.00", "committed": "100.00", "asset": "USD", "decimals": 2 },
"actors": { "payer": { "subject": "spoolis:<payer>", "kind": "human" }, "provider": { "subject": "spoolis:<provider>", "kind": "human" } },
"issued_at": "<ISO 8601 time>", "nonce": "<unique nonce>",
"signing_key_id": "<64 hex>", "signature": "<base64 Ed25519 signature>", "algorithm": "Ed25519"
}Verify against the published key
Install @spoolis/receipt-verifier, or its thin alias @spoolis/outcome (verifyOutcome, same options and result), then run this with a production receipt. For production, review and pin the accepted trust entry instead of fetching it during every verification. Sandbox consumers must change the expected environment to demo.
import { verifyOutcomeReceipt } from '@spoolis/receipt-verifier'
const keys = await fetch('https://spoolis.com/.well-known/spoolis-keys.json').then(r => r.json())
// A sandbox consumer uses 'demo'.
const expectedEnvironment = 'production'
const trustSet = keys.receipts[expectedEnvironment]
if (!trustSet) throw new Error('No receipt key is published for this environment')
const check = await verifyOutcomeReceipt(receipt, {
trustSet,
environment: expectedEnvironment,
})
if (!check.valid) throw new Error(check.reasons.join(', '))
console.log({ authentic: true, earned: receipt.amounts.earned })Read offline verification and status checks for corrections, revocations, and advisories.