Developers
API overview
A resource-oriented HTTP API covering the AI systems you connect, the policies that govern them and the record of what they did.
This page vs the reference
Here: the resource model and the four conventions you inherit. Read once.
Reference: every endpoint, every error, every SDK equivalent. Returned to.
The resource model
Six resources, and one of them is the reason for the others.
Resource-oriented HTTP with no surprises in the transport. What is worth understanding is which resource owns which idea — and in a couple of cases, which endpoint deliberately does not exist.
https://api.opsai.dev/v1
Authorization: Bearer $OPSAI_API_KEY
Content-Type: application/json- /actionsActions
- The unit of governance. Everything else exists to decide or explain one.
- An action is scoped to one proposed change with one subject. Not a session, not a request, not a conversation.
- /approvalsApprovals
- Held actions waiting on a person, with their deadline.
- There is no endpoint to extend a hold. A hold that can be extended indefinitely is a queue.
- /boundsBounds
- The rules. Versioned, owned, replayable.
- No PATCH. A version is published; the previous one stays readable so past decisions remain explainable.
- /agentsAgents
- The inventory of AI systems under governance.
- `owner` is required and cannot be a team. Registration describes something that already exists.
- /connectionsConnections
- Credential-holding relationships with systems of record.
- `credential.value` is absent from every response at every scope. Not redacted — never returned.
- /evidenceEvidence
- The sealed, digest-chained record of every decision.
- Read-only by construction. No endpoint writes or amends one, which is what makes "sealed" a property.
A missing endpoint is a design statement.
There is no way to extend a hold, edit a bound in place, or read a stored credential. Each of those absences is what makes a claim elsewhere on this site true rather than aspirational, which is why they are listed here alongside the things you can do.
Four decisions you inherit
These are the parts you would otherwise discover in production.
Idempotency and error shape are the two that prevent real incidents. The other two are ordinary, and are here so you do not have to guess.
Idempotency
Every mutating endpoint accepts idempotency_key. A repeat with the same key returns the original decision rather than producing a second one.
# First call: evaluates, decides, files a record.
curl -X POST .../actions/evaluate -d '{ …, "idempotency_key": "ACT-7512" }'
# → 200 { "id": "ACT-7512", "decision": "authorized" }
# Retry after a timeout: returns the SAME decision. No second record.
curl -X POST .../actions/evaluate -d '{ …, "idempotency_key": "ACT-7512" }'
# → 200 { "id": "ACT-7512", "decision": "authorized", "replayed": true }Error shape
Every error carries a code as well as a status, because a status alone rarely tells you what to do. A policy refusal goes further: it names the bound, the check and the values observed.
{
"error": {
"code": "bound_violation",
"bound": "refund.ceiling",
"bound_version": "v7",
"check": "amount <= ceiling",
"observed": { "amount": 41200, "ceiling": 25000 },
"evidence": "EV-7601"
}
}Note the evidence reference on a failure. A refusal produces a record as complete as an authorization, which is what makes it usable in a review rather than just a log line. The full error table.
Pagination
Cursor-based. next_cursor is present until it is null. Do not construct or parse a cursor — it is opaque and its format is not part of the contract.
let cursor: string | null = null;
do {
const page = await opsai.actions.list({ decision: 'denied', cursor });
for (const action of page.data) review(action);
cursor = page.next_cursor;
} while (cursor);Versioning
The version is in the path. Additive changes — a new field, a new endpoint, a new enum member on a response — ship within a version. Anything that could break a caller gets a new one.
Treat unknown fields as ignorable and unknown enum values as “something new” rather than as an error. A client that throws on an unrecognised event type will break the first time an event is added.
Operational notes
Two things about limits that affect how you design around them.
No numbers here, because a published rate limit that later changes is worse than a documented mechanism. What is stable is how the limits are scoped and what happens when you reach one.
- Limits are per tenant, not per key
- Issuing more keys does not buy more throughput. Keys exist to bound capability, not to shard quota — which means a runaway integration affects the organization rather than only itself, and that is deliberate.
- 429 carries Retry-After
- Honour it rather than backing off on your own schedule. Evaluation is in the path of a user-visible action, so a client that retries aggressively makes its own latency worse.
Where to go
The reference has the endpoints. This page had the decisions.
If you have read this far you know the resource model and the four conventions. What remains is the endpoint list, and the errors that actually come up.