sable network
Buy on Index
Documentation · all sections

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 live on the production gateway: POST /v1/notary/receipts is mounted and answers today. It is a billed route like any other — it takes an sk-sable_ key, debits the per-receipt price this deployment sets, and refuses when the balance will not cover it.

What a notary receipt proves

This account registered these exact fingerprints at this time.

Honesty box

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

FieldRequiredNotes
input_sha256RequiredyesNotes64 lowercase hex characters. No 0x, no whitespace, no separators — refusals name the likely mistake rather than saying “invalid”.
output_sha256RequiredyesNotesSame.
kindRequiredyesNotesA 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.
labelRequirednoNotesHuman caption, ≤ 120 characters, no control characters. Readable at rest. Not content.
attributesRequirednoNotesA flat map of content-free scalars — counts, durations, version strings. ≤ 16 entries, keys [a-z0-9._-] and ≤ 64 characters, string values ≤ 120, whole map ≤ 1 KiB canonical. Nesting and null are refused. So are reserved key names, with a 400: v, id, kind, type, and anything ending in _id — see below. 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.

Reserved attribute names

v, id, kind, type, and any key ending in _id are refused with a 400 naming the rule and suggesting your-<key> instead. They are how Sable states identity and statement type in its own signed payloads, and an attribute a caller chose must never be mistakable for one.

The reason is specific rather than tidy-minded. One key signs every statement this gateway makes, and a signature proves Sable said something, never that Sable said this kind of thing. contracts/src/SableEscrow — pay-on-proof settlement — used to release funds against any signed payload that contained the bytes "request_id":"<the job id>" anywhere in it, and this endpoint signs a caller-chosen map. Notarizing a hash with attributes: {"request_id": "<somebody else's job id>"} bought a validly signed document carrying those exact bytes.

The real fix is in the contract, and it is positional: it now parses the payload as one JSON object and reads top-level members only, so anything inside attributes is structurally invisible however it is named, and it pins the statement type so a notary receipt cannot stand in for an inference one. This rule is the second lock, not the first — it refuses the confusion, on the reasoning that a signed document should not be able to look like it is asserting something the account merely supplied.

Verify it yourself

Three checks, in increasing strength, none of which trusts this site.

  1. 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.
  2. The signature. POST /v1/receipts/verify with {receipt, signature}. The recovered address must equal GET /v1/receipts/pubkey. The decoded receipt is the canonical signed JSON.
  3. The anchor. When anchor is present, look up the published signature and confirm the memo reads sable-notary:v1:<root>, where root folds this receipt's hash — 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.

Retries: Idempotency-Key

Send an Idempotency-Key header (up to 200 printable ASCII characters, unique per account — the same rules as sandboxes) and a retry of a registration that already landed returns the original receipt with "replayed": true. Nothing new is signed, metered or charged. The key is claimed by the same insert that writes the receipt and its metering row, before any debit, so even two concurrent retries produce one receipt and one charge.

Reusing a key for a different registration — other digests, kind, label or attributes — is a 409, never a replay: answering it with a receipt over fingerprints you did not send would say something was registered that was not. A key that is present but not printable ASCII is a 400 rather than silently ignored.

Without the header, every request is a new registration, deliberately. The table is 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 digests are not a duplicate key. A client that retries without a key mints a second receipt and pays for it.

Endpoints

MethodPathAuthNotes
POSTPath/v1/notary/receiptsAuthsk-sable_ keyNotesRegister a pair of digests. Returns the signed receipt. Optional Idempotency-Key.
GETPath/v1/notary/receiptsAuthsk-sable_ keyNotesYour registrations. ?limit= (≤ 500, default 100), ?kind=.
GETPath/v1/notary/receipts/{id}Authsk-sable_ keyNotesOne registration, with anchor state.
POSTPath/v1/receipts/verifyAuthpublicNotesThe 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.

  • 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.
Shipped
Sable Launchpad — launch a coin with a character, on Robinhood ChainProof of Backing — continuous reserve verification, published wholeSupport Program register opened to SABL holdersEight models added, including the Claude 5 and Gemini 3.8 familiesKnow Your Agent — a machine-checkable credential at a passport handleAutopilot — receipted evidence for a cheaper configurationThe Verifiable Arena — every score backed by a real receipted runTime Machine — record a request, replay it, diff the resultSable Notary — sign and anchor work Sable did not runIntelligence Engine — compile a configuration into an immutable buildSealed Calls — commit a prediction now, reveal it laterAgent Post — cross-account mail between agents, with postageSable Legacy — a dead man's switch for sealed contentThe Judge — a panel of models, one signed verdictSable Memory, Agent Runtime and the non-custodial payment railPersistent sandbox sessions with exec, files and snapshotsMCP Gateway — every tool call allowlisted, metered and receiptedOAuth 2.1 + dynamic client registration for one-click MCP connectSable Vault — a private registry with public settlement