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.
Service endpoints
Section titled “Service endpoints”| Port | Flag | Purpose |
|---|---|---|
8080 | --api-addr | REST API |
8081 | --health-addr | /healthz and /readyz (readiness pings Postgres) |
9090 | --metrics-addr | Prometheus metrics at /metrics |
4317 | --otlp-grpc-addr | OTLP gRPC trace ingestion (only with --otlp-enabled) |
4318 | --otlp-http-addr | OTLP 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.
Authentication
Section titled “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.
| Flag | Environment variable | Meaning |
|---|---|---|
--auth-enabled | SESSION_API_AUTH_ENABLED | Require ServiceAccount tokens. The binary default is false. |
--auth-allowed-subjects | SESSION_API_AUTH_ALLOWED_SUBJECTS | Comma-separated system:serviceaccount:<namespace>:<name> subjects (cross-namespace callers such as the dashboard) |
--auth-allowed-namespaces | SESSION_API_AUTH_ALLOWED_NAMESPACES | Comma-separated namespaces whose ServiceAccounts are all admitted |
--auth-audiences | SESSION_API_AUTH_AUDIENCES | Comma-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"}.
Workspace scoping
Section titled “Workspace scoping”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.
| Parameter | Meaning |
|---|---|
namespace | Kubernetes namespace. Required on list, search, delete, the per-session eval summary and every aggregate, discovery and arena route. A missing value returns 400. |
workspace | GET /sessions and /sessions/search only: filters on the workspace name in addition to namespace. It does not replace namespace. |
workspaceUid | Optional 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:
| Route | Check |
|---|---|
POST /sessions | Body 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/discover | Same 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/summary | Same 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.
Session operations
Section titled “Session operations”| Method | Path | Purpose |
|---|---|---|
POST | /sessions | Create a session (201). Requires agentName, namespace, workspaceName and virtualUserId; id is generated when omitted. Body schema CreateSessionRequest. |
GET | /sessions | List sessions in a namespace. Returns SessionListResponse. |
GET | /sessions/search | Full-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 | /sessions | Delete every session matching namespace, with optional agent and before (RFC 3339). Returns {"deleted": N}. |
PATCH | /sessions/{sessionID}/status | Update lifecycle status (200). Body SessionStatusUpdate. |
PATCH | /sessions/{sessionID}/stats | Alias of /status. |
PATCH | /sessions/{sessionID}/decorate | Merge tags, removeTags and state into a session (200). promptPackDigest and promptPackVersion fill an empty column and never overwrite. |
POST | /sessions/{sessionID}/ttl | Refresh the session TTL (200). Body RefreshTTLRequest. |
GET | /sessions/disclosure-summary | Per-agent EU AI Act Art. 50 disclosure posture for a namespace. Returns DisclosureSummaryResponse; 503 when no store is configured. |
GET | /sessions/{sessionID}/composition | Resolve 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:
| Parameter | Meaning |
|---|---|
namespace | Required |
workspace | Workspace name filter |
agent | Agent name |
virtual_user_id | Pseudonymised end-user identity |
tag | Repeatable; filters conjunctively, up to 16 tags |
status | active, completed or error; any other value is 400 |
from, to | RFC 3339 creation-time bounds |
limit, offset | Default limit 20, maximum 100; an offset above 10000 is clamped to 10000 |
count | true includes the total in the response |
passed | false 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, evalType | Keep 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.
Session record
Section titled “Session record”A session is an archive row. It is not the agent’s working context, which the
runtime keeps in the context store (spec.context).
| Field | Meaning |
|---|---|
id | Session UUID |
agentName, namespace, workspaceName, workspaceUid | The agent and workspace the session belongs to |
virtualUserId | Pseudonymised end-user identity. Required on create. |
status | active, 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, endedAt | Timestamps. A zero expiresAt means no expiry. |
state | String map of values written at creation, such as user.id, promptpack.name, promptpack.version and a client declaration |
tags | Labels such as source:interactive |
promptPackName, promptPackVersion, promptPackDigest, compositionDigest | What the agent ran with |
messageCount, toolCallCount, totalInputTokens, totalOutputTokens, estimatedCostUSD | Counters 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”| Method | Path | Purpose |
|---|---|---|
POST | /sessions/{sessionID}/messages | Append a message (201). Body Message. |
GET | /sessions/{sessionID}/messages | Read a page of messages. Returns MessagesResponse. |
POST | /sessions/{sessionID}/tool-calls | Record a tool call (201). Body ToolCall. |
GET | /sessions/{sessionID}/tool-calls | List tool calls. |
POST | /sessions/{sessionID}/provider-calls | Record a provider call (201). Body ProviderCall. |
GET | /sessions/{sessionID}/provider-calls | List provider calls. |
POST | /sessions/{sessionID}/events | Record a runtime event (201). Body RuntimeEvent. |
GET | /sessions/{sessionID}/events | List 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.
| Parameter | Meaning |
|---|---|
limit | Default 50, maximum 500 |
after | Forward page after this sequence number. With no cursor, the first page. |
before | Backward page immediately before this sequence number |
tail | true returns the last page |
around | RFC 3339 time; returns the window around the sequenced message written soonest at or after it, and sets anchor |
include | toolCalls 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.
Eval results
Section titled “Eval results”| Method | Path | Purpose |
|---|---|---|
POST | /eval-results | Record a batch of eval results (201). Body is an array of EvalResult. |
GET | /eval-results | List eval results. Requires namespace. Filters: agentName, evalId, evalType, passed, limit (default 20, maximum 100), offset. |
GET | /sessions/{sessionID}/eval-results | Eval results for one session. |
GET | /sessions/{sessionID}/eval-results/summary | Per-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}/evaluate | Queue 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/aggregate | Group-by aggregate. Returns EvalAggregateResponse. |
GET | /eval-results/discover | Evals, agents and PromptPacks seen in a namespace. |
GET | /eval-results/scenario-history | Each arena scenario’s outcome per run, oldest first. Returns ScenarioHistoryResponse. |
GET | /arena/matrix | Arena scenario by provider-group cross-tab. Returns ArenaMatrixResponse; 503 when no store is configured. |
GET /eval-results/aggregate parameters:
| Parameter | Meaning |
|---|---|
namespace | Required |
groupBy | Required. Comma-separated: eval_id, eval_type, agent, arenaScenario, arenaProviderGroup, arenaRun, arenaSource, time:hour, time:day. Multiple values form a composite key joined with |. |
metric | Required: 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, arenaSource | Filters |
from, to | RFC 3339 bounds |
limit | Default 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).
Cost and usage
Section titled “Cost and usage”| Method | Path | Purpose |
|---|---|---|
GET | /provider-calls/aggregate | Group-by aggregate over provider calls. Returns ProviderCallsAggregateResponse. |
GET | /provider-calls/discover | Providers and models seen in a namespace. |
POST | /provider-usage | Record a batch of session-less provider usage (201). Body is an array of ProviderUsage. 503 when no store is configured. |
GET | /provider-usage/aggregate | Group-by aggregate over session-less usage. Returns ProviderUsageAggregateResponse. |
GET | /provider-usage/discover | Providers, models and sources seen in a namespace. |
Aggregate parameters:
| Parameter | provider-calls/aggregate | provider-usage/aggregate |
|---|---|---|
namespace | Required | Required |
groupBy | Required, comma-separated | Required, comma-separated: provider, provider_name, model, source, time:hour, time:day |
metric | Required: count, sum_cost_usd, sum_input_tokens, sum_output_tokens, sum_cached_tokens, sum_tokens, avg_duration_ms, p95_duration_ms | Required: count, sum_call_count, sum_cost_usd, sum_input_tokens, sum_output_tokens, sum_cached_tokens, sum_tokens |
| Filters | workspaceUid, agentName, provider, providerName, model | workspaceName, provider, providerName, model, source |
from, to | RFC 3339 | RFC 3339 |
limit | Default 500 | Optional |
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.
Enterprise routes
Section titled “Enterprise routes”These routes are registered only when the service runs with --enterprise.
| Method | Path | Purpose |
|---|---|---|
GET | /audit/sessions | Query the audit log. Parameters: sessionId, userId, workspace, runId, eventTypes (comma-separated), from, to (RFC 3339), limit (default 50), offset. |
POST | /privacy/sessions/delete-by-user | Erase 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.
OTLP ingestion
Section titled “OTLP ingestion”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.
Session identity
Section titled “Session identity”The sender’s conversation ID is the first of these that is set:
gen_ai.conversation.idon the spansession.idon the spanlangfuse.session.idon the spansession.idon the resourcelangfuse.session.idon 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.
Attributes read
Section titled “Attributes read”| Attribute | Used for |
|---|---|
service.name (resource) | agentName |
service.namespace (resource) | namespace |
gen_ai.provider.name, or deprecated gen_ai.system | state["gen_ai.provider"] |
gen_ai.response.model, else gen_ai.request.model | state["gen_ai.model"] and each message’s metadata["gen_ai.model"] |
gen_ai.input.messages, gen_ai.output.messages | Messages. 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 event | Messages, when the span attributes carry none |
gen_ai.prompt.{i}.role and .content, gen_ai.completion.{i}.role and .content | Messages, 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_tokens | inputTokens 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.
Error format
Section titled “Error format”Errors return a JSON body with a single error field (ErrorResponse):
{"error": "session not found"}| Status | Cause |
|---|---|
400 | Missing or invalid parameter, malformed body, invalid session ID (not a UUID), invalid status, invalid time (invalid time format, expected RFC3339), invalid message window |
401 | Missing or invalid ServiceAccount token ({"error":"unauthorized"}) |
403 | A 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 |
404 | Session or composition not found |
413 | Request body exceeds MAX_BODY_SIZE (default 16 MiB) |
429 | Rate limit exceeded |
500 | Internal error (internal server error) |
503 | A required store is not configured (warm store, eval, provider calls, provider usage, arena, disclosure) |