Sable Notary
Every other receipt this gateway mints describes work that crossed it: an inference it proxied, a sandbox it scheduled, a batch line it executed. That makes the proof layer available only to people who move their compute here — and most people never will. A training run sits on a cluster that took a year to get budget for. A nightly ETL job is load-bearing and nobody is rewriting it to get a receipt.
The Notary detaches the proof from the compute. Your own process hashes what
went in and what came out and registers the two digests. Sable signs the
registration with the same receipt key that signs an inference receipt,
stores it in the same table — so GET /v1/receipts/{id} and the public
POST /v1/receipts/verify serve it with no new verification machinery — and
queues its hash for the public anchor stream.
The Notary is built and tested. It is not on the production deployment
yet, so the calls below describe a surface you cannot reach at
api.buildsable.com today.
What a notary receipt proves
This account registered these exact fingerprints at this time.
Proven: that this Sable account registered these digests
at the signed notarized_at, and — once anchored — that the
hash of that signed statement was published in a public memo.
Not proven: that the computation happened. That it was
performed correctly. That the fingerprints describe what you say they
describe — an input_sha256 labelled
training-run might be a photograph. Sable did not run the
work, did not observe it, and cannot check any part of it. The digests are
supplied by the account and taken on trust.
This is not verified computation. That is worth more and this is not it.
The asymmetry is the whole product, and it cuts one way only: a notary receipt cannot make a false claim true, but it makes a later claim falsifiable. You cannot backdate a digest you did not register, you cannot quietly change an output whose hash you published, and you cannot retroactively pick which of five runs you meant.
Sable attests to the registration. The account makes the claim. Those
are different acts by different parties, and the receipt keeps them apart — the
limit rides inside the signature as trust_model, so a third party
verifying the receipt in a year reads the claim and its bounds in the same
breath.
Content-free by construction
The request accepts digests, never payloads. There is no column in
notary_receipts that could hold a dataset, a model, a prompt or an output,
and nothing here is sealed because there is nothing to seal. The Notary adds no
plaintext surface to the privacy contract.
Two caller-controlled fields are readable at rest: label and the string
values inside attributes. Both are hard capped, both are never logged — the
success path emits ids, the digests, a byte count and a price, never the map —
and both are labels rather than payload. A caller who puts content in a label
has put content in a label; the schema cannot prevent that, so this says so
plainly rather than implying a guarantee the shape does not give.
Quickstart
IN=$(shasum -a 256 dataset.parquet | cut -d' ' -f1)
OUT=$(shasum -a 256 model.safetensors | cut -d' ' -f1)
curl -s https://api.buildsable.com/v1/notary/receipts \
-H "Authorization: Bearer $SABLE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"input_sha256\": \"$IN\",
\"output_sha256\": \"$OUT\",
\"kind\": \"training-run\",
\"label\": \"nightly ranker, seed 7\",
\"attributes\": {\"epochs\": 3, \"gpu_hours\": 41.5, \"commit\": \"a1b2c3d\"}
}"
# {
# "id": "nt_9f3c…",
# "kind": "training-run",
# "input_sha256": "…", "output_sha256": "…",
# "hash": "…", <- sha256 of the canonical signed JSON
# "receipt": "eyJ2IjoxLCJraW5kIjoibm90YXJ5Iiw…",
# "signature": "0x…", "signer": "0x…",
# "usage_event_id": "…", "cost_micro_usd": 1000,
# "notarized_at": "2026-09-13T12:00:00Z",
# "anchor": null, <- until the anchor pass runs
# "trust_model": "A notary receipt proves that this Sable account registered…"
# }Fields
| Field | Required | Notes |
|---|---|---|
input_sha256 | yes | 64 lowercase hex characters. No 0x, no whitespace, no separators — refusals name the likely mistake rather than saying “invalid”. |
output_sha256 | yes | Same. |
kind | yes | A short grouping label: training-run, etl-job, build. [a-z0-9._-], ≤ 64 characters. It lands in usage_events.resource as notary:<kind>, where it becomes a grouping key in spend exports — which is why it is a charset, not prose. |
label | no | Human caption, ≤ 120 characters, no control characters. Readable at rest. Not content. |
attributes | no | A flat map of content-free scalars — counts, durations, version strings. ≤ 16 entries, keys ≤ 64 characters, string values ≤ 120, whole map ≤ 1 KiB canonical. Nesting and null are refused. Readable at rest. Not content. |
Digests are case-strict on purpose. Every common producer already emits
lowercase (sha256sum, shasum -a 256, Python's hexdigest(), openssl dgst), and strictness buys a real property: one digest has exactly one
canonical signed form, so two parties who notarize the same bytes produce
byte-identical payloads and can compare receipts directly instead of
normalising first. Silently lowercasing would give one digest two signatures
over two different byte strings.
Attributes are stored and signed in sorted key order. Iteration order that varied run to run would sign different byte strings for the same map.
Verify it yourself
Three checks, in increasing strength, none of which trusts this site.
- The digests. Recompute sha256 over your own input and output bytes and
compare with
input_sha256/output_sha256. This is the step Sable is not in. - The signature.
POST /v1/receipts/verifywith{receipt, signature}. The recovered address must equalGET /v1/receipts/pubkey. The decoded receipt is the canonical signed JSON. - The anchor. When
anchoris present, look up the published signature and confirm the memo readssable-notary:v1:<root>, whererootfolds this receipt'shash— itself sha256 of that canonical JSON. So the public anchor commits to the entire statement, both digests, the instant and the trust model, and not merely to one fingerprint you supplied.
Until the anchor pass runs, the proof rests on Sable's signature alone,
and the response says "anchor": null rather than inventing one.
Billing
One ordinary metered usage event, kind: "notary", unit receipts, one
receipt per request. It goes through the same gate every billed request goes
through, which is why credit holds, per-key
spend caps and the circuit breaker all apply with no new
enforcement code. The usage row and the notary row are written in one
transaction; the debit is idempotent on the event id.
The default price is $0.001 per receipt, operator-configurable, so a job notarizing every nightly run is not punished for it.
A retry bills twice
There is no Idempotency-Key here, and the table is deliberately not unique
on (account, input_sha256, output_sha256) — re-notarizing the same pair later
is a legitimate act (a reproducible build proving it still reproduces a year
on), so the natural key is not a duplicate key.
The consequence is small and stated rather than hidden: a client that retries a request whose response it never saw mints a second receipt and pays twice. Nothing executes twice, because nothing executes at all — the cost is one price of one receipt.
Endpoints
| Method | Path | Auth | Notes |
|---|---|---|---|
POST | /v1/notary/receipts | sk-sable_ key | Register a pair of digests. Returns the signed receipt. |
GET | /v1/notary/receipts | sk-sable_ key | Your registrations. ?limit= (≤ 500, default 100), ?kind=. |
GET | /v1/notary/receipts/{id} | sk-sable_ key | One registration, with anchor state. |
POST | /v1/receipts/verify | public | The ordinary verifier. A notary receipt needs nothing special. |
Key-authed rather than session-authed, because the caller is an external process on a cluster somewhere, not a browser.
Every notarization also fires the ordinary receipt_minted
webhook, so a notary receipt lands in the same audit stream
as everything else you meter.
Related
- Verifiable receipts — the signature scheme, the public verifier, and what the other receipt kinds prove.
- Anchoring — how a batch root reaches a public memo.
- Billing — holds, and why a metered path is the one that gets enforcement for free.