Start here
What are you anchoring?
Pick one path. Every path produces the same portable .mbnt bundle that any reviewer can verify against the BSV chain — no Satsignal account needed.
Anchoring documents or evidence rather than agent runs? Files or reports (first card below) is the start-to-finish recipe; the litigation-evidence playbook covers preservation and the qualified-witness declaration template; and the no-code path below never touches a terminal.
Files or reports
Hash exact bytes, anchor once, download .mbnt, verify later.
Direct API or CLI →SaaS events / webhooks
Anchor canonical event payloads from another platform you don't control.
Webhook Ingest →Agent runtime
Anchor policy snapshots, decision commitments, outputs, and final evidence manifests.
Agent Session Proofs →CI/CD artifacts
Anchor build outputs, eval results, and release gates from a pipeline.
GitHub Action or Direct API →Sealed / private commitments
Anchor a commitment now; reveal the payload later (or never).
Sealed mode →Manifest-backed proofs
Anchor many items in one batch with selective disclosure per item — with Standard or Sealed privacy.
Manifest guide →Redact & selectively disclose
Anchor a CSV, text, or JSON file, then publish a validated redacted copy that still proves into the original on-chain root. Local-only, no re-anchor.
Headless redaction →- Verify anything at proof.satsignal.cloud/verify — no account, ever.
- Create proofs in the browser: sign in by email magic link at app.satsignal.cloud → create a folder → anchor a file from the folder page (hashing runs in your browser; the file itself never uploads) → for selective disclosure, open the folder’s Disclosure Redaction Tool — also fully in-browser, nothing uploaded.
- API keys are for programs. A Bearer key drives the read/operational dashboard surfaces programmatically, but interactive browser use rides the magic-link session; keys are minted at
/w/<workspace>/keysafter signing in.
Build with Satsignal.
Every integration produces the same canonical
evidence object —
satsignal.provenance.v1
— and anchors its sha256 to the public Bitcoin SV chain.
Verification runs offline in any browser or CI runner against any
public block explorer — no Satsignal account or API key
required at verify time.
The core words this whole API turns on.
Plain-English glosses for the canonical terms. Each term keeps its exact name everywhere on this site — this is what each one means.
- anchor — a timestamped commitment recorded on-chain
- proof — the verifiable claim you keep and share — the fingerprint, the chain anchor, and the metadata used later to check timing and tamper-evidence
- bundle — the portable
.mbntfile a verifier uses to re-check the proof - folder — the workspace namespace each proof files under (the
folder_slugon every request) - sealed — a private proof outsiders can’t test against the original unless you share the bundle
- verifier — the browser tool that checks the proof, the matching item, and the on-chain anchor
- manifest — a list of items proven together as one set
Standard vs Sealed is your one real choice — the privacy level. A Standard anchor is publicly checkable against the original; a Sealed anchor keeps the original private until you share the bundle (stored, or blind if you withhold the salt) — same chain, same proof. A manifest isn’t a third privacy mode: it’s the evidence shape — many items under one Merkle root — and works with either. All are documented under the endpoint.
How this differs from logs and timestamping services.
If you already keep audit logs, or you’ve used a timestamping service, the useful question isn’t “which product” — it’s what property each one actually gives you. Two distinctions matter operationally.
An operator-controlled log vs. an independently verifiable proof.
An append-only log — an application log, an audit trail, a cloud activity feed — is written and held by the same party whose actions it records. Checking it means trusting that party not to have rewritten or pruned it; the log is its own only witness. A proof’s evidence is a Bitcoin SV transaction the operator does not control and cannot alter or backdate once it is broadcast. Anyone can re-hash the bundle and check it against the chain — no account, no call to us. What to look for in any proof system: can a third party verify it without trusting whoever produced it?
A shared-root timestamping service vs. a per-customer bundle.
Many timestamping services batch many customers’ hashes into one tree and anchor a single root. That shows your hash was in a batch at a point in time, but verifying usually means asking the service for the inclusion path, and the service decides what is revealed and how long it is kept. A per-customer bundle ships the full proof material to you — the canonical bytes, the Merkle path for any selective-disclosure rows, and the transaction reference — so the check runs entirely on artifacts you already hold, and you can reveal one row of a batch without exposing the rest. What to look for: do you receive enough to verify offline, or only a pointer back to the service?
Core
Routes into satsignal.provenance.v1.
Same canonical object, many ways in. Each route below normalizes its input into the canonical model and anchors the canonical sha256. The product is the canonical model; these are routes into it. Standards bridges (C2PA, Sigstore, SLSA, OpenTelemetry, MCP) project in the same way — by digest, not by replacing the standard’s own semantics.
Fastest path
satsignal anchor <file> --broadcast
returns a chain-confirmed proof. Group anchors with
--folder <slug> (the legacy
--matter flag is still accepted inbound). A
satsignal-cli verify verb that adds
bundle-v1
verification with chain-confirm by default shipped in
satsignal-cli 0.5.0. Right for file-anchor workflows, CI, and
one-off shells.
Python session helper
agent_anchor.py wraps
the four-part agent-session pattern — policy
snapshot, a commitment per decision, an evidence-bundle
manifest, plus a handoff.json for offline
audit (N + 2 chain anchors)
— into a six-line Session() context
manager. Stdlib only. Right when your runtime is Python
and you want the agent-shaped anchoring discipline
without writing it from scratch.
Direct API
Bearer-auth JSON
POST /api/v1/anchors. Mode (standard
/ manifest / sealed) inferred from body shape; category
is an explicit field. Right when you're in a non-Python
runtime, you want explicit control over the wire shape,
or you're embedding anchoring into a custom flow.
Drop into any MCP agent
pip install satsignal-mcp exposes ten
tools over the
Model Context
Protocol — six core (anchor_file /
anchor_text / anchor_json /
lookup_hash / verify_file_against_bundle /
chain_confirm_bundle), three selective-disclosure
tools (anchor_disclosable /
create_disclosure / verify_disclosure),
and a deprecated verify_bundle alias.
verify_file_against_bundle is
the full verify (re-hashes the original file, detects
tampering); chain_confirm_bundle is a fast
chain-only check that does NOT detect file tampering. Any
MCP-compatible client — Claude Desktop, Claude Code,
agent frameworks — can call Satsignal without a
custom SDK. Right when your agent runtime already speaks
MCP.
A session proof for an agent run.
For agent runs, Satsignal binds the whole run into one
proof trail — the policy snapshot anchored before the run starts,
a fresh-nonce commitment per decision as it happens, and a final
evidence-bundle manifest binding the decision set together. The
stdlib-only agent_anchor.py Session() context
manager and the ten-tool satsignal-mcp server (drop-in for
any MCP-speaking agent runtime) cover the four-part flow without an
SDK.
Full guide: A session proof for an agent run →
Worked example: ResearchAgent makes its decisions auditable →
Anchor build artifacts as a workflow step.
Anchor release artifacts, eval results, security-scan
outputs, and provenance manifests directly from a pipeline. The
composite GitHub Action (Steleet/satsignal-action@v0) is
bash-only on ubuntu-latest — no
setup-python, no setup-node — and
exposes proof_id, txid, and
proof_url (the browser proof page — for
programmatic .mbnt download use the response’s
bundle_url instead) as step outputs the next step
can read. Outside
GitHub, the same flow runs with sha256sum +
curl + jq in any CI runner, with adapter
recipes for GitLab CI, Bitbucket Pipelines, Docker BuildKit, npm, and
PyPI PEP 740.
0110-minute quickstart
Anchor a file or report
The shortest path from zero to a verifiable proof: mint
a key, create a folder, hash your artifact locally, and
POST /api/v1/anchors — the response carries a
chain-confirmed proof_id, txid, and
bundle_url for the portable .mbnt. The fields
integrators store alongside the original artifact are
proof_id, txid, bundle_url,
sha256_hex, folder_slug, and a free-text
label. Verification then runs in any browser or CI runner
with no Satsignal account.
One endpoint, three request shapes.
proof /
folder; responses emit canonical field names only, and
requests sent with legacy spellings keep working forever. Full alias
map (request fields, routes, scopes, CLI flags, error codes): the
Compatibility map.
Every anchor — commitment, policy snapshot, manifest, sealed envelope, or plain file proof — goes through the same call. Mode is inferred from the body shape; category is an explicit field that tags the proof without changing the primitive.
POST https://app.satsignal.cloud/api/v1/anchors Authorization: Bearer <your-api-key> Content-Type: application/json
- An API key with the
anchors:createscope (mint one at/w/<workspace>/keysafter signing in). - A
folder_slugin your workspace — the namespace each proof files under. - A SHA-256 fingerprint, or a list of
{label, sha256_hex}items, computed locally — the payload stays with you. - No human in the loop? Autonomous agents can self-serve
the account itself via the
agent
signup lane (invite code or proof-of-work). Provisioning
proof for your own tenants? Mint a
keys:adminkey once in the dashboard, thenPOST /api/v1/keysto issue per-tenant scoped sub-keys headlessly — no browser per tenant.
Create a folder (one-time per project)
A folder is the namespace each proof files
under — create one once per integration. The slug becomes the
folder_slug on every anchor request below. (Requests to
the legacy alias route are still accepted — see the
compatibility
map.)
POST https://app.satsignal.cloud/api/v1/folders
Authorization: Bearer <your-api-key>
Content-Type: application/json
{
"name": "AcmeCorp Events Dev", // human-readable display name
"slug": "acme-events-dev" // a-z0-9 with '-' or '_', 2-64 chars, becomes folder_slug
}
Standard mode (single fingerprint)
{
"folder_slug": "agent-runs-prod",
"sha256_hex": "d2f658c562d7bccc...d0fecded",
"file_size": 208,
"category": "commitment",
"label": "agent-alpha sealed bid",
"filename": "alpha-bid.json",
"session_id": "run-2026-05-09-001"
}
Copy-paste: anchor a file in four lines
Hash the file locally, then POST the
fingerprint. The file bytes never leave your machine — only
sha256_hex and file_size cross the wire.
export SATSIGNAL_API_KEY=sk_...
SHA=$(sha256sum file.pdf | cut -d' ' -f1)
SIZE=$(stat -c%s file.pdf)
curl -sS https://app.satsignal.cloud/api/v1/anchors \
-H "Authorization: Bearer $SATSIGNAL_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"folder_slug\":\"agent-runs-prod\",\"sha256_hex\":\"$SHA\",\"file_size\":$SIZE}"
label and filename are transmitted and retained. The file bytes never leave your machine, but these two free-text fields DO cross the wire and are stored with the workspace proof (off-chain, never on chain). Avoid sensitive client names, case references, or document titles in them — use a neutral label. Same guidance as the privacy page, stated here because this is where you type them.
Anchors are chain-tagged with an issuer fingerprint. Every anchor minted through the hosted API carries a 4-byte issuer_id TLV in its on-chain payload. On the hosted tier this is one shared constant (d5b0b0c6 = the first 4 bytes of sha256("did:web:satsignal.cloud")) across all workspaces, so a chain observer can enumerate Satsignal-issued anchors as a class — your workspace is not individually identified, but the fact that an anchor went through Satsignal is public. A deployment that gives each customer their own issuer DID makes that customer’s anchors individually enumerable on-chain. The hosted API has no per-request opt-out today; suppression exists only as an operator-level pipeline setting for self-run deployments. The issuer_id is a discoverability handle, not an authenticity guarantee — because authenticity is “this key signed it,” anyone holding the signing key could mint an anchor carrying the same fingerprint, so verify the bound doc_hash (re-hash your document and match the anchor) rather than relying on the issuer tag. See security §04. Property and detail: /whats-on-chain §4 and /spec-mbnt §11.
Standard mode — multi-proof / selective disclosure (proof_set)
A standard anchor can carry an optional
proof_set instead of binding the lone
sha256_hex. proof_set.byte_exact is
required and must equal the submitted sha256_hex +
file_size; alongside it you may add
content_canonical (a normalized-content fingerprint)
and/or chunk_merkle (a Merkle root over per-chunk
leaves — the basis for revealing one page / row / record
later without disclosing the rest). The companion
proof_leaves carries the leaf hashes.
{
"folder_slug": "agent-runs-prod",
"sha256_hex": "d2f658c562d7bccc...d0fecded",
"file_size": 208,
"proof_set": {
"byte_exact": {"algo": "sha256", "size": 208,
"hash": "d2f658c562d7bccc...d0fecded"},
"content_canonical": {"scheme": "pdf-text-v1", "algo": "sha256",
"hash": "9f1c...e3"},
"chunk_merkle": {"scheme": "pdf-page-v1", "algo": "sha256",
"leaf_count": 12, "root": "a3b9...77"}
},
"proof_leaves": {"scheme": "pdf-page-v1",
"merkle_leaves": ["...", "..."],
"metadata": {"leaf_count": 12}}
}
Manifest-backed proofs (Merkle batch, up to 10,000 items)
Merkle-batch up to 10,000 items into one
anchor, with selective disclosure per item: reveal one row, file, or
decision later and the rest stay opaque. Use cases include bulk
evidence bundles, agent decision manifests, and tabular row
commitments (the merkle-row-v1 scheme rides on this
mode).
The request is the same endpoint with an
items[] array instead of a single
sha256_hex — its presence is what selects
manifest mode (don’t also send sha256_hex /
file_size / mode: "sealed"):
{
"folder_slug": "agent-runs-prod",
"category": "evidence_bundle",
"label": "eval run 2026-06-10",
"items": [
{"label": "rows/0001.json", "sha256_hex": "63d5c3e67b088a51...d2c33d22dfc724e6"},
{"label": "rows/0002.json", "sha256_hex": "3cad58f5027bc880...61401d81ca90b766"}
]
}
Full guide: Manifest / batch proofs →
Sealed mode (HMAC commitment, salt private)
{
"mode": "sealed",
"folder_slug": "agent-runs-prod",
"salt_b64": "<32-byte salt, base64url>",
"byte_exact_commitment": "<HMAC-SHA256 of the canonical bytes>",
"file_size": 208,
"category": "policy_snapshot"
// Mirror mode (salt_b64 present): omitting retain_days
// (the default) keeps the salt-bearing bundle on our disk
// until you delete the proof — retain_until comes back
// null. Send retain_days >= 1 for an explicit auto-delete
// window instead (honored as given on every plan). To keep
// NOTHING on our disk, use blind mode (omit salt_b64) —
// see below.
}
Sealed mode — blind submission (salt never reaches us)
Omit salt_b64 to opt
into the cryptographically-blind protocol: the salt never crosses
the wire, we return the canonical-doc bytes, and your client
assembles the .mbnt bundle locally. No server-side
bundle is ever written. retain_days must be
0 or absent (nothing to retain).
{
"mode": "sealed",
"folder_slug": "agent-runs-prod",
// no salt_b64 — its absence is the blind signal.
"byte_exact_commitment": "<HMAC-SHA256 of the canonical bytes>",
"file_size": 208,
"category": "policy_snapshot"
}
The response carries
canonical_b64 + doc_hash in place of
bundle_b64; retain_until is omitted.
Your client builds manifest.json (with the salt it
already holds), drops in the verbatim canonical.json
bytes, optionally adds proofs.json rebuilt from
local commitments, and zips. The verifier accepts the result
transparently — bundle shape is identical regardless of
where it was assembled.
Sealed mode — multi-proof / selective disclosure (proof_set)
A sealed anchor can carry the same
proof_set envelope as standard mode — same
field name, same API symmetry — with HMAC algos inside
instead of plain SHA-256. proof_set.byte_exact is
required and its commitment must equal the submitted
byte_exact_commitment; alongside it you may add
content_canonical (a normalized-content HMAC) and/or
chunk_merkle (a Merkle-HMAC root over per-chunk
leaves under HKDF-derived per-leaf salts — revealing one
leaf reveals only that leaf, siblings stay opaque). The
companion proof_leaves carries the leaf HMACs.
{
"mode": "sealed",
"folder_slug": "agent-runs-prod",
"salt_b64": "<32-byte salt, base64url>", // omit for blind
"byte_exact_commitment": "<HMAC-SHA256(salt, file)>",
"file_size": 208,
"proof_set": {
"byte_exact": {"algo": "hmac-sha256",
"commitment": "<same HMAC as above>"},
"content_canonical": {"algo": "hmac-sha256",
"scheme": "pdf-text-v1",
"commitment": "<HMAC of canonicalized text>"},
"chunk_merkle": {"algo": "merkle-hmac-sha256",
"scheme": "pdf-page-v1",
"leaf_count": 12,
"root": "<Merkle-HMAC root>"}
},
"proof_leaves": {"scheme": "pdf-page-v1",
"merkle_leaves": ["<leaf HMAC>", "..."],
"metadata": {"leaf_count": 12}}
}
Response (all modes)
{
"proof_id": "f83649e3846c4ea2",
"txid": "2e042a64...7a3db61b",
"mode": "standard",
"category": "commitment",
"retain_until": 1780704000, // standard mode: bookkeeping only, not an expiry — no automatic deletion is applied to the server copy (kept until you delete the proof). Sealed semantics differ — see notes below
"dry_run": false,
"folder_slug": "agent-runs-prod",
"bundle_url": "https://app.satsignal.cloud/bundle/f83649e3846c4ea2.mbnt",
// GET with the same Authorization: Bearer
// header to download the .mbnt zip
"proof_url": "https://app.satsignal.cloud/w/.../r/f83649e3846c4ea2"
// browser web-UI URL (Bearer-authed curl
// → 303 /login). Use bundle_url for
// programmatic clients.
// manifest mode also: "leaf_count", "root"
// sealed mirror, retain_days omitted (default): "retain_until"
// is null — the server keeps the bundle until you delete the
// proof. With an explicit retain_days it is the window's end
// epoch.
// sealed + salt_b64 omitted (blind): "canonical_b64" + "doc_hash"
// are returned instead; "retain_until" is omitted entirely.
// Your client assembles the .mbnt locally.
}
Reserved fields (will 400)
Two field names that look reasonable are deliberately rejected:
dry_run— the broadcast policy is fixed per deploy on this endpoint. Sendingdry_run: truewill not gate the anchor; we 400 so you notice rather than accidentally creating an anchor while testing.- Asymmetry to note: the
satsignalCLI and the satsignal-mcp server build the bundle locally before any broadcast attempt, so their dry-run paths don’t touch the chain — but the two shapes differ. The CLI’s--dry-runprints a short text summary of the canonical doc (file, sha256, size, mode, label, out) and writes no sidecar; the MCP server’sdry_run=truereturns the would-be bundle as a structured object. The HTTP endpoint is broadcast-only because broadcast-only is its single job. If you want a programmatic dry-run shape, use MCP; if you want a quick visual sanity-check, use the CLI; submit through HTTP when you’re sure. chunk_merkle/content_canonical— not top-level fields. Nest them insideproof_set(see multi-proof / selective disclosure above); sent at the top level they400with a hint pointing you there.
Folder-level audit (selective-reveal defense)
A holder disclosing one anchor to an auditor can’t say this is the only one I created on their own word. The folder (the namespace each proof files under) is the unit the operator’s API key controls; an auditor with read scope on that key can list every anchor — current and soft-deleted — the folder has ever contained:
GET https://app.satsignal.cloud/api/v1/folders/<slug>/anchors Authorization: Bearer <key with proofs:read>
Returns anchor_count
plus an anchors[] array (proof_id,
txid, mode, category,
label, anchored_at,
retain_until, deleted,
proof_url, bundle_url). Soft-deleted
rows are included — chain anchors are permanent and the
workspace-side delete must not mask siblings from the audit.
Single-proof callers get the same number on
GET /api/v1/proofs/<id> as
folder_anchor_count.
Workspace-wide listing (cross-folder audit)
For audit / compliance scripts asking what did I anchor last week? across every folder:
GET https://app.satsignal.cloud/api/v1/anchors?limit=100 Authorization: Bearer <key with proofs:read>
Most-recent-first. Each row carries
the folder-scoped fields plus folder_id /
folder_slug / folder_name so a caller
can stitch folder context without a second round trip. Soft-
deleted rows are included for the same reason as the folder-
scoped listing — chain anchors are permanent and the
workspace-side delete must not mask siblings.
Pagination. Cursor-based on
anchored_at:
{
"anchor_count": 100, // size of THIS page
"workspace_total": 432, // across the whole workspace
"limit": 100,
"before": null,
"next_cursor": 1778196000, // unix ts; pass as ?before= for next page
"anchors": [ ... ]
}
Loop until next_cursor
is null. limit defaults to 100, capped
at 1000.
Filter by session. If you
tagged anchors with a session_id at POST time, the
listing accepts the same key as a query param to scope an audit
to one agent run:
GET https://app.satsignal.cloud/api/v1/anchors?session_id=run-2026-05-09-001 Authorization: Bearer <key with proofs:read>
Quota visibility
Every POST /api/v1/anchors
response (200 success and 429 quota denial) carries standard
rate-limit headers so callers can back-pressure before hitting
a hard stop:
X-RateLimit-Limit: 100 # anchors allowed per window X-RateLimit-Remaining: 87 # post-anchor on success; pre-anchor on 429 X-RateLimit-Reset: 1780272000 # unix ts of next window reset X-RateLimit-Window: month # 'day' (legacy) or 'month' (free/starter/pro/scale)
The plan quota window is the anchor
API’s only key-level throttle — there is no
separate per-minute or per-hour burst limit on
POST /api/v1/anchors today. Window ceilings: Free
100/month, Starter 10,000/month, Pro 100,000/month, Scale custom.
A 429 quota_exceeded carries these same headers (no
Retry-After — the window is a fixed calendar
window, so wait for X-RateLimit-Reset or mail us for
a cap lift). The auth-free /lookup_hash endpoint has
its own hour-windowed limits: 120/hour per IP, or 5,000/hour per
workspace with a bearer key.
Or fetch the same numbers without creating an anchor:
GET https://app.satsignal.cloud/api/v1/usage
Authorization: Bearer <key with proofs:read>
{
"plan": "free",
"window": "month",
"used": 13,
"limit": 100,
"remaining": 87,
"period_end_utc": 1780272000,
"anchors_allowed": true,
"reason": null
}
Idempotent retries
Every write endpoint
(POST /api/v1/anchors,
POST /api/v1/folders,
POST /api/v1/webhooks) accepts an optional
Idempotency-Key header (Stripe-style; any
client-chosen opaque string, ≤ 256 chars). Same key + same
request body within 24h returns the verbatim cached response
— no second on-chain broadcast, no
second quota tick, no duplicate folder / webhook /
one-shot signing secret. Same key + a different body returns
409 idempotency_key_reuse_body_mismatch loud
instead of silently picking one. Omit the header to opt out
(pre-existing behaviour unchanged).
The three agent-shaped categories.
The category field is a tag, not a primitive
switch — the chain anchor is the same shape regardless.
Tagging the proof lets a verifier say this is a
commitment rather than this is some hash.
| Category | Mode | What gets hashed |
|---|---|---|
commitment |
standard or sealed | Canonicalized {nonce_hex, payload} wrapper.
The nonce makes the hash unguessable from the payload
alone, so anchoring before reveal is safe even if the
payload space is small (a score, a vote, a category). |
policy_snapshot |
standard or sealed | Canonicalized snapshot JSON. Each component (system prompt, user instruction, tool permissions, budget caps, model config) is hashed independently so an auditor with one component can verify it without seeing the others. |
evidence_bundle |
manifest (always) | A list of {label, sha256_hex} leaves.
The server combines them into a Merkle tree and
anchors the root. Recipients later prove a single
leaf with its inclusion path; the other 9,999 stay
private. |
Reveal one row of a table; the others stay sealed.
Sealed mode commits to a value now without revealing
the payload — reveal it later, or never. Two variants ship:
mirror (salt is sent and the server may retain the bundle for
recovery) and blind (salt never crosses the wire, the server
retains nothing, the client assembles .mbnt locally). For
low-entropy row tables — bids, scores, grades, yes/no answers
— the merkle-row-sealed-v1 scheme replaces each
Merkle leaf with an HMAC under an HKDF-derived per-leaf salt, so a
plain SHA-256 leaf can’t be brute-forced and disclosing one row
leaks nothing about the others.
Full guide: Reveal one row of a table; the others stay sealed →
Advanced recipes · optional — most integrations don’t need these
Anchor selected OpenTelemetry GenAI spans.
If your stack already speaks OTLP —
Langfuse,
LangSmith,
Arize, Datadog, Honeycomb — you don’t need a second
integration to anchor what flows through it.
Steleet/satsignal-otel
is a SpanProcessor you drop into your
TracerProvider. Spans you mark with
satsignal.anchor=true are batched and anchored as a
single manifest proof on BSV; everything else flows through to
your existing exporters untouched. Your observability stack shows
the run; Satsignal proves the run record hasn’t been edited
since.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from satsignal_otel import SatsignalSpanProcessor, auto_anchor_on_eval_fail
provider = TracerProvider()
provider.add_span_processor(SatsignalSpanProcessor(
api_key=os.environ["SATSIGNAL_API_KEY"],
folder_slug="otel-evals",
))
trace.set_tracer_provider(provider)
with trace.get_tracer(__name__).start_as_current_span("eval.scorer") as span:
span.set_attribute("gen_ai.eval.score", score)
auto_anchor_on_eval_fail(span, threshold=0.7)
Opt-in per span keeps the anchor surface narrow: only the spans your scorer (or release pipeline, or human review) tags get a proof — not every trace. The headline use case is failed-eval auto-anchor — when a scorer drops below threshold, the failing span anchors with a per- event timing claim. The secondary is release-gate anchor — one proof per deploy that binds prompt version + model + eval pass rate + config hash to the chain.
Anchor S3 / R2 / GCS / Azure Blob objects in place.
Your files already live in a bucket. You don’t need to
move them or re-upload them to anchor them.
Steleet/satsignal-blob
walks a prefix, streams the bytes through sha256, anchors the
digest on BSV, and writes two sidecars next to each original:
contract.pdf.mbnt (the portable evidence bundle) and
contract.pdf.proof.json (the one-page summary with
txid + proof URL). The original is untouched; file bytes never
leave your process.
pip install satsignal-blob[s3] # or [gcs], [azure], [all]
export SATSIGNAL_API_KEY=sk_...
satsignal-blob anchor s3://my-bucket/contracts/2026/ \
--folder contracts-2026 \
--include '*.pdf' \
--max-files 50
Backed by
fsspec, so the
same CLI speaks s3:// (AWS, R2, MinIO, Wasabi, B2),
gs:// (Google Cloud Storage), az://
(Azure Blob), and local paths. For real-time anchoring on every
PUT, wire an S3 ObjectCreated:* event to a Lambda
that calls anchor_object — the
examples/lambda_handler.py
template covers it in 30 lines.
Anchor incoming webhooks from systems you already use.
Paste a Satsignal-provisioned webhook URL into any
SaaS that speaks outgoing webhooks — Stripe, GitHub, Langfuse,
or a custom signer — and every signed event is canonicalized to
its raw bytes, hashed, anchored on-chain, and filed in the folder you
pick. No SDK, no per-source integration code. Per-source adapters
(stripe, github, langfuse,
none) ship as enum values on the API and handle the
upstream signature scheme automatically.
Privacy carve-out.
Webhook ingest is one of two paths where raw content reaches
Satsignal — the other is the plaintext-provenance manifest
endpoint;
privacy documents
both. Here an upstream SaaS POSTs the event to us and we hash it
server-side, in memory, to verify the source signature and compute
its SHA-256 before anchoring; the body itself is not stored. That
differs from the client-hashed flows — file, contract, agent,
and batch-manifest proofs — where you hash locally and only the
digest ever leaves your systems. Only the fingerprint is anchored
on-chain in every case; if the raw body must never leave your
infrastructure, hash it yourself and anchor the digest via
POST /api/v1/anchors instead.
The non-obvious decision when wiring a webhook is which bytes to hash — raw request body vs. a canonicalized projection. See what bytes do I hash? for the body-normalization rules.
Full guide: Anchor incoming webhooks from systems you already use →
Embed a BSV anchor inside an AAR, C2PA, RFC 3161, or TAP proof.
The chain-anchor-v1 scheme is a cross-system /
cross-domain Merkle-batching format. One BSV transaction commits
to a Merkle root over N leaves; each leaf can be a proof from a
different system (an
Agent
Action Proof, an RFC 3161-style timestamp request, a
C2PA-credentialed image, a
Visa
Trusted Agent Protocol signed request). Each proof carries
its own leaf + inclusion proof in an evidenceRef
entry, so an auditor with just one proof can independently
verify the on-chain commitment without seeing the others.
Five live BSV-mainnet samples cover the canonical interop shapes — AAR batch, dual-attest (RFC 3161 + BSV), cross-domain (Ethereum + HuggingFace + C2PA), C2PA-credentialed image, and Visa TAP — each with full reproduction recipes, real txids, and pure-stdlib verification snippets.
- All five samples in one document:
/samples/chain-anchor/RECEIPTS.md(or browse the directory listing). - Scheme spec:
/spec-chain-anchor. - Underlying wire format:
/spec-mbnt§5 (manifest mode — the leaf-hashing rulechain-anchor-v1rides on). - Sibling sample directory — sealed deep-content mode
(client-side hashing, selective disclosure of paragraphs and
JSON fields, live mainnet anchors):
/samples/sealed-deep/.
Drop-in libraries, byte-for-byte across runtimes.
Stdlib only. No Satsignal SDK to install. Each helper produces the same canonical bytes the in-browser verifier expects, so anything you commit on one side reveals cleanly on the other.
commit_reveal.py
Python 3 stdlib only. CLI with
commit / reveal /
verify subcommands. Use it from an agent
runtime, a CI step, or a one-off shell.
commit-reveal.js
ES module for the browser and Node 18+. Exports
makeCommit and verifyReveal.
Drop into a frontend, an Edge Function, or a runtime
guardrail.
policy_snapshot.py
Python 3 stdlib only. CLI subcommands
hash-component, build, and
verify. Selective-disclosure verify (one
field at a time) just works.
agent_anchor.py
Stdlib-only Session() context manager
that bundles the four-part agent-session pattern (policy
+ N commitments + evidence-bundle manifest =
N + 2 anchors) into a six-line
integration. Writes a handoff.json on exit
so an auditor can verify the run offline. See
Agent session proofs;
deep cookbook at
/agents.
/api/v1/anchors
when SATSIGNAL_API_KEY is set, then takes a
(deterministic) action. Replace the
decide() stub with a real agent loop and you
have an audit-trail-clean configuration anchor in your
runtime today. For a fuller pattern (policy + decisions +
manifest + handoff JSON in one context manager), see
/agent_anchor.py +
Agent session proofs.
handoff.json)
ships today as the stdlib-Python Session() in
agent_anchor.py. JavaScript ships
commit-reveal.js and
merkle-row.js — browser/edge/Node 18+
modules for commit-reveal and selective-disclosure verification, byte-for-byte
with the Python side. There is no first-class JS Session() yet:
TypeScript/JS runtimes anchor today through the language-agnostic
MCP server or by POSTing JCS-canonical JSON to
/api/v1/anchors from any HTTP client — the wire format is
the binding, not the helper. A first-class TS/JS Session() at
parity with agent_anchor.py is on the roadmap.
Commit and anchor, end to end
export SATSIGNAL_API_KEY=sk_... # mint at /w/<workspace>/keys
curl -O https://satsignal.cloud/commit_reveal.py
echo '{"agent_id": "alpha", "score": 73}' | \
python3 commit_reveal.py commit \
--payload-json - \
--out alpha.json \
--out-anchor anchor_body.json
# alpha.json — KEEP PRIVATE until reveal (carries the nonce)
# anchor_body.json — ready-to-curl body for /api/v1/anchors
curl -H "Authorization: Bearer $SATSIGNAL_API_KEY" \
-H "Content-Type: application/json" \
-d @anchor_body.json \
https://app.satsignal.cloud/api/v1/anchors
# Anchor sha256(canonical {nonce_hex, payload}). Do NOT sha256
# alpha.json itself — that file's sha is not the commitment.
From CI, from a browser, from any language.
Every proof ships with everything a verifier needs — the canonical doc, the on-chain transaction id, the chain name, and (for manifest proofs) every leaf with its Merkle path — plus, when present, an informational broadcast acceptance record (unsigned; not used by verification). Verifying is a re-hash and a public block-explorer lookup. Three common shapes:
The audit packet
The audit packet is everything an external auditor
needs to re-derive your proof without contacting Satsignal: the
canonical bytes, the chain reference, the manifest leaves, and
(for sealed-row proofs) the selective-reveal path. Per-proof,
this is the contents of the .mbnt bundle plus the
proof-page proof-package download (sha256 / HMAC dual-mode,
browser-built zip). For a whole workspace, hit the bulk-export
endpoint:
GET https://app.satsignal.cloud/api/v1/audit-packet Authorization: Bearer <key with proofs:read>
Returns a deterministic ZIP archive
(Content-Type: application/zip) containing every
in-scope proof's canonical bytes, a
verification_report.json, the chain reference, and
a handoff.json manifest. Same scope + verifier
version in → byte-identical bytes out. The
X-Audit-Packet-Digest header carries a deterministic
packet_digest — the sha256 of the JCS-canonical
handoff.json with its own packet_digest
field removed (not the sha256 of the zip bytes). Re-derive it
offline: unzip handoff.json, drop
packet_digest, JCS-canonicalize, then sha256. Optional
filters scope the export:
?folder_slug=<slug> # one folder inside the workspace ?session_id=<s> # restrict to proofs tagged with this off-chain session id ?since=<unix> # inclusive lower bound on anchored_at ?until=<unix> # inclusive upper bound on anchored_at
Proofs whose canonical bytes are
unavailable on disk (retention-evicted, etc.) appear in
handoff.skipped[] with a reason — never
silently dropped. Hyphen-form (/api/v1/audit-packet)
is canonical; /api/v1/audit_packet is accepted as
an alias. See canonical
model § 02 for the typed slots that bind a packet to a
run.
Drop the bundle.
Open proof.satsignal.cloud/verify
and drop the .mbnt. Three pills populate
from the proof’s acceptance block
and a public block-explorer fetch — no Satsignal
API call.
Re-hash and curl.
Two checks bind the chain to the payload. (1)
Re-hash the revealed payload with the helper and confirm
it matches the schema-appropriate field inside the
bundle’s canonical.json. Server-emitted
bundles (the .mbnt you download from
bundle_url after POST /api/v1/anchors)
are schema_version: 2: use
subject.proofs.byte_exact.hash for standard
proofs (commitment / output / policy_snapshot /
evidence_bundle / file),
subject.proofs.byte_exact.commitment for sealed
proofs (subject.kind == "file_anchor"), or
subject.root for manifest-mode proofs
(subject.kind == "manifest"). Older
schema_version: 1.1 bundles emitted by some
legacy CLI flows use subject.document_sha256
instead — check canonical.json.schema_version
to disambiguate. (2)
Fetch the OP_RETURN of the proof’s txid
from any block explorer’s API, strip the 4-byte
MBNT tag, 1-byte version, 1-byte subtype, and
2-byte TLV length (8 bytes / 16 hex chars total), and
compare the next 20 bytes (40 hex chars) to
manifest.doc_hash_expected — that field
is the first 40 hex chars of
sha256(canonical.json), which is what the
chain actually commits to. Full byte layout is at
/spec-mbnt.
50 lines, any language.
JCS-canonicalize {nonce_hex, payload}.
SHA-256 the canonical bytes. Compare to the on-chain
hash. The JS helper does it in 50 lines for browser or
Node 18+; the Python helper is the same algorithm.
For the on-chain payload byte layout, see
/spec-mbnt.
txid on the chain without re-hashing the
payload only proves an anchor exists at that height; it
cannot detect that the file in your hand differs from the
one originally anchored. Chain-confirmation alone is not
verification — it’s the half that needs (1) to
become a tamper-evident claim about this file.
.mbnt from 2026 still verifies in
2030, even if Satsignal isn’t around —
how you ensure you have a
saved .mbnt varies by mode.
Programmatic verification — three paths for non-browser integrators
If you’re wiring verification into a CI runner, an agent runtime, or a webhook consumer, you can’t drive a browser. Three paths, in increasing order of "I already have Python":
Path A — hosted verifier or the stdlib helper (no install)
# Hosted: drag-drop the .mbnt at proof.satsignal.cloud/verify (account-free) # Or, stdlib-only, no pip install: python agent_anchor.py verify-handoff # exit 0 = pass
Path B — inspect the .mbnt by hand (bash + jq)
A .mbnt is a zip with two files (manifest.json and canonical.json):
unzip -q proof.mbnt -d /tmp/bundle/ # 1. Bundle’s claimed file hash matches your local file? # Path depends on canonical.json's schema_version: # v2 → subject.proofs.byte_exact.hash # v1 → subject.document_sha256 (older bundles emitted by some flows) jq -r 'if .schema_version >= 2 then .subject.proofs.byte_exact.hash else .subject.document_sha256 end' /tmp/bundle/canonical.json sha256sum event.json # both should print the same 64-hex string # 2. Pull the on-chain commitment. jq -r '.txid' /tmp/bundle/manifest.json # look up this txid on any BSV explorer; OP_RETURN binds to canonical sha # Optional: broadcast acceptance record (informational). manifest.json # carries an .acceptance block ONLY when the broadcaster returned a # network-validated status (SEEN_ON_NETWORK, ACCEPTED_BY_NETWORK, ...). # Unsigned strings; not load-bearing for verification. Freshly minted # bundles typically omit it — absence is normal, not a failure. jq -r '.acceptance.status // "no acceptance block (normal)"' /tmp/bundle/manifest.json
00 6a 22 # OP_FALSE, OP_RETURN, push 0x22 (34 payload bytes)
4d 42 4e 54 # protocol tag — ASCII "MBNT"
01 01 # version 0x01, subtype 0x01 (generic)
00 06 # TLV section length = 6 bytes
01 e6 29 9c 3b 1d 69 7a 84 d6 b4 92 a0 30 6e 14 36 8a 98 59
# 20-byte doc_hash = sha256(canonical.json)[:20]
# — equals manifest.json's doc_hash_expected
05 04 d5 b0 b0 c6
# TLV: tag 0x05 issuer_id, len 4 (operator DID fingerprint)
Path C — /lookup_hash for hash-existence (auth-free)
If all you need is "has this exact file ever been anchored on this server?", the public lookup endpoint answers in one curl — no key, no bundle:
SHA=$(sha256sum event.json | awk '{print $1}')
curl -sS "https://app.satsignal.cloud/lookup_hash?sha=$SHA"
# Hit: {"proof_id":"...","created_utc":"...","txid":"..."}
# Miss: {"miss":true,"reason":"sha_not_indexed_as_file_hash"}
miss on an anchor you know exists? If the anchor was sealed (mode=sealed), /lookup_hash will always return miss with reason: sha_not_indexed_as_file_hash. Sealed anchors commit to an HMAC of the document, not the file sha, so they're intentionally excluded from this endpoint. Use the /verify browser UI (which takes the document + salt) to confirm a sealed anchor, or see the sealed integration guide for the full flow.
The inverse cuts too: Standard-mode hashes are publicly discoverable. Anyone who holds (or can reconstruct) the exact file bytes can compute the sha256 and ask /lookup_hash whether this server anchored it — no account needed. If the existence of an anchor is itself sensitive (a source document, a privileged draft), anchor it in sealed mode: sealed anchors commit to an HMAC and are excluded from this endpoint by design.
RECEIVED vs CONFIRMED — and why fresh anchors 404 on public explorers
Right after POST /api/v1/anchors returns, the transaction has been accepted by the BSV broadcaster (3-tier failover across independent broadcast services) — the broadcast-lifecycle state broadcasters call RECEIVED. That state is not a manifest field: the bundle’s manifest.json is a static file written once at anchor time and never updates. Broadcast acceptance also does not yet mean a public explorer has indexed the tx. WhatsOnChain typically picks it up within 1–5 minutes of broadcast, occasionally longer (~10); during that window:
api.whatsonchain.com/v1/bsv/main/tx/hash/<txid>may return404— expected, not a failure.api.bitails.iotypically indexes faster — useful as a fallback while WoC catches up.- The bundle is fully verifiable offline already — you do not need an explorer hit to prove the anchor exists.
CONFIRMED means a block has been mined containing the transaction (typically ~10 min on BSV under normal load). Confirmation depth is a property of the public chain, not of the bundle — poll any explorer for it. A CI gate that waits for depth ≥ 6:
TXID=$(jq -r '.txid' /tmp/bundle/manifest.json)
until [ "$(curl -s "https://api.whatsonchain.com/v1/bsv/main/tx/hash/$TXID" \
| jq -r '.confirmations // 0')" -ge 6 ]; do sleep 60; done
Treat RECEIVED as pending, not failed. The by-hand check (Paths A–B) already passes for an indexed-but-unconfirmed transaction — it does not gate on depth. To require a minimum confirmation depth today, wrap it in the curl+jq CI loop shown above (depth ≥ N). A standalone satsignal verify CLI with a --min-confirmations flag (defaulting to 0) is planned but not yet shipped. In the first minutes after broadcast, before explorers have indexed the txid, an explorer lookup returns non-zero regardless — have CI wait and retry rather than fail the build.
Durability across modes — what the auditor needs at verify time
"Verify forever" assumes the
auditor has the .mbnt bytes in hand. How the
holder gets those bytes differs across modes — and
for one mode, that's the holder's job from anchor time onward, not an
automatic guarantee:
| mode | how the auditor gets the bundle bytes | verify works without Satsignal? |
|---|---|---|
| Standard commitment / output / policy_snapshot / evidence_bundle / file proof |
Anchor response carries bundle_url. Holder MUST GET it (with bearer auth) and persist locally — the server copy is kept until you delete the proof (the numeric retain_until on standard responses is bookkeeping, not an enforced expiry). After you delete it, the server-side copy is gone. |
Yes while the server copy lives (until you delete the proof) — or from your own downloaded copy after that. Otherwise the chain anchor still exists, but binding txid back to a specific submitted hash needs Satsignal-hosted /lookup_hash. |
| Sealed — Mirror salt_b64 sent |
bundle_url + bearer auth. By default the server copy is kept until you delete the proof (retain_until: null); an explicit retain_days window auto-deletes it at retain_until. Bundle is salted; selective reveals happen via /unseal. |
Yes while the server copy lives (indefinitely by default) — or from your own copy after an explicit window lapses. |
| Sealed — Blind salt_b64 omitted |
Anchor response carries canonical_b64 + doc_hash; your client assembles the .mbnt locally. Server keeps NO copy. Holder MUST persist at anchor time. |
Yes, unconditionally — server has nothing to lose; the holder is sole custodian from anchor time onward. |
Download your .mbnt at anchor time. By default the server-side copy is kept until you delete the proof — on every plan, for standard and sealed-mirror bundles alike. A sealed-mirror anchor may instead set an explicit retain_days window; a server sweep then deletes that bundle when the window expires — and your own downloaded copy never depends on ours. See the DPA for the full retention statement. On sealed anchors the response’s retain_until is authoritative (null = kept until you delete; a number = the enforced window’s end); on standard anchors the numeric value is bookkeeping — no automatic expiry is applied. The chain anchor is permanent regardless, but if the server-side copy is ever gone — you deleted the proof, or an explicit retain_days window lapsed — re-binding the txid to your hash depends on Satsignal-hosted /lookup_hash. One GET bundle_url at anchor time removes that dependency forever.
The asymmetry: in Standard and Sealed-mirrored modes,
never downloading means that if the server copy is ever
gone (you delete the proof, or an explicit
retain_days window lapses) the chain
anchor is permanent but binding txid back to your
payload’s hash relies on /lookup_hash
(a Satsignal-hosted service). Persist the
.mbnt at anchor time and that dependency
goes away.
The bundle, in plain JSON.
Each proof is a .mbnt file — a small
bundle containing the canonical-doc JSON and the manifest
(txid, network, expected doc hash — plus the optional
informational broadcast acceptance record when the broadcaster
returned a network-validated status). Download a sample to inspect:
Off-chain “this was a typo” without rewriting history.
The chain is permanent. If you anchored the wrong payload, or a later run supersedes an earlier one, you can’t edit the transaction. What you can do is attach an operator annotation to the workspace proof. The chain anchor stays untouched; the annotation surfaces on the dashboard proof page and on any share link you mint, with explicit workspace metadata, not chain mutation framing so an auditor isn’t misled into thinking the original anchor was revoked.
Verifiers ignore annotations entirely — they consume the bundle, not the workspace’s customer DB.
Endpoint
PATCH https://app.satsignal.cloud/api/v1/anchors/<proof_id>
Authorization: Bearer sk_...
Content-Type: application/json
{
"annotation": {
"kind": "typo", // typo | superseded | redacted | note
"text": "off-by-one in the label" // optional, ≤ 1024 chars
}
}
Kinds
| Kind | When to use it |
|---|---|
typo |
You anchored a payload that has a mistake in it but is not sensitive. The annotation explains the error so an auditor reviewing the folder understands the anchor should not be relied on. |
superseded |
A later anchor in the same workspace replaces this one.
Pass the replacement proof id in the legacy
superseded_by_bundle_id field; the proof
page links across.
Same-workspace only; cross-workspace pointers are
rejected. |
redacted |
The bundle’s context (label, memo, filename) is now flagged as sensitive or wrong. The on-chain hash stays public; the workspace surfaces a redaction notice on the proof page. (You cannot retract chain data; this is a workspace-side disclosure that the anchor should not be referenced.) |
note |
Catch-all for non-status context (a citation, a cross-reference, an operator’s comment). No behavioral implication. |
Supersede shape
PATCH /api/v1/anchors/<proof_id>
{
"annotation": {
"kind": "superseded",
"superseded_by_bundle_id": "<replacement proof id>", // legacy field name; value is the replacement proof id
"text": "replaced by run #42 after the metric revision"
}
}
Clearing an annotation
PATCH /api/v1/anchors/<proof_id>
{ "annotation": null }
Response
Returns the same JSON shape as
GET /api/v1/proofs/<proof_id>, with an
annotation field (the new state) when one is set.
The field is omitted when the annotation has been cleared.
When a check doesn’t pass.
This section covers common integration friction points. For a full pre-flight checklist before going live, see the Production checklist — covers idempotency wiring, key rotation, retry policy, broadcast failure recovery, and the verification UX.
Every failure below is diagnosable client-side — the verifier and the public chain are the source of truth, not us.
The proof won’t verify.
Open the in-browser verifier and drop the bundle on its own first. If the proof structure and the on-chain anchor both pass and the match step is the only failure, the bundle is intact — the item you supplied isn’t the one that was anchored. Re-check you uploaded the exact original, not a re-export or a re-encoded copy.
Hash mismatch on the original.
The fingerprint is over the exact bytes. A file that was re-saved, re-compressed, line-ending-converted, or had metadata rewritten is a different byte stream and a different SHA-256. Verify against the artifact as it was at anchor time; if it passed through a pipeline, anchor the post-pipeline bytes.
Lost the sealed bundle.
A Sealed proof can’t be re-derived without the bundle — that privacy is the point. The on-chain commitment still proves timing, but the match step needs the bundle (and, for sealed rows, the per-leaf salt). Treat the bundle as the artifact of record: store it the way you’d store a signed contract. See selective disclosure for what each salt unlocks.
Chain lookup fails or shows nothing.
A just-anchored transaction can take a few minutes to surface
on a public explorer; the proof is valid before the explorer
indexes it. Resolve the commitment SHA without auth at
/lookup_hash —
if it resolves to a bundle and txid there but an explorer still
404s, the explorer is lagging, not the anchor. Still stuck after
the transaction confirms? Email
us the proof ID.
How we publish.
Every Satsignal package on PyPI — satsignal-cli,
satsignal-mcp, satsignal-otel — is
published by GitHub Actions via PyPI Trusted Publishers. There is no
long-lived API token on a maintainer’s machine, no
~/.pypirc, no shared team secret. PyPI accepts each
upload because GitHub mints a short-lived OIDC token from a specific
workflow file (publish.yml) in a specific environment
(pypi) in a specific repo — and PyPI was told in
advance exactly which workflow it will accept from. Revocation is a
single action in the PyPI UI; there is no token-rotation choreography
because there is no token.
The release trigger is gh release create. A separate
workflow anchors each release tag to BSV through Satsignal itself,
giving the release a tamper-evident timing claim independent of
GitHub.
Each upload also carries a PEP 740 Sigstore attestation that
binds the artifact to the exact GitHub Actions workflow run that
produced it. This has been live and machine-verifiable on
satsignal-mcp since 0.4.1. The verification path:
fetch the PEP 691 simple-index JSON at
https://pypi.org/simple/satsignal-mcp/ (with
Accept: application/vnd.pypi.simple.v1+json), read the
files[].provenance URL, then fetch the PEP 740
integrity endpoint at
https://pypi.org/integrity/<pkg>/<ver>/<file>/provenance.
The response is a Sigstore attestation bundle whose certificate SAN
binds the artifact to publish.yml at the tagged commit
and whose Rekor entry transparency-logs the signature.
So both guarantees — OIDC publish (no maintainer token)
and Sigstore attestation (independently-verifiable build
provenance) — are live and verifiable today on the
machine-readable surface. The human-readable badge on
pypi.org/project/<pkg>/<ver>/ is not yet
rendered; that’s a separate PyPI UI rollout and does not
block today’s verification. The legacy warehouse JSON at
pypi.org/pypi/<pkg>/<ver>/json predates
PEP 740 and still reports provenance: null —
it is not the canonical attestation surface.
The byte-level reference — workflow file path, the PyPI
configuration, what an integrator can verify — lives in the
pilot RELEASE.md in
Steleet/satsignal-mcp.
Get an API key, anchor your first commit.
Sign in to mint a key, create a folder, and anchor a proof against your own runtime. The free plan covers a full integration and verify cycle — upgrade to Starter when 100 anchors a month stops being enough.
Mint an API key →
Sign in at app.satsignal.cloud, create a workspace, then
a folder. Mint an API key with the
anchors:create scope.
Agent self-serve signup →
Autonomous agents can create the account without a human
— redeem an invite code or solve a proof-of-work
challenge. GET /api/v1/signup is the discovery
document listing the lanes this host accepts.
Read the agent reference →
The three outcomes in detail — commit-reveal, policy snapshots, and manifest proofs — with worked flows and links to the live agent demos.
Watch the sealed-bid demo →
Two agents committed before reveal. Both reveals match the on-chain commitments byte-for-byte. Bundles downloadable; chain order verifiable.
Need higher limits or a custom integration? →
We read every email. Tell us about your runtime, your threat model, and the buyer who’d use the proof. We’ll figure out the rest.
Implementer specifications
- Bundle spec — the
.mbntfile format - On-chain wire format — the MBNT OP_RETURN byte layout
- Sealed-mode spec — HMAC commitments and the salt protocol
- Chain-anchor interop spec — a BSV anchor inside AAR / C2PA / RFC 3161 / TAP
- Provenance manifest spec — the
satsignal.provenance.v1ingest schema - Merkle-row scheme spec — selective row reveal
- Worked on-chain example — a real anchor decoded byte by byte
- Sealed-mode disclosure reference — how to disclose a sealed proof