API & MCP
Operational dashboard capabilities are available over a REST API, and the same capabilities are exposed as an MCP server so AI tools — Claude Code, IDE agents, your own — can operate Ozone directly. Credential management and other sensitive account surfaces remain browser-session-only; GET /v1/me answers API keys with a service identity (name, scopes, project confinement) so an agent can always orient itself.
The complete endpoint-by-endpoint documentation lives in the API reference (OpenAPI spec at /v1/openapi.json). This page covers how to connect.
API keys#
Admins create keys in Settings → API keys. A key is shown once, looks like oz_live_…, and is an organization service identity. Reads and writes are granted per resource — projects, runs, findings, knowledge, schedules, activity, usage, billing, feedback, and assistant access — so a key can be exactly its integration. New keys default to operational reads with no writes; events:read, which reads across every other resource, is granted only when requested. Admins can edit scopes or revoke keys from the same screen.
Keys authenticate with a standard bearer header:
curl https://ozone.cecuro.ai/v1/runs \
-H "Authorization: Bearer oz_live_your_key_here"
Keys with assistant:read or assistant:write access only conversations owned by that key. They never inherit the creator’s private chats and cannot access another key’s conversations. API-key lifecycle, billing checkout, and connecting GitHub installations remain session-only; GET /v1/me works for keys and returns the key’s own service identity, never a person’s.
Project-scoped keys#
A key can be confined to one project when it's created — the right shape for a CI pipeline, a per-team bot, or an MCP agent that should only ever see its own project. A project-scoped key:
- lists and reads only that project's runs, findings, knowledge, and schedules — resources in other projects don't exist for it (404);
- can omit
project_idwhen starting runs and creating schedules — its project is implied; - is refused (
403 project_key_forbidden) by org-wide endpoints — billing, usage, the assistant — and by writes that would reach beyond its project: deleting projects, re-tagging knowledge, or editing a knowledge document shared with other projects (shared documents are readable but org-wide keys edit them). The dashboard overview still answers, scoped to the key's project.
The confinement is fixed for the key's lifetime; to widen access, mint a new key. If the project is deleted, the key stops working with it.
Common automations#
- Drive reviews from your own CI. Set a repository's trigger mode to Off and
POST /v1/runswith the PR number from your pipeline — you decide exactly when reviews happen. - File findings from your own tools.
POST /v1/findingsrecords a finding from an external scanner, importer, or agent; it joins triage and the fix flow like any review finding, badged with its API provenance. - Export findings into your issue tracker or SIEM from
GET /v1/findings, filtered by project, status and severity, each row naming its repository. It's the same endpoint the dashboard's JSON and PDF downloads are built from. - Sync knowledge from your internal docs with the knowledge endpoints.
- Set up projects programmatically. Org-wide keys with
projects:writecan create projects and attach repositories from your connected GitHub installations, andprojects:readlists the connectable repos — connecting the GitHub App itself stays in the dashboard.
Connect your agent#
Ozone's MCP server lives at POST /v1/mcp (streamable HTTP) and exposes the platform as tools — ozone_list_findings, ozone_trigger_run, ozone_get_run, ozone_create_schedule, and the rest of the catalogue. It's the same capability surface the built-in assistant uses.
Run this once. Claude Code stores the server in your MCP settings.
claude mcp add --transport http ozone https://ozone.cecuro.ai/v1/mcp \
--header "Authorization: Bearer oz_live_your_key_here"Set OZONE_API_KEY in your environment, then run this once. Codex shares the connection across its desktop app, CLI, and IDE extension.
codex mcp add ozone --url https://ozone.cecuro.ai/v1/mcp \
--bearer-token-env-var OZONE_API_KEYSet OZONE_API_KEY in your environment, then add this to opencode.json.
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ozone": {
"type": "remote",
"url": "https://ozone.cecuro.ai/v1/mcp",
"oauth": false,
"headers": {
"Authorization": "Bearer {env:OZONE_API_KEY}"
}
}
}
}Use Streamable HTTP and send the API key as a bearer token.
- Server URL
https://ozone.cecuro.ai/v1/mcp- Header
Authorization: Bearer oz_live_your_key_here
Any MCP client that speaks streamable HTTP works the same way: point it at /v1/mcp with the bearer header. Each tool requires its matching grant — reading tools the resource's :read scope, mutating tools its :write capability — and a refused call comes back as a tool error the agent can read and explain. Project-scoped keys work over MCP too: every tool call is confined to the key's project.
The activity ledger#
Every change Ozone records — findings reported and triaged, runs started and finished, projects and repositories connected, schedules and knowledge edited, API keys minted and revoked, budgets changed — is written to one append-only ledger, along with the actor behind it. GET /v1/events reads it, and ozone_list_events exposes the same thing over MCP. It needs its own events:read grant, which is deliberately not part of a new key's defaults.
Each event carries a seq: an integer assigned in the order changes commit. That makes syncing a matter of remembering one number.
curl "https://ozone.cecuro.ai/v1/events?after=4821" \
-H "Authorization: Bearer oz_live_your_key_here"
You get everything that happened after seq 4821, oldest first, and the response's next is what you pass as after next time. Nothing is repeated and nothing is skipped, because an event is written in the same database transaction as the change it describes — the ledger cannot claim something that did not happen, and a change cannot quietly fail to appear. Omit after to read newest-first instead, paging back with before.
Filter with type for one kind of change (run.completed, or the wildcard finding.*), project_id for one project, or entity_id for one thing's entire history. Prose stays on the resource that owns it: a status change made with a note carries comment_id, not the note's text.
Callers who can only see some of an organization's projects — restricted members, project-confined keys — receive only those projects' events, and never the organization-level ones.
Webhooks the other way#
Ozone consumes GitHub webhooks through its GitHub App — you don't need to configure any. Outbound webhooks (Ozone calling you) aren't available yet. Until they are, polling GET /v1/events?after=<seq> is the intended way to follow state: one request returns every change across every resource, so it replaces polling GET /v1/runs and GET /v1/findings separately and re-diffing them yourself.