API reference · built for the engineer wiring this in
Your agent already calls Anthropic or OpenAI. Point it at the gateway instead.
Change one base URL and every call gets evaluated, signed, and receipted on the way through. Need a human to approve a risky one first: the native risk endpoint dispatches the approval and your caller long-polls the decision, no separate webhook infrastructure to stand up. Prefer to call SynOI directly: a native REST surface and the TypeScript SDK cover that too.
Authentication
Two schemes, matched to how your client already authenticates.
OpenAI-compatible + native routes
Authorization: Bearer synoi-sk-...
Send your SynOI tenant key (synoi-sk-..., minted at POST /keys) or a license key issued by billing (synoi-lk-...) as a Bearer token. Used by /v1/chat/completions, /v1/risk/evaluate, and /receipts/*. Add an X-Provider-Key header with your own upstream provider key if you want to bring your own credential instead of a stored Profile.
Anthropic-compatible route
x-api-key: <your key>
POST /anthropic/v1/messages authenticates exactly the way api.anthropic.com does, an x-api-key header. Point ANTHROPIC_BASE_URL at the gateway and existing Anthropic SDK code needs no other change.
Endpoints
The routes that exist today.
OpenAI-compatible proxy. The calls your OpenAI SDK already makes, evaluated and receipted on the way through to the provider.
Request
{
"model": "gpt-4o",
"messages": [{ "role": "user", "content": "..." }]
}Response
// Standard OpenAI chat-completion body, plus response headers: X-SynOI-Receipt-Id: rcpt-1721... // verify at GET /verify/:receipt_id X-SynOI-OID: <hex> X-SynOI-Provider: openai X-SynOI-Cache: HIT | MISS
Anthropic-compatible proxy. Same request and response shape as api.anthropic.com; auth via x-api-key instead of a Bearer token.
Request
{
"model": "claude-opus-4-7",
"max_tokens": 1024,
"messages": [{ "role": "user", "content": "..." }]
}Response
// Standard Anthropic message body, plus: X-SynOI-Receipt-Id: rcpt-1721...
Native decide-only endpoint: the same call @synoi/sdk's gate() and decide() make under the hood. Evaluates a proposed tool call against your risk policy without proxying to any LLM. Add ?dispatch=1 so the gateway raises a human approval when the verdict is require_approval, and returns a hitl_id for the next call.
Request
{
"tool_name": "shell.exec",
"tool_input": { "command": "rm -rf /tmp/build" },
"model": "claude-opus-4-7"
}Response
{
"action": "require_approval",
"matched_rule": { "id": "high-risk-shell" },
"reason": "...",
"receipt_id": "rcpt-risk-1721...",
"tenant_id": "acme",
"hitl_id": "hitl-4f2a..." // present only with ?dispatch=1 and action=require_approval
}Long-polls for the operator's decision on a dispatched approval. This is the real human-in-the-loop round trip, there is no webhook to stand up: the caller just holds this request open. Returns HTTP 408 if nobody has decided within wait_ms; the SDK retries automatically until its own timeout.
Request
GET /v1/risk/hitl/hitl-4f2a.../wait?wait_ms=30000
Response
{
"hitl_id": "hitl-4f2a...",
"status": "approved", // or "denied" | "expired"
"decided_by": "operator@acme.com",
"decision_note": "looks safe, go ahead"
}Public. No auth. Anyone holding a receipt id can verify it was signed by SynOI, with no SynOI account and no ongoing dependency on SynOI's servers.
Request
GET /verify/rcpt-1721...
Response
{
"verified": true,
"receipt": { "receipt_id": "rcpt-1721...", "tenant_id": "acme", "decision": "allow", "...": "..." },
"verification": {
"scheme": "hybrid Ed25519 + ML-DSA-65",
"ed25519_valid": true,
"ml_dsa_valid": true,
"pubkey_url": "/verify/pubkey"
}
}Lists your tenant's receipts (admin key required). GET /receipts/:tenant_id/:receipt_id fetches a single receipt the same way.
Request
GET /receipts/acme?limit=50
Response
{
"receipts": [ { "receipt_id": "rcpt-1721...", "decision": "allow", "...": "..." } ],
"summary": { "...": "..." },
"shadow_block_7d": 0
}Errors
Status codes and the shapes behind them.
- 401 - missing or invalid key. Body:
{ error: { message, type: "auth_error" } }. - 400 - malformed request body (
validation_error). - 403 - a Class C (high-risk) action needs an approved receipt, or you queried another tenant's data.
- 404 - receipt or HITL request not found, or expired off the store.
- 408 - long-poll wait endpoint only: nobody decided within the window. Not a failure, retry the same GET.
- 429 - rate limit or concurrency cap reached for your plan.
SDKs and what's pending
@synoi/sdk (TypeScript) wraps /v1/risk/evaluate and the long-poll above into one gate() call and ships on npm today. There is no published Python SDK yet, call the REST routes above directly from Python in the meantime.
Generated OpenAPI docs are still pending. The routes, headers, and shapes above are read directly from the running gateway source, not drafted ahead of it.