SENTINEL REST API
All endpoints live under /api/sentinel. JWT-based auth via Authorization: Bearer <token>.
- Public routes require
verifyJwtonly. - Admin routes require
verifyJwt + requireOwner— wallet must appear inAUTHORIZED_WALLETSenv var.
Error Envelope
Section titled “Error Envelope”All SENTINEL routes that emit errors return a structured envelope:
{ "error": { "code": "<CODE>", "message": "<human-readable>" } }| Code | HTTP status | Meaning |
|---|---|---|
VALIDATION_FAILED | 400 | Required fields missing or malformed |
NOT_FOUND | 404 | Resource ID does not exist |
FORBIDDEN | 403 | Reserved (auth middleware emits its own legacy shape today) |
UNAVAILABLE | 503 | Server up but a dependency is unconfigured |
INTERNAL | 500 | Unexpected server-side failure |
Note on auth errors: 401/403 responses from verifyJwt and requireOwner middleware still use the legacy {error: "string"} shape. Normalizing those is tracked separately as a future API-wide cleanup.
Public Endpoints
Section titled “Public Endpoints”POST /api/sentinel/assess
Section titled “POST /api/sentinel/assess”Auth: verifyJwt
Description: Run a one-shot risk assessment for a proposed action. Used by external integrators or internal callers that want a verdict without committing to execution. The SENTINEL assessor (a Pi-SDK LLM agent) evaluates the action context and returns a structured RiskReport.
Request body:
{ "action": "string (required)", "wallet": "string (required)", "recipient": "string (optional)", "amount": "number (optional)", "token": "string (optional)", "metadata": "object (optional)"}Response 200 — RiskReport:
{ "risk": "high", "score": 100, "reasons": [ "SENTINEL output failed schema validation" ], "recommendation": "block", "blockers": [ "schema-violation" ], "decisionId": "01KQP24PG0KZCJVWJDQM8H3JAY", "durationMs": 440}Response 400:
{ "error": { "code": "VALIDATION_FAILED", "message": "action and wallet are required strings" } }Returned when action or wallet is missing or not a string.
Response 500:
{ "error": { "code": "INTERNAL", "message": "<error message from assessor>" } }Returned when the assessor throws an unexpected error.
Response 503:
{ "error": { "code": "UNAVAILABLE", "message": "SENTINEL assessor not configured" } }Only returned when no assessor is registered at startup. Production agents always register one via setSentinelAssessor, so this path is rarely hit in deployed environments.
Example:
curl -X POST http://localhost:5006/api/sentinel/assess \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "action": "vault_refund", "wallet": "C1phrE76Wrkmt1GP6Aa9RjCeLDKHZ7p4MPVRuPa8x85N", "amount": 1.5 }'GET /api/sentinel/blacklist
Section titled “GET /api/sentinel/blacklist”Auth: verifyJwt
Description: List blacklisted addresses. Soft-removed entries (where removed_at is set) are filtered out by default.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Maximum entries to return |
Response 200:
{ "entries": []}Example:
curl -H "Authorization: Bearer $JWT" \ "http://localhost:5006/api/sentinel/blacklist?limit=50"GET /api/sentinel/pending
Section titled “GET /api/sentinel/pending”Auth: verifyJwt
Description: List queued circuit-breaker pending actions (SQLite-backed). Optionally filter by wallet or status.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
wallet | string | — | Filter by originating wallet address |
status | string | — | Filter by action status — lifecycle is pending → executing → executed | cancelled |
Response 200:
{ "actions": []}Example:
curl -H "Authorization: Bearer $JWT" \ "http://localhost:5006/api/sentinel/pending?wallet=C1phrE76Wrkmt1GP6Aa9RjCeLDKHZ7p4MPVRuPa8x85N&status=pending"GET /api/sentinel/status
Section titled “GET /api/sentinel/status”Auth: verifyJwt
Description: SENTINEL runtime status snapshot — current mode, preflight scope, configured model, daily LLM cost accrued, and daily budget ceiling.
Response 200:
{ "mode": "advisory", "preflightScope": "fund-actions", "model": "openrouter:anthropic/claude-sonnet-4.6", "dailyBudgetUsd": 10, "dailyCostUsd": 0, "blockOnError": false}| Field | Type | Description |
|---|---|---|
mode | "yolo" | "advisory" | "off" | Current operating mode |
preflightScope | string | Which tool categories trigger the preflight gate |
model | string | LLM model identifier used for assessments |
dailyBudgetUsd | number | Maximum daily spend allowed for LLM calls |
dailyCostUsd | number | Actual spend accumulated today (UTC) |
blockOnError | boolean | Whether assessment errors cause the action to be blocked |
Example:
curl -H "Authorization: Bearer $JWT" \ http://localhost:5006/api/sentinel/statusAdmin Endpoints
Section titled “Admin Endpoints”Admin routes require both verifyJwt AND requireOwner middleware. The calling wallet must appear in the AUTHORIZED_WALLETS environment variable (comma-separated list).
POST /api/sentinel/blacklist
Section titled “POST /api/sentinel/blacklist”Auth: verifyJwt + requireOwner
Description: Insert a new blacklist entry. The added_by field is set to admin:<wallet> using the verified wallet from the JWT, or admin if no wallet is present.
Request body:
{ "address": "string (required)", "reason": "string (required)", "severity": "string (required)", "expiresAt": "string (optional, ISO 8601 timestamp)", "sourceEventId": "string (optional)"}Response 200:
{ "success": true, "entryId": "01KQP25BM8RVZJ92CTANFDNTMG"}entryId is a ULID generated by the SQLite insert.
Response 400:
{ "error": { "code": "VALIDATION_FAILED", "message": "address, reason, severity required" } }Example:
curl -X POST http://localhost:5006/api/sentinel/blacklist \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "address": "BadActor11111111111111111111111111111111111", "reason": "Flagged by SENTINEL risk assessment", "severity": "high" }'DELETE /api/sentinel/blacklist/:id
Section titled “DELETE /api/sentinel/blacklist/:id”Auth: verifyJwt + requireOwner
Description: Soft-remove a blacklist entry. Sets removed_at, removed_by, and removed_reason columns. The row is not deleted from SQLite — it is retained for audit purposes and filtered from GET /blacklist responses.
Path params:
| Param | Type | Description |
|---|---|---|
id | string | ULID of the blacklist entry to remove |
Request body (optional):
{ "reason": "string (default: 'manual removal')" }Response 200:
{ "success": true}Example:
curl -X DELETE http://localhost:5006/api/sentinel/blacklist/01KQP25BM8RVZJ92CTANFDNTMG \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "reason": "false positive, cleared by review" }'POST /api/sentinel/circuit-breaker/:id/cancel
Section titled “POST /api/sentinel/circuit-breaker/:id/cancel”Auth: verifyJwt + requireOwner
Description: Cancel a circuit-breaker pending action (SQLite-backed). Calls cancelCircuitBreakerAction in packages/agent/src/sentinel/circuit-breaker.ts. The cancelled_by field is set to user:<wallet> from the JWT, or admin if unavailable.
Path params:
| Param | Type | Description |
|---|---|---|
id | string | ULID of the circuit-breaker pending action |
Request body (optional):
{ "reason": "string (default: 'manual cancel')" }Response 200:
{ "success": true }Returned when the action ID exists and was cancelled.
Response 404:
{ "error": { "code": "NOT_FOUND", "message": "pending action not found or already resolved" } }Returned when the action ID does not exist or is already settled. A missing or already-resolved action returns this 404 envelope (not 200 + {success: false}).
Example:
curl -X POST http://localhost:5006/api/sentinel/circuit-breaker/01KQP25BM8RVZJ92CTANFDNTMG/cancel \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "reason": "admin override, action no longer needed" }'GET /api/sentinel/decisions
Section titled “GET /api/sentinel/decisions”Auth: verifyJwt + requireOwner
Description: List recent SENTINEL decisions — the assessment outcomes recorded each time SENTINEL evaluated an action. Optionally filter by source.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Maximum decisions to return |
source | string | — | Filter by source label (e.g., agent, scanner, assess) |
Response 200:
{ "decisions": []}Example:
curl -H "Authorization: Bearer $JWT" \ "http://localhost:5006/api/sentinel/decisions?limit=20&source=agent"GET /api/sentinel/config
Section titled “GET /api/sentinel/config”Auth: verifyJwt + requireOwner
Description: Return the current SENTINEL configuration snapshot — operating mode, preflight scope/thresholds, blacklistAutonomy, rate limits, scan intervals, autoRefundThreshold, model, dailyBudgetUsd, accrued dailyCostUsd, blockOnError, and the fundMovingTools list. See Configuration for what each field controls.
Response 200:
{ "mode": "advisory", "preflightScope": "fund-moving", "blacklistAutonomy": true, "model": "openrouter:anthropic/claude-sonnet-4.6", "dailyBudgetUsd": 10, "dailyCostUsd": 0, "blockOnError": false, "fundMovingTools": ["executeRefund", "addToBlacklist"]}Example:
curl -H "Authorization: Bearer $JWT" \ http://localhost:5006/api/sentinel/configPOST /api/sentinel/promise-gate/:flagId/resolve
Section titled “POST /api/sentinel/promise-gate/:flagId/resolve”Auth: verifyJwt + requireOwner
Description: Resolve a pending in-memory promise-gate flag — the admin approval path when SENTINEL is in advisory mode and has paused an action for human review. Calls resolvePending(flagId) in packages/agent/src/sentinel/pending.ts.
Path params:
| Param | Type | Description |
|---|---|---|
flagId | string | ID of the in-memory pending promise-gate flag |
Response 204: No body — flag was found and resolved. The paused agent tool call proceeds.
Response 404:
{ "error": { "code": "NOT_FOUND", "message": "flag not found or expired" } }Returned when no in-memory flag exists for the given flagId. Flags expire when the waiting agent times out.
Example:
curl -X POST http://localhost:5006/api/sentinel/promise-gate/flag_01KQP24PG0KZCJVWJDQM8H3JAY/resolve \ -H "Authorization: Bearer $JWT"POST /api/sentinel/promise-gate/:flagId/reject
Section titled “POST /api/sentinel/promise-gate/:flagId/reject”Auth: verifyJwt + requireOwner
Description: Reject a pending in-memory promise-gate flag — the admin denial path when SENTINEL is in advisory mode and has paused an action. Calls rejectPending(flagId, 'cancelled_by_user') in packages/agent/src/sentinel/pending.ts. The paused tool call receives a rejection and the agent aborts the action.
Path params:
| Param | Type | Description |
|---|---|---|
flagId | string | ID of the in-memory pending promise-gate flag |
Response 204: No body — flag was found and rejected. The paused agent tool call receives a rejection error.
Response 404:
{ "error": { "code": "NOT_FOUND", "message": "flag not found or expired" } }Returned when no in-memory flag exists for the given flagId.
Example:
curl -X POST http://localhost:5006/api/sentinel/promise-gate/flag_01KQP24PG0KZCJVWJDQM8H3JAY/reject \ -H "Authorization: Bearer $JWT"