> ## Documentation Index
> Fetch the complete documentation index at: https://puzzlet-9ba7bb98.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference

> Programmatic access to the AgentMark Gateway API.

The AgentMark Gateway API provides direct HTTP access to trace ingestion, scoring, and template retrieval.

## Base URL

<Tabs>
  <Tab title="Cloud">
    ```text theme={null}
    https://api.agentmark.co
    ```
  </Tab>

  <Tab title="Local">
    ```text theme={null}
    http://localhost:9418
    ```
  </Tab>
</Tabs>

The local dev server (`agentmark dev`) and Cloud implement the same `/v1/*` endpoints. What differs is which handlers run where:

* **Both surfaces:** `/v1/traces` (ingest + read), `/v1/sessions`, `/v1/spans`, `/v1/scores` (full CRUD + batch), `/v1/capabilities`, `/v1/pricing`, `/v1/filter-schema`, and the root `/health` liveness check.
* **Cloud-only** (local returns `404` or a `501 not_available_locally` stub): `/v1/metrics`, `/v1/scores/aggregations`, the structured-search endpoints (`/v1/traces/search`, `/v1/spans/search`, `/v1/scores/search`), and the dependency health endpoints (`/v1/health/*`).

Call `GET /v1/capabilities` to probe which features a server supports at runtime.

Every endpoint carries the `/v1/` prefix except the root health check.

## Available endpoints

The generated pages in this sidebar, built from the OpenAPI spec, are the authoritative per-endpoint reference for parameters, schemas, and responses; this table is a map. The **Where** column shows which environments implement each route. "Cloud + Local" means the same handler semantics on both; "Cloud only" / "Local only" mean the other side returns `501` (with a `not_available_on_cloud` / `not_available_locally` error code) or `404`.

| Endpoint                              | Method                     | Where                          | Description                                                                                                                                                                           |
| ------------------------------------- | -------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/v1/traces`                          | `POST`                     | Cloud + Local                  | Ingest trace data in OTLP format (supports gzip)                                                                                                                                      |
| `/v1/traces`                          | `GET`                      | Cloud + Local                  | List traces with filtering. Supports `dataset_run_id` and `session_id` for scoped listings                                                                                            |
| `/v1/traces/search`                   | `POST`                     | Cloud only                     | Search traces with structured JSON filters (AND of predicates + OR-groups)                                                                                                            |
| `/v1/traces/{traceId}`                | `GET`                      | Cloud + Local                  | Get a single trace with all its spans                                                                                                                                                 |
| `/v1/traces/{traceId}/spans`          | `GET`                      | Cloud + Local                  | List every span belonging to a trace                                                                                                                                                  |
| `/v1/traces/{traceId}/spans/{spanId}` | `GET`                      | Cloud + Local                  | Get full input/output payload for a single span                                                                                                                                       |
| `/v1/traces/{traceId}/graph`          | `GET`                      | Cloud + Local · **deprecated** | Use `/v1/traces/{traceId}?fields=graph` instead. Removal scheduled for October 21, 2026                                                                                               |
| `/v1/sessions`                        | `GET`                      | Cloud + Local                  | List sessions. `?search=` matches session ID or name (case-insensitive substring)                                                                                                     |
| `/v1/sessions/{sessionId}/traces`     | `GET`                      | Cloud + Local · **deprecated** | Use `/v1/traces?session_id={sessionId}` instead. Removal scheduled for October 21, 2026                                                                                               |
| `/v1/spans`                           | `GET`                      | Cloud + Local                  | Query spans across traces with filtering by type, status, model, and duration                                                                                                         |
| `/v1/spans/search`                    | `POST`                     | Cloud only                     | Search spans with structured JSON filters (AND of predicates + OR-groups)                                                                                                             |
| `/v1/spans/{spanId}`                  | `GET`                      | Cloud only                     | Get a single span by its globally unique ID, including metadata and input/output payload, without needing the trace ID                                                                |
| `/v1/scores`                          | `POST`                     | Cloud + Local                  | Create a score record for a span or trace                                                                                                                                             |
| `/v1/scores/batch`                    | `POST`                     | Cloud + Local                  | Create up to 1000 scores in one request (per-item results, 207-style)                                                                                                                 |
| `/v1/scores`                          | `GET`                      | Cloud + Local                  | List scores for a specific span or trace                                                                                                                                              |
| `/v1/scores/{scoreId}`                | `GET`                      | Cloud + Local                  | Get a single score by ID                                                                                                                                                              |
| `/v1/scores/{scoreId}`                | `DELETE`                   | Cloud + Local                  | Delete a score record                                                                                                                                                                 |
| `/v1/scores/names`                    | `GET`                      | Cloud + Local                  | List distinct score names (for UI filters)                                                                                                                                            |
| `/v1/scores/aggregations`             | `GET`                      | Cloud only                     | Aggregated score statistics grouped by name                                                                                                                                           |
| `/v1/scores/search`                   | `POST`                     | Cloud only                     | Search scores with structured JSON filters over name, score, source, user, resource, and time                                                                                         |
| `/v1/filter-schema`                   | `GET`                      | Cloud + Local                  | Machine-readable filter schema: fields, operators, and limits per searchable resource                                                                                                 |
| `/v1/score-configs`                   | `GET`                      | Cloud + Local                  | List score configurations (reusable score schemas)                                                                                                                                    |
| `/v1/score-configs/{name}`            | `GET`                      | Cloud + Local                  | Get a single score configuration by name                                                                                                                                              |
| `/v1/metrics`                         | `GET`                      | Cloud only                     | Aggregated analytics (trace volume, latency, cost, tokens, error rates)                                                                                                               |
| `/v1/config`                          | `GET`                      | Cloud + Local                  | Retrieve the synced `agentmark.json` project configuration plus the current commit SHA                                                                                                |
| `/v1/capabilities`                    | `GET`                      | Cloud + Local                  | Check which features the server supports (no auth required)                                                                                                                           |
| `/v1/pricing`                         | `GET`                      | Cloud + Local                  | Per-model LLM pricing data (no auth required)                                                                                                                                         |
| `/v1/api-keys`                        | `GET` · `POST`             | Cloud only                     | List API keys (metadata only, no plaintext) or mint a new key. The `POST` response returns the plaintext value of a newly created key exactly once, and it's unrecoverable afterward. |
| `/v1/api-keys/{apiKeyId}`             | `DELETE`                   | Cloud only                     | Revoke an API key. The gateway rejects revoked keys immediately.                                                                                                                      |
| `/v1/apps`                            | `GET` · `POST`             | Cloud only                     | List apps or create a new app                                                                                                                                                         |
| `/v1/apps/{appId}`                    | `GET` · `PATCH` · `DELETE` | Cloud only                     | Read, update, or delete an app                                                                                                                                                        |
| `/v1/apps/{appId}/git`                | `GET`                      | Cloud only                     | Get the app's git connection status                                                                                                                                                   |
| `/v1/apps/{appId}/git/connect`        | `POST`                     | Cloud only                     | Start a git connection (install) flow for the app                                                                                                                                     |
| `/v1/apps/{appId}/git/repositories`   | `GET`                      | Cloud only                     | List repositories available to the app's git connection                                                                                                                               |
| `/v1/apps/{appId}/git/branches`       | `GET`                      | Cloud only                     | List branches for the connected repository                                                                                                                                            |
| `/v1/apps/{appId}/git/link`           | `POST` · `DELETE`          | Cloud only                     | Link or unlink a repository/branch to the app                                                                                                                                         |
| `/v1/alerts`                          | `GET` · `POST`             | Cloud only                     | List alerts or create a new alert                                                                                                                                                     |
| `/v1/alerts/{alertId}`                | `GET` · `PUT` · `DELETE`   | Cloud only                     | Read, update, or delete an alert                                                                                                                                                      |
| `/v1/alerts/{alertId}/history`        | `GET`                      | Cloud only                     | List trigger history for an alert                                                                                                                                                     |
| `/v1/alerts/slack-channels`           | `GET`                      | Cloud only                     | List Slack channels available for alert notifications                                                                                                                                 |
| `/v1/deployments`                     | `GET`                      | Cloud only                     | List deployments                                                                                                                                                                      |
| `/v1/deployments/{deploymentId}`      | `GET`                      | Cloud only                     | Get a single deployment by ID                                                                                                                                                         |
| `/v1/environments`                    | `GET` · `POST`             | Cloud only                     | List environments or create a new environment                                                                                                                                         |
| `/v1/environments/{id}`               | `GET` · `DELETE`           | Cloud only                     | Get or delete an environment by ID                                                                                                                                                    |
| `/v1/environments/{id}/deployments`   | `GET`                      | Cloud only                     | List deployments for an environment                                                                                                                                                   |
| `/v1/environments/{id}/promote`       | `POST`                     | Cloud only                     | Promote a deployment into the environment                                                                                                                                             |
| `/v1/environments/{id}/rollback`      | `POST`                     | Cloud only                     | Roll the environment back to a previous deployment                                                                                                                                    |
| `/health`                             | `GET`                      | Cloud + Local                  | Root health check (no auth required)                                                                                                                                                  |
| `/v1/health/ingestion`                | `GET`                      | Cloud only                     | Ingestion pipeline health with dependency statuses                                                                                                                                    |
| `/v1/health/files`                    | `GET`                      | Cloud only                     | Files service health with dependency statuses                                                                                                                                         |

Use the sidebar to browse interactive documentation for each endpoint.

<Tip>
  Two programmatic surfaces, same OpenAPI spec under the hood:

  * **From shell / CI:** call the REST endpoints with `curl` and an `AGENTMARK_API_KEY` (or the session bearer from `~/.agentmark/auth.json` after `agentmark login`).
  * **From an IDE agent:** run the [`agentmark-mcp`](/coding-agents/gateway-mcp) MCP server. It fetches this spec at startup and exposes one MCP tool per operation (for example, `list_traces`, `create_app`, `start_app_git_connect`), so your Claude Code / Cursor / etc. agent can drive the gateway headlessly.
</Tip>

## Response format

All responses are JSON unless otherwise noted (for example, CSV exports). Error responses follow a consistent canonical envelope:

```json theme={null}
{
  "error": {
    "code": "string_snake_case",
    "message": "Description of what went wrong"
  }
}
```

The `error.code` field is the programmatic discriminator: use it to branch on specific error cases. The `error.message` field is the human-readable description to show to users. Additional context (for example, `retry_after_seconds`, `jobId`) appears as extra fields **directly inside `error`**, alongside `code` and `message`. The one exception is `400` validation errors, which nest per-field messages in an `error.details` map (see [Authentication](/api-reference/authentication#error-responses)):

```json theme={null}
{
  "error": {
    "code": "span_limit_exceeded",
    "message": "Monthly span limit of 20000 reached. Upgrade your plan to raise this limit.",
    "currentCount": 20000,
    "limit": 20000,
    "upgradeUrl": "https://app.agentmark.co/settings/billing"
  }
}
```

The shape matches Stripe, OpenAI, and Anthropic error conventions, so one parser works across all endpoints.

## Rate limiting

Requests are rate-limited per tenant. When you exceed your rate limit, the API returns a `429` status code.
Trace ingestion has additional monthly span and storage quotas depending on your plan.

See [Authentication](/api-reference/authentication) for details.

## Versioning

Every endpoint carries the `/v1/` prefix. Breaking changes ship under new version prefixes (`/v2/`, etc.) with a 90+ day deprecation window, so `/v1/` keeps working while you migrate.

See [API versioning & stability](/api-reference/versioning) for the full policy on what's breaking, what's additive, and how AgentMark announces deprecations.

## Why there is no `PATCH /v1/traces`

Traces are immutable in AgentMark. Once AgentMark stores a span, the row representing what happened during that execution becomes permanent. No endpoint mutates it.

Other observability platforms expose a "patch trace" endpoint that lets clients backfill metadata, attach a label, or correct a field after ingestion. AgentMark covers those workflows through two separate, append-only resources instead:

* **Scores** (`POST /v1/scores`, `POST /v1/scores/batch`): attach a graded value (numeric, categorical, or boolean) to a trace or span after the fact. AgentMark versions scores by `created_at`, and they never overwrite the underlying span.
* **Comments**: free-form human notes on a trace or span, stored alongside the trace as a separate resource.

The two resources above are the migration targets for any "patch trace" workflow you'd build on a competitor. This split is intentional: it keeps the audit trail clean (you can always tell what the model did versus what a reviewer added later) and lets retention, RBAC, and export rules apply differently to raw execution data than to human-attached metadata.

This is a permanent design choice, not a missing feature. `PATCH /v1/traces` won't ship in `/v1/`, `/v2/`, or any future version.

## The `filter` query grammar

`GET /v1/traces` and `GET /v1/spans` accept a `filter` query parameter, a human-readable string expression. One expression composes across both surfaces: write it once, reuse it for trace and span listings.

A filter is one or more clauses combined with `and`. A clause is a single predicate, or a parenthesized OR-group of predicates:

```text theme={null}
filter      = clause ("and" clause)*
clause      = predicate | "(" predicate ("or" predicate)* ")"
predicate   = field operator [value]
```

`or` is only valid inside parentheses and `and` only outside them, so there are no precedence rules to learn. Groups don't nest.

URL-encode the whole expression. For example, you send `filter=metadata.env = "prod" and status = ERROR` as:

```http theme={null}
GET /v1/traces?filter=metadata.env%20%3D%20%22prod%22%20and%20status%20%3D%20ERROR
```

### Operators

| Operator                  | Meaning                         | Applies to                                                                |
| ------------------------- | ------------------------------- | ------------------------------------------------------------------------- |
| `=` `!=`                  | equals / not equals             | all fields                                                                |
| `>` `>=` `<` `<=`         | numeric comparison              | numeric fields                                                            |
| `contains` `not contains` | substring match                 | string + `metadata.*`; `contains` also applies to `tags` (tag membership) |
| `starts with` `ends with` | prefix / suffix match           | string + `metadata.*`                                                     |
| `exists` `does not exist` | key present / absent (no value) | `metadata.*`                                                              |

Quote any value containing spaces or special characters (`"..."` or `'...'`). Bare values are fine for simple tokens (`status = ERROR`, `cost > 0.01`).

### Fields

| Field                                                                                                    | Kind                  | Operators                                                    |
| -------------------------------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------ |
| `model`, `user_id`, `session_id`, `trace_id`, `prompt_name`, `input`, `output`, `props`, `semantic_kind` | string                | `=` `!=` `contains` `not contains` `starts with` `ends with` |
| `latency_ms`, `cost`, `prompt_tokens`, `completion_tokens`                                               | numeric               | `=` `!=` `>` `>=` `<` `<=`                                   |
| `status`                                                                                                 | enum (`OK` / `ERROR`) | `=`                                                          |
| `tags`                                                                                                   | array                 | `=` `!=` `contains`                                          |
| `metadata.<key>`                                                                                         | custom metadata       | all string operators + `exists` / `does not exist`           |
| `score__<name>`                                                                                          | evaluation score      | numeric operators                                            |

`<key>` matches `[a-zA-Z_][a-zA-Z0-9_]{0,63}`. Up to 20 predicates per request, counting every predicate inside OR-groups.

### Examples

```http theme={null}
GET /v1/traces?filter=metadata.debug_screenshot_url exists
GET /v1/traces?filter=metadata.debug_screenshot_url = "https://example.com/x"
GET /v1/traces?filter=cost > 0.01 and latency_ms <= 2000
GET /v1/spans?filter=model starts with "gpt-4" and metadata.env = "prod"
GET /v1/traces?filter=(model = "gpt-4o" or model = "o3") and status = ERROR
```

A malformed or unsupported filter returns **`400`** with an `invalid_filter` code and a message describing the problem. It's never silently ignored.

## Structured JSON filters (search endpoints)

`POST /v1/traces/search`, `POST /v1/spans/search`, and `POST /v1/scores/search` accept the same filters as a JSON request body, the form to use when building filters programmatically (SDKs, agents) instead of string-assembling DSL expressions:

```json theme={null}
{
  "filters": [
    { "field": "status", "operator": "equals", "value": "ERROR" },
    { "or": [
      { "field": "model", "operator": "equals", "value": "gpt-4o" },
      { "field": "model", "operator": "equals", "value": "o3" }
    ]},
    { "field": "latency_ms", "operator": "between", "value": [1000, 5000] }
  ],
  "limit": 50,
  "sort_by": "start_time",
  "sort_order": "desc"
}
```

Semantics mirror the string DSL exactly: the list is an AND of clauses, a clause is a predicate or a one-level OR-group, and both forms compile to the same query. Operator names are the canonical camelCase set (`equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `endsWith`, `gt`, `gte`, `lt`, `lte`, `exists`, `doesNotExist`). The JSON form additionally supports membership and range operators that have no DSL syntax:

| Operator  | Value               | Meaning                                                               |
| --------- | ------------------- | --------------------------------------------------------------------- |
| `in`      | array (≤ 50 values) | field matches any listed value (`tags`: trace carries any listed tag) |
| `notIn`   | array (≤ 50 values) | field matches none of the listed values                               |
| `between` | `[min, max]`        | inclusive range (numeric fields; `created_at` on scores)              |

Trace/span search uses the field set above; score search filters on `name`, `score`, `source`, `user_id`, `resource_id`, `label`, and `created_at`. **`GET /v1/filter-schema`** returns the full machine-readable schema (fields, operators, and limits per resource), so a client (or an agent via MCP) can construct valid filters without trial-and-error.

Search endpoints apply guardrails the frozen GET contracts don't: requests default to the **last 7 days** when `start_date` is unset, the maximum window is **90 days**, and the routes are rate-limited per tenant.

The search endpoints are Cloud-only (the local dev server answers them with `501 not_available_locally`), but the gateway serves `GET /v1/filter-schema` on both surfaces from the same generated contract.

<Note>
  The `filter` string grammar (GET endpoints) and the JSON form (POST search endpoints) are the same filter language in two encodings; use whichever fits the call site. JSON filters are only accepted in POST request bodies, never as a GET query parameter.
</Note>

## Reading custom metadata

The read endpoints return custom metadata you attach at ingestion (OTLP `agentmark.metadata.*` attributes, or the SDK's `metadata` option):

* `GET /v1/traces/{traceId}`: a trace-level `metadata` object (the root span's metadata) and a per-span `metadata` object on each entry of `spans`.
* `GET /v1/traces/{traceId}/spans/{spanId}`: a `metadata` object alongside the span's `input` / `output`.
* `GET /v1/spans` and `GET /v1/spans/{spanId}`: `metadata` on each span.

The endpoints return metadata as a flat string→string object. The public `metadata` object excludes reserved internal namespaces (such as `graph.node.*`, which `GET /v1/traces/{traceId}?fields=graph` surfaces separately).

<Note>
  Reading traces requires an API key with the `trace.read` permission (and `span.read` / `session.read` for the span/session endpoints). Write-only SDK keys (`trace.write` + `score.write`) can ingest traces and post scores but **can't** read traces back. A programmatic consumer (CI pipeline, agent) that needs readback must use a key minted with the read permissions (the **read-only** or **full-access** preset, or check `trace.read` in the key's permission picker). This is least-privilege by design: it keeps a leaked write-only SDK key from exfiltrating trace contents.
</Note>

`/v1/scores` accepts `session_id` (scope scores to a session), alongside `start_date`, `end_date`, and `source`.
