Skip to content

Session API

The session-api is a per-workspace HTTP service that stores sessions and their messages, tool calls, provider calls, runtime events and eval results. It also serves the cost and eval aggregates the dashboard reads. The facade, runtime, eval worker, memory-api and dashboard are its callers.

Schemas are referenced by name. The service serves its own machine-readable contract at /api/v1/openapi.yaml and a rendered reference at /docs.

PortFlagPurpose
8080--api-addrREST API
8081--health-addr/healthz and /readyz (readiness pings Postgres)
9090--metrics-addrPrometheus metrics at /metrics
4317--otlp-grpc-addrOTLP gRPC trace ingestion (only with --otlp-enabled)
4318--otlp-http-addrOTLP HTTP trace ingestion, POST /v1/traces (only with --otlp-enabled)

The base path for the REST API is /api/v1. The REST API listener also answers GET /healthz without authentication.

Internal service authentication is enabled by the chart value internalServiceAuth.enabled, which defaults to true. When enabled, every request except /healthz carries a Kubernetes ServiceAccount bearer token. The service validates it with TokenReview. A caller is authorised when its subject matches an entry in --auth-allowed-subjects exactly, or when its ServiceAccount namespace is in --auth-allowed-namespaces.

FlagEnvironment variableMeaning
--auth-enabledSESSION_API_AUTH_ENABLEDRequire ServiceAccount tokens. The binary default is false.
--auth-allowed-subjectsSESSION_API_AUTH_ALLOWED_SUBJECTSComma-separated system:serviceaccount:<namespace>:<name> subjects (cross-namespace callers such as the dashboard)
--auth-allowed-namespacesSESSION_API_AUTH_ALLOWED_NAMESPACESComma-separated namespaces whose ServiceAccounts are all admitted
--auth-audiencesSESSION_API_AUTH_AUDIENCESComma-separated accepted token audiences

With auth enabled and both allowlists empty, the service refuses to start. With auth disabled, the API is unauthenticated and the service logs a warning at startup. The OTLP listeners use the same authentication.

When the request carries an x-omnia-subject-proof header and the service has a delegation JWKS URL (--delegation-jwks-url), the service verifies the token. A token that fails verification is rejected with 403 and a plain-text body. When the token names a user the operator verified, that subject replaces the X-Omnia-User-ID header, and POST /sessions requires virtualUserId to equal it (otherwise 403).

The service rate-limits requests per pod, whoever sends them: the limit is the pod’s capacity, not a per-caller quota. Defaults are RATE_LIMIT_RPS=5000 and RATE_LIMIT_BURST=10000, about what one pod sustains; RATE_LIMIT_RPS=0 turns the limiter off. An exceeded limit returns 429 with {"error":"rate limit exceeded"}.

Each session-api serves one workspace service group (--workspace, --service-group). Rows are scoped by Kubernetes namespace, which is the tenant boundary on every read.

ParameterMeaning
namespaceKubernetes namespace. Required on list, search, delete, the per-session eval summary and every aggregate, discovery and arena route. A missing value returns 400.
workspaceGET /sessions and /sessions/search only: filters on the workspace name in addition to namespace. It does not replace namespace.
workspaceUidOptional additional filter on eval and provider-call aggregates and on scenario history

When auth is enabled, the service applies a tenancy check against the caller’s ServiceAccount:

RouteCheck
POST /sessionsBody workspaceName must equal the service’s --workspace. Unless the caller’s subject is in --auth-allowed-subjects, body namespace must equal the caller’s ServiceAccount namespace.
DELETE /sessions, DELETE /sessions/{sessionID}Query namespace must equal the caller’s namespace unless the subject is in --auth-allowed-subjects.
GET /sessions/disclosure-summary, GET /eval-results/discover, GET /provider-calls/discover, GET /provider-usage/discoverSame namespace check as delete.
GET /eval-results/aggregate, GET /eval-results/scenario-history, GET /provider-calls/aggregate, GET /provider-usage/aggregate, GET /sessions/{sessionID}/eval-results/summarySame namespace check as delete.

A failed check returns 403. List, search, session reads, /arena/matrix and writes other than POST /sessions do not apply the check.

MethodPathPurpose
POST/sessionsCreate a session (201). Requires agentName, namespace, workspaceName and virtualUserId; id is generated when omitted. Body schema CreateSessionRequest.
GET/sessionsList sessions in a namespace. Returns SessionListResponse.
GET/sessions/searchFull-text search. Requires q (at most 500 bytes). Same filters as list.
GET/sessions/{sessionID}Read session metadata. The transcript is /messages.
DELETE/sessions/{sessionID}Delete one session (204). Requires namespace; a session in another namespace is 404.
DELETE/sessionsDelete every session matching namespace, with optional agent and before (RFC 3339). Returns {"deleted": N}.
PATCH/sessions/{sessionID}/statusUpdate lifecycle status (200). Body SessionStatusUpdate.
PATCH/sessions/{sessionID}/statsAlias of /status.
PATCH/sessions/{sessionID}/decorateMerge tags, removeTags and state into a session (200). promptPackDigest and promptPackVersion fill an empty column and never overwrite.
POST/sessions/{sessionID}/ttlRefresh the session TTL (200). Body RefreshTTLRequest.
GET/sessions/disclosure-summaryPer-agent EU AI Act Art. 50 disclosure posture for a namespace. Returns DisclosureSummaryResponse; 503 when no store is configured.
GET/sessions/{sessionID}/compositionResolve the composition the session ran under. Returns AgentComposition; 404 when none was recorded.
GET/compositions/{digest}Resolve a sha256:<hex> composition digest. 404 when none was recorded.

List and search query parameters:

ParameterMeaning
namespaceRequired
workspaceWorkspace name filter
agentAgent name
virtual_user_idPseudonymised end-user identity
tagRepeatable; filters conjunctively, up to 16 tags
statusactive, completed or error; any other value is 400
from, toRFC 3339 creation-time bounds
limit, offsetDefault limit 20, maximum 100; an offset above 10000 is clamped to 10000
counttrue includes the total in the response
passedfalse keeps sessions with at least one negative eval verdict. true keeps sessions with at least one positive verdict and no negative one. Any other value is 400.
evalId, evalTypeKeep sessions with at least one eval result of this id or type. With passed, the verdict is tested on those results only, so evalType=faithfulness&passed=false means faithfulness failed.

An eval result with a score and no verdict (passed null) is a measurement. It does not count as a failure or as a pass, so a session whose results are all measurements matches neither passed=false nor passed=true.

A session is an archive row. It is not the agent’s working context, which the runtime keeps in the context store (spec.context).

FieldMeaning
idSession UUID
agentName, namespace, workspaceName, workspaceUidThe agent and workspace the session belongs to
virtualUserIdPseudonymised end-user identity. Required on create.
statusactive, completed or error. completed and error are terminal, and a terminal status is never overwritten. No status marks expiry: a session past its retention is deleted or archived, not marked.
createdAt, updatedAt, expiresAt, endedAtTimestamps. A zero expiresAt means no expiry.
stateString map of values written at creation, such as user.id, promptpack.name, promptpack.version and a client declaration
tagsLabels such as source:interactive
promptPackName, promptPackVersion, promptPackDigest, compositionDigestWhat the agent ran with
messageCount, toolCallCount, totalInputTokens, totalOutputTokens, estimatedCostUSDCounters derived from the records written to the session
evalSummary{total, passed, failed} counts of the session’s eval results, on list and search responses only. failed > 0 means a failing eval; total > 0 with failed == 0 means evaluated with nothing failed; total == 0 means never evaluated. The counts cover the whole session and ignore the request’s eval filters.

A message has id, role, content, timestamp, metadata (a string map), token and cost fields, toolCallId and sequenceNum. role is user, assistant or system.

Messages, tool calls, provider calls and events

Section titled “Messages, tool calls, provider calls and events”
MethodPathPurpose
POST/sessions/{sessionID}/messagesAppend a message (201). Body Message.
GET/sessions/{sessionID}/messagesRead a page of messages. Returns MessagesResponse.
POST/sessions/{sessionID}/tool-callsRecord a tool call (201). Body ToolCall.
GET/sessions/{sessionID}/tool-callsList tool calls.
POST/sessions/{sessionID}/provider-callsRecord a provider call (201). Body ProviderCall.
GET/sessions/{sessionID}/provider-callsList provider calls.
POST/sessions/{sessionID}/eventsRecord a runtime event (201). Body RuntimeEvent.
GET/sessions/{sessionID}/eventsList runtime events.

Tool-call, provider-call and event lists take limit (default 100, maximum 500) and offset.

GET .../messages selects a page with one of these combinations. Every page is returned oldest first with hasOlder and hasNewer.

ParameterMeaning
limitDefault 50, maximum 500
afterForward page after this sequence number. With no cursor, the first page.
beforeBackward page immediately before this sequence number
tailtrue returns the last page
aroundRFC 3339 time; returns the window around the sequenced message written soonest at or after it, and sets anchor
includetoolCalls also returns the tool calls that belong to the page

tail and around cannot be combined with before, after or each other (400). An unparseable around is 400.

MethodPathPurpose
POST/eval-resultsRecord a batch of eval results (201). Body is an array of EvalResult.
GET/eval-resultsList eval results. Requires namespace. Filters: agentName, evalId, evalType, passed, limit (default 20, maximum 100), offset.
GET/sessions/{sessionID}/eval-resultsEval results for one session.
GET/sessions/{sessionID}/eval-results/summaryPer-eval totals for this session. namespace is required; agentName and evalType narrow further. A session in another namespace returns an empty summaries list.
POST/sessions/{sessionID}/evaluateQueue an evaluation (202, EvaluateAcceptedResponse). 500 when no event publisher is configured (no Redis hot cache); 400 when the session has no agent name on record.
GET/eval-results/aggregateGroup-by aggregate. Returns EvalAggregateResponse.
GET/eval-results/discoverEvals, agents and PromptPacks seen in a namespace.
GET/eval-results/scenario-historyEach arena scenario’s outcome per run, oldest first. Returns ScenarioHistoryResponse.
GET/arena/matrixArena scenario by provider-group cross-tab. Returns ArenaMatrixResponse; 503 when no store is configured.

GET /eval-results/aggregate parameters:

ParameterMeaning
namespaceRequired
groupByRequired. Comma-separated: eval_id, eval_type, agent, arenaScenario, arenaProviderGroup, arenaRun, arenaSource, time:hour, time:day. Multiple values form a composite key joined with |.
metricRequired: pass_rate, count, avg_score, p50_score, p95_score, avg_latency_ms or p95_latency_ms
workspaceUid, variant, agentName, promptpackName, evalId, evalType, arenaScenario, arenaProviderGroup, arenaRun, arenaSourceFilters
from, toRFC 3339 bounds
limitDefault 500, maximum 5000

pass_rate is computed over verdicts (passed divided by passed plus failed). A group with no verdicts is omitted from the response.

GET /eval-results/scenario-history takes namespace (required), arenaSource, workspaceUid and window (number of runs, default 9).

GET /arena/matrix takes namespace (required), job, run, scenario, provider, from, to and limit (default 500, maximum 5000).

MethodPathPurpose
GET/provider-calls/aggregateGroup-by aggregate over provider calls. Returns ProviderCallsAggregateResponse.
GET/provider-calls/discoverProviders and models seen in a namespace.
POST/provider-usageRecord a batch of session-less provider usage (201). Body is an array of ProviderUsage. 503 when no store is configured.
GET/provider-usage/aggregateGroup-by aggregate over session-less usage. Returns ProviderUsageAggregateResponse.
GET/provider-usage/discoverProviders, models and sources seen in a namespace.

Aggregate parameters:

Parameterprovider-calls/aggregateprovider-usage/aggregate
namespaceRequiredRequired
groupByRequired, comma-separatedRequired, comma-separated: provider, provider_name, model, source, time:hour, time:day
metricRequired: count, sum_cost_usd, sum_input_tokens, sum_output_tokens, sum_cached_tokens, sum_tokens, avg_duration_ms, p95_duration_msRequired: count, sum_call_count, sum_cost_usd, sum_input_tokens, sum_output_tokens, sum_cached_tokens, sum_tokens
FiltersworkspaceUid, agentName, provider, providerName, modelworkspaceName, provider, providerName, model, source
from, toRFC 3339RFC 3339
limitDefault 500Optional

provider-usage has no agent dimension and no duration metrics. Its count is the number of rows; sum_call_count is the number of provider calls.

These routes are registered only when the service runs with --enterprise.

MethodPathPurpose
GET/audit/sessionsQuery the audit log. Parameters: sessionId, userId, workspace, runId, eventTypes (comma-separated), from, to (RFC 3339), limit (default 50), offset.
POST/privacy/sessions/delete-by-userErase one subject’s sessions in this service group from the warm store, hot cache, cold archive and media. Body {"virtual_user_id","workspace","date_from","date_to"}. Returns 200 with {"sessions_deleted":N,"errors":[...]}. An empty virtual_user_id is 400.

Enterprise mode also enables PII redaction, opt-out enforcement and encryption on write routes, driven by the effective SessionPrivacyPolicy.

With --otlp-enabled, the service records OpenTelemetry GenAI traces as sessions. The operator sets the flag when the Workspace sets spec.services[].session.otlp.enabled: true, and then adds ports otlp-grpc (4317) and otlp-http (4318) to the group’s session-<workspace>-<group> Service. See Ingest OpenTelemetry traces into sessions.

The sender’s conversation ID is the first of these that is set:

  1. gen_ai.conversation.id on the span
  2. session.id on the span
  3. langfuse.session.id on the span
  4. session.id on the resource
  5. langfuse.session.id on the resource

A span with none of them is skipped. Spans that share an ID land in one session, including across traces.

A conversation ID that is a UUID is the session ID. Any other value maps to a UUIDv5 of that value in the namespace 55662429-9911-44c0-8774-cf4b4c650782, because sessions.id is a UUID column. The same value always maps to the same session. Such a session keeps the original ID in state["otlp.session_id"] and the tag otlp-session-id:<id>, so GET /api/v1/sessions?tag=otlp-session-id:<id> finds it.

AttributeUsed for
service.name (resource)agentName
service.namespace (resource)namespace
gen_ai.provider.name, or deprecated gen_ai.systemstate["gen_ai.provider"]
gen_ai.response.model, else gen_ai.request.modelstate["gen_ai.model"] and each message’s metadata["gen_ai.model"]
gen_ai.input.messages, gen_ai.output.messagesMessages. Each needs a role of user, assistant or system, except output messages, which default to assistant.
The same two attributes on a gen_ai.client.inference.operation.details span eventMessages, when the span attributes carry none
gen_ai.prompt.{i}.role and .content, gen_ai.completion.{i}.role and .contentMessages, when neither of the above carries any
gen_ai.usage.input_tokens and gen_ai.usage.output_tokens, or deprecated gen_ai.usage.prompt_tokens and gen_ai.usage.completion_tokensinputTokens and outputTokens on the span’s last assistant message, and one provider call

A span that reports token usage records one completed provider call, with or without message content. The call carries the provider, model, token counts, the span’s duration and the labels ingest: otlp, otlp.trace_id and otlp.span_id. Provider calls are what totalInputTokens and totalOutputTokens on the session add up. Their cost_usd is stored as 0, which the API omits like any zero cost, so estimatedCostUSD is unchanged.

Spans named tool.*, workflow.transition and workflow.completed become system messages carrying tool-call and workflow metadata instead.

Errors return a JSON body with a single error field (ErrorResponse):

{"error": "session not found"}
StatusCause
400Missing or invalid parameter, malformed body, invalid session ID (not a UUID), invalid status, invalid time (invalid time format, expected RFC3339), invalid message window
401Missing or invalid ServiceAccount token ({"error":"unauthorized"})
403A valid token whose subject is outside both allowlists ({"error":"forbidden"}), tenancy check failed (request fields do not match the authenticated caller), or subject proof rejected
404Session or composition not found
413Request body exceeds MAX_BODY_SIZE (default 16 MiB)
429Rate limit exceeded
500Internal error (internal server error)
503A required store is not configured (warm store, eval, provider calls, provider usage, arena, disclosure)