Authentication
All API requests require an API key passed via the X-API-Key header.
X-API-Key: your_api_key_here
Request an API key from the Get API Key page. Free tier keys are provisioned within 24 hours.
The GET /health endpoint does not require authentication.
Base URL
All endpoints are served from:
https://api.starksentinel.com
Health Check
GET /health
Returns the current status of the SENTINEL API. No authentication required.
Example Request
curl https://api.starksentinel.com/health
Example Response
{
"status": "ok",
"version": "1.3.1",
"service": "sentinel-attestation-service"
}
Response Fields
| Field | Type | Description |
|---|---|---|
status | string | Service status. "ok" when operational. |
version | string | Current API version. |
service | string | Service identifier. |
Generate Proof
POST /v1/prove
Generates a STARK proof attesting that a set of behavioral observations stays within defined EWMA control limits. The proof is zero-knowledge: raw observations are used to compute the EWMA in-memory, then discarded. Only public inputs (observation count, final EWMA, control limits, pass/fail) are included in the proof envelope.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
observations | number[] | Yes | Array of behavioral metric values (unsigned integers). Minimum 2, maximum 1024 observations. |
baseline_mean | number | Yes | Expected baseline mean for the EWMA calculation. |
ucl | number | Yes | Upper Control Limit. EWMA must stay below this value to pass. |
lcl | number | Yes | Lower Control Limit. EWMA must stay above this value to pass. |
Example Request
# Generate a STARK proof for 8 behavioral observations curl -X POST https://api.starksentinel.com/v1/prove \ -H "X-API-Key: $SENTINEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "observations": [95, 98, 92, 97, 100, 94, 96, 99], "baseline_mean": 96, "ucl": 110, "lcl": 80 }'
Example Response
{
"version": "1.2.0",
"proof_bytes": [4, 0, 0, 4, ...],
"public_inputs": {
"num_observations": 8,
"first_observation": 95,
"final_ewma": 95,
"lambda_num": 1,
"lambda_den": 5,
"baseline_mean": 96,
"ucl": 110,
"lcl": 80,
"within_limits": true
},
"metadata": {
"trace_length": 16,
"num_constraints": 3,
"security_level": 97,
"generation_time_ms": 2,
"proof_size_bytes": 5995
},
"chain_hash": "72f2dd09...8b0c33"
}
Response Fields
| Field | Type | Description |
|---|---|---|
version | string | Proof format version (currently "1.2.0"). |
proof_bytes | number[] | STARK proof as a byte array (integers 0–255). Pass the entire proof envelope to /v1/verify. |
public_inputs.num_observations | number | Count of observations included in the proof. |
public_inputs.first_observation | number | The first observation in the measurement window. |
public_inputs.final_ewma | number | Final EWMA value after processing all observations. |
public_inputs.lambda_num / lambda_den | number | EWMA smoothing factor as a fraction (1/5 = λ 0.2). |
public_inputs.baseline_mean | number | The baseline mean used for EWMA seeding. |
public_inputs.ucl | number | Upper Control Limit used in the proof. |
public_inputs.lcl | number | Lower Control Limit used in the proof. |
public_inputs.within_limits | boolean | true if EWMA stayed within [lcl, ucl] for all observations. |
metadata.trace_length | number | Number of rows in the STARK execution trace. |
metadata.num_constraints | number | Number of algebraic constraints in the AIR. |
metadata.security_level | number | Conjectured security level in bits. |
metadata.generation_time_ms | number | Time to generate the proof in milliseconds. |
metadata.proof_size_bytes | number | Size of the proof in bytes. |
chain_hash | string | Blake3 hash of this proof (64-char hex). Use as previous_proof_hash in the next proof to build a chain. |
Privacy Guarantee
Individual observation values are never included in the response. The proof attests to the statistical properties (EWMA within control limits) without revealing the underlying data. This is the Prove-Don't-Share architecture.
Verify Proof
POST /v1/verify
Verifies a previously generated STARK proof. Verification confirms that the proof was correctly generated and that the public inputs are authentic. Verification is always free and does not count against your monthly proof limit.
Request Body
Send the complete proof envelope — the entire JSON response from /v1/prove. All fields are required.
| Field | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Proof format version from the prove response. |
proof_bytes | number[] | Yes | STARK proof byte array from the prove response. |
public_inputs | object | Yes | The public_inputs object from the prove response. |
metadata | object | Yes | The metadata object from the prove response. |
chain_hash | string | Yes | The chain_hash from the prove response. |
Example Request
# Save the prove response, then pass it directly to verify curl -s -X POST https://api.starksentinel.com/v1/prove \ -H "X-API-Key: $SENTINEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"observations": [95,98,92,97,100,94,96,99], "baseline_mean": 96, "ucl": 110, "lcl": 80}' > proof.json # Verify the full proof envelope curl -X POST https://api.starksentinel.com/v1/verify \ -H "X-API-Key: $SENTINEL_API_KEY" \ -H "Content-Type: application/json" \ -d @proof.json
Example Response
{
"valid": true
}
Response Fields
| Field | Type | Description |
|---|---|---|
valid | boolean | true if the proof is cryptographically valid. |
Client-Side Verification
For zero-trust verification, use the open-source WASM verifier. It runs entirely client-side — no network calls, no trust in SENTINEL required. See the Quickstart Guide for setup instructions.
Verify Proof Chain
POST /v1/verify-chain
Verifies a chain of linked STARK proofs. Checks that each proof is cryptographically valid and that chain links are intact — each proof's previous_proof_hash matches the preceding proof's chain_hash. The first proof in the chain must have a null previous hash (genesis proof).
Request Body
A JSON array of proof envelopes, ordered from genesis (first) to most recent (last). Maximum 100 proofs per request.
| Field | Type | Description |
|---|---|---|
[].proof_bytes | number[] | Serialized STARK proof (byte array). |
[].public_inputs | object | EWMA public inputs from the prove response. |
[].chain_hash | string | Blake3 hash of this proof (64-char hex). |
[].previous_proof_hash | string|null | Blake3 hash of the previous proof, or null for genesis. |
[].metadata | object | Proof metadata (security level, generation time, etc.). |
[].version | string | Proof format version. |
Example Request
# Verify a 2-proof chain curl -X POST https://api.starksentinel.com/v1/verify-chain \ -H "X-API-Key: $SENTINEL_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "version": "1.2.0", "proof_bytes": [...], "public_inputs": {...}, "chain_hash": "a1b2c3...", "previous_proof_hash": null, "metadata": {...} }, { "version": "1.2.0", "proof_bytes": [...], "public_inputs": {...}, "chain_hash": "d4e5f6...", "previous_proof_hash": "a1b2c3...", "metadata": {...} } ]'
Example Response
{
"valid": true,
"chain_length": 2,
"valid_proofs": 2,
"valid_links": 2,
"failed_proofs": [],
"broken_links": []
}
Response Fields
| Field | Type | Description |
|---|---|---|
valid | boolean | true if all proofs verify and all links are intact. |
chain_length | number | Total number of proofs in the chain. |
valid_proofs | number | Count of individually valid STARK proofs. |
valid_links | number | Count of valid chain links (hash continuity checks). |
failed_proofs | number[] | Indices of proofs that failed verification. |
broken_links | number[] | Indices where chain link continuity is broken. |
Aggregate Proofs
POST /v1/aggregate
Aggregates a chain of proofs into a single Merkle commitment. Verifies all proofs and chain links, then produces a compact aggregate with a Merkle root, summary statistics, and per-proof summaries. Maximum 10,000 proofs per request.
Request Body
Same format as /v1/verify-chain — a JSON array of proof envelopes ordered from genesis to most recent.
Example Response
{
"version": "1.0.0",
"chain_id": "my-agent-chain",
"chain_length": 2,
"merkle_root": "f8a3b1c9d2e4...",
"aggregate_inputs": {
"first_observation": 95,
"final_ewma": 97,
"all_within_limits": true,
"min_security_level": 128,
"total_observations": 16
},
"proof_summaries": [
{
"chain_hash": "a1b2c3...",
"within_limits": true,
"num_observations": 8,
"final_ewma": 95,
"security_level": 128
}
],
"metadata": {
"aggregation_time_ms": 1,
"proofs_verified": 2,
"links_verified": 1,
"aggregated_at": "2026-04-03T12:00:00.000Z"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
merkle_root | string | Blake3 Merkle root committing to all proofs in the chain. |
aggregate_inputs.all_within_limits | boolean | true if every proof in the chain passed its control limits. |
aggregate_inputs.total_observations | number | Sum of observations across all proofs. |
aggregate_inputs.min_security_level | number | Minimum security level across the chain (weakest link). |
proof_summaries | array | Per-proof summary with chain hash, EWMA, and status. |
metadata.aggregation_time_ms | number | Time to aggregate and verify in milliseconds. |
chain_id | string? | Optional chain identifier. Assigned after creation for chain grouping and registry queries. |
Verify Aggregate
POST /v1/verify-aggregate
Verifies a previously produced aggregate by recomputing the Merkle root from the proof summaries and confirming it matches. This is a lightweight check — no STARK proofs need to be re-verified.
Request Body
Pass the full aggregate response object from /v1/aggregate.
Example Response
{
"valid": true
}
Response Fields
| Field | Type | Description |
|---|---|---|
valid | boolean | true if the Merkle root matches the proof summaries. |
error | string | Present only if valid is false. Describes the mismatch. |
Proof Registry API
The Proof Registry is a separate service at registry.starksentinel.com. It provides immutable, publicly queryable storage for proof records.
Registry Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/proofs | Required | Register a proof. Returns 201 on success, 409 if already registered. |
| GET | /v1/proofs/:chain_hash | Public | Look up a single proof by its chain hash. |
| GET | /v1/proofs | Public | List proofs with pagination. Filter by publisher or chain_id. |
| GET | /v1/chains/:chain_id | Public | Retrieve all proofs in a chain, ordered by registration time. |
| GET | /health | Public | Registry health check with capacity monitoring. |
Base URL
https://registry.starksentinel.com
Register a Proof
# Register a proof after generating and verifying it curl -X POST https://registry.starksentinel.com/v1/proofs \ -H "X-API-Key: $SENTINEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "chain_hash": "a1b2c3d4e5f6...", "previous_proof_hash": null, "version": "1.2.0", "publisher": "my-company", "chain_id": "agent-quality-chain", "num_observations": 8, "final_ewma": 95, "within_limits": true, "ucl": 110, "lcl": 80, "baseline_mean": 96, "security_level": 128, "proof_size_bytes": 6761 }'
Look Up a Proof
# Anyone can look up a registered proof — no API key needed
curl https://registry.starksentinel.com/v1/proofs/a1b2c3d4e5f6...
Example Lookup Response
{
"id": 1,
"chain_hash": "a1b2c3d4e5f6...",
"previous_proof_hash": null,
"version": "1.2.0",
"publisher": "my-company",
"chain_id": "agent-quality-chain",
"num_observations": 8,
"final_ewma": 95,
"within_limits": true,
"ucl": 110,
"lcl": 80,
"baseline_mean": 96,
"security_level": 128,
"proof_size_bytes": 6761,
"registered_at": "2026-04-03T12:00:00.000Z"
}
Registration is immutable — once a proof is registered, it cannot be modified or deleted. Attempting to register a duplicate chain_hash returns 409 Conflict.
MCP Tools
SENTINEL ships as an MCP server with 11 tools for direct integration into AI agent frameworks — including 3 dedicated SRTAP tools for real-time attestation.
| Tool | Description |
|---|---|
sentinel_prove_behavior | Generate a STARK proof from behavioral observations via the SENTINEL API |
sentinel_verify_behavior | Cryptographically verify a single STARK proof |
sentinel_verify_chain | Verify an ordered chain of linked proofs |
sentinel_aggregate_chain | Aggregate a proof chain into a single Merkle commitment (up to 10,000 proofs) |
sentinel_verify_aggregate | Verify an aggregate proof by recomputing the Merkle root |
sentinel_compute_bid | Calculate Behavioral Injection Score locally (0-100) |
sentinel_sanitize_input | Strip invisible Unicode, reasoning tags, and model control tokens |
sentinel_check_health | Check SENTINEL Attestation Service availability |
sentinel_srtap_configure | Set behavioral barriers for real-time attestation (baseline, UCL, LCL, security level) |
sentinel_srtap_observe | Submit observations and receive a chained STARK proof (auto-links via previous_proof_hash) |
sentinel_srtap_status | Query SRTAP session state — chain length, latest hash, config, aggregate readiness |
Compute BID Score
MCP sentinel_compute_bid
Calculates a Behavioral Injection Score (BIS) locally by comparing 5 behavioral metrics against their baselines. Returns a composite 0-100 score, a response level, and per-metric sigma distances. Runs entirely in-process — no API call required.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
enforcementRate | number | Yes | Ratio of blocked to total actions (0-1) |
targetEntropy | number | Yes | Shannon entropy of action targets |
actionFrequency | number | Yes | Actions per minute |
typeConcentration | number | Yes | Ratio of dominant action type (0-1) |
latencyMs | number | Yes | Enforcement processing time in ms |
*_mean | number | Yes | Baseline mean for each metric (e.g. enforcementRate_mean) |
*_stdDev | number | Yes | Baseline standard deviation for each metric |
Example Request
# Via MCP tool call
{
"enforcementRate": 0.35,
"enforcementRate_mean": 0.05,
"enforcementRate_stdDev": 0.02,
"targetEntropy": 0.8,
"targetEntropy_mean": 2.1,
"targetEntropy_stdDev": 0.3,
"actionFrequency": 45,
"actionFrequency_mean": 12,
"actionFrequency_stdDev": 3,
"typeConcentration": 0.9,
"typeConcentration_mean": 0.4,
"typeConcentration_stdDev": 0.1,
"latencyMs": 15,
"latencyMs_mean": 5,
"latencyMs_stdDev": 2
}
Example Response
{
"bis": 82.4,
"response_level": "PAUSE",
"metrics": {
"enforcementRate": { "value": 0.35, "sigma": 15.0, "weight": 0.35 },
"targetEntropy": { "value": 0.8, "sigma": -4.33, "weight": 0.25 },
"actionFrequency": { "value": 45, "sigma": 11.0, "weight": 0.20 },
"typeConcentration": { "value": 0.9, "sigma": 5.0, "weight": 0.15 },
"latencyMs": { "value": 15, "sigma": 5.0, "weight": 0.05 }
}
}
Response Levels
| Level | BIS Range | Action |
|---|---|---|
NORMAL | 0 – 25 | No action. Baseline behavior. |
ENHANCED | 25 – 50 | Increased logging and monitoring. |
ALERT | 50 – 75 | Alert operators. Restrict sensitive operations. |
PAUSE | 75 – 90 | Pause agent execution. Require human review. |
TERMINATE | 90 – 100 | Terminate session immediately. |
Sanitize Input
MCP sentinel_sanitize_input
Strips invisible Unicode characters, reasoning tags, and model control tokens from input text. Returns the cleaned text with a breakdown of what was removed. Runs locally — no API call required.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The input text to sanitize |
Example Response
{
"cleaned": "Please summarize the quarterly report",
"strippedCount": 14,
"categories": {
"tags_block": 0,
"zero_width": 8,
"bidi_control": 2,
"reasoning_tags": 1,
"model_control": 3
}
}
What Gets Stripped
| Category | Examples |
|---|---|
tags_block | Unicode Tags block (U+E0001–U+E007F) — invisible tag characters |
zero_width | Zero-width space (U+200B), zero-width joiner (U+200D), zero-width non-joiner (U+200C) |
bidi_control | Bidirectional overrides (U+202A–U+202E, U+2066–U+2069) — text direction manipulation |
reasoning_tags | <thinking>, <thought>, <antThinking> — reasoning process hijack attempts |
model_control | <|im_start|>, <|im_end|>, <|endoftext|> — model control token injection |
Error Codes
SENTINEL uses standard HTTP status codes. Error responses include a JSON body with a message field.
| Code | Meaning | Common Cause |
|---|---|---|
400 | Bad Request | Missing or invalid fields in the request body. Check that observations is a non-empty array and ucl > lcl. |
401 | Unauthorized | Missing or invalid X-API-Key header. |
429 | Too Many Requests | Monthly proof limit exceeded for your tier. Upgrade your plan or wait for the next billing cycle. |
500 | Internal Server Error | Unexpected server error. Retry with exponential backoff. If persistent, contact support. |
Error Response Format
{
"error": "Bad Request",
"message": "observations must be a non-empty array of numbers"
}
Rate Limits
Rate limits are based on your plan tier and apply to proof generation only. Verification calls and health checks are unlimited.
| Tier | Proofs per Month | Requests per Second |
|---|---|---|
| Free | 100 | 5 |
| Pro | 5,000 | 50 |
| Enterprise | Unlimited | Custom |
When you exceed your monthly limit, subsequent /v1/prove calls return 429 Too Many Requests. Rate limit headers are included in every response:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 42 X-RateLimit-Reset: 2026-05-01T00:00:00Z
API Key Security
API keys authenticate your requests to the SENTINEL Attestation Service. Treat them like passwords.
Key Format
All keys follow the format sk-sentinel-{tier}-{random}. The prefix identifies the tier (free, pro, enterprise) but carries no special privileges beyond rate limits.
Key Provisioning
Free and Pro keys are provisioned instantly via the self-serve form. Enterprise keys require manual review within 24 hours. Keys are generated server-side and returned once — they cannot be recovered after the initial display. Store them securely.
Key Scoping
Each key is scoped to a single tier with fixed rate limits. Keys cannot be used to access endpoints beyond their tier's allowance. Verification endpoints (/v1/verify, /v1/verify-chain, /v1/verify-aggregate) are free and unlimited for all tiers.
Rotation
To rotate a key, request a new one via the API key form using the same email address. The old key remains active until you stop using it — SENTINEL does not enforce automatic expiry at this time. We recommend rotating keys every 90 days or immediately if you suspect compromise.
Revocation
To revoke a compromised key, email admin@forgewerke.com with the key prefix (first 20 characters). Revoked keys return 401 Unauthorized on all subsequent requests. Revocation takes effect within 5 minutes.
Expiry Policy
Keys do not currently expire automatically. Keys that have not been used for 180 days may be subject to deactivation with 30 days advance notice to the registered email. Enterprise keys follow custom retention policies agreed during onboarding.
Best Practices
Store keys in environment variables or a secrets manager — never commit them to source control. Use separate keys for development and production. Monitor your usage via the X-RateLimit-* response headers to detect unauthorized use. If a key is exposed in a public repository, rotate it immediately.