Production checklist

A pre-flight checklist for any integrator about to enable Satsignal in production. Goes beyond the per-path tutorials — covers the cross-cutting operational concerns that don't fit any single chooser card: which events you anchor, the fields you must persist, the retry / backoff matrix, the key-rotation runbook, and the recovery path for stuck broadcasts. Walk through this list before you ship. Revisit it when you onboard a new team or change an integration's volume.

Companion docs: API reference · Compatibility map · What to hash

1. The 60-second framing

Satsignal anchors hash commitments in BSV OP_RETURN outputs. Your integration calls POST /api/v1/anchors, gets back a proof_id + txid + bundle_url, persists those fields, and later can prove that a given byte string existed in bit-identical form at or before a specific block time. The per-path guides (Files, Webhooks, Agents, etc.) walk you through the wire shape for each integration shape. This page covers everything else: the decisions and runbooks that apply to every shape.

Use it as a literal pre-flight checklist before turning anchoring on for real traffic. Section 11 has the copy-paste version your team can paste into a launch ticket.

2. Decisions to make before any anchor goes out

These are the irreversible choices. Get them wrong and you'll be retrofitting your data model later — or worse, anchoring the wrong thing for months and discovering it at audit time.

3. The "store these fields" checklist

The single most important integration rule. For every anchor your code POSTs, persist these fields together — alongside the original canonical artifact you hashed. The proof is only meaningful when paired with the bytes it commits to.

FieldWhySource
proof_idStable handle for later lookup, dashboard links, audit logs.Response body.
txidThe BSV transaction id that carries the OP_RETURN commitment. The only thing strictly needed to re-derive the proof from a block explorer.Response body.
bundle_urlAbsolute URL of the portable .mbnt bundle. Persist it; downloading once and caching locally is the standard pattern.Response body.
proof_urlDashboard page for humans. A counterparty with the link can verify without an account.Response body.
sha256_hexThe hash you submitted. Lets you re-verify without recomputing from the artifact every time.Request body.
folder_slugGrouping namespace. Useful for inventory queries via GET /api/v1/anchors?folder_slug=....Request body.
labelFree-text tag (if you sent one).Request body.
categoryYour taxonomy bucket. Useful for filtering at audit time.Request body.
The original canonical artifactThe exact bytes whose SHA-256 you submitted. Without these, the chain side still verifies but you cannot tie the on-chain commitment to your specific record.Your storage.

A minimal SQL-ish row:

CREATE TABLE anchored_records (
  proof_id        TEXT PRIMARY KEY,
  txid            TEXT NOT NULL,
  sha256_hex      TEXT NOT NULL,
  folder_slug     TEXT NOT NULL,
  category        TEXT NOT NULL,
  label           TEXT,
  bundle_url      TEXT NOT NULL,
  proof_url       TEXT NOT NULL,
  artifact_path   TEXT NOT NULL,   -- where the canonical bytes live in YOUR storage
  anchored_at_utc TIMESTAMP NOT NULL
);

The on-chain transaction is the source of truth for the proof itself; the row above is your local index into it. Lose the row and you can still find the proof on chain — lose the canonical artifact and you have a hash with nothing to compare it against.

4. Idempotency wiring

Every retry-prone POST gets an Idempotency-Key header. This is the single most important reliability guard in the API.

Concrete shape:

curl -X POST https://app.satsignal.cloud/api/v1/anchors \
  -H "Authorization: Bearer $SATSIGNAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-events-evt_demo_123" \
  -d '{"folder_slug":"acme-events","sha256_hex":"...","file_size":256,"category":"commitment"}'

On a retry — same key, same body — the response carries X-Idempotent-Replayed: true and you can short-circuit the rest of your post-anchor work (the cached row already has everything).

5. Rate limits and quotas

Every 2xx and every 429 response from /api/v1/anchors (and the sibling read endpoints) carry the rate-limit headers.

HeaderMeaning
X-RateLimit-LimitAnchors allowed in the plan's quota window.
X-RateLimit-RemainingRemaining in the current window.
X-RateLimit-ResetUTC epoch seconds when the window resets.
X-RateLimit-Windowmonth (free/starter/pro/scale) or day (legacy trial/paid).

The plan quota window is the anchor API's only key-level throttle — there is no separate per-minute or per-hour burst limiter on POST /api/v1/anchors today. (Auth-free endpoints carry their own hour-windowed per-IP limits — e.g. /lookup_hash at 120/hour, or 5,000/hour per workspace with a bearer key.)

For a quota check without firing an anchor:

curl -H "Authorization: Bearer $SATSIGNAL_API_KEY" \
  https://app.satsignal.cloud/api/v1/usage

This returns the current month's count, plan ceiling, and reset epoch — the same numbers the headers carry. Wire it into your monitoring; surprise quota exhaustion is the single most common cause of "why did anchors stop landing?".

A 429 quota_exceeded from the anchor API carries the same X-RateLimit-* headers but no Retry-After, and the window is a fixed calendar window (not a sliding bucket) — a backoff-and-retry loop cannot clear it. Alert on it, wait for X-RateLimit-Reset, or email hello@satsignal.cloud for a cap lift. Hour-windowed endpoints like /lookup_hash do return Retry-After on 429 — honor it there.

Plan tiers (rough shape; subject to change — confirm at satsignal.cloud/pricing):

Self-serve billing is not yet enabled. Sustained production volume onboards directly today: email hello@satsignal.cloud with your expected anchor volume and we'll lift the cap (or open a design-partner slot if you need invoice billing). Sign up early if you expect to exceed the free tier in your first month.

6. Key rotation policy

API keys are minted at app.satsignal.cloud/w/{workspace}/keys/. The format is sk_<8-char prefix>_<32-char secret> — the prefix is shown in the dashboard for fast identification; the secret is shown ONCE at mint time and is unrecoverable thereafter.

When to rotate

How to rotate without downtime

  1. Mint the new key first. Same scopes, same folder restrictions.
  2. Update every consumer. CI variables, secrets managers, environment files, deployed services. Don't forget local dev keys if they share scope.
  3. Verify the new key works end-to-end. Fire a real anchor through each consumer; confirm the 2xx + the proof_url resolves.
  4. Then revoke the old key. Once revocation is in, every request using the old key returns 401.

The Authorization header alone identifies the workspace + scope; no separate workspace id needs to be sent in requests, so rotation is a single header swap on the client side.

Scoped keys

A key can be scoped to specific folders. Use scoped keys for partner / customer integrations to limit blast radius:

Document who can mint and revoke keys in your runbook. The most common failure mode is "the key works, but we don't know which engineer owns it" — keep a keys.md in your ops repo listing every active key prefix, its purpose, and its owner.

7. Retry and error handling

Decision matrix. The retry policy depends on which class of error you get back, NOT on the HTTP code alone.

CodeClassRetry?How
200 / 201successPersist response fields.
400 invalid_type / unknown_field / mode_conflictprogramming errorNOFix your code.
400 rejected_fieldinputNO (until input is normalized)Sanitize the offending field.
400 invalid_sha256inputNORe-hash; ensure 64 lowercase hex chars.
400 conflicting_aliasprogramming errorNOSend only folder_slug, not both folder_slug and matter_slug with different values.
401 missing_bearer / invalid_keyauthNO (until key fixed)Refresh credentials.
403 insufficient_scopeauthNO (until key fixed)Mint a key with the required scope.
404 folder_not_foundinputNOCreate the folder first via POST /api/v1/folders.
409 idempotency_key_reuse_body_mismatchprogramming errorNOFix your retry logic — body must be byte-identical on replay.
409 proof_set_requires_force_newby designconditionalRe-submit with force_new: true IF you intentionally want a new anchor for the same sha.
429 quota_exceededrateNO — fixed calendar windowAlert and wait for X-RateLimit-Reset, or request a cap lift. No Retry-After is sent (per-IP 429s like /lookup_hash do send one — honor it there).
500 / 502 / 503 / 504transientYES, with Idempotency-KeyThe cache returns your in-flight or completed result.

Two rules of thumb that cover ~95% of cases:

  1. Programming errors do not become transient by being retried. If you get idempotency_key_reuse_body_mismatch, alert — don't loop.
  2. Always retry transient errors with the same Idempotency-Key. The record is written before the broadcast, so the original anchor may already exist on chain even though your client got a 504.

8. Broadcast failure and stuck-anchor recovery

Satsignal's broadcast path is 3-tier failover across independent broadcast services. Most broadcast failures self-recover via fallback, invisible to your client.

Symptoms of a stuck anchor

Diagnosis path

curl -H "Authorization: Bearer $SATSIGNAL_API_KEY" \
  https://app.satsignal.cloud/api/v1/anchors/<proof_id>

Inspect the response:

Idempotency safety

Re-POSTing with the same Idempotency-Key is safe — the cached row will return without re-broadcasting. So if you're unsure whether your original POST landed, retry with the same key and inspect the response.

What NOT to do

DO NOT re-POST without Idempotency-Key to "force" a fresh broadcast. That's how you double-anchor and double-bill — the de-dup gate is on the hash, but a separate proof_id will be created if a different body shape (e.g. an additional force_new: true) is sent. Always retry through the idempotency layer.

9. Verification UX

If your users or auditors will ever want to verify a proof later, surface the verification path in your UI. The verification flow does not require a Satsignal account — anyone with the bundle + the original bytes can verify.

Minimum surface:

The web verifier at https://proof.satsignal.cloud/verify accepts the .mbnt + the original artifact and returns pass/fail in ~3s. For a scriptable check today, the unzip + sha256sum recipe hashes the bundle's canonical.json, compares it to doc_hash_expected, and resolves the txid against any public BSV node — fully offline except that last chain-resolution step. (A standalone satsignal verify CLI, with --spv / --min-confirmations, is planned but not yet shipped.)

10. Support flow

Lightweight, by design — most issues are diagnosable from the proof_id.

Don't include API keys in support tickets. The proof_id + the txid (if you have one) are enough for us to pull the workspace audit trail.

11. Pre-flight checklist (the literal checklist)

Copy-paste markdown for your launch ticket. Tick each box before flipping anchoring on for production traffic.

12. Where this fits

Questions about this specification? Email hello@satsignal.cloud.