SpoolisDocs

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.

FieldMeaning
schemaThe versioned identifier. Version 1 is spoolis/outcome-receipt@1.
environmentproduction or demo. The account-free sandbox issues receipts in the demo environment. A verifier must require the environment it expects.
idA content-derived identifier. It is ocr_ plus the first 24 hex characters of the canonical unsigned body digest.
agreementThe Spool ID, accepted agreement version, and SHA-256 agreement hash bound into the receipt.
verificationPlan, compiler, evidence-policy, run, digest, and aggregation-policy identifiers used for this outcome.
evidence_rootA SHA-256 digest binding the evidence set used by the run.
declared_contextOptional 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_retentionOptional 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_resultsOne 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).
resultThe aggregate pass, partial, fail, or uncertain result.
unitsOptional unitized outcome: total, accepted, rejected, earning rule, rejection summary, and unit-results digest.
amountsDecimal-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_bindingOptional 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.
actorsPayer and provider subject references, kinds, and optional alternate identifiers.
acceptance_authorityOptional policy origin and author, plus version-pinned accepted and acknowledged parties listed separately.
reviewOptional review window copied from the agreement dispute policy: duration, end time, who may dispute, scope, and what happens on dispute.
issued_at and execute_byIssue time and an optional deadline for acting on the receipt. Passing execute_by does not invalidate the signature.
nonce, series, and supersedesUniqueness, optional recurring-series context, and correction, review-decision, appeal-reversal, or dispute-resolution linkage.
signing_key_id, signature, and algorithmThe 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.

MethodStatusMeaning
Live today
deterministicLiveFixed code and inputs produce a fixed evaluation. Durable evidence may support later reproduction; time-bound network observations may not.
human_confirmedLiveAn authorized person supplied the confirmation. Spoolis recorded it and applied the agreed rule.
ai_assistedLive for claim_citedLive 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_toolLive when declaredNames 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.

Rail independence

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.

Trust boundary

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 verification and online status

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.

What the receipt now answers

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

outcome-receipt.json
{
  "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.

verify-receipt.mjs
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.

Run the 30-second sandbox walk →

Outcome Receipt specification · Spoolis