Developer documentation
API Reference
Last reviewed 31 August 2026
All docs
Quickstart#
Five minutes from nothing to a protected agent. Nothing below is aspirational -- every command exists and every output block was captured from a real run.
Fastest path: the HTTP API (nothing to install)#
Everything the SDK does goes through one HTTP call, so you can protect an agent today with curl or any HTTP client. The SDK and CLI further down are conveniences on top of it.
See a decision without an account#
curl -s -X POST 'https://cainstudio.online/fabric/try?scenario=safe-read'
{"scenario":{"id":"safe-read", ...},
"verdict":"REQUIRE_APPROVAL","blocked":false,
"decision_id":"fd_2a0da78b47d34964b049851c",
"chain":[{"stage":"identity","verdict":"allow",...},
{"stage":"trust","verdict":"require_approval",
"detail":"trust=unknown, risk=low, decision=require_approval"}, ...]}
GET /fabric/try lists the other fixed scenarios (prompt injection, malformed plan, ...). They run against a throwaway tenant.
Ask before every consequential tool call#
Get an API key at /signup, then send the tool call *before* you run it:
export CAIN_API_KEY=...
curl -s https://cainstudio.online/fabric/decisions \
-H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
-d '{"path":"/tools/send_email","agent_id":"support-bot",
"payload":{"to":"a@example.com","subject":"hi"}}'
{
"decision_id": "fd_aeffae8d45cb4cf79576f823",
"verdict": "REQUIRE_APPROVAL",
"blocked": false,
"enforcing": true,
"mode": "enforce",
"path": "/tools/send_email",
"stages": [ {"stage": "identity", "verdict": "allow", ...},
{"stage": "trust", "verdict": "require_approval",
"detail": "trust=unknown, risk=low, decision=require_approval"}, ... ]
}
Run the tool only when verdict starts with ALLOWED and blocked is false. Treat every other verdict, a timeout and a non-200 response as "do not run". A new agent has no trust history, so its first actions come back REQUIRE_APPROVAL. Unknown trust never turns into ALLOWED by itself.
No key gives 401 {"detail":"missing X-API-Key"}, which is never a permit.
How an agent earns autonomy#
Give each agent its own key, so the agent asks and you approve (a key can never approve its own request):
curl -s https://cainstudio.online/fabric/agent-keys \
-H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
-d '{"name":"support-bot"}'
# -> {"agent_key_id":"ak_...","agent_key":"agt_...", ...} (the key is shown once)
The agent sends its decisions with its agt_... key. A held action is not a dead end: every REQUIRE_APPROVAL lands in your review queue with an approval_id (the approval stage carries it), and you resolve it with your own key:
cainstudio approvals # what is waiting cainstudio approve <approval_id> # approve it (with a key other than the agent's own)
Resend the same call and it runs, once. The approval covers that exact action, tool and arguments: the same tool with different arguments (another amount, another recipient) is held again. Each approved, completed action is evidence for the agent:
| Agent's record | Low-risk actions | Medium (writes, email) | High (shell, payments, deletes) | Critical |
New (unknown) | held for you | held for you | held for you | held for you |
Mostly approved actions (normal) | run | run | held for you | held for you |
Long clean record (trusted: 50+ approved, 95%+) | run | run | run, monitored | held for you |
Blocked attempts on record (degraded, suspended) | held for you | held for you | held for you | held for you |
| Revoked | denied | denied | denied | denied |
A payload the injection screen flags (for example "ignore all previous instructions") is denied outright for a new or degraded agent and held for a normal one.
Critical actions (rm -rf /, DROP TABLE, curl ... | sh, transfers of 10,000 or more) always need a human, however good the record. Trust is tracked per agent and can never exceed the trust of the API key it runs under, so a new agent under an established key still starts held. Each decision's trust stage shows the agent's state and why (data.action_risk.reasons).
Add a rule and watch it bite#
curl -s https://cainstudio.online/fabric/tool-rules \
-H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
-d '{"name":"no outbound email","effect":"deny","match_path":"/tools/send_email"}'
Send the same decision request again:
"verdict": "BLOCKED",
"blocked": true,
...
{"stage": "policy", "verdict": "deny", "enforcing": true,
"detail": "tool rule 'no outbound email' (tr_66a83083d50c46b8 v1) denies this action ..."}
Rules can also allow or require_approval. You can manage them in the console at /dashboard under *Tools & rules*.
Check the record afterwards#
curl -s https://cainstudio.online/fabric/decisions?limit=20 -H "X-API-Key: $CAIN_API_KEY" curl -s https://cainstudio.online/fabric/decisions/<decision_id>/signature -H "X-API-Key: $CAIN_API_KEY"
The signature covers the decision id, tenant, time, verdict and every stage's verdict. It shows the record hasn't changed since it was signed. It is not a third-party notarisation.
The same thing in Python, standard library only#
import json, os, urllib.request
def cain_allows(tool, payload, agent_id="my-agent"):
req = urllib.request.Request(
"https://cainstudio.online/fabric/decisions",
data=json.dumps({"path": f"/tools/{tool}", "payload": payload,
"agent_id": agent_id}).encode(),
headers={"X-API-Key": os.environ["CAIN_API_KEY"],
"content-type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=5) as r:
d = json.load(r)
# ALLOWED, ALLOWED_DEGRADED, ALLOWED_WITH_DENIALS (shadow mode) permit; nothing else does
return d.get("blocked") is False and str(d.get("verdict", "")).startswith("ALLOWED")
except Exception:
return False # unreachable or error: do not run the tool
if cain_allows("send_email", {"to": to, "subject": subject}):
send_email(to, subject, body)
Every endpoint you need to get started is on this page and in the Python SDK page. The customer API is published as OpenAPI 3.1 at /fabric/openapi.json -- import it into Postman, Insomnia or an SDK generator; every call takes your key in X-API-Key. (The full internal schema at /openapi.json is operator-only.)
*How the output above was captured:* the /fabric/try output came from the live site. The /fabric/decisions and /fabric/tool-rules output came from the production gateway code run locally with a test key. On the hosted service, responses also include a consensus stage, and ids and timestamps will differ.
The Python SDK#
The same calls, wrapped: a fail-closed decorator, a LangChain integration, human approval that can wait, and traces. No runtime dependencies.
# From the CAIN package index (PyPI publication is pending). Pins and upgrades work as usual: pip install --extra-index-url https://cainstudio.online/simple cainstudio # requirements.txt: --extra-index-url https://cainstudio.online/simple # cainstudio>=0.3 # Or the wheel directly: pip install https://cainstudio.online/cainstudio-0.3.0-py3-none-any.whl # LangChain extra: pip install "cainstudio[langchain] @ https://cainstudio.online/cainstudio-0.3.0-py3-none-any.whl"
The wheel's SHA-256 is published at https://cainstudio.online/cainstudio-0.3.0-py3-none-any.whl.sha256; verify it before installing. The SDK calls exactly the HTTP path above and has no runtime dependencies (the langchain extra adds langchain-core only).
1. See a decision, no key needed#
cainstudio try
2. Guard a tool#
import cainstudio # reads CAIN_API_KEY
@cainstudio.guard()
def send_email(to: str, subject: str, body: str):
... # unchanged
Not allowed, not executed: the decorator raises ActionBlocked, ApprovalRequired or CainUnavailable before the body runs. A timeout or an outage is never an allow.
3. LangChain / LangGraph#
from cainstudio.langchain import protect
tools = protect([search, send_email], agent_id="support-agent")
with cainstudio.run(): # one trace per agent run
agent.invoke({"messages": [...]})
4. See what happened#
cainstudio decisions cainstudio explain <decision-id> cainstudio approvals cainstudio approve <approval-id> --note "checked"
Or open the console at /dashboard: decisions, traces, the approval queue, tool rules and the kill switch.