Ir al contenido
↑↓Navigate↵SelectescClose

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.

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

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.

GET /api/requests and GET /api/requests/:request_id include:

  • reporting_dimensions: The dimension values captured on the request.
  • reporting_dimension_sources: request when a client supplied a nexus-property header; 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.

This is the gateway’s primary edge. See the OpenAI-compatible gateway decision for its rationale.

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.

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.type and error.code to no_compatible_candidate.
  • The Anthropic edge uses error.type: invalid_request_error, omits code, and supplies the reason in error.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.

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.

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.

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

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.

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.

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

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 in x-nexus-request-id.
  • OpenAI edge: The array lives at error.attempts, alongside error.request_id in the response body.
  • Single attempt: Neither edge includes an attempts array. An upstream rejection exposes the provider’s error.message when present, or its raw response body otherwise.

The Anthropic extension uses the nexus namespace to avoid conflicting with future Anthropic envelope fields.

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.

Branch on attempts[].kind instead of parsing free-form messages:

  • MissingProviderKey
  • UpstreamStatus
  • UpstreamError
  • Timeout
  • ResponseTooLarge
  • Internal

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.

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.

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:

  1. message_start
  2. content_block_start
  3. Repeated content_block_delta, including text_delta and tool-call input_json_delta
  4. content_block_stop
  5. message_delta, carrying stop_reason and output usage
  6. message_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.

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.

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).

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-mantle Claude models reconstruct the request from raw inbound JSON. Only the model identifier, endpoint-clamped max_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-mantle models, 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.

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.

Not implemented.

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.

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.

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

The finance export uses this row grain:

usage_date_utc × organization × project × api_key
× provider × model × cost_band × selected reporting labels
  • Money: cost_usd is the single money column. Summing it equals priced request spend for the range. An unitemized band 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.

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>

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.

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.

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

NATS runs in Compose.

  • Audit events: gateway, auth, control-plane, and ingest publish canonical audit-event bytes to tenant-scoped subjects nexus.audit.events.<tenant_id> through nexus_runtime::NatsAuditSink or equivalent producer helpers.
  • Request analytics: gateway publishes one JSON request.completed envelope per request on subjects nexus.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 onto nexus.requests.<org_id>.<request_id>.body.{req,res}.chunk.<n> and referenced from the parent envelope with chunk metadata + blake3 hash.
  • Trace records: trace-ingest publishes tenant-scoped trace lifecycle events on subjects such as nexus.traces.<org_id>.<trace_id>.<event_id>. Event payloads carry TraceSpan, TraceEvent, or TraceArtifactRef records normalized by trace-ingest or gateway-owned producers.

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 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_name
  • llm.provider
  • llm.system
  • llm.token_count.prompt
  • llm.token_count.completion
  • llm.cost.total
  • tool.name
  • agent.name
  • session.id
  • user.id
  • Nexus attributes such as nexus.request_id, nexus.classification, and nexus.trace_source

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

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.