# Agent API (OpenAI-compatible)

> Each pillar agent as an OpenAI-compatible chat-completions endpoint: live and sandbox base URLs, curl and Python examples, and how one completion behaves.

_Reference · Updated 2026-10-18 · Describes release 0.1.164_

The agent API takes a [personal access token](/help/personal-access-tokens.md) with `write` scope and a role that holds `incidents.act`; the rules every doorway shares are in [Concepts you need first](/help/rest-api.md#concepts-you-need-first).

## Agent API (OpenAI-compatible) {#agent-api-openai-compatible}

### What it is

Each enabled pillar agent is exposed as an OpenAI-compatible chat-completions endpoint. **One completion equals one full agentic run**: an ephemeral investigation with the pillar's persona, its datasource tools, the context budget, the cost cap and the structured wrap-up. It is not a chat proxy and not a single model call.

| Property | Value |
|---|---|
| Base URL | `https://aiops.example.com/api/aiops/{tenant_id}/agents/{pillar}/v1` |
| Sandbox base URL | `https://aiops.example.com/api/aiops/{tenant_id}/agents/{pillar}/sandbox/v1` |
| Completions | `POST {base}/chat/completions` |
| Model list | `GET https://aiops.example.com/api/aiops/{tenant_id}/agents/v1/models` |
| Pillars | `incident`, `compliance`, `change`, `secops`, `finops`, `improvement`, `reporting` |
| Permission | `incidents.act` (on both endpoints) |

The **Agent API — OpenAI-compatible** card on the **Alert intake & tokens** page renders every URL with your real tenant id already filled in. The card is hidden if you lack `incidents.act`. It keeps the live endpoints and the note on skipped writes in view; the sandbox URLs and the code samples sit in two collapsed sections, **Sandbox endpoints — same agent, no estate** and **curl and Python examples**.

### Two flavours: live estate and sandbox

Each pillar is served at two base URLs. Same protocol, same permission, same token — the difference is what the agent may reach.

| | `…/{pillar}/v1` | `…/{pillar}/sandbox/v1` |
| --- | --- | --- |
| Datasources, MCP servers, lakehouse | yes | **no** |
| Knowledge base and skill pack | yes | yes |
| Web search / fetch, `http_get` | yes | **no** |
| Model id in the response | `acme/incident` | `acme/incident-sandbox` |

`GET /agents/v1/models` lists both ids for every enabled pillar, so an SDK that enumerates models sees the pair.

Reach for the sandbox endpoint when you are evaluating the agent rather than the estate — the answer stops moving every time a deploy lands — and when you want to try the API before any datasource is connected. Reach for the live endpoint when you want the real serving path.

The choice is a **route**, never a request parameter. The reach an API key gets is visible in the URL the holder was handed, rather than something the holder can toggle.

### curl

```bash
curl https://aiops.example.com/api/aiops/{tenant_id}/agents/incident/v1/chat/completions \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"model":"agent","messages":[{"role":"user","content":"Investigate: checkout p99 latency spiked"}]}'
```

### Python (openai SDK)

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://aiops.example.com/api/aiops/{tenant_id}/agents/incident/v1",
    api_key="ntp_your_token_here",
)
r = client.chat.completions.create(
    model="agent",  # informational; the pillar in the URL selects the agent
    messages=[{"role": "user", "content": "Investigate: checkout p99 latency spiked"}],
)
print(r.choices[0].message.content)
```

### How the endpoint behaves

- `model` in the body is **ignored entirely**. The pillar in the URL selects the agent; the actual model comes from tier routing. `GET /agents/v1/models` returns ids shaped `acme/incident` for every *enabled* pillar. A pillar segment that is not a known pillar name is a 404; a pillar that is disabled or unconfigured is a 409. A deployment with no agent engine wired returns 503.
- Only `system` and `user` message content is used, joined in order. Assistant history is dropped, because each completion is one investigation and not a resumed conversation. An empty prompt returns 422.
- `temperature` and other harness parameters are accepted and ignored.
- The response is a normal `chat.completion` object plus three extension fields that OpenAI clients ignore: `aiops_investigation_id`, `aiops_status`, `aiops_cost_usd`. `usage` carries real prompt and completion token counts.
- `stream=true` returns a protocol-valid SSE stream, but the loop runs to completion first and the answer arrives as a **single delta chunk**, then a `finish_reason: "stop"` chunk carrying `usage`, then `[DONE]`. It is compatible, not incremental.
- **A completion does not block on a human — with the shipped platform setting.** `AIOPS_DIAGNOSIS_WRITES` defaults to `deny`: a write the agent proposes mid-diagnosis is refused, fed back to the agent as a "do not retry, put it in your remediation plan" tool result, and the run continues. Nothing is executed. The change is then approved in the portal, where the full tool trace is visible. If your platform operator switches that setting to `pause`, the completion instead returns a short "paused, decide it in the portal" answer with `aiops_status: paused_approval` and the investigation id — worth asserting on in a harness so a fleet-wide posture change does not silently break your suite.

### Point your own test suite at it

Requires `incidents.act` and a token whose role carries it.

1. Create a dedicated token named for the harness, for example `eval-harness`, and hold it in the harness secret store. Set its scope to `write`; REST and MCP both enforce token scope in addition to your workspace permissions.
2. Point `base_url` at the pillar you are grading and pass the token as the SDK's `api_key`; the SDK turns it into the bearer header.
3. Run your suite. Each case costs one full agentic run, so plan concurrency against your per-run cost cap rather than against request rate.
4. Read `aiops_cost_usd` and `usage` per case.
5. Correlate failures back to the portal with `aiops_investigation_id`.

You should see one investigation in the portal per case, each reachable by that id.

When your gateway's model ids are absent from the pricing map, `aiops_cost_usd` reads `0` while token counts stay accurate. Grade on tokens in that case; what a zero cost figure means for the cost cap is covered in [Guardrails and cost limits](/help/guardrails-and-cost-limits.md#the-cost-cap-and-what-happens-at-70).

The platform's own benchmark runner drives this exact code path, so harness scores measure the real serving surface rather than a test double. Benchmarks are covered in [Benchmarks and scores](/help/benchmarks-and-scores.md).

## Troubleshooting {#troubleshooting}

| Symptom | What it means | What to do |
|---|---|---|
| `503 agent engine not wired` on the agent API | the process started without the agent runtime — a deployment fault, not a missing model | ask your operator |
| `aiops_cost_usd` is `0` but tokens are non-zero | your gateway's model ids are not in the pricing map | grade on `usage` tokens instead |

## Next steps {#next-steps}

- [Benchmarks and scores](/help/benchmarks-and-scores.md) — the platform's own back-tests run over this path.
- [REST API](/help/rest-api.md) — read the investigation each completion opened.
- [MCP server](/help/mcp-server.md) — the same agents from an MCP client.
