Skip to content
Docs / StateMirror
DOCUMENT / STATEMIRROR

Run StateMirror locally

Submit one evidence snapshot, retrieve it and verify its integrity. You need Git, Node.js 20 or later, npm, Docker with Compose and curl. Commands below use Bash (macOS, Linux or WSL).

Full GitHub quickstart, including Windows CMD examples ↗

TYPE
Local development

1. Clone and configure

#
FIG 01 / BASHCLONE_CORE
git clone https://github.com/simplestates/statemirror-core.git
cd statemirror-core
cp .env.example .env
npm install
Clone Core and install its dependencies.

Set these values in the cloned Core repository’s .env. These published test credentials are for local development only.

FIG 02 / ENV / LOCAL TEST VALUESLOCAL_CONFIG
PORT=8080
READ_API_KEYS=smr_test_read_key_123
WRITE_API_KEYS=smw_test_write_key_456
DATABASE_URL=postgres://statemirror:statemirror@localhost:5432/statemirror
Local test values. Replace before a shared deployment.

2. Start PostgreSQL and Core

#

Start only the postgres service from the repository’s Compose file. Run Core through npm in this terminal.

FIG 03 / BASHSTART_CORE
docker compose up -d postgres
docker compose exec postgres pg_isready -U statemirror -d statemirror
npm run migrate
npm run build
npm run start
Start PostgreSQL, apply migrations and run Core.

Wait for pg_isready to report “accepting connections” before migrating. With older Compose installations, use docker-compose in place of docker compose.

In a second terminal:

FIG 04 / HTTP / CURLHEALTH_CHECK
curl http://localhost:8080/v1/health
Check the local service health.

Confirm status: healthy and database: connected.

3. Submit evidence

#

StateMirror accepts application-defined JSON; this site uses one consistent example shape for demonstration. The outer envelope below uses Core’s API fields; Evidence Lanes remain optional.

FIG 05 / HTTP / CURL + JSONTX_EVIDENCE
curl -X POST http://localhost:8080/v1/snapshots \
  -H 'Authorization: Bearer smw_test_write_key_456' \
  -H 'Idempotency-Key: website-local-demo-001' \
  -H 'Content-Type: application/json' \
  -d '{
  "evidence_ref": "access:customer_123:request_001",
  "evidence_type": "access_decision",
  "captured_at": "2026-06-15T18:22:31.000Z",
  "state_payload": {
    "subject": "customer_123",
    "inputs": {
      "plan_status": "active",
      "seats_used": 10,
      "seat_limit": 10,
      "read_at": "2026-06-15T18:22:30.000Z"
    },
    "rule_version": "access-rules@2026.06.12",
    "decision": "deny",
    "reason": "seat_limit_reached",
    "correlation_id": "request_001"
  }
}'
Submit the application evidence with a write key and idempotency key.

Save the returned snapshot_id. The receipt also includes sequence_num, received_at, payload_hash and chain_hash. Use the same idempotency key only to retry the same submission; choose a new key for a new evidence moment.

Receipt

#

Abbreviated successful response shape. These values are illustrative; use your returned ID, sequence and hashes.

FIG 06 / JSON / EXAMPLE RECEIPTRX_RECEIPT
{
  "snapshot_id": "71be5886-a779-4c28-ae90-c389165c90a2",
  "sequence_num": 1,
  "received_at": "2026-06-15T18:22:31.120Z",
  "payload_hash": "PbQTKH7/sGHUg+GjB0acqsNluk4DuU42HAZaHUwQ5r4=",
  "chain_hash": "NZpNkbVJ5z7OzkbmqpiGM89RqzAA9A0+eUVvAyUPkOg="
}
Example response; use the values returned by your local service.

4. Retrieve the snapshot

#

Replace PASTE_RETURNED_ID with the ID from the response.

FIG 07 / HTTP / CURLRETRIEVE
SNAPSHOT_ID='PASTE_RETURNED_ID'
curl "http://localhost:8080/v1/snapshots/$SNAPSHOT_ID" \
  -H 'Authorization: Bearer smr_test_read_key_123'
Retrieve the snapshot using the returned ID.

Inspect the returned state_payload: the consulted seat counts and declared denial remain the submitted values, regardless of changes in your application database.

5. Verify integrity

#
FIG 08 / HTTP / CURLVERIFY
curl "http://localhost:8080/v1/snapshots/$SNAPSHOT_ID/verify" \
  -H 'Authorization: Bearer smr_test_read_key_123'
Verify the preserved snapshot using a read key.

Verification result

#

Abbreviated successful response for the example snapshot:

TEST 01 / JSON / EXAMPLE RESULTINTEGRITY_VERIFY
{
  "snapshot_id": "71be5886-a779-4c28-ae90-c389165c90a2",
  "sequence_num": 1,
  "valid": true,
  "message": "Preserved evidence integrity verified."
}
Example response; use the values returned by your local service.

This checks the preserved payload and its chain linkage. It does not establish that the seat counts were true or that denying access was correct. For a wider chain check, use the documented sequence-range verification endpoint.