The Keyring server provides the API used by the web UI. It stores grants, ApprovalCards, scan state, execution results, and the audit ledger. TrueForge owns the agent loop and its own session API.
POST /scansstarts a scan. Supplyperson,scope, or both. The response containsscanId.GET /scans/:idreturns scan status, metadata, identity counts, and costs.GET /scans/:id/streamsends scan progress as server sent events.GET /scans/:id/cardsreturns the ApprovalCards for a scan.POST /cards/:id/decisionrecordsapprove,hold, orreject. This records intent and never executes a provider action.POST /scans/:id/executeexecutes approved cards. An event stream is returned when the request acceptstext/event-stream.GET /auditreturns ledger records and hash verification.GET /audit/exportreturns a signed JSON or CSV export.GET /recordingslists available local recordings.POST /scans/:id/demo-resetresets card decisions for an aborted demo take. It is available only whenKEYRING_DEMO=1; append-only audit records are retained.
The driver field or KEYRING_SCAN_DRIVER selects the scan implementation:
fixtureruns local fixture connectors and emits local subagent events.trueforgestarts a TrueForge session and mirrors its events.recordruns a scan and stores its tool summaries, events, costs, and cards.replayreads a recording and makes no provider calls.
The scan metadata and /scans/:id/cards response expose cardCount,
humanIdentityCount, agentIdentityCount, and systemCount. Agent cards
include their runtime, purpose, reachable tools, declaration status, and
evidence in the serialized grant principal. The fixture and replay drivers
keep the authoritative demo queue at nine cards or fewer, retaining agent,
protected, and held findings before lower-risk findings.
Approval is separate from execution. A decision changes the card state and writes an audit record. Execution selects approved cards, writes a before record, calls the connector or dry run plan, then writes the result. Protected cards cannot be approved in bulk.
Dry run is enabled by default. Set dryRun to false in the execute request only when live mutation is intentional and the live backend is configured.
Start a fixture scan:
SCAN=$(curl -s -X POST http://localhost:3001/scans \
-H 'content-type: application/json' \
-d '{"person":"Ada Lovelace","driver":"fixture"}' | jq -r .scanId)Read progress and cards:
curl -N "http://localhost:3001/scans/$SCAN/stream"
curl -s "http://localhost:3001/scans/$SCAN/cards" | jq .Record an approval and execute it as a dry run:
CARD=$(curl -s "http://localhost:3001/scans/$SCAN/cards" | jq -r '.cards[] | select(.status=="pending") | .id' | head -1)
curl -s -X POST "http://localhost:3001/cards/$CARD/decision" \
-H 'content-type: application/json' \
-d '{"decision":"approve","by":"you@example.com"}'
curl -s -X POST "http://localhost:3001/scans/$SCAN/execute" \
-H 'content-type: application/json' \
-d '{"approvedBy":"you@example.com"}' | jq .Verify the ledger:
curl -s http://localhost:3001/audit | jq .verification
curl -s "http://localhost:3001/audit/export?format=json" -o audit-export.json
pnpm verify:audit audit-export.jsonEvery request is validated with Zod. Scan logs include the scan id. A failed connector is reported as a partial scan when other systems return usable data.