Reading your data: the API and the MCP server

Everything the console shows you, your own scripts can read. A read API token is the credential for that: it belongs to your organization rather than to a person, it reads and never writes, and it is the same credential an AI assistant uses through the MCP server at the end of this page.

The examples below use https://api.nexovue.com. Use the address your own console is served from; in local development the API is http://localhost:18000.

Create a token

Console → Settings → API tokens → Create. You choose three things:

NameWhat uses it: Nightly report, CI. It is what you revoke by, so five tokens called "token" is a bad afternoon.
ReadsOne project, or every project. A token scoped to one project asking about another gets exactly the same answer as a project that does not exist.
Expires in30 days, 90 days, or a year. Every token ends; you choose when.

The secret is shown once, at creation. We store a SHA-256 hash of it and never the token, so nobody — including us — can read it back. If you lose it, revoke that token and create another.

Tokens are owner-and-admin work to create and revoke; anyone signed in can see the list, because the list holds no secrets.

A token is not an ingest key. Ingest keys (nvk_live_…) write telemetry and read nothing; API tokens (nvt_live_…) read and write nothing. Each is refused by the other's door, and says so.

Authenticate

curl -H "Authorization: Bearer $NEXOVUE_TOKEN" \
  "https://api.nexovue.com/api/v1/projects"

Every endpoint below takes that header and nothing else. A revoked token is refused on its very next request; an expired one is refused with the date it expired on.

The read surface

This is the whole of it. These endpoints are versioned under /api/v1 and are what a token may reach; anything else answers 403 — API tokens are read-only and reach only the documented read API.

EndpointWhat it answers
GET /projectsThe projects this token may read, with the ids the rest take
GET /servicesEvery service in a window, worst first
GET /services/{name}One service's health in a window
GET /tracesTraces matching filters, newest first (paged)
GET /traces/{trace_id}One trace and its spans
GET /logs/searchLog records matching a phrase (paged)
GET /metricsThe metric catalogue
GET /metrics/{name}/histogramOne metric's histogram over a window
GET /error-groupsDistinct errors in a window, most frequent first
GET /error-groups/{group_id}One error group and sample occurrences
GET /alerts/incidentsOpen incidents, then the last thirty days
GET /alerts/incidents/{incident_id}One incident and who was told
GET /checksSynthetic checks and their confirmed state
GET /checks/{check_id}One check
GET /synthetics/checksCheck results in a window
GET /synthetics/checks/{check_id}/runsOne check's recent runs
GET /deploysRecent deploys, newest first
GET /llm/callsEvery model call reported in a window, grouped by service, span and model

Most take project_id as a query parameter, and a window as from and to (ISO-8601). Leaving the window out reads the last hour.

Machine-readable. The same list is published as OpenAPI 3.1 at GET /api/v1/openapi.read.json — no credential needed to fetch it — so you can generate a typed client rather than writing calls by hand:

curl -s https://api.nexovue.com/api/v1/openapi.read.json -o nexovue.json
npx @openapitools/openapi-generator-cli generate \
  -i nexovue.json -g typescript-fetch -o ./nexovue-client

It describes this surface and nothing else, declares the bearer token every endpoint needs, and is generated from the routes themselves — so it cannot drift from what a token can actually reach.

Paging

Two endpoints return a cursor, because their lists are long by nature:

curl -H "Authorization: Bearer $NEXOVUE_TOKEN" \
  "https://api.nexovue.com/api/v1/traces?project_id=$PROJECT&limit=100"
# → { "traces": [...], "next_cursor": "MTc2..." }

Pass it back as cursor to get the next page. A page without next_cursor is the last one. Every row appears on exactly one page — the cursor is a position, not an offset, so rows arriving while you page do not shift anything.

The other lists are capped rather than paged, and each says so: a response with "truncated": true means the cap bit and there is more. Narrow by service, by time, or with the endpoint's own limit.

Rate limits

A token gets 300 requests a minute and 8 in flight at once. That is its own allowance: two tokens behind one CI runner do not spend each other's, and a token never spends your colleagues' console budget.

Every response carries the limit, and a refusal tells you when to come back:

RateLimit-Policy: "token";q=300;w=60
RateLimit: "token";r=284;t=41
Retry-After: 41          # on a 429 only

Errors

Failures are RFC 9457 problem documents:

{
  "type": "https://nexovue.com/problems/not-found",
  "title": "Not found",
  "status": 404,
  "detail": "No such project.",
  "instance": "urn:nexovue:request:5f1c…"
}
StatusWhat it means
401The token is unknown, revoked, or expired — the detail says which
403A real token pointed at something outside the read surface
404The project, trace or incident does not exist or is outside this token's scope
422A parameter is missing or malformed; the detail names it
429Over the rate limit; Retry-After says when

Quote instance when you ask us about a request: it is how we find it.

A CI example

Fail a deploy when the error rate of a service is above a threshold over the last fifteen minutes. It needs curl and jq, and nothing else.

#!/usr/bin/env bash
# Fails when SERVICE's error rate over the last 15 minutes exceeds THRESHOLD.
set -euo pipefail

: "${NEXOVUE_TOKEN:?set NEXOVUE_TOKEN}"
: "${NEXOVUE_PROJECT:?set NEXOVUE_PROJECT}"
API="${NEXOVUE_API:-https://api.nexovue.com}"
SERVICE="${SERVICE:-checkout}"
THRESHOLD="${THRESHOLD:-0.02}"

from=$(date -u -v-15M +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
  || date -u -d '15 minutes ago' +%Y-%m-%dT%H:%M:%SZ)

rate=$(curl -sS --fail-with-body \
  -H "Authorization: Bearer $NEXOVUE_TOKEN" \
  --get "$API/api/v1/services/$SERVICE" \
  --data-urlencode "project_id=$NEXOVUE_PROJECT" \
  --data-urlencode "from=$from" \
  | jq -r '.service.error_rate // 0')

echo "$SERVICE error rate: $rate (threshold $THRESHOLD)"
awk -v rate="$rate" -v limit="$THRESHOLD" \
  'BEGIN { exit (rate > limit) ? 1 : 0 }'

Exit 0 means the service is within the threshold; exit 1 fails the pipeline.

Connect an AI assistant

MCP is an open protocol, and this is an ordinary remote MCP server. Any client that can reach one with a custom header works — the two things every client needs are the same:

Endpoint:  https://api.nexovue.com/api/mcp/
Header:    Authorization: Bearer nvt_live_…

Claude Code

claude mcp add --transport http nexovue https://api.nexovue.com/api/mcp/ \
  --header "Authorization: Bearer $NEXOVUE_TOKEN"

Cursor — ~/.cursor/mcp.json, or .cursor/mcp.json for one project

{
  "mcpServers": {
    "nexovue": {
      "url": "https://api.nexovue.com/api/mcp/",
      "headers": { "Authorization": "Bearer nvt_live_…" }
    }
  }
}

VS Code (GitHub Copilot) — .vscode/mcp.json. The key is servers, not mcpServers, and tools only appear in agent mode — in ask mode the server looks connected and offers nothing.

{
  "servers": {
    "nexovue": {
      "type": "http",
      "url": "https://api.nexovue.com/api/mcp/",
      "headers": { "Authorization": "Bearer nvt_live_…" }
    }
  }
}

Codex — ~/.codex/config.toml. The CLI, the IDE extension and the ChatGPT desktop app share this file.

[mcp_servers.nexovue]
url = "https://api.nexovue.com/api/mcp/"
http_headers = { "Authorization" = "Bearer nvt_live_…" }

Keeping the token out of the file is better, and Codex supports it directly — bearer_token_env_var names an environment variable and Codex sends it as the bearer:

[mcp_servers.nexovue]
url = "https://api.nexovue.com/api/mcp/"
bearer_token_env_var = "NEXOVUE_TOKEN"

codex mcp add nexovue --url … --bearer-token-env-var NEXOVUE_TOKEN writes that second form for you. There is no --header flag, so a literal header is hand-edited. Codex cloud tasks are not covered — MCP there is undocumented.

Windsurf — ~/.codeium/windsurf/mcp_config.json, with serverUrl:

{
  "mcpServers": {
    "nexovue": {
      "serverUrl": "https://api.nexovue.com/api/mcp/",
      "headers": { "Authorization": "Bearer nvt_live_…" }
    }
  }
}

Zed — settings.json, under context_servers. Without the Authorization header Zed starts an OAuth flow instead, which this server does not serve.

{
  "context_servers": {
    "nexovue": {
      "url": "https://api.nexovue.com/api/mcp/",
      "headers": { "Authorization": "Bearer nvt_live_…" }
    }
  }
}

Gemini CLI — ~/.gemini/settings.json. The field is httpUrl; plain url means Server-Sent Events, which is a different transport.

{
  "mcpServers": {
    "nexovue": {
      "httpUrl": "https://api.nexovue.com/api/mcp/",
      "headers": { "Authorization": "Bearer nvt_live_…" }
    }
  }
}

JetBrains IDEs, Cline, Roo Code and Continue take the same URL-and-header pair in their own MCP settings.

Claude Desktop and claude.ai are the awkward ones. Remote servers go through Settings → Connectors → Add custom connector, not the config file — a url in claude_desktop_config.json is ignored silently. Enter the endpoint, then a request header named authorization with the value Bearer nvt_live_…. Request headers there are still in beta and are not enabled for every organization yet.

ChatGPT cannot hold a static API key for a connector today; OpenAI supports OAuth or no authentication. OpenAI's Responses API can connect, by passing the token in its authorization field.

Asking it things

Once connected: "what is failing in production?", "what deployed just before the checkout errors started?", "show me the slowest service this hour".

The server offers these tools, and no others:

list_projects, list_services, get_service, list_traces, search_logs, list_error_groups, list_incidents, list_checks, list_deploys.

Three things are true of all of them:

  • Every tool reads. There is no tool that changes a check, an alert rule, a member or a setting. A model cannot alter your account through this.
  • A tool sees exactly what the token sees. Same project scope, same refusals, same rate limit. Revoking the token ends the assistant's access on its next call.
  • Answers are bounded — at most 100 rows and 32 KB per call — and a cut answer says so and says how to narrow, so a model never treats a partial answer as the whole picture.

Every tool call counts against the token's rate limit and updates its last used, so the token list in the console shows you when an assistant last read.