# 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](/v1/docs)** (OpenAPI spec at [`/v1/openapi.json`](/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:

```bash
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_id` when 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/runs` with the PR number from your pipeline — you decide exactly when reviews happen.
- **File findings from your own tools.** `POST /v1/findings` records 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:write` can create projects and attach repositories from your connected GitHub installations, and `projects:read` lists 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](/docs/assistant.md) uses.

### Claude Code

```bash
claude mcp add --transport http ozone https://ozone.cecuro.ai/v1/mcp \
  --header "Authorization: Bearer oz_live_your_key_here"
```

### Codex

Set `OZONE_API_KEY` in your environment, then run:

```bash
codex mcp add ozone --url https://ozone.cecuro.ai/v1/mcp \
  --bearer-token-env-var OZONE_API_KEY
```

### OpenCode

Set `OZONE_API_KEY` in your environment, then add this to `opencode.json`:

```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}" }
    }
  }
}
```

### Other MCP clients

Connect with Streamable HTTP at `https://ozone.cecuro.ai/v1/mcp` and send `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.

```bash
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.
