Ir al contenido
↑↓Navigate↵SelectescClose

Personal Developer Keys

User-bound nxd_ credentials for the Query and Trace HTTP APIs, separate from gateway API keys

Esta página aún no está disponible en tu idioma.

Personal developer keys are user-bound credentials for CLI, SDK, and automation clients that need Nexus Query and Trace HTTP APIs without a browser session.

In the console they appear under Account → Personal developer keys (/account). The auth service stores them as developer access tokens and issues secrets with the nxd_… prefix.

They are not gateway API keys (nxs_…) and not trace ingest keys. Gateway keys send model traffic; personal keys read observability data (and optionally write request feedback) as the issuing user.

On a Traefik-backed deployment, consumers and tenants reach Nexus through the published hostnames, not the internal service ports.

Public hostname (env) Typical role Example
NEXUS_CONSOLE_HOSTNAME Tenant console (UI + same-origin BFF) https://console.example.com
NEXUS_HOSTNAME Gateway edge (model traffic, /v1/traces) https://api.example.com

Auth (auth:14453) and Query (query:14461) stay on the private Compose network. The console container calls them with AUTH_HTTP_ENDPOINT and QUERY_HTTP_ENDPOINT. Stock Traefik labels publish console and gateway (plus optional Grafana / path-routed trace ingest on the gateway host). They do not publish auth or query directly.

What that means for you:

  1. Mint and revoke keys in the console UI on the console hostname (works with OIDC or password login, depending on deployment policy).
  2. Send model traffic to the gateway hostname with a gateway API key (nxs_…).
  3. Call Query/Trace with nxd_… only against a Query HTTP base URL your operator has made reachable to you (published Query hostname, VPN, or private network). The stock public edge does not put Query on the gateway hostname.

Browser sessions in the console do not need a personal key: the console BFF under /api/query/** forwards the signed-in session cookie to Query on the private network.

Terminal window
# Set these to your deployment's public URLs.
CONSOLE_URL="${CONSOLE_URL:-https://console.example.com}"
GATEWAY_URL="${GATEWAY_URL:-https://api.example.com}"
# Query base URL only if your operator publishes Query HTTP to clients.
# Stock Traefik compose leaves query on the private network.
QUERY_URL="${QUERY_URL:-https://query.example.com}"
Capability Available to a deployed-app user
Create / list / revoke in the console UI Yes
Org-wide or single-project scope Yes
Expiry (default 30 days, bounded by the effective maximum key age) Yes
Scoped permissions (see below) Yes
Authorization: Bearer nxd_… against Query HTTP (when Query is reachable) Yes
Use against the gateway for chat/completions No — use nxs_…
Control-plane admin via console-minted scopes No
Rotate in place (edit secret) No — revoke and create a new key
Machine / service-account tokens (non-user) No
Call Query via the public gateway hostname No (gateway does not serve /api/requests, …)
Call Query via console /api/query/** with an nxd_… bearer No — that BFF uses the browser session cookie

Effective access on every use is the intersection of:

  1. Scopes stored on the token
  2. The user’s current org/project membership and role
  3. Token not revoked or expired; user still active
  4. Policy (PDP) allow for the specific read or feedback action

Demotion, membership removal, disablement, expiry, or revocation takes effect on the next verification. Personal keys are not subject to console session idle timeout.

The console scope picker offers these values. Creation rejects any scope the caller’s effective role does not hold.

Scope Console label Unlocks
query:read Request metadata Request rows, sessions, session timelines
trace:read Trace metadata Trace envelopes and safe tool summaries
trace_payload:read Full trace payloads Stored bodies and full tool payload refs when tracing captured them
metrics:read Metrics Usage, metrics overview, panel exports
feedback:write Feedback Submit request feedback from SDKs or automation

Role ceilings for mintable scopes:

Role Can include trace_payload:read Notes
manager Yes Full set above
auditor No Metadata, metrics, feedback; no full payloads via personal keys
user No Same as auditor for this surface

Full payload reads also require a successful trace_payload.read PDP decision and stored payload references. Metadata reads remain available with query:read, trace:read, or metrics:read as appropriate.

The console does not offer control-plane scopes (control_plane:read, control_plane:write, …) on personal keys. Endpoints that require those scopes (for example GET /api/reports/workspace) return HTTP 403 for typical nxd_ tokens even when the user could call them with a console session.

Prefix Purpose Where you send it
nxd_… Personal developer key (this doc) Query HTTP base URL (when published / reachable)
nxs_… Gateway API key Gateway hostname (/v1/…, /anthropic/…)
Trace ingest key Project-scoped OTLP write Gateway hostname /v1/traces (path-routed)

Query HTTP rejects gateway API keys with HTTP 401 (gateway api keys cannot read query APIs).

  1. Open the tenant console on your deployment’s console hostname (for example https://console.example.com).
  2. Sign in (OIDC and/or password, per that deployment’s auth config).
  3. Open Account (/account).
  4. Select the organization, then New key.
  5. Set name, optional project scope, expiry days, and permissions. The expiry field defaults to the lesser of 30 days and the effective maximum key age for the selected organization/project, and the dialog shows that maximum when one is configured.
  6. Copy the secret once — it is not shown again.

The raw secret is returned only on create. The Account list is a searchable, paginated table that defaults to Active (hides revoked and past-expiry keys); switch the state filter to Revoked / Expired / All when needed. List rows show name, prefix, project scope, permissions, status (Active / Expires in N days / Expired / Revoked), and last-used time. Revoke stays available for every status except already-revoked keys.

This is the path that works on OIDC-only deployments (password login disabled).

Use the personal key as a bearer token against Query HTTP, not against the gateway hostname.

Every list/detail route requires org_id (and respects optional project_id filters and the token’s project grant).

Terminal window
QUERY_URL="${QUERY_URL:?set QUERY_URL to the Query HTTP base your operator published}"
NEXUS_DEV_TOKEN="nxd_…" # secret shown once at create time
ORG_ID="org_…"
# Recent requests
curl -sS "$QUERY_URL/api/requests?org_id=$ORG_ID&limit=20" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# One request
REQUEST_ID="req_…"
curl -sS "$QUERY_URL/api/requests/$REQUEST_ID?org_id=$ORG_ID" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# Sessions
curl -sS "$QUERY_URL/api/sessions?org_id=$ORG_ID&limit=20" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# Session timeline
SESSION_ID="sess_…"
curl -sS "$QUERY_URL/api/sessions/$SESSION_ID/timeline?org_id=$ORG_ID" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# Metrics overview (needs metrics:read)
curl -sS "$QUERY_URL/api/metrics/overview?org_id=$ORG_ID" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# Trace metadata for a request (needs trace:read)
curl -sS "$QUERY_URL/api/traces/requests/$REQUEST_ID?org_id=$ORG_ID" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" | jq .
# Stored body (needs trace_payload:read + stored body + PDP allow)
curl -sS "$QUERY_URL/api/body/by-request/$REQUEST_ID?org_id=$ORG_ID&kind=response" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN"
# Feedback (needs feedback:write)
curl -sS -X POST "$QUERY_URL/api/requests/$REQUEST_ID/feedback" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -n \
--arg org "$ORG_ID" \
--arg project "$PROJECT_ID" \
'{org_id:$org, project_id:$project, score:1, label:"helpful", comment:"worked well"}')"

score must be -1, 0, or 1.

Requires query:read and membership for the org/session. Project-scoped keys only see events for their project.

Terminal window
curl -N "$QUERY_URL/api/sessions/$SESSION_ID/live?org_id=$ORG_ID" \
-H "Authorization: Bearer $NEXUS_DEV_TOKEN" \
-H 'Accept: text/event-stream'
import os
import urllib.parse
import urllib.request
query_url = os.environ["QUERY_URL"] # operator-published Query HTTP base
token = os.environ["NEXUS_DEV_TOKEN"] # nxd_…
org_id = os.environ["ORG_ID"]
params = urllib.parse.urlencode({"org_id": org_id, "limit": 20})
req = urllib.request.Request(
f"{query_url}/api/requests?{params}",
headers={"Authorization": f"Bearer {token}"},
)
with urllib.request.urlopen(req) as resp:
print(resp.read().decode())
const queryUrl = process.env.QUERY_URL!; // operator-published Query HTTP base
const token = process.env.NEXUS_DEV_TOKEN!; // nxd_…
const orgId = process.env.ORG_ID!;
const url = new URL("/api/requests", queryUrl);
url.searchParams.set("org_id", orgId);
url.searchParams.set("limit", "20");
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`);
}
console.log(await response.json());

When the token carries the required scope(s) and Query HTTP is reachable, these routes accept nxd_… bearers:

Method Path Typical scopes
GET /api/requests query:read
GET /api/requests/:request_id query:read
GET /api/sessions query:read
GET /api/sessions/:session_id/timeline query:read
GET /api/sessions/:session_id/live query:read
GET /api/usage metrics:read
GET /api/metrics/overview metrics:read
GET /api/exports/panel metrics:read
POST /api/requests/:request_id/feedback feedback:write
GET /api/body/by-request/:request_id trace_payload:read
GET /api/traces/requests/:request_id trace:read (+ payload scope for full refs)
GET /api/traces/sessions/:session_id trace:read
GET /api/traces/tools/:request_id trace:read
GET /api/traces/:trace_id/tree trace:read
GET /api/traces/:trace_id/spans/:span_id trace:read
GET /api/traces/:trace_id/artifacts/:artifact_id trace:read / trace_payload:read as applicable
GET /api/requests/:request_id/trace trace:read
GET /api/sessions/:session_id/traces trace:read
GET /api/sessions/:session_id/traces/live trace:read

Canonical route descriptions live in Wire Contracts.

GET /api/reports/workspace additionally requires control_plane:read, which console-minted personal keys do not receive.

Minting and revocation require a console session, not an nxd_ key. On a deployed app, prefer the console UI above—especially when password login is disabled and only OIDC is offered.

When password login is enabled, you can script against the console origin (auth is rewritten/proxied; session cookies are set by the console session routes):

Terminal window
CONSOLE_URL="${CONSOLE_URL:-https://console.example.com}"
ORG_ID="org_…"
# Sign in and keep cookies (console session route).
curl -sS -c nexus-cookies.txt -X POST "$CONSOLE_URL/api/session/sign-in" \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"your-password"}' >/dev/null
# Create (console BFF attaches the session cookie to auth).
curl -sS -b nexus-cookies.txt -X POST "$CONSOLE_URL/api/auth/developer-tokens" \
-H 'Content-Type: application/json' \
-d "$(jq -n \
--arg org "$ORG_ID" \
--arg name "CI observability reader" \
'{
org_id: $org,
project_id: null,
name: $name,
scopes: ["query:read","trace:read","metrics:read","feedback:write"],
expires_at: "2026-12-31T00:00:00Z"
}')"

Successful create response shape:

{
"id": "dt_…",
"token": "nxd_…",
"prefix": "…",
"org_id": "org_…",
"project_id": null,
"scopes": ["query:read", "trace:read", "metrics:read", "feedback:write"],
"expires_at_unix": 1770000000
}

Store token immediately. Only the hash and prefix are persisted.

Project-scoped create:

Terminal window
curl -sS -b nexus-cookies.txt -X POST "$CONSOLE_URL/api/auth/developer-tokens" \
-H 'Content-Type: application/json' \
-d "$(jq -n \
--arg org "$ORG_ID" \
--arg project "$PROJECT_ID" \
--arg name "CI project reader" \
'{
org_id: $org,
project_id: $project,
name: $name,
scopes: ["query:read","metrics:read"],
expires_at: "2026-12-31T00:00:00Z"
}')"

expires_at is an optional RFC3339 timestamp. When it is omitted, the token expires after 30 days. A workspace, organization, or project maximum key age — or NEXUS_MAX_KEY_AGE_DAYS — caps the requested timestamp and lowers the 30-day default; a request beyond the cap is rejected with HTTP 400 and {"message":"key expiration exceeds the maximum key age"}. With no cap configured, any future timestamp is accepted. Create also returns 400 with a message for empty names or scopes the caller’s role cannot mint, and 403 with a message when the caller has no membership in the requested organization. Those validation failures do not invalidate the console session.

List and revoke:

Terminal window
curl -sS -b nexus-cookies.txt \
"$CONSOLE_URL/api/auth/developer-tokens?org_id=$ORG_ID" | jq .
TOKEN_ID="dt_…"
curl -sS -b nexus-cookies.txt -X DELETE \
"$CONSOLE_URL/api/auth/developer-tokens/$TOKEN_ID" \
-o /dev/null -w '%{http_code}\n'

Auth also exposes the same lifecycle paths on its private HTTP listener (POST|GET /api/auth/developer-tokens, DELETE /api/auth/developer-tokens/{token_id}) for operators with network access to that service.

Situation Typical result
Missing / bad bearer on Query HTTP 401
Gateway nxs_… key on Query HTTP 401
Hitting /api/requests on the gateway hostname HTTP 404 (not a Query route there)
Scope missing for the route HTTP 403
Org/project outside membership or token grant HTTP 403
Requested create scopes exceed role HTTP 400 {"message":"scope … is not available to your role"}
Create expiry exceeds maximum key age HTTP 400 {"message":"key expiration exceeds the maximum key age"}
Create without membership in the org HTTP 403 {"message":"no membership in organization"}
Revoke unknown or already-revoked token HTTP 404 {"message":"developer token not found"}
Token revoked or expired Verification fails
User disabled or membership removed Verification fails / 403
Full body when tracing did not store it HTTP 404 (body not stored)

Create and revoke emit developer_token.created and developer_token.revoked. Verified principals carry an internal developer_token:<id> scope for correlation. Query reads emit data_access.* events (for example data_access.request_viewed, data_access.trace_payload_viewed) that include the token id without the raw secret. last_used_at updates on successful verification.

When you run the stock local Compose stack with published host ports (see Ports), the same APIs are also reachable as http://127.0.0.1:14449 (console), http://127.0.0.1:14453 (auth), and http://127.0.0.1:14461 (query). Prefer the deployed hostname model above when writing client integrations; use localhost only for contributor debugging on a machine that can reach those ports.