# Screwtape action receipt format

License note: this specification text is dedicated to the public domain under CC0 1.0 (or the closest local equivalent). You may implement, verify, and republish it without asking Screwtape.ai. Screwtape.ai trademarks and the live service are not included.

## Purpose

An action receipt is an Ed25519-signed, hash-chained attestation of an agent-reported action and status. Screwtape issues it from `POST /v1/receipt`. Anyone can verify it for free with `GET /v1/receipt/verify` or independently with the steps below.

## Honest limits

A receipt proves the recorded fields have not changed since Screwtape issued them, and that Screwtape made that decision or recorded that action at the stated time.

A receipt does not prove that a payment was correct, that money moved, or that an outside action actually happened. Screwtape advises and records; your agent must honor the result.


## Schema

`schema` is always `screwtape-action-receipt-v1`.

| Field | Type | Notes |
| --- | --- | --- |
| schema | string | Constant `screwtape-action-receipt-v1` |
| receipt_id | string (UUID) | Unguessable id; keep private if agent ids are sensitive |
| issued_at | string (ISO-8601) | Issue time |
| agent_id | string | Caller-chosen agent id |
| event_id | string | Idempotency key per agent |
| chain | string | SHA-256 of canonical JSON `[scope, agent_id]` |
| sequence | integer >= 1 | Position in the per-caller, per-agent chain |
| previous_hash | string or null | SHA-256 of the prior receipt's canonical JSON; null for sequence 1 |
| action_sha256 | string | SHA-256 (hex) of UTF-8 action text |
| status | string | `requested`, `succeeded`, `failed`, or `paid_not_served` |
| reference_sha256 | string? | Present when a reference was supplied |
| children | string[]? | Ordered child receipt ids when this receipt closes a run |
| children_root | string? | SHA-256 of canonical JSON of the ordered child `receipt_hash` values |
| alg | string | `Ed25519` |
| kid | string | Key id from `/.well-known/screwtape-receipt-key` |
| signature | string | Base64url Ed25519 signature |

Raw action and reference text are hashed, not stored.

## Canonicalization

1. Take the receipt object and drop `signature`.
2. Serialize with canonical JSON: object keys sorted lexicographically, no insignificant whitespace, UTF-8.
3. The signature is Ed25519 over those UTF-8 bytes.
4. `receipt_hash` (returned by the API, not always inside the signed object) is SHA-256 of the canonical JSON of the full receipt including `signature`.

## Whole-run (summary) receipts

Send `children` as an ordered list of prior receipt ids on `POST /v1/receipt`. Screwtape loads each child, takes its `receipt_hash` in that order, and sets:

`children_root = SHA-256(canonical_json([hash1, hash2, …]))`

Both `children` and `children_root` are signed. Adding, dropping, or swapping a step changes `children_root` and fails verification.

## Independent verification

1. Fetch `https://screwtape.ai/.well-known/screwtape-receipt-key` and find the key with matching `kid` (`kty=OKP`, `crv=Ed25519`, `x` is the public key).
2. Drop `signature`, build canonical JSON, verify Ed25519.
3. Optionally recompute `action_sha256` from the original action bytes if you still have them.
4. If `sequence > 1`, confirm the predecessor receipt's `receipt_hash` equals `previous_hash`.
5. If `children_root` is present, load each child id in order, collect each child's `receipt_hash`, recompute the root, and compare.

Or call `GET /v1/receipt/verify?id=<receipt_id>` or `?receipt=<url-encoded JSON>`.

## Human tools

- Verify page: https://screwtape.ai/verify/
- This document: https://screwtape.ai/spec/receipts/ and https://screwtape.ai/spec/receipts.md
