Wire Contracts
Complete API surface documentation for all external and internal contracts
Esta página aún no está disponible en tu idioma.
This reference describes Nexus’s external and internal interfaces, including supported routes, request and response behavior, and service contracts.
External interfaces serve applications, client libraries, consoles, and telemetry exporters. Internal interfaces connect Nexus services through Protocol Buffers, remote procedure calls, and asynchronous events.
Sections explicitly identify stubs and interfaces that are not implemented.
| If you are working on | Start here |
|---|---|
| OpenAI-compatible applications | OpenAI-compatible edge |
| Claude Code or Anthropic clients | Anthropic Messages edge |
| Reporting dimensions | Reporting-dimension endpoints |
| Console request, session, and trace views | Console query endpoints |
| Agentic trace exporters | Trace ingest |
| Service-to-service integrations | Protocol services and event topics |
Internal: reporting-dimension control-plane REST
Section titled “Internal: reporting-dimension control-plane REST”Reporting dimensions attach workspace-defined labels to requests and reports. Only workspace managers may change the vocabulary.
Define and archive dimensions
Section titled “Define and archive dimensions”Base path: /api/workspaces/{workspace_id}/reporting-dimensions
| Method | Path suffix | Behavior |
|---|---|---|
GET |
Base path | List string dimensions. Set include_archived=true to include display names for archived labels. |
POST |
Base path | Define a string dimension. |
DELETE |
/{definition_id} |
Archive a dimension. |
POST |
/assignments/lookup |
Resolve assignment identifiers and their scopes within the workspace. |
Any workspace member may call the lookup endpoint. It includes end-dated assignments and omits identifiers belonging to other workspaces.
Lookup request:
{ "ids": ["<assignment-id>"] }Response shape:
{ "assignments": [{ "id": "<assignment-id>", "scope": "<scope>" }] }Assign dimension values to keys
Section titled “Assign dimension values to keys”Base path: /api/api-keys/{api_key_id}/reporting-dimensions
| Method | Path suffix | Behavior |
|---|---|---|
GET |
Base path | List dimension values for the key. |
PUT |
Base path | Set a dimension value. |
DELETE |
/{definition_id} |
End-date the active assignment. |
The interface returns guidance that reporting metadata must not contain secrets.
Read dimensions in requests and reports
Section titled “Read dimensions in requests and reports”GET /api/requests and GET /api/requests/:request_id include:
reporting_dimensions: The dimension values captured on the request.reporting_dimension_sources:requestwhen a client supplied anexus-propertyheader; otherwise, the assignment identifier whose scope is available through the lookup endpoint.
Workspace reports accept optional parameter-bound dimension and dimension_value query parameters. When a dimension is requested, each row returns dimension_value, or null if that request did not snapshot the key.
External: OpenAI-compatible REST + SSE
Section titled “External: OpenAI-compatible REST + SSE”This is the gateway’s primary edge. See the OpenAI-compatible gateway decision for its rationale.
Supported routes
Section titled “Supported routes”These routes are available on the gateway binary and nexus ops serve:
| Method | Route | Purpose |
|---|---|---|
POST |
/v1/chat/completions |
Chat completions |
POST |
/v1/responses |
Responses |
POST |
/v1/embeddings |
Embeddings |
POST |
/v1/moderations |
Moderation |
POST |
/v1/images/generations |
Image generation |
POST |
/v1/images/edits |
Image editing |
POST |
/v1/images/variations |
Image variations |
GET |
/v1/models |
Model catalog |
| Multiple | /v1/files |
File lifecycle |
| Multiple | /v1/vector_stores |
Vector-store lifecycle, search, and file attachments |
POST |
/v1/traces/ingest |
Trace-export acknowledgment |
Unknown /v1/* routes return HTTP 404 with the OpenAI error envelope.
Capability checks and routing
Section titled “Capability checks and routing”Embeddings, moderation, and image operations filter candidate endpoints against the required model capability before dispatch.
If no candidate survives:
- The request returns HTTP 400, identifying the unsupported operation.
- The OpenAI edge sets both
error.typeanderror.codetono_compatible_candidate. - The Anthropic edge uses
error.type: invalid_request_error, omitscode, and supplies the reason inerror.message. - No upstream call is made and no attempt is recorded.
The same rejection can occur on any route, including chat completions and responses, when the organization’s provider disallow list excludes every endpoint. The reason in error.message distinguishes this cause from a capability mismatch.
Request and response shape
Section titled “Request and response shape”The route accepts OpenAI-shaped JSON with model, provider/model, comma-separated fallback lists, or the reserved nexus/auto alias.
Non-streaming requests return OpenAI-shaped JSON.
For embeddings, request-global malformed input is rejected before dispatch. Candidate-specific validation failures may route to another compatible endpoint; Titan multi-input fan-out becomes terminal once any upstream call has completed, preserving the OpenAI error envelope while accounting for the observed successful calls.
Streaming
Section titled “Streaming”Requests with stream: true return text/event-stream; charset=utf-8 using Server-Sent Events (SSE). Normalized chat.completion.chunk frames carry stable top-level id, object, created, and model fields. The response exposes x-nexus-request-id.
A terminal error event followed by data: [DONE] ends the stream when:
- An upstream stream read fails.
- The upstream sends an in-band
{"error": ...}chunk on an otherwise-successful stream. - A size cap is exceeded.
No later upstream content is relayed after that terminal event.
The gateway adds upstream stream_options.include_usage = true for streaming requests so usage can be recorded from the final provider chunk.
Provider field policies
Section titled “Provider field policies”Each candidate’s adapter applies its request field policy before dispatch, as defined in services/gateway/src/providers/field_policy.rs.
| Adapter behavior | Result |
|---|---|
| Passthrough | Forward every field. |
| Unsupported hint field | Drop the field and name it in x-nexus-dropped-params. The header is comma-separated and absent when nothing was dropped. |
| Field whose meaning cannot be preserved | Return HTTP 400 invalid_request_error with error.param for that candidate, allowing routing to try the next one. Examples include n above 1, logprobs: true, and an inexpressible response_format. |
| Adjacent same-role turns | Merge them into one turn with tool results first, preserving a valid conversation for upstreams that require role alternation. |
Responses served by an Anthropic upstream surface provider-specific detail through the OpenAI shape: extended-thinking text appears as reasoning_content on the assistant message (and as reasoning_content stream deltas), and cached prompt tokens are reported as usage.prompt_tokens_details.cached_tokens.
External: Anthropic Messages-compatible REST + SSE
Section titled “External: Anthropic Messages-compatible REST + SSE”The native Anthropic Messages edge is mounted under /anthropic, keeping /v1/* for the OpenAI-compatible contract. See the Anthropic edge decision and OpenAI edge decision.
| Method | Route | Purpose |
|---|---|---|
POST |
/anthropic/v1/messages |
Text, tools, images, and tool-result turns, buffered or streaming |
POST |
/anthropic/v1/messages/count_tokens |
Token counting |
Connection and authentication
Section titled “Connection and authentication”Anthropic SDK clients set their base URL to the gateway’s /anthropic prefix (for example ANTHROPIC_BASE_URL=https://nexus.example.com/anthropic), and the SDK appends /v1/messages itself.
The nexus API key is read from x-api-key: nxs_... (the Anthropic SDK default) or Authorization: Bearer nxs_...; upstream Anthropic credentials are never accepted.
The edge translates the request into the internal contract and dispatches it through the same attempt pipeline as /v1/chat/completions, so the request can be served by any provider the router selects (Anthropic or otherwise) and the response is returned in the Anthropic Messages shape regardless of which provider answered.
Request fields
Section titled “Request fields”max_tokens is required: its absence returns an Anthropic-shaped invalid_request_error with HTTP 400 (it is not defaulted the way the Anthropic adapter defaults it for OpenAI-shaped requests).
| Input | Internal representation |
|---|---|
model, max_tokens, temperature, top_p, stream |
Passed through |
messages and system |
OpenAI messages, with system text as a leading system message |
stop_sequences |
Internal stop field |
tools and tool_choice |
Translated with the parallel-tool flag; see Tools and images |
thinking |
Removed from the internal body; handled by the native-field preservation envelope |
| Other top-level fields | Omitted from the internal body, including metadata, output_config, context_management, top_k, and service_tier |
metadata.user_id is not mapped to an end-user identifier. Non-Anthropic attempts name omitted fields in x-nexus-dropped-params, for both buffered and streaming responses. Anthropic targets receive them through the native body and report nothing dropped.
Response fields
Section titled “Response fields”Responses carry id, type: "message", role: "assistant", text and tool_use content blocks, the canonical model, stop_reason, stop_sequence, and usage.input_tokens / usage.output_tokens.
| Internal stop reason | Anthropic stop reason |
|---|---|
stop |
end_turn |
length |
max_tokens |
tool_calls |
tool_use |
pause_turn |
pause_turn |
refusal |
refusal |
content_filter or an unrecognized reason |
end_turn, with refusal text preserved in content |
Errors and retries
Section titled “Errors and retries”Errors use the Anthropic envelope:
{ "type": "error", "error": { "type": "invalid_request_error", "message": "<failure description>" }}| Error type | HTTP status |
|---|---|
invalid_request_error |
400 |
authentication_error |
401 |
permission_error |
403 |
not_found_error |
404 |
rate_limit_error |
429 |
api_error |
500 |
overloaded_error |
529 |
Unknown routes under /anthropic, including telemetry probes from agent clients, return this envelope with HTTP 404.
Attempt details
Section titled “Attempt details”When multiple attempts all fail, each detail includes source, kind, status_code, and message:
- Anthropic edge: The array lives at
error.nexus.attempts. The request identifier is supplied only inx-nexus-request-id. - OpenAI edge: The array lives at
error.attempts, alongsideerror.request_idin the response body. - Single attempt: Neither edge includes an attempts array. An upstream rejection exposes the provider’s
error.messagewhen present, or its raw response body otherwise.
The Anthropic extension uses the nexus namespace to avoid conflicting with future Anthropic envelope fields.
Message contents
Section titled “Message contents”For failures carrying an upstream HTTP status, attempts[].message contains the upstream body up to 64 KiB, or NEXUS_GATEWAY_MAX_RESPONSE_BYTES if that limit is smaller. Longer content ends with … (truncated by nexus).
Bedrock messages are normalized into an OpenAI-shaped envelope and redact the caller’s identity and access-management identifiers. See Threat model, row I9.
Transport failures and gateway-side timeouts use fixed failure categories. Oversized responses use a gateway-authored size message. Raw reqwest errors are never exposed because their display text can contain upstream URLs.
Failure categories
Section titled “Failure categories”Branch on attempts[].kind instead of parsing free-form messages:
MissingProviderKeyUpstreamStatusUpstreamErrorTimeoutResponseTooLargeInternal
Timeout and ResponseTooLarge are gateway-side verdicts. A timeout may warrant a retry; retrying the same candidate after an oversized response is not useful because it would return the same body.
Gateway rate limits return Retry-After with HTTP 429. Upstream Anthropic rate-limit headers are not forwarded because Nexus terminates client authentication before upstream calls complete.
Response headers
Section titled “Response headers”| Header | Behavior |
|---|---|
x-nexus-request-id |
Included on successful responses and errors after an identifier has been assigned, such as policy denials, exhausted attempts, and upstream errors. Absent on early authentication, body-shape, and unknown-route failures. |
Retry-After |
Retry window for HTTP 429, in whole seconds rounded up, with a minimum of 1. |
anthropic-version |
Accepted and ignored on inbound requests. Outbound Anthropic calls set their own provider-targeted version. |
Streaming events
Section titled “Streaming events”With stream: true, the response uses Content-Type: text/event-stream. Server-Sent Events (SSE) frames have the form event: <name>\ndata: <json>\n\n and follow this sequence:
message_startcontent_block_start- Repeated
content_block_delta, includingtext_deltaand tool-callinput_json_delta content_block_stopmessage_delta, carryingstop_reasonand output usagemessage_stop
There is no [DONE] marker. When upstream usage arrives only at stream end, message_start.usage.input_tokens is 0; message_delta.usage is authoritative.
An upstream failure after streaming begins emits an Anthropic error event with error.type: api_error and terminates the stream.
Tools and images
Section titled “Tools and images”Tool definitions, assistant tool_use blocks, and user tool_result blocks translate through the internal OpenAI contract.
Anthropic tool_choice |
Internal value |
|---|---|
auto |
auto |
any |
required |
none |
none |
{type: "tool", name} |
Named function choice |
disable_parallel_tool_use: true independently sets parallel_tool_calls: false. Anthropic any requires exactly 1 tool call, whereas OpenAI required does not have identical semantics; this mapping is not pre-validated.
Assistant tool_use.id and user tool_result.tool_use_id must be non-empty strings. They are preserved byte-for-byte; missing or empty identifiers return invalid_request_error.
Image blocks with base64 or HTTP(S) URL sources translate to OpenAI image_url parts, and back to Anthropic image blocks for Anthropic upstreams. Missing source fields or non-HTTP(S) URLs return invalid_request_error.
Token counting and accounting
Section titled “Token counting and accounting”POST /anthropic/v1/messages/count_tokens returns {"input_tokens": N}. It does not require max_tokens; that field is dropped before the upstream proxy call.
| Target | Counting behavior |
|---|---|
| Anthropic | Proxies to upstream count_tokens using Bring Your Own Key (BYOK) credentials, without Pass-Through Billing (PTB) budget or cost accounting. |
Any other provider, including bedrock-mantle Claude |
Returns ceil(serialized_internal_body_bytes / 4). This byte-length estimate can differ from the provider’s tokenizer; use it as an approximate planning bound, not an exact count. |
Request analytics, audit events, and cost accounting use the same schema as /v1/chat/completions, distinguished by the /anthropic/v1/messages route value. The count_tokens route records request analytics only (no usage or cost lines).
Native-field preservation envelope
Section titled “Native-field preservation envelope”Anthropic-only request fields (thinking, per-block cache_control, block-array system, and inbound anthropic-beta header values) have no OpenAI-shaped equivalent. The edge captures them in a preservation envelope attached to the internal request alongside the raw inbound Anthropic Messages JSON, then translates a clean OpenAI-shaped internal contract for routing, classification, and any cross-provider attempt.
There are 2 mutually exclusive dispatch paths:
- Native Anthropic body: The Anthropic adapter and
bedrock-mantleClaude models reconstruct the request from raw inbound JSON. Only the model identifier, endpoint-clampedmax_tokens, and streaming flag change. Thinking configuration, signed history blocks, cache hints, and block-array system content retain their JSON-equivalent representation. - Translated body: Other providers, including other
bedrock-mantlemodels, receive the OpenAI-shaped body with no untranslated native fields.
The native path avoids an Anthropic-to-OpenAI-to-Anthropic conversion that could discard fields or break signatures.
Cross-provider field behavior:
| Field | Anthropic target | Non-Anthropic target |
|---|---|---|
thinking (config) |
Preserved verbatim from the envelope. Within each model in the route, these upstreams are tried first. | Not sent; named in x-nexus-dropped-params when enabled. |
thinking / redacted_thinking history blocks (with signature) |
Replayed byte-equivalent from the envelope. | Not sent; the turn’s text and tool calls are kept. |
per-block cache_control |
Preserved on the exact block from the envelope. | Stripped silently (caching is an optimization, not semantics); fallback succeeds with no cache usage reported. |
block-array system |
Preserved verbatim from the envelope. | Flattened to a single joined system string with cache hints removed. |
anthropic-beta (header) |
Forwarded verbatim to Anthropic and recorded in request analytics; to bedrock-mantle Claude models only the values Mantle accepts are forwarded, the rest named in x-nexus-dropped-params as anthropic-beta:<value>. |
Never forwarded. |
other untranslated top-level fields (metadata, output_config, …) |
Preserved verbatim from the envelope. | Not sent; named in x-nexus-dropped-params. |
Extended thinking: Only upstreams receiving the native body honor it, because thinking-block signatures cannot be fabricated. Those endpoints are tried first within each model in the route. Other endpoints omit thinking and signed thinking history blocks, and name thinking in x-nexus-dropped-params.
Prompt caching: Caching does not pin the route. A cached request may fall back to a non-Anthropic provider with cache_control stripped.
Cache accounting: Anthropic responses expose cache_creation_input_tokens and cache_read_input_tokens; input_tokens excludes both classes. Cache reads and writes are priced at their registry rates.
Thinking streams: With stream: true, a leading thinking content block carries thinking_delta frames and a signature_delta frame before the answer text. The serializer passes through unrecognized content-block types and delta kinds, allowing beta wire-shape changes without rejecting the stream.
External: Gemini-compatible REST
Section titled “External: Gemini-compatible REST”Route stub registered at POST /v1beta/models/gemini-pro:generateContent. Returns 501; native Gemini pass-through is not implemented.
External: OpenAI Agents SDK trace-ingest acknowledgment
Section titled “External: OpenAI Agents SDK trace-ingest acknowledgment”POST /v1/traces/ingest on the gateway accepts trace-export payloads from the OpenAI Agents SDK under a Nexus API key and returns a success envelope. The payload is acknowledged but not persisted; native trace storage is the OTLP trace-ingest service below.
External: OTLP ingress (Tier 2 operator telemetry)
Section titled “External: OTLP ingress (Tier 2 operator telemetry)”Collector configuration ships in Compose; per-service OTLP depth varies. Canonical attribute rules: ../../vortex-common-crates/contracts/telemetry.md.
External: OTLP / OpenInference trace ingest
Section titled “External: OTLP / OpenInference trace ingest”The dedicated trace-ingest HTTP service accepts tenant-visible agentic traces at POST /v1/traces.
The endpoint accepts OTLP/HTTP protobuf ExportTraceServiceRequest payloads authenticated by a project-level trace ingest key.
The authenticated project is the source of truth for org_id and project_id; any tenant fields in the submitted span attributes are hints and must match that authenticated scope.
The contract requires accepted spans to be normalized into Nexus trace records before they become product-visible.
OpenTelemetry trace IDs and span IDs are preserved.
OpenInference attributes are preserved in attributes_json and selected fields are promoted for query and UI use.
Full inputs, outputs, tool arguments, tool results, retrieved document content, image payloads, audio payloads, and raw provider payloads are represented by TraceArtifactRef values and are stored only when full payload trace capture is enabled and policy allows capture.
External: SCIM 2.0
Section titled “External: SCIM 2.0”Not implemented.
External: OIDC / SAML SSO endpoints
Section titled “External: OIDC / SAML SSO endpoints”Deployment-wide OIDC sign-in is implemented through console same-origin routes that call the auth service REST API. SAML is not implemented.
| Surface | Method | Path | Purpose |
|---|---|---|---|
| Console | GET |
/api/session/oidc/:provider/start |
Creates an auth-service OIDC login state, binds the returned state in an HTTP-only cookie, and redirects the browser to the IdP authorization URL. |
| Console | GET |
/api/session/oidc/:provider/callback |
Verifies the browser-bound state, exchanges the callback with auth, sets the console session cookies, and redirects to the safe relative return path. |
| Auth | POST |
/api/auth/oidc/:provider/start |
Starts Authorization Code + PKCE for the configured provider and returns an authorization URL. |
| Auth | POST |
/api/auth/oidc/:provider/callback |
Exchanges and validates the provider callback, resolves or creates a Nexus user according to registration policy, and returns an auth session. |
Registration status checks are exposed for invite-only sign-up forms. They reveal only validity, expiry, and remaining uses; they do not return email addresses or stored digests.
| Surface | Method | Path | Purpose |
|---|---|---|---|
| Console | GET |
/api/auth/registration-invite/status?invite=... |
Same-origin proxy for registration invite status. |
| Console | GET |
/api/auth/registration-code/status?code=... |
Same-origin proxy for registration code status. |
| Auth | GET |
/api/auth/registration-invite/status?invite=... |
Returns { valid, expired } for a registration invite token. |
| Auth | GET |
/api/auth/registration-code/status?code=... |
Returns { valid, expired, uses_remaining } for a registration code. |
External: personal developer keys (developer access tokens)
Section titled “External: personal developer keys (developer access tokens)”User-bound credentials for non-browser Query, Trace, and control-plane clients. Console UI: Account → Personal developer keys. Operator guide with examples: ../dev/personal-developer-keys.md.
Each token is bound to exactly one scope: a workspace (workspace_id), an organization (org_id), or a project inside an organization (org_id + project_id).
The caller of the lifecycle routes is a console session bearer or an nxd_… bearer for the same user.
An nxd_… caller creates, lists, and revokes only tokens bound inside its own binding, and creates only with scopes it holds.
| Service | Method | Path | Purpose |
|---|---|---|---|
| Auth | POST |
/api/auth/developer-tokens |
Create an nxd_… token bound to workspace_id, or to org_id with optional project_id; returns the secret once. A workspace binding requires workspace manager or auditor. Validation failures return 400/403 with {"message":"…"}; a missing or invalid caller credential returns 401. |
| Auth | GET |
/api/auth/developer-tokens |
List the caller’s tokens (metadata only, including workspace_id, org_id, project_id). Optional org_id and workspace_id filter on the binding columns. |
| Auth | DELETE |
/api/auth/developer-tokens/:token_id |
Revoke one of the caller’s tokens. Unknown, already-revoked, or out-of-binding ids return 404 with {"message":"…"}; a missing or invalid caller credential returns 401. |
| Console | POST / GET / DELETE |
/api/auth/developer-tokens… |
Same-origin BFF proxies. A request with Authorization: Bearer nxd_… forwards that bearer; otherwise the BFF attaches the HTTP-only session access token. The console auth rewrite uses fallback so dynamic routes such as DELETE …/:token_id are not shadowed by the auth reverse-proxy. |
Verified nxd_… bearers are accepted on Query HTTP routes documented in the console query HTTP section below, and on control-plane REST and gRPC.
On the control plane, developer-key permissions are the user’s live memberships limited to the token’s binding:
- Targets outside the binding return
403. - An organization-wide role accessed through a project-bound token applies only to that project.
- Workspace authority applies only to workspace-bound tokens. Organization- or project-bound tokens inherit a workspace manager or auditor role only at their own binding.
Workspace creation and invitation acceptance answer 403 for any developer key.
Verifiers do not cache developer-key principals, so revocation, demotion, and membership removal apply to the next request.
They are rejected on the gateway model edge; gateway nxs_… keys are rejected on Query HTTP.
External: console query HTTP
Section titled “External: console query HTTP”The query service exposes a small HTTP read API consumed by the console via Next.js API proxies under apps/console/app/api/query/**. All routes require ?org_id=<id>; the console proxy enforces session-scoped access before forwarding.
| Method | Path | Returns |
|---|---|---|
GET |
/api/requests |
Paginated request rows for the org/project (+ optional q, provider, model, status_class, is_stream, session_id, status_code, time window). Response includes total and has_more. |
GET |
/api/requests/facets |
Distinct providers and models for the org/project (filter dropdowns). |
GET |
/api/requests/:request_id |
Single request row with authenticated query-proxy body URLs when payload access is authorized. |
GET |
/api/sessions |
Aggregated session summaries (count, cost, tokens, last model/provider) with optional q / provider / model / time filters. Response includes total and has_more. |
GET |
/api/sessions/facets |
Distinct last-request providers and models across sessions for the org/project. |
GET |
/api/sessions/:session_id/timeline |
Ordered request rows for the session (limit/offset; has_more when another page exists). |
GET |
/api/sessions/:session_id/live |
text/event-stream of request.completed events bridged from NATS nexus.requests.<org>.*, filtered by org_id+session_id. |
POST |
/api/requests/:request_id/feedback |
Record thumbs-style feedback on one request row. |
GET |
/api/usage |
Usage aggregates for the org/project + time window. |
GET |
/api/metrics/overview |
Overview metrics powering the console usage dashboard. |
GET |
/api/reports/workspace |
Exact spend for one workspace and one closed calendar month, grouped by organization, project, API key, provider, and model. The caller names only the workspace; its organizations are resolved through the control plane with the caller’s own credential. |
GET |
/api/exports/panel |
Export cost details or activity data. See Panel exports for parameters and row semantics. |
GET |
/api/body/by-request/:request_id |
Authenticated body fetch by request id. Packed references are served through one exact object range, pinned to the row’s organization prefix, and verified against the row’s byte count and BLAKE3 hash. |
GET |
/api/traces/:trace_id/tree |
Tenant-scoped trace tree with sorted spans, events, artifact refs, and request-row metadata joined onto matching LLM spans. |
GET |
/api/sessions/:session_id/traces |
Trace summaries for one session, grouped by trace id with request ids and full-trace availability. |
GET |
/api/requests/:request_id/trace |
Trace tree for the trace containing the LLM span linked to the request row. |
GET |
/api/traces/:trace_id/spans/:span_id |
Span detail with attached events, artifact refs, and joined request metadata when applicable. |
GET |
/api/traces/:trace_id/artifacts/:artifact_id |
Trace artifact metadata and a signed object URL when an object key exists and payload access is authorized. |
GET |
/api/sessions/:session_id/traces/live |
text/event-stream of trace lifecycle events from NATS nexus.traces.<org>.> plus joined request.completed events from nexus.requests.<org>.*, filtered by org/project/session and deduped by stable ids. |
GET |
/api/traces/requests/:request_id |
Request trace metadata, trace availability, body URLs when full tracing is enabled, and safe tool summary. |
GET |
/api/traces/sessions/:session_id |
Ordered per-request trace summaries for a session. |
GET |
/api/traces/tools/:request_id |
Tool-call summary and full tool trace ref when full tracing is enabled. |
Panel exports
Section titled “Panel exports”GET /api/exports/panel returns an export attachment.
| Parameter | Accepted values |
|---|---|
from, to |
Absolute time window of at most 366 days |
| Scope | org_id, optionally with project_id or api_key_id; or workspace_id, whose organizations are resolved through the control plane |
panel |
cost_detail (default), usage_daily, or usage_breakdown |
format |
csv or json |
dimension |
Comma-separated reporting-dimension keys, up to 8 |
Cost-detail rows
Section titled “Cost-detail rows”The finance export uses this row grain:
usage_date_utc × organization × project × api_key × provider × model × cost_band × selected reporting labels- Money:
cost_usdis the single money column. Summing it equals priced request spend for the range. Anunitemizedband accounts for incomplete cost-line evidence; do not add other money columns. - Dimensions: Each requested key becomes a value column on the same row, preventing duplicated spend when summing
cost_usd. - Labels: Display names are included where resolvable, together with
api_key_prefix. - Dates: Dates use Coordinated Universal Time (UTC). Idle days are omitted.
- Size limit: Row count is hard-capped; exceeding it returns HTTP 400.
Activity panels (usage_daily and usage_breakdown) remain available for charts. They use the legacy manifest comma-separated values (CSV) shape for organization scope only.
Console proxy and inspector routes
Section titled “Console proxy and inspector routes”The console exposes same-origin proxy routes under /api/query/** for these Query HTTP routes. Trace inspector pages are available at:
- Organization scope:
/o/<org>/traces/<trace_id> - Project scope:
/o/<org>/p/<project>/traces/<trace_id>
External: console control-plane HTTP
Section titled “External: console control-plane HTTP”The console proxies selected control-plane APIs so the server-side BFF can attach the HTTP-only session access token.
A request carrying Authorization: Bearer nxd_… is forwarded with that bearer instead, without consulting the session cookie, on every control-plane proxy route (including artifacts and /api/users/lookup).
Generated secrets (gateway, trace ingest, and developer keys) appear only in create and rotate responses; provider-connection secrets are accepted on create and never returned.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/trace-ingest-keys |
Create a project-scoped trace ingest key and return the secret once. |
GET |
/api/trace-ingest-keys |
List trace ingest key metadata without secrets. |
POST |
/api/trace-ingest-keys/:trace_ingest_key_id/rotate |
Rotate one trace ingest key and return the new secret once. |
DELETE |
/api/trace-ingest-keys/:trace_ingest_key_id |
Revoke one trace ingest key. |
PATCH |
/api/workspaces/:workspace_id/tracing |
Toggle full payload tracing for a workspace (workspace managers). |
PATCH |
/api/orgs/:org_id/tracing |
Toggle full payload tracing for an organization. |
PATCH |
/api/projects/:project_id/tracing |
Toggle full payload tracing for a project. |
PATCH |
/api/api-keys/:api_key_id/tracing |
Toggle full payload tracing for one Nexus API key. |
Traceability and Auditability
Section titled “Traceability and Auditability”Nexus treats audit evidence, tenant analytics metadata, and full trace payloads as separate data classes.
- Auditability is always on: gateway/control-plane/query lifecycle and security events continue to emit even when full trace capture is disabled.
- Tenant analytics metadata is always on: request/session rows keep identifiers, model/provider, status, latency, cost/tokens, attempt trail, trace flags, and safe tool summaries.
- Full trace payloads are opt-in debug artifacts: request bodies, response bodies, tool arguments, tool results, and replayable context are stored only when tracing is enabled by org/project/API-key configuration or
nexus-enable-tracing: 1.
Product stance: every request is accountable; full payload traces are opt-in.
Internal: nexus-proto gRPC services
Section titled “Internal: nexus-proto gRPC services”Proto definitions live under proto. Regenerate Rust/TS clients with cargo build -p nexus-proto and just proto.
| Service | Server | Client |
|---|---|---|
OrgService / ProjectService / ApiKeyService / ProviderKeyService |
control-plane |
CLI, internal tests |
AuthService |
auth |
Internal callers (VerifyApiKey, session ops) |
QueryService (ListRequests, GetRequest, MonthlyUsageSnapshot) |
query |
Internal callers needing authenticated query-proxy body URLs; the gateway’s spend-cap cache calls MonthlyUsageSnapshot |
AuditService (ListEvents, GetEvent, GetInclusionProof) |
audit |
Console read facade backing, verifier tooling |
PolicyService (Evaluate) |
policy |
Gateway (when POLICY_GRPC_ENDPOINT is set) and query read authorization |
HealthService (Ping) |
shared service skeleton | Internal health probes |
Internal: async event bus topics
Section titled “Internal: async event bus topics”NATS runs in Compose.
- Audit events:
gateway,auth,control-plane, andingestpublish canonical audit-event bytes to tenant-scoped subjectsnexus.audit.events.<tenant_id>throughnexus_runtime::NatsAuditSinkor equivalent producer helpers. - Request analytics: gateway publishes one JSON
request.completedenvelope per request on subjectsnexus.requests.<org_id>.<request_id>. The envelope carries normalized usage lines, qualified cost lines, exact decimal strings, rate-card provenance, and billing status/reason. Bodies <= 256 KB are inline in the envelope; larger bodies are chunked ontonexus.requests.<org_id>.<request_id>.body.{req,res}.chunk.<n>and referenced from the parent envelope with chunk metadata + blake3 hash. - Trace records:
trace-ingestpublishes tenant-scoped trace lifecycle events on subjects such asnexus.traces.<org_id>.<trace_id>.<event_id>. Event payloads carryTraceSpan,TraceEvent, orTraceArtifactRefrecords normalized bytrace-ingestor gateway-owned producers.
Internal: Nexus trace record contract
Section titled “Internal: Nexus trace record contract”These wire shapes define the normalized trace record contract.
The ingest service persists these records into ClickHouse trace tables.
They are Nexus-owned records that preserve OpenTelemetry identity and OpenInference semantics without making incoming attributes the authority for tenancy or authorization.
TraceSpan
Section titled “TraceSpan”TraceSpan represents a unit of work in an agentic trace.
Required fields:
| Field | Description |
|---|---|
trace_id |
OpenTelemetry-compatible trace identifier. |
span_id |
OpenTelemetry-compatible span identifier. |
parent_span_id |
Parent span identifier, empty for a root span. |
org_id |
Nexus organization scope from authenticated project context. |
project_id |
Nexus project scope from authenticated trace ingest key or internal producer. |
session_id |
Nexus session grouping key; mirrors OpenInference session.id when present. |
request_id |
Optional Nexus request row join key for gateway-backed LLM spans. |
span_name |
Low-cardinality operation name. |
span_kind |
OpenTelemetry span kind such as internal, client, server, producer, or consumer. |
openinference_span_kind |
OpenInference kind: AGENT, CHAIN, LLM, TOOL, RETRIEVER, RERANKER, EMBEDDING, GUARDRAIL, PROMPT; EVALUATOR is reserved for evaluator workflows. |
service_name |
Originating service or SDK name. |
source |
Nexus source category, for example gateway, sdk, trace-ingest, or integration. |
started_at |
Span start timestamp. |
ended_at |
Span end timestamp when complete; pending spans use the start timestamp for ClickHouse compatibility. |
duration_ms |
Span duration in milliseconds when complete, otherwise 0. |
status |
ok, error, unset, or pending. |
status_message |
Optional status details. |
attributes_json |
Full normalized attributes JSON after privacy filtering. |
payload_state |
summary_only, full, omitted, or redacted. |
Promoted query fields are derived from attributes when present:
llm.model_namellm.providerllm.systemllm.token_count.promptllm.token_count.completionllm.cost.totaltool.nameagent.namesession.iduser.id- Nexus attributes such as
nexus.request_id,nexus.classification, andnexus.trace_source
TraceEvent
Section titled “TraceEvent”TraceEvent represents a point-in-time event attached to a trace or span.
Required fields:
| Field | Description |
|---|---|
event_id |
Stable event identifier for live-stream dedupe. |
trace_id |
Trace identifier. |
span_id |
Span identifier the event belongs to. |
org_id |
Nexus organization scope. |
project_id |
Nexus project scope. |
session_id |
Nexus session grouping key. |
event_name |
Low-cardinality event name such as trace.span.started, trace.span.updated, trace.span.ended, trace.event, or provider/framework event names. |
occurred_at |
Event timestamp. |
level |
Optional log level or severity. |
message |
Optional human-readable event message. |
attributes_json |
Event attributes after privacy filtering. |
artifact_ref |
Optional TraceArtifactRef identifier for a large payload associated with the event. |
TraceArtifactRef
Section titled “TraceArtifactRef”TraceArtifactRef represents large or sensitive trace payload content stored outside hot trace rows.
Required fields:
| Field | Description |
|---|---|
artifact_id |
Stable artifact identifier. |
trace_id |
Trace identifier. |
span_id |
Span identifier associated with the artifact. |
kind |
input, output, tool_arguments, tool_result, retrieved_document, image, audio, or raw_provider_payload. |
object_key |
Object-store key when a full artifact is stored. Empty when omitted or redacted. |
mime_type |
Artifact MIME type. |
compression |
Compression applied to stored bytes. |
byte_count |
Original byte count when known. |
blake3_hash |
Hash of original bytes when available. |
payload_state |
summary_only, full, omitted, or redacted. |
redaction_metadata_json |
Metadata describing applied redaction without exposing redacted values. |
Example examples/agent-cli turn
Section titled “Example examples/agent-cli turn”TraceSpan(openinference_span_kind=AGENT, span_name="agent-cli turn", session_id="sess_123") TraceSpan(openinference_span_kind=LLM, span_name="Nexus chat completion stream", request_id="req_456") TraceSpan(openinference_span_kind=TOOL, span_name="local stress-test tool call")The session_id groups the turn in live session monitoring.
The request_id joins the LLM span to the Nexus request row.
Tool input and result artifacts use summary_only unless full tracing is enabled and policy allows artifact storage.
The authenticated project trace ingest key supplies the Nexus org/project scope; OpenInference attributes can mirror that scope but do not authorize it.
Internal: audit event schema
Section titled “Internal: audit event schema”Canonical shape: ../../vortex-common-crates/contracts/audit-event.md. The audit service consumes the tenant-scoped NATS stream, persists canonical hashes in Postgres, seals per-tenant Merkle batches, signs batch envelopes, and writes anchor manifests plus compressed event archives to the configured audit archive.
Internal: policy decision contract (PolicyQuery / PolicyDecision)
Section titled “Internal: policy decision contract (PolicyQuery / PolicyDecision)”Defined in ../../vortex-common-crates/contracts/policy-obligations.md. The policy service serves PolicyService.Evaluate over gRPC behind service-token auth; the gateway can also evaluate against the same engine in-process.
