# Generated by scripts/sync-openapi.mjs from nvoken's Runtime and Identity specs.
# The public hosted reference intentionally omits installation-management operations.
openapi: 3.1.0
info:
  title: nvoken API
  version: 0.1.0
  summary: Run agent turns and keep their conversation history
  description: The hosted nvoken API runs the agent loop, stores Sessions and messages, and lets your backend recover or stream every accepted turn. Create an App and API key at nvoken.com before calling these endpoints.
servers:
  - url: /
    description: Use the API base URL shown on your App's API keys page
security:
  - bearerAuth: []
tags:
  - name: Identity
    description: The App and permissions attached to the current API key.
  - name: Agents
    description: Stable Agent identities used across an App's Sessions and Invocations.
  - name: Invocations
    description: Durable agent turns, from admission through their final result.
  - name: Models
    description: Discover the models and controls available through nvoken.
  - name: MCP
    description: Preview the tools exposed by a remote MCP server.
  - name: Sessions
    description: Conversations, messages, transcripts, and durable state.
  - name: Usage
    description: Daily token and estimated-cost totals for the current App.
paths:
  /v1/agents:
    get:
      tags:
        - Agents
      operationId: listAgents
      summary: List Agent identity anchors
      description: |
        Returns newest-first identity anchors scoped to the caller's app. An
        Agent stores only its nvoken ID, the host-owned `agent_key`, and
        creation time; instructions, models, tools, and credentials still
        travel on each Invocation. An exact `agent_key` filter returns zero or
        one item.

        Unconstrained credentials see the anchors referenced by a Session in
        one of their app's tenants. Tenant-constrained credentials see only
        anchors referenced by a Session in their effective partition.
        Session-constrained credentials see only that Session's anchor. The
        opaque cursor is bound to the authenticated caller, credential
        constraint, and exact key filter.
      parameters:
        - $ref: "#/components/parameters/AgentKeyFilter"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One bounded page of Agent identity anchors.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentList"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/agents/{agent_id}:
    parameters:
      - $ref: "#/components/parameters/AgentID"
    get:
      tags:
        - Agents
      operationId: getAgent
      summary: Read one Agent identity anchor
      description: |
        Reads identity without admitting work. Out-of-scope and undisclosable
        constrained resources use `not_found`.
      responses:
        "200":
          description: One Agent identity anchor.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Agent"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/mcp/list-tools:
    post:
      tags:
        - MCP
      operationId: listMCPTools
      summary: Discover and project one remote MCP server's tools
      description: |
        Opens one short-lived streamable-HTTP MCP session through guarded
        public-only egress, drains tools/list pagination, applies the same
        allowlist and projection rules used by Invocation execution, and then
        closes the session. Supplied headers are used only for this request,
        are never logged or returned, and are not persisted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MCPListToolsRequest"
      responses:
        "200":
          description: Stable projected tools and bounded exclusions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MCPListToolsResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "502":
          $ref: "#/components/responses/MCPDiscoveryFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations:
    get:
      tags:
        - Invocations
      operationId: listInvocations
      summary: List authoritative Invocations
      description: |
        Returns newest-first durable Invocation state. Exact filters combine
        with AND. An App-wide caller may list all tenant partitions, one
        named partition with `tenant_key`, or the default partition with
        `default_tenant=true`. A tenant-constrained credential is always scoped
        to its partition. The opaque cursor is bound to the normalized filter
        set and credential tenant scope. `agent_id` and `agent_key` are
        mutually exclusive; both normalize to the resolved Agent ID for cursor
        binding, so an equivalent cursor may resume under either spelling.
      parameters:
        - $ref: "#/components/parameters/TenantKeyFilter"
        - $ref: "#/components/parameters/DefaultTenantFilter"
        - $ref: "#/components/parameters/UserKeyFilter"
        - name: session_id
          in: query
          schema:
            $ref: "#/components/schemas/SessionID"
        - name: agent_id
          in: query
          description: Mutually exclusive with agent_key.
          schema:
            $ref: "#/components/schemas/AgentID"
        - $ref: "#/components/parameters/AgentKeyFilter"
        - name: status
          in: query
          description: |
            Repeat to select a union of statuses. Order and duplicates are
            normalized before cursor binding.
          style: form
          explode: true
          schema:
            type: array
            minItems: 1
            items:
              $ref: "#/components/schemas/InvocationStatus"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One bounded page of Invocations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvocationList"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
    post:
      tags:
        - Invocations
      operationId: createInvocation
      summary: Durably admit one background agent turn
      description: |
        Resolves or creates the App-wide Agent identity anchor, resolves or
        creates a Session, snapshots the inline execution spec, appends exactly
        one caller-input message, and creates one queued Invocation in a single
        transaction. The response is sent only after that transaction commits;
        the request handler never executes the model.

        `session_id` and `session_key` are mutually exclusive. A Session ID must
        belong to the named Agent, or be a Session created without one — the
        first admitted Invocation binds the Agent permanently. An App-wide
        credential may omit `tenant_key` and use the Session's stored partition.
        A tenant-constrained credential cannot cross its partition; an explicit
        mismatch is rejected with `403 forbidden` before resource lookup.

        `idempotency_key` is scoped to the effective
        tenant partition, and `agent_key`. A same-key replay is checked before
        the Session's one-nonterminal-Invocation rule. It returns the original
        records without appending input, even when the Invocation is terminal.
        Material equality covers the Session selector kind and value, the inline
        spec, and input. Requested limits are compared before default resolution,
        so an explicit default differs from omission. JSON object member
        order is ignored, array order is significant, and strings are not
        rewritten. A changed material field returns `idempotency_conflict`.

        `if_active` defaults to `reject`, preserving
        `session_invocation_active`. Explicit `supersede` requires both create
        and cancel authority. While holding the Session lock, it durably
        cancels any nonterminal Invocation and admits this replacement in the
        same transaction. With no active work it admits normally. Equal replay
        returns the original work before supersession and never cancels newer
        active work.

        `interrupt` requires the same authority and keeps the same lock
        ordering, but stops the active Invocation gracefully instead of
        cancelling it: work already at a seam settles and the replacement is
        admitted in the same transaction, while work under a live lease has
        the interrupt recorded and this request waits for it to settle. If it
        has not settled within the wait, the response is
        `session_invocation_active` with `details.interrupt_requested = true`
        and the request may be reissued.

        A text-only request body is limited to 1 MiB. A body carrying image or
        document blocks is limited to 24 MiB, within which decoded media is
        bounded separately: at most 8 media blocks, 16 MiB decoded in total,
        5 MiB per image, and 16 MiB per document. Requests over any of these
        limits are rejected before admission.

        The supported streaming pattern is admit-then-stream: admit with a
        plain JSON POST, then follow the Invocation over
        `GET /v1/invocations/{invocation_id}/stream`. It survives a dropped
        connection without re-admitting and does not depend on front-end
        streaming behavior. `Accept: text/event-stream` on this request is a
        deployment-dependent convenience, not a first-class mode: the
        committed admission is the first `invocation.accepted` frame and the
        connection tails that Invocation through `invocation.result`, but
        delivery requires the deployment front end to stream a non-`200`
        POST response unbuffered. Some managed front ends (including Cloud
        Run's) buffer it until the Invocation settles, which makes a
        host-tool turn's `waiting` state invisible on this connection.
      parameters:
        - name: X-Anthropic-Api-Key
          in: header
          required: false
          description: |
            Caller-supplied Anthropic API key, equivalent to a
            caller_ephemeral `provider_credentials` selection. The header
            must name the spec model provider and cannot be combined with
            the body field. Siblings: X-Openai-Api-Key, X-Gemini-Api-Key,
            X-Xai-Api-Key.
          schema:
            type: string
            minLength: 1
            maxLength: 65536
        - name: X-Openai-Api-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 65536
        - name: X-Gemini-Api-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 65536
        - name: X-Xai-Api-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 65536
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateInvocationRequest"
            examples:
              newSession:
                summary: First use creates the Agent anchor and a new Session
                value:
                  agent_key: support-triage
                  tenant_key: tenant-acme
                  idempotency_key: ticket-483:first-reply
                  input: Why was I charged twice?
                  spec:
                    model:
                      provider: anthropic
                      id: claude-sonnet-5
              keyedSession:
                summary: Resolve or create a tenant-partitioned host Session key
                value:
                  agent_key: support-triage
                  tenant_key: tenant-acme
                  session_key: ticket-483
                  idempotency_key: ticket-483:follow-up-2
                  input:
                    - type: text
                      text: Can you show me the two charge dates?
                  spec:
                    instructions: You are a concise billing support agent.
                    model:
                      provider: anthropic
                      id: claude-sonnet-5
              existingSession:
                summary: Continue an existing Session by durable ID
                value:
                  agent_key: support-triage
                  session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                  idempotency_key: ticket-483:follow-up-3
                  input:
                    - type: text
                      text: Please summarize what happened.
                  spec:
                    instructions: You are a concise billing support agent.
                    model:
                      provider: openai
                      id: gpt-5.4
              structuredOutput:
                summary: Require one validated machine-readable object
                value:
                  agent_key: support-triage
                  session_key: ticket-483
                  idempotency_key: ticket-483:classification
                  input:
                    - type: text
                      text: Classify the billing problem.
                  spec:
                    instructions: Classify the request, then submit the result.
                    model:
                      provider: anthropic
                      id: claude-sonnet-5
                    structured_output:
                      schema:
                        type: object
                        properties:
                          category:
                            type: string
                            enum:
                              - duplicate_charge
                              - refund
                              - other
                        required:
                          - category
                        additionalProperties: false
      responses:
        "202":
          description: |
            The Invocation is durably admitted, or an existing Invocation was
            returned for an equal idempotent replay. A replay remains `202` even
            when the returned status is terminal. `Accept: text/event-stream`
            selects an Invocation-scoped stream with `invocation.accepted` as
            its first frame and `invocation.result` as its terminal durable
            frame — a deployment-dependent convenience; the supported pattern
            is a plain JSON admission followed by
            `GET /v1/invocations/{invocation_id}/stream`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvocationAcknowledgement"
              examples:
                admitted:
                  summary: New durable admission
                  value:
                    agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                    session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                    invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                    status: queued
                    deduplicated: false
                    deadline_at: 2026-07-20T14:31:02Z
                replayed:
                  summary: Lost acknowledgement followed by equal replay
                  value:
                    agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                    session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                    invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                    status: running
                    deduplicated: true
                    deadline_at: 2026-07-20T14:31:02Z
                terminalReplay:
                  summary: Equal replay after settlement still returns 202
                  value:
                    agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                    session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                    invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                    status: completed
                    deduplicated: true
                    deadline_at: 2026-07-20T14:31:02Z
            text/event-stream:
              schema:
                $ref: "#/components/schemas/InvocationStreamEvent"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: The idempotency key conflicts or the Session already has nonterminal work.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                idempotencyConflict:
                  summary: Same scope and key, changed material request
                  value:
                    code: idempotency_conflict
                    message: The idempotency key was already used with a different request.
                    request_id: req_01K0NV89D93D0W2V5CGF8BC2R7
                    details:
                      conflicting_paths:
                        - /input
                activeInvocation:
                  summary: Distinct request races existing nonterminal work
                  value:
                    code: session_invocation_active
                    message: This Session already has a nonterminal Invocation.
                    request_id: req_01K0NV8AJQ9FB89N5MZ5HDZDR3
                    details:
                      invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                      status: running
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    get:
      tags:
        - Invocations
      operationId: getInvocation
      summary: Read authoritative Invocation identity and state
      description: |
        Returns the durable current state. Post-admission failures appear here.
        A credential authenticated for Runtime but denied this read operation
        receives `forbidden`.
        A resource outside the caller's tenant constraint is
        reported as `not_found` when disclosure would reveal its existence.
      responses:
        "200":
          description: Authoritative Invocation state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Invocation"
              examples:
                queuedAfterEngineLoss:
                  value:
                    id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                    agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                    session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                    spec_id: spec_019b0a12-5b21-7c04-8ed2-0e07c1bdb300
                    spec:
                      model:
                        provider: anthropic
                        id: claude-sonnet-5
                    status: queued
                    error: null
                    usage: null
                    provenance: null
                    structured_output: null
                    structured_output_provenance: null
                    limits:
                      total_timeout_seconds: 1800
                      active_timeout_seconds: 1800
                      waiting_timeout_seconds: 1800
                      max_iterations: 1
                    active_execution_ms: 7000
                    deadline_at: 2026-07-20T14:31:02Z
                    created_at: 2026-07-20T14:01:02Z
                    updated_at: 2026-07-20T14:01:09Z
                    ended_at: 2026-07-20T14:01:09Z
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/result:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    get:
      tags:
        - Invocations
      operationId: getInvocationResult
      summary: Read the composed Invocation result
      description: |
        Returns one InvocationResult at any status: the authoritative
        Invocation, this Invocation's canonical messages composed at read
        time, and the output_text convenience projection. The Invocation and
        its messages are read in one repeatable-read snapshot, so the payload
        never shows a terminal status with a missing message tail.
        Authentication, tenant scoping, and the nondisclosing not_found rule
        match the plain Invocation read exactly.
      responses:
        "200":
          description: Composed Invocation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvocationResult"
              examples:
                completedTextTurn:
                  value:
                    invocation:
                      id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                      agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                      session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                      spec_id: spec_019b0a12-5b21-7c04-8ed2-0e07c1bdb300
                      spec:
                        model:
                          provider: anthropic
                          id: claude-sonnet-5
                      status: completed
                      error: null
                      usage: null
                      provenance: null
                      structured_output: null
                      structured_output_provenance: null
                      limits:
                        total_timeout_seconds: 1800
                        active_timeout_seconds: 1800
                        waiting_timeout_seconds: 1800
                        max_iterations: 1
                      active_execution_ms: 7000
                      deadline_at: 2026-07-20T14:31:02Z
                      created_at: 2026-07-20T14:01:02Z
                      updated_at: 2026-07-20T14:01:09Z
                      ended_at: 2026-07-20T14:01:09Z
                    messages:
                      - id: smsg_019b0a12-9e61-7a44-bed2-0e07c1bdb333
                        session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                        agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                        invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                        sequence: 1
                        role: user
                        content:
                          - type: text
                            text: Reply with a short hello.
                        created_at: 2026-07-20T14:01:02Z
                      - id: smsg_019b0a12-af71-7b54-9ed2-0e07c1bdb344
                        session_id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                        agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                        invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                        sequence: 2
                        role: assistant
                        content:
                          - type: text
                            text: Hello!
                        created_at: 2026-07-20T14:01:09Z
                    output_text: Hello!
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/stream:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    get:
      tags:
        - Invocations
      operationId: streamInvocation
      summary: Resume and tail one Invocation over SSE
      description: |
        Replays durable updates after `cursor`, optionally forwards ephemeral
        output previews for this Invocation, and ends only when this Invocation
        settles or the connection rotates. Durable frames carry an SSE `id`;
        previews and control frames do not. After `stream.resync`, discard
        provisional output and wait for durable state.

        The explicit `cursor` query parameter takes precedence over
        `Last-Event-ID`. `stream.end` reason `rotate` means reconnect with the
        last durable ID. `deltas=false` skips preview fan-out without changing
        replay, polling, cursor, or terminal semantics. Disconnecting never
        cancels the Invocation.
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - name: deltas
          in: query
          description: Include id-less output and thinking preview frames. Defaults to true.
          schema:
            type: boolean
            default: true
        - name: Last-Event-ID
          in: header
          description: Opaque cursor from the last durable frame; ignored when `cursor` is supplied.
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Invocation-scoped event stream.
          content:
            text/event-stream:
              schema:
                $ref: "#/components/schemas/InvocationStreamEvent"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    post:
      tags:
        - Invocations
      operationId: cancelInvocation
      summary: Idempotently cancel an Invocation
      description: |
        Atomically makes nonterminal work `cancelled`. Repeating the request,
        or cancelling an Invocation that already completed or failed, returns
        the unchanged authoritative terminal row. A successful response means
        cancellation is durable; provider work stops cooperatively and may
        have already incurred external cost. The request body must be empty.
      responses:
        "200":
          description: Authoritative terminal Invocation state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Invocation"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/interrupt:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    post:
      tags:
        - Invocations
      operationId: interruptInvocation
      summary: Gracefully stop an Invocation and keep its work
      description: |
        Asks the turn to stop at its next execution seam and settle
        `completed` with `stop_reason = interrupted`, so its assistant and
        tool messages stay in the next turn's generation context. That is the
        whole difference from cancellation, which drops the turn's work from
        every later turn.

        The request is durable and idempotent. Work already at a seam —
        `queued`, `waiting`, or `running` with no live owner — settles before
        this call returns, closing any pending tool calls with synthetic
        results; a subsequently submitted tool result then gets the ordinary
        terminal `409`. Work under a live lease records the request and
        returns the Invocation unchanged, still `running`: its executor stops
        at the next checkpoint boundary, at worst one provider call away.
        Follow the Invocation stream or re-read it to observe settlement.

        Interrupting terminal work is a no-op that returns the unchanged
        terminal row. A turn whose spec carries `structured_output` and that
        never published a validated object fails
        `structured_output_unsatisfied` instead of completing — the
        obligation was not met. Usage and settlement evidence are complete:
        the work was kept, so it is charged. The request body must be empty.
      responses:
        "200":
          description: Authoritative Invocation state after the request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Invocation"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/nudge:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    post:
      tags:
        - Invocations
      operationId: nudgeInvocation
      summary: Append steering to a running Invocation
      description: |
        Stages caller guidance against a turn that is already running — "focus
        on the marine segment" — without ending it and without discarding the
        work being steered. This is the difference from
        `if_active: supersede`, which rewinds: a superseded turn's assistant
        and tool work product is dropped from the next generation, so steering
        a long turn by supersession throws away exactly what is being steered.

        A nudge is not an interrupt. The earliest injection point is the next
        execution seam: the start of the next execution segment, which is a
        host-tool turn's park boundary, or the stop seam where a turn that
        considers itself finished re-enters its loop to answer the input in
        place. An in-flight provider call or tool run is never aborted for it,
        and an Invocation that has been interrupted is never given more to do —
        the interrupt wins the seam and the staged input expires.

        A nudge is never implicitly minted and never implicitly converted.
        `POST /v1/invocations` against a busy Session keeps its `if_active`
        semantics; admission is never silently turned into a nudge, and a
        nudge is never silently turned into an Invocation. nvoken holds no
        agent configuration — the spec travels inline on every admission — so
        there is nothing it could legitimately run converted input with.

        Input that the turn never takes is settled `expired` when the
        Invocation settles, in the same transaction and under the same Session
        lock this request takes. It never influences a later Invocation.
        Re-sending missed direction as the next Invocation's input is the
        host's call to make, and `GET .../pending-inputs` is where the miss is
        visible.

        `content` is text: a string, or an array of text blocks. Image and
        document blocks are accepted on an Invocation's own input but not
        here, because the stop seam carries a continuation as text and staging
        content one seam would silently drop is worse than refusing it.

        Requires the same authority as cancelling the Invocation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NudgeInvocationRequest"
            example:
              content: Focus on the marine segment for the rest of this pass.
              idempotency_key: nudge-1
      responses:
        "202":
          description: The input is staged for the next execution seam.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NudgeAcknowledgement"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: |
            `invocation_not_active` when the Invocation is already terminal;
            `idempotency_conflict` when `idempotency_key` was already used with
            different content; `nudge_queue_full` when the Invocation already
            holds the maximum pending inputs, with the bound in
            `details.limit`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/pending-inputs:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    get:
      tags:
        - Invocations
      operationId: listPendingInputs
      summary: List staged input for an Invocation
      description: |
        Returns the staged queue in `(created_at, id)` ascending order — the
        order the turn will consume it — with settled rows retained so a
        caller can answer "what did the user say, and did the model see it".
        This, not an event vocabulary, is the reconciliation source for a
        surface that shows queued direction: a drained input also appears on
        the transcript stream as an ordinary `user` message.
      parameters:
        - name: status
          in: query
          description: Restrict to one status.
          schema:
            $ref: "#/components/schemas/PendingInputStatus"
        - $ref: "#/components/parameters/Cursor"
        - name: limit
          in: query
          description: Maximum items in this page. Defaults to 20.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: One page of staged input in consumption order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PendingInputList"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/pending-inputs/{pending_input_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
      - $ref: "#/components/parameters/PendingInputID"
    post:
      tags:
        - Invocations
      operationId: cancelPendingInput
      summary: Withdraw staged input the turn has not taken
      description: |
        Cancels input that is still `pending`. Cancelling input that is
        already `cancelled` returns it unchanged, so a retry is safe.

        Cancel and drain serialize on the row, so whichever commits first
        wins outright: a cancelled input is never seen by the model, and input
        the executor already drained is reported `409 pending_input_settled`
        with the current resource in `details.pending_input` rather than being
        withdrawn from a transcript it is already part of. The request body
        must be empty.

        Requires the same authority as cancelling the Invocation.
      responses:
        "200":
          description: The pending input after the request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PendingInput"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: |
            `pending_input_settled` when the input was already drained or
            expired. `details.pending_input` carries its current state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/invocations/{invocation_id}/tool-results:
    parameters:
      - $ref: "#/components/parameters/InvocationID"
    post:
      tags:
        - Invocations
      operationId: submitHostToolResults
      summary: Submit durable results for pending host ToolCalls
      description: |
        Atomically accepts one bounded batch for a waiting Invocation. The
        first committed result for each ToolCall wins. An equal replay is
        acknowledged as deduplicated; a changed replay conflicts. Partial
        batches leave the Invocation waiting. Closing the final pending call
        queues the same Invocation and its successor execution dispatch before
        returning `202`.

        This command accepts only host-mode calls owned by the path
        Invocation and authenticated tenant scope. It is not a generic
        Session append endpoint. The body is limited to 1 MiB; each result
        content value is valid JSON limited to 256 KiB and 32 nesting levels.

        `content` accepts any JSON value and the stored transcript retains it
        verbatim. Before a result reaches the model, a string or an array of
        content blocks passes through unchanged; any other value is
        serialized to its compact JSON text and sent as a string, so the
        model sees the same bytes a host that pre-stringifies would send.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitHostToolResultsRequest"
            example:
              results:
                - tool_call_id: tcal_019b0a12-9e61-7144-bed2-0e07c1bdb333
                  content:
                    order_id: order-123
                    state: ready
                - tool_call_id: tcal_019b0a12-af71-7254-8ed2-0e07c1bdb344
                  content:
                    delivered: false
                  is_error: true
      responses:
        "202":
          description: The complete batch committed, possibly as equal replays.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmitHostToolResultsResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: The Invocation cannot accept the supplied result batch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                notWaiting:
                  value:
                    code: invocation_not_waiting
                    message: The Invocation is not waiting for this host tool result.
                    request_id: req_01K0NV8AJQ9FB89N5MZ5HDZDR3
                conflict:
                  value:
                    code: tool_result_conflict
                    message: A different result was already accepted for this ToolCall.
                    request_id: req_01K0NV8AJQ9FB89N5MZ5HDZDR3
                expired:
                  value:
                    code: tool_result_expired
                    message: The host tool result deadline has expired.
                    request_id: req_01K0NV8AJQ9FB89N5MZ5HDZDR3
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/usage/daily:
    get:
      tags:
        - Usage
      operationId: getDailyUsage
      summary: Read daily usage rollups
      description: Returns daily model usage for the current App, grouped by provider and model. Token counts and estimated cost come from durable Invocation and Session records.
      parameters:
        - name: start_date
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Inclusive first UTC day. Defaults to 29 days before `end_date`.
        - name: end_date
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Inclusive last UTC day. Defaults to the current UTC day.
      responses:
        "200":
          description: Daily usage buckets ordered by day, app, provider, and model.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DailyUsage"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/models:
    get:
      tags:
        - Models
      operationId: listModels
      summary: List nvoken's curated model catalog
      description: |
        Returns the complete bounded set of text-generation models nvoken
        intentionally advertises. Catalog membership means nvoken maintains
        metadata for the exact provider/model selection. It does not prove that
        the caller's provider account, region, or selected credential can access
        the model. Ordering is deterministic but has no semantic meaning.
      parameters:
        - name: provider
          in: query
          description: Limit results to one installed canonical provider.
          schema:
            $ref: "#/components/schemas/ModelProvider"
        - name: include_deprecated
          in: query
          description: Include catalog entries nvoken marks as deprecated.
          schema:
            type: boolean
            default: false
        - $ref: "#/components/parameters/IfNoneMatch"
      responses:
        "200":
          description: The complete matching model catalog.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"
              examples:
                catalog:
                  value:
                    items:
                      - provider: anthropic
                        id: claude-opus-4-8
                        cataloged: true
                        display_name: Claude Opus 4.8
                        description: Deep reasoning for complex agent workloads.
                        context_window_tokens: 1000000
                        max_output_tokens: 128000
                        input_modalities:
                          - text
                        recommended: false
                        deprecated: false
                        pricing:
                          status: priced
                          currency: USD
                          unit: per_million_tokens
                          input: "5"
                          output: "25"
                          cache_read: "0.5"
                          cache_write: "6.25"
                          updated_at: 2026-05-28
                          pricing_version: b3f91e2a
                    catalog_version: 665048c7
        "304":
          description: The matching representation has not changed.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/models/{provider}/{model_id}:
    parameters:
      - name: provider
        in: path
        required: true
        description: Installed canonical model provider.
        schema:
          $ref: "#/components/schemas/ModelProvider"
      - name: model_id
        in: path
        required: true
        allowReserved: false
        description: |
          Exact model ID used in `spec.model.id`. Encode the complete value as
          one path segment, including `/`, reserved characters, and Unicode.
        schema:
          type: string
          minLength: 1
          maxLength: 255
    get:
      tags:
        - Models
      operationId: getModel
      summary: Inspect one exact provider/model selection
      description: |
        Returns a descriptor for any valid installed provider and exact model
        ID, including IDs outside the curated catalog. `cataloged` distinguishes
        maintained metadata from tolerant inspection. Pricing is the standard
        local USD estimate used by nvoken's estimated-cost guardrail, not
        provider billing or proof of account access.
      parameters:
        - $ref: "#/components/parameters/IfNoneMatch"
      responses:
        "200":
          description: Exact model-selection descriptor.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelDescriptor"
              examples:
                uncataloged:
                  value:
                    provider: openai
                    id: experimental/model
                    cataloged: false
                    pricing:
                      status: unpriced
                      pricing_version: 8fd0a7c2
        "304":
          description: The exact descriptor has not changed.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/sessions:
    post:
      tags:
        - Sessions
      operationId: createSession
      summary: Create a Session without admitting an Invocation
      description: |
        Creates a session shell with zero messages and zero invocations, in
        the same transaction shape admission uses for its session resolution.
        Every body field is optional. An omitted `agent_key` leaves the
        Session unbound: `agent_id` is null until the first admitted
        Invocation binds it, and the binding is immutable once set either
        way. A `session_key` requires an `agent_key`, because key uniqueness
        and lookup are scoped per (tenant partition, Agent); a keyed create
        is an upsert that returns an existing keyed Session unchanged.
        Without a `session_key`, every call creates a fresh Session.
        Precedence for the tenant partition is credential constraint,
        explicit `tenant_key`, then the default partition.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSessionRequest"
      responses:
        "201":
          description: The created (or key-resolved existing) Session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
    get:
      tags:
        - Sessions
      operationId: listSessions
      summary: List authoritative Sessions
      description: |
        Returns newest-first Session identity and current nonterminal
        Invocation state. Exact filters combine with AND. Tenant filtering and
        cursor binding follow the Invocation-list rules. `agent_id` and
        `agent_key` are mutually exclusive and normalize to the same cursor
        filter.
      parameters:
        - $ref: "#/components/parameters/TenantKeyFilter"
        - $ref: "#/components/parameters/DefaultTenantFilter"
        - $ref: "#/components/parameters/UserKeyFilter"
        - name: agent_id
          in: query
          description: Mutually exclusive with agent_key.
          schema:
            $ref: "#/components/schemas/AgentID"
        - $ref: "#/components/parameters/AgentKeyFilter"
        - name: session_key
          in: query
          schema:
            type: string
            minLength: 1
            maxLength: 255
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One bounded page of Sessions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionList"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/sessions/{session_id}:
    parameters:
      - $ref: "#/components/parameters/SessionID"
    get:
      tags:
        - Sessions
      operationId: getSession
      summary: Read authoritative Session identity and current state
      description: |
        An App-wide credential may resolve a Session in any tenant
        partition. A tenant-constrained credential resolves only Sessions
        in its partition. Missing, incompatible, and undisclosable resources use
        `not_found`; a credential denied the read operation itself receives
        `forbidden`.
      responses:
        "200":
          description: Authoritative Session identity and state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
              examples:
                keyedTenantSession:
                  value:
                    id: sesn_019b0a12-7c41-7e24-8ed2-0e07c1bdb311
                    agent_id: agnt_019b0a12-6a31-7d14-9ed2-0e07c1bdb300
                    tenant_key: tenant-acme
                    session_key: ticket-483
                    user_key: null
                    active_invocation_id: invk_019b0a12-8d51-7f34-aed2-0e07c1bdb322
                    active_invocation_status: running
                    compaction: null
                    usage: null
                    created_at: 2026-07-20T14:01:02Z
                    updated_at: 2026-07-20T14:01:05Z
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
    patch:
      tags:
        - Sessions
      operationId: updateSession
      summary: Merge host metadata into a Session
      description: |
        Merges a metadata patch: a present key replaces its value, an explicit
        `null` deletes that key, and a key the patch does not mention survives.

        Merge rather than replace, because independent writers share this map —
        a conversation UI writing a title, correlation tooling writing a trace
        id — and a full replacement would make each silently discard the
        other's keys. The merge happens under the Session lock, so two
        concurrent patches compose instead of one overwriting the other's read.

        `"metadata": null` is refused rather than guessed at: it could mean
        "clear everything" or "leave it alone", and either reading is
        destructive or silent. Delete keys one at a time.

        Bounds apply to the merged result, not to the patch, so a patch that
        deletes as many keys as it adds is not refused for a count it never
        produces. Requires the `update_session` operation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateSessionRequest"
            examples:
              setTitleAndDropTrace:
                value:
                  metadata:
                    title: Refund policy
                    trace_id: null
      responses:
        "200":
          description: The Session after the merge.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
    delete:
      tags:
        - Sessions
      operationId: deleteSession
      summary: Erase a Session and everything under it
      description: |
        Removes the Session, its Invocations, transcript messages, checkpoints,
        tool calls, provider artifacts, compactions, credential and MCP
        bindings, and undelivered notifications. The erasure is immediate and
        irreversible; a subsequent read is `not_found`.

        An Invocation still running is stopped. No cancellation is recorded,
        because there is nothing left to record it against — the Invocation is
        removed rather than settled, and no `invocation.settled` notification
        is emitted for it. A host that needs a settled record should cancel and
        observe the outcome before deleting.

        Erasure is scoped like every other Session operation: an unknown or
        out-of-scope `session_id` returns `not_found`, so a retry after a lost
        response can treat `404` as already-done. A Runtime key can delete a Session; a Viewer key cannot.

        **This is not account deletion by itself.** nvoken keeps no account
        tombstone, so a host honouring a deletion request must first stop
        admitting work for that tenant, then page `GET /v1/sessions` and delete
        until the list is empty. Otherwise a concurrent request creates a new
        Session behind the sweep.

        Two consequences worth planning for. Usage reporting shrinks
        retroactively: `GET /v1/usage/daily` is computed at read time from
        Invocation evidence that erasure removes, which is why the endpoint is
        documented as operational visibility rather than a billing ledger — a
        billing host records usage at settlement, keyed by Invocation id.
        And the erased Invocations' idempotency keys become free for reuse,
        consistent with the documented guarantee that deduplication holds while
        the original Invocation is retained.
      responses:
        "204":
          description: The Session and its subtree no longer exist.
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/sessions/{session_id}/messages:
    parameters:
      - $ref: "#/components/parameters/SessionID"
    get:
      tags:
        - Sessions
      operationId: listSessionMessages
      summary: Page through the canonical Session transcript
      description: |
        Returns persisted SessionMessage rows in ascending sequence order.
        The opaque forward cursor is bound to the authenticated caller and
        Session. This history endpoint contains no lifecycle or live-preview
        copies.
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One bounded page of canonical messages.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionMessageList"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/sessions/{session_id}/transcript:
    parameters:
      - $ref: "#/components/parameters/SessionID"
    get:
      tags:
        - Sessions
      operationId: getSessionTranscript
      summary: Drain a fixed-cut incremental transcript snapshot
      description: |
        Projects canonical messages and append-only Invocation lifecycle
        changes. Supply a prior `resume_cursor` as `cursor` to drain newer
        durable state. Continue a multi-page fixed cut with `page_token` until
        `has_more` is false. Each page contains one phase: all message pages are
        delivered before lifecycle-change pages, so terminal state cannot
        precede the transcript rows committed with it. Omitting both positions
        starts at the beginning of the retained Session.
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - name: page_token
          in: query
          description: Opaque fixed-cut continuation from `next_page_token`.
          schema:
            type: string
            minLength: 1
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One phase of a fixed-cut transcript snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptSnapshot"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/sessions/{session_id}/transcript/stream:
    parameters:
      - $ref: "#/components/parameters/SessionID"
    get:
      tags:
        - Sessions
      operationId: streamSessionTranscript
      summary: Replay and tail one Session transcript over SSE
      description: |
        Opens a resumable Server-Sent Events projection over the same fixed-cut
        transcript read model as the JSON endpoint. With deltas enabled, the
        server subscribes to live fan-out before its first Postgres drain. In
        either mode it re-drains Postgres on a bounded poll and closes after
        authoritative terminal reconciliation or deliberate rotation.
        Disconnecting never cancels the Invocation.

        Every nonempty `transcript.update` frame carries
        `id: <resume_cursor>`; that opaque ID is the only replay position
        clients persist. `output_text.delta`, `thinking.delta`,
        `stream.resync`, and `stream.end` never carry an `id`. Deltas are
        ephemeral and may be lost; after `stream.resync`, discard provisional
        output and wait for canonical messages. `stream.end` reason `terminal`
        means the final Postgres drain observed no nonterminal Invocation.
        Reason `rotate` means reconnect with the last durable ID. An abnormal
        close has no terminal meaning. `deltas=false` skips preview fan-out
        without changing replay, polling, cursor, or terminal semantics.

        The explicit `cursor` query parameter takes precedence over
        `Last-Event-ID`. Bearer authentication requires an SSE-capable HTTP
        client that can set the `Authorization` header; the browser EventSource
        constructor alone cannot do so. The server emits `retry: 1000` as its
        default reconnect delay.
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - name: deltas
          in: query
          description: Include id-less output and thinking preview frames. Defaults to true.
          schema:
            type: boolean
            default: true
        - name: Last-Event-ID
          in: header
          description: Opaque `resume_cursor` from the last durable update frame; ignored when `cursor` is supplied.
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: |
            SSE frames named `transcript.update`, `output_text.delta`,
            `thinking.delta`, `stream.resync`, and `stream.end`.
          content:
            text/event-stream:
              schema:
                $ref: "#/components/schemas/TranscriptStreamEvent"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthenticated"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Internal"
        "503":
          $ref: "#/components/responses/Unavailable"
  /v1/identity:
    get:
      operationId: getCurrentIdentity
      summary: Explain the authenticated caller
      responses:
        "200":
          description: The resolved caller
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentIdentity"
        "401":
          $ref: "#/components/responses/Unauthenticated"
      tags:
        - Identity
      description: Returns the App, credential profile, tenant constraint, and operations resolved for the current API key.
components:
  parameters:
    AgentKeyFilter:
      name: agent_key
      in: query
      description: |
        Exact host-owned Agent key. On Session and Invocation lists this is
        mutually exclusive with agent_id.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    Cursor:
      name: cursor
      in: query
      description: Opaque cursor returned by the same operation and filter set.
      schema:
        type: string
        minLength: 1
    Limit:
      name: limit
      in: query
      description: Maximum items in this page. Defaults to 100.
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 100
    AgentID:
      name: agent_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/AgentID"
    TenantKeyFilter:
      name: tenant_key
      in: query
      description: Exact non-default tenant partition reference.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    DefaultTenantFilter:
      name: default_tenant
      in: query
      description: Select only the default tenant partition. Mutually exclusive with tenant_key.
      schema:
        type: boolean
        default: false
    UserKeyFilter:
      name: user_key
      in: query
      description: |
        Exact host-owned end-user reference. Filters to rows whose Session
        carries this label.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    InvocationID:
      name: invocation_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/InvocationID"
    PendingInputID:
      name: pending_input_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/PendingInputID"
    IfNoneMatch:
      name: If-None-Match
      in: header
      description: ETag from a prior response for this exact representation.
      schema:
        type: string
        minLength: 1
    SessionID:
      name: session_id
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/SessionID"
  schemas:
    AgentList:
      type: object
      additionalProperties: false
      required:
        - items
        - has_more
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Agent"
        has_more:
          type: boolean
        next_cursor:
          oneOf:
            - type: string
            - type: "null"
    Agent:
      type: object
      additionalProperties: false
      required:
        - id
        - agent_key
        - created_at
      properties:
        id:
          $ref: "#/components/schemas/AgentID"
        agent_key:
          type: string
          minLength: 1
          maxLength: 255
          description: Stable host-owned key, unique within the App.
        created_at:
          type: string
          format: date-time
      description: |
        App-wide identity anchor only. Agent behavior is not registered;
        instructions, model, tools, and credentials travel per Invocation.
    AgentID:
      type: string
      pattern: ^agnt_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `agnt_` prefix.
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - code
        - message
        - request_id
      properties:
        code:
          $ref: "#/components/schemas/ErrorCode"
        message:
          type: string
        request_id:
          type: string
        details:
          type: object
          additionalProperties: true
    ErrorCode:
      type: string
      enum:
        - invalid_request
        - unauthenticated
        - forbidden
        - not_found
        - idempotency_conflict
        - provider_credential_conflict
        - session_invocation_active
        - invocation_not_waiting
        - tool_result_conflict
        - tool_result_expired
        - mcp_discovery_failed
        - rate_limited
        - internal
        - unavailable
    MCPListToolsRequest:
      type: object
      additionalProperties: false
      required:
        - server
      properties:
        server:
          $ref: "#/components/schemas/MCPServerSpec"
    MCPServerSpec:
      type: object
      additionalProperties: false
      required:
        - name
        - url
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 24
          pattern: ^[A-Za-z0-9_-]+$
          description: |
            Unique server name used as the projected tool-name prefix. Names
            beginning with nvoken in any letter case are reserved.
        url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://
          description: Public HTTPS streamable-HTTP MCP endpoint without userinfo or a fragment.
        transport:
          type: string
          enum:
            - streamable_http
          x-enum-varnames:
            - TransportStreamableHTTP
        allowed_tools:
          type: array
          minItems: 1
          maxItems: 32
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 128
          description: Optional ordered allowlist. Every named tool must be discovered and projectable.
        headers:
          type: object
          maxProperties: 16
          additionalProperties:
            type: string
            minLength: 1
          writeOnly: true
          description: |
            Secret request headers, limited to 8 KiB encoded. Routing,
            framing, proxy, cookie, hop-by-hop, and MCP session headers are
            rejected. Headers are encrypted per Invocation and never returned.
        timeouts:
          $ref: "#/components/schemas/MCPTimeouts"
    MCPTimeouts:
      type: object
      additionalProperties: false
      properties:
        discovery_seconds:
          type: integer
          minimum: 1
          maximum: 30
          default: 10
        call_seconds:
          type: integer
          minimum: 1
          maximum: 120
          default: 30
    MCPListToolsResponse:
      type: object
      additionalProperties: false
      required:
        - tools
        - exclusions
      properties:
        tools:
          type: array
          maxItems: 64
          items:
            $ref: "#/components/schemas/MCPProjectedTool"
        exclusions:
          type: array
          items:
            $ref: "#/components/schemas/MCPToolExclusion"
    MCPProjectedTool:
      type: object
      additionalProperties: false
      required:
        - server_name
        - projected_name
        - remote_name
        - description
        - input_schema
        - annotations
      properties:
        server_name:
          type: string
        projected_name:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[A-Za-z0-9_-]+$
        remote_name:
          type: string
        description:
          type: string
          maxLength: 4096
        input_schema:
          type: object
          additionalProperties: true
        annotations:
          $ref: "#/components/schemas/MCPToolAnnotations"
    MCPToolAnnotations:
      type: object
      additionalProperties: false
      required:
        - read_only_hint
        - idempotent_hint
        - destructive_hint
      properties:
        read_only_hint:
          type:
            - boolean
            - "null"
        idempotent_hint:
          type:
            - boolean
            - "null"
        destructive_hint:
          type:
            - boolean
            - "null"
    MCPToolExclusion:
      type: object
      additionalProperties: false
      required:
        - server_name
        - remote_name
        - reason
      properties:
        server_name:
          type: string
        remote_name:
          type: string
        reason:
          type: string
          enum:
            - not_allowlisted
            - invalid_name
            - name_collision
            - invalid_schema
            - schema_too_large
            - schema_too_deep
    SessionID:
      type: string
      pattern: ^sesn_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `sesn_` prefix.
    InvocationStatus:
      type: string
      enum:
        - queued
        - running
        - waiting
        - completed
        - incomplete
        - failed
        - cancelled
      description: |
        `completed`, `incomplete`, `failed`, and `cancelled` are terminal and
        immutable.

        `completed` means exactly one thing: the turn ended the way it was
        asked to — the model finished, or the caller interrupted it.
        `incomplete` means the runtime enforced a budget at a coherent
        execution seam. Any budget exhaustion observed at such a seam settles
        `incomplete` with `stop_reason` naming the budget; the transcript is
        valid and stays in the next turn's context, exactly as a completed
        turn's does. Exhaustion that cannot reach a seam — a deadline landing
        mid-request, reaper settlement — still settles `failed` with `error`
        as the authority. A schema-bearing turn that never published a
        validated object also fails, `structured_output_unsatisfied`,
        whichever budget stopped it.

        `waiting` means the Invocation has durable pending host ToolCalls and
        owns no execution lease. Final result acceptance moves it back to
        `queued`. Checkpoint recovery may also move `running` back to
        `queued`; the `attempt` counter distinguishes a retry from tool-result
        acceptance, and lifecycle revision orders every transition.
    InvocationList:
      type: object
      additionalProperties: false
      required:
        - items
        - has_more
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Invocation"
        has_more:
          type: boolean
        next_cursor:
          oneOf:
            - type: string
            - type: "null"
    Invocation:
      type: object
      additionalProperties: false
      required:
        - id
        - agent_id
        - session_id
        - spec_id
        - spec
        - status
        - stop_reason
        - attempt
        - error
        - usage
        - provenance
        - structured_output
        - structured_output_provenance
        - metadata
        - limits
        - active_execution_ms
        - deadline_at
        - created_at
        - updated_at
        - ended_at
      properties:
        id:
          $ref: "#/components/schemas/InvocationID"
        agent_id:
          $ref: "#/components/schemas/AgentID"
        session_id:
          $ref: "#/components/schemas/SessionID"
        spec_id:
          $ref: "#/components/schemas/ExecutionSpecID"
        spec:
          oneOf:
            - $ref: "#/components/schemas/ExecutionSpec"
            - type: "null"
          description: |
            The admitted spec, returned exactly as snapshotted. Remote MCP
            request headers were never stored and never appear here. Present
            on `GET /v1/invocations/{id}` and on the result; null in list
            items, where `spec_id` identifies it. `spec.limits` is what the
            host requested; `limits` is what the service resolved.
        status:
          $ref: "#/components/schemas/InvocationStatus"
        stop_reason:
          oneOf:
            - $ref: "#/components/schemas/InvocationStopReason"
            - type: "null"
          description: |
            Why the turn stopped. Present on `completed` and `incomplete`,
            null on every other status — a failure keeps `error` as the
            authority. Treat an unrecognized value as an ordinary end.
        attempt:
          type: integer
          minimum: 0
          description: |
            Execution attempts this Invocation has been claimed for. It
            increases on every claim, so an attempt increase across a
            `running → queued → running` transition is the retry signal that
            status alone cannot give, and it is the durable anchor for
            discarding provisional output from an earlier attempt. Zero
            before the first claim.
        error:
          oneOf:
            - $ref: "#/components/schemas/InvocationFailure"
            - type: "null"
        usage:
          oneOf:
            - $ref: "#/components/schemas/ModelUsage"
            - type: "null"
          description: One normalized terminal aggregate, not a billing ledger.
        provenance:
          oneOf:
            - $ref: "#/components/schemas/ModelProvenance"
            - type: "null"
        structured_output:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
          description: |
            Server-validated terminal object for a schema-bearing Invocation.
            Null until successful terminal settlement and always null when no
            output contract was admitted.
        structured_output_provenance:
          oneOf:
            - $ref: "#/components/schemas/StructuredOutputProvenance"
            - type: "null"
        metadata:
          oneOf:
            - $ref: "#/components/schemas/Metadata"
            - type: "null"
          description: |
            Host correlation data recorded at admission, returned verbatim.
        limits:
          $ref: "#/components/schemas/ResolvedLimits"
        active_execution_ms:
          type: integer
          minimum: 0
        deadline_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
          description: |
            The deadline currently enforced by the runtime. Null while the
            Invocation is waiting without an explicit waiting timeout; the
            explicit waiting deadline while bounded; otherwise the total-time
            deadline for queued, running, and terminal Invocations.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        ended_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
        pending_tool_calls:
          type: array
          items:
            $ref: "#/components/schemas/PendingHostToolCall"
          description: Present for a waiting Invocation with unresolved host calls.
    InvocationID:
      type: string
      pattern: ^invk_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `invk_` prefix.
    ExecutionSpecID:
      type: string
      pattern: ^spec_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `spec_` prefix.
    ExecutionSpec:
      type: object
      additionalProperties: false
      required:
        - model
      properties:
        instructions:
          type: string
          minLength: 1
          description: Optional model instructions. Omission adds no hidden default.
        model:
          $ref: "#/components/schemas/Model"
        sampling:
          $ref: "#/components/schemas/SamplingSpec"
        reasoning:
          $ref: "#/components/schemas/ReasoningSpec"
        tool_choice:
          $ref: "#/components/schemas/ToolChoiceSpec"
        limits:
          $ref: "#/components/schemas/Limits"
        structured_output:
          $ref: "#/components/schemas/StructuredOutputSpec"
        outcome:
          $ref: "#/components/schemas/OutcomeSpec"
        tools:
          type: array
          maxItems: 32
          items:
            $ref: "#/components/schemas/ToolSpec"
        mcp_servers:
          type: array
          maxItems: 8
          items:
            $ref: "#/components/schemas/MCPServerSpec"
        provider_tools:
          type: array
          maxItems: 4
          items:
            $ref: "#/components/schemas/ProviderToolSpec"
      description: |
        Immutable launch snapshot. Everything inside `spec` is stored as one
        immutable snapshot row and re-read unchanged on every model iteration
        and every recovery. The rest of the request is durable too, in its own
        place: `input` is appended to the Session transcript, and identity,
        session selection, idempotency, `if_active`, `notify`, and
        `provider_credentials` are recorded on the Invocation. What `spec`
        marks off is not durability but reuse — it is the part that repeats
        byte-identically across turns, so two Invocations carrying the same
        spec share one snapshot row. Unknown or deferred fields, including
        spec references, are rejected rather than ignored. Callback
        declarations require notification signing configuration.
        Remote MCP credential headers are encrypted outside this snapshot and
        never returned. A tools-bearing, structured-output, or outcome-review
        spec requires at least two model iterations; omission resolves to
        three or the lower service limit.
    Model:
      type: object
      additionalProperties: false
      required:
        - provider
        - id
      properties:
        provider:
          $ref: "#/components/schemas/ModelProvider"
        id:
          type: string
          minLength: 1
          maxLength: 255
    ModelProvider:
      type: string
      pattern: ^[a-z][a-z0-9_]*$
      description: |
        Extensible canonical provider identifier. Consumers must preserve
        unknown values so adding a provider does not break decoding. Request
        positions still reject providers not available from the service.
    SamplingSpec:
      type: object
      additionalProperties: false
      required:
        - temperature
      properties:
        temperature:
          type: number
          format: double
          minimum: 0
          maximum: 1
          description: |
            Portable sampling temperature. Omit sampling to preserve the
            provider default. An explicit zero remains explicit. Admission
            fails closed unless the selected model descriptor positively
            declares support.
    ReasoningSpec:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        effort:
          $ref: "#/components/schemas/ReasoningEffort"
        budget_tokens:
          type: integer
          minimum: 1024
          description: |
            Provider reasoning target, not a separate Invocation hard budget.
            Requires an explicit, larger limits.max_output_tokens and exact
            support in the selected model descriptor.
    ReasoningEffort:
      type: string
      enum:
        - low
        - medium
        - high
        - xhigh
        - max
      x-enum-varnames:
        - EffortLow
        - EffortMedium
        - EffortHigh
        - EffortXHigh
        - EffortMax
    ToolChoiceSpec:
      type: object
      additionalProperties: false
      required:
        - mode
      properties:
        mode:
          $ref: "#/components/schemas/ModelToolChoiceMode"
        name:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[A-Za-z0-9_-]+$
      description: |
        Portable Invocation tool selection. auto preserves normal selection;
        none disables tools for the Invocation; required and named apply only
        to the first durable model iteration, then return to auto. name is
        required only for named mode. The final bounded iteration always
        disables tools.
    ModelToolChoiceMode:
      type: string
      enum:
        - auto
        - none
        - required
        - named
      x-enum-varnames:
        - ChoiceAuto
        - ChoiceNone
        - ChoiceRequired
        - ChoiceNamed
    Limits:
      type: object
      additionalProperties: false
      properties:
        total_timeout_seconds:
          type: integer
          minimum: 1
        active_timeout_seconds:
          type: integer
          minimum: 1
        waiting_timeout_seconds:
          type: integer
          minimum: 1
          description: |
            Optional cumulative external-waiting SLA. When omitted, host and
            callback ToolCalls may remain pending indefinitely; waiting does
            not consume the Invocation's total or active execution budgets.
        max_output_tokens:
          type: integer
          minimum: 1
        max_estimated_cost_usd:
          type: number
          exclusiveMinimum: 0
          multipleOf: 0.000001
          description: |
            nvoken's standard list-price guardrail, not preauthorization or a billing
            ledger. Requires known USD pricing for the selected model and
            otherwise fails closed with `budget_exceeded` and
            `details.kind = estimated_cost_unavailable`. When pricing absence
            is knowable before execution, nvoken rejects before a provider call.
            Reaching the ceiling itself is not a failure when it is noticed at
            an execution seam: the turn settles `incomplete` with
            `stop_reason: max_estimated_cost` and keeps its work.
        max_iterations:
          type: integer
          minimum: 1
      description: |
        Optional requested limits. Total time bounds the entire turn, active
        time bounds model and tool execution, and waiting time bounds the
        cumulative time parked for host or callback tool results. Service
        defaults supply all three time limits and the iteration limit.
        Output-token and estimated-cost limits are unlimited when omitted.
        Service limits may be lower than the schema's numeric range.
    StructuredOutputSpec:
      type: object
      additionalProperties: false
      required:
        - schema
      properties:
        schema:
          type: object
          additionalProperties: true
          description: |
            Self-contained JSON Schema for an object result. Compact canonical
            JSON is limited to 32 KiB and 16 schema positions. Supported
            keywords are type, title, description, properties, required,
            additionalProperties, items, enum, pattern, minLength, maxLength,
            minItems, maxItems, uniqueItems, minimum, and maximum. Every schema
            position has one string type; pattern values are limited to 1,024
            UTF-8 bytes; references and other keywords are rejected. Numeric
            bounds are read as values, not spellings: 10, 10.0, and 1e1 are the
            same bound.
      description: |
        Optional per-Invocation structured-output contract. nvoken exposes a
        reserved durable submit tool and publishes only a server-validated
        terminal object. This does not enable host-defined tools.
    OutcomeSpec:
      type: object
      additionalProperties: false
      required:
        - description
        - rubric
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 1024
          description: Caller-defined description of the desired turn outcome.
        rubric:
          type: string
          minLength: 1
          maxLength: 16384
          description: Criteria used by the selected model for finish-time self-review.
      description: |
        Experimental finish-time self-review. The selected model does not see
        these criteria during its initial work. When the turn would otherwise
        finish, nvoken supplies them once through Dive's system-message
        reminder mechanism and lets the same turn continue within its ordinary
        limits. This does not produce an independent grade or durable verdict.
        This experimental slice cannot be combined with structured_output.
    ToolSpec:
      oneOf:
        - $ref: "#/components/schemas/BuiltinToolSpec"
        - $ref: "#/components/schemas/HostToolSpec"
        - $ref: "#/components/schemas/CallbackToolSpec"
      discriminator:
        propertyName: mode
        mapping:
          builtin: "#/components/schemas/BuiltinToolSpec"
          host: "#/components/schemas/HostToolSpec"
          callback: "#/components/schemas/CallbackToolSpec"
    BuiltinToolSpec:
      type: object
      additionalProperties: false
      required:
        - name
        - mode
      properties:
        name:
          type: string
          enum:
            - nvoken_fetch
          x-enum-varnames:
            - NameNvokenFetch
          description: Fixed runtime-owned public-web fetch capability.
        mode:
          type: string
          enum:
            - builtin
          x-enum-varnames:
            - ModeBuiltin
    HostToolSpec:
      type: object
      additionalProperties: false
      required:
        - name
        - description
        - mode
        - input_schema
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[A-Za-z0-9_-]+$
          description: |
            Unique within the spec; the `nvoken_` prefix is reserved.

            The charset is deliberately the strictest tool-name constraint any
            supported provider imposes, so every admitted name reaches every
            provider unchanged. Widening it — to dotted names, for instance —
            would require a mangling layer whose collisions nvoken would then
            own, and would sit ambiguously beside the `__` separator MCP tool
            projection already uses. A host whose own permission grammar uses
            dots should map it injectively (`naming.domain.check` becomes
            `naming_domain_check`) and state the mapping to the model once.
        description:
          type: string
          minLength: 1
          maxLength: 4096
        mode:
          type: string
          enum:
            - host
          x-enum-varnames:
            - ModeHost
        input_schema:
          type: object
          additionalProperties: true
          description: |
            Object-root JSON Schema using the same bounded, self-contained
            subset as structured output, including `uniqueItems`. Compact
            canonical JSON is limited to 32 KiB. A rejection carries
            `details.{code,path,keyword}` naming the offending schema position.
    CallbackToolSpec:
      type: object
      additionalProperties: false
      required:
        - name
        - description
        - mode
        - input_schema
        - callback
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[A-Za-z0-9_-]+$
          description: |
            Unique within the spec; the `nvoken_` prefix is reserved.

            The charset is deliberately the strictest tool-name constraint any
            supported provider imposes, so every admitted name reaches every
            provider unchanged. Widening it — to dotted names, for instance —
            would require a mangling layer whose collisions nvoken would then
            own, and would sit ambiguously beside the `__` separator MCP tool
            projection already uses. A host whose own permission grammar uses
            dots should map it injectively (`naming.domain.check` becomes
            `naming_domain_check`) and state the mapping to the model once.
        description:
          type: string
          minLength: 1
          maxLength: 4096
        mode:
          type: string
          enum:
            - callback
          x-enum-varnames:
            - ModeCallback
        input_schema:
          type: object
          additionalProperties: true
          description: |
            Object-root JSON Schema using the same bounded, self-contained
            subset as structured output, including `uniqueItems`. Compact
            canonical JSON is limited to 32 KiB. A rejection carries
            `details.{code,path,keyword}` naming the offending schema position.
        callback:
          $ref: "#/components/schemas/CallbackTarget"
    CallbackTarget:
      type: object
      additionalProperties: false
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          maxLength: 2048
          pattern: ^https://
          description: |
            Public HTTPS endpoint. Userinfo and fragments are rejected. Every
            dial revalidates resolved addresses and refuses redirects.
    ProviderToolSpec:
      type: object
      additionalProperties: false
      required:
        - type
        - web_search
      properties:
        type:
          type: string
          enum:
            - web_search
          x-enum-varnames:
            - ProviderToolWebSearch
        web_search:
          $ref: "#/components/schemas/WebSearchToolSpec"
      description: |
        Selects one server-side tool the provider runs inside the generation
        call. Unlike a host tool the turn does not park for it, and unlike a
        builtin nvoken does not execute it — the provider does, and bills it on
        the same credential the model runs on. That is deliberate: a
        runtime-operated search would make nvoken hold a search vendor
        relationship and answer "who pays per query" for every customer,
        while this uses the billing relationship the host already has.

        **Anthropic only, for now.** The providers' search controls differ
        materially, and a "common" option set would either be the empty
        intersection or would silently drop fields on some providers — which is
        exactly what nvoken's fail-closed posture forbids. Another provider is
        rejected at admission rather than served a degraded search. Widening is
        per-provider capability work.

        A model that does not declare `controls.tools.web_search` is refused
        too, including any uncataloged model: a tool configuration the provider
        quietly ignored would bill a turn that did no searching and return an
        answer the host believes was researched.

        **Cost accounting.** Search charges ride the provider's bill outside
        nvoken's estimate. `spec.limits.max_estimated_cost_usd` and the usage
        reporting cover model tokens; the provider does not report a
        per-search fee through the interface nvoken reads, so a host that needs
        a hard ceiling on search spend should set `max_uses`.

        **What appears in the transcript.** The model's `server_tool_use` block
        is public, so a host can show what was searched for. The matching
        `web_search_tool_result` is not: it carries the provider's encrypted
        continuation bytes, which the next turn must echo back verbatim and
        which mean nothing to a host. The sources behind a claim arrive as
        citations on the visible text blocks.
    WebSearchToolSpec:
      type: object
      additionalProperties: false
      properties:
        max_uses:
          type: integer
          minimum: 1
          maximum: 20
          description: |
            Searches this turn may run. Omitted means the provider default.
            This is the only bound nvoken can place on search spend, because
            the provider does not report a per-search fee it can meter.
        allowed_domains:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 255
          description: |
            Restrict results to these hosts. Bare hostnames only — a scheme,
            path, or port is rejected rather than reinterpreted. Mutually
            exclusive with `blocked_domains`, which is the provider's rule.
        blocked_domains:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 255
          description: |
            Exclude these hosts. Same format as `allowed_domains`, and mutually
            exclusive with it.
        user_location:
          $ref: "#/components/schemas/WebSearchLocation"
      description: |
        Anthropic web search options, passed through as the provider defines
        them.
    WebSearchLocation:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        city:
          type: string
          maxLength: 128
        region:
          type: string
          maxLength: 128
        country:
          type: string
          maxLength: 128
        timezone:
          type: string
          maxLength: 128
      description: |
        Approximate location used to bias results. Every member is optional;
        the host decides how precise to be about its end user.
    InvocationStopReason:
      type: string
      enum:
        - end_turn
        - interrupted
        - max_iterations
        - deadline
        - max_output_tokens
        - max_estimated_cost
      description: |
        Why a turn stopped. It spans both non-failure terminals and is
        partitioned between them.

        A `completed` turn carries `end_turn` (the model finishing) or
        `interrupted` (a caller's graceful stop, taken at the next execution
        seam). An `incomplete` turn carries the budget it exhausted at a seam:
        `max_iterations`, `deadline`, `max_output_tokens`, or
        `max_estimated_cost`. An interrupt is deliberately not `incomplete` —
        the caller asked the turn to end there, so ending there is the
        requested outcome. The vocabulary is extensible.
    InvocationFailure:
      type: object
      additionalProperties: false
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - deadline_exceeded
            - budget_exceeded
            - credential_unavailable
            - context_window_exceeded
            - input_media_rejected
            - provider_error
            - mcp_discovery_failed
            - structured_output_unsatisfied
            - internal
        message:
          type: string
        details:
          type: object
          additionalProperties: true
      description: |
        Failed Invocations may carry paired usage and provenance when a model
        response produced safe normalized evidence before deadline or limit
        settlement. Cancellation and pre-response failures carry neither.

        `details` is a bounded, code-specific object. Each code names its own
        keys and nothing else reads them:

        - `deadline_exceeded` carries `details.scope`, the deadline that was
          breached, drawn from `total | active_execution | execution_segment
          | waiting`. One condition has one code: a deadline reports this
          wherever the runtime noticed it.
        - `budget_exceeded` carries `details.kind`, the consumption ceiling
          the turn overran, drawn from `output_tokens | estimated_cost |
          estimated_cost_unavailable`. A ceiling only lands here when no
          coherent seam absorbed it; at a seam the same ceiling settles
          `incomplete` with the matching `stop_reason` instead.
          `estimated_cost_unavailable` is always a failure — nothing was
          exhausted, nvoken simply cannot price the model.
        - `structured_output_unsatisfied` carries `details.reason`, drawn from
          `missing | invalid | oversized`.
        - `context_window_exceeded` carries the integer keys
          `input_tokens`, `context_window_tokens`, and
          `requested_output_tokens`, each present only when known.
        - `input_media_rejected` carries `modality`, `media_type`, and
          `reason` as the provider reported them, each present only when
          known.

        A `provider_error` failure carries
        `details.provider_failure_class`, nvoken's own classification of the
        provider failure, drawn from `configuration | canceled | throttled |
        upstream_rejected | upstream_unavailable | timeout_or_transport |
        invalid_response | unknown`. `upstream_rejected` means the provider
        refused this request — do not retry it as-is. `upstream_unavailable`
        and `throttled` mean the provider could not serve it — retry with
        backoff. No raw provider error material ever crosses into this
        payload.
    ModelUsage:
      type: object
      additionalProperties: false
      required:
        - input_tokens
        - output_tokens
      properties:
        input_tokens:
          type: integer
          minimum: 0
        output_tokens:
          type: integer
          minimum: 0
        cache_creation_input_tokens:
          type: integer
          minimum: 0
        cache_read_input_tokens:
          type: integer
          minimum: 0
        reasoning_tokens:
          type: integer
          minimum: 0
        iterations:
          type: integer
          minimum: 0
          description: Model requests represented by this aggregate. Older retained evidence may omit it.
        estimated_cost:
          $ref: "#/components/schemas/ModelCost"
    ModelCost:
      type: object
      additionalProperties: false
      required:
        - input
        - output
        - cache_read
        - cache_write
        - total
      properties:
        input:
          type: number
          minimum: 0
        output:
          type: number
          minimum: 0
        cache_read:
          type: number
          minimum: 0
        cache_write:
          type: number
          minimum: 0
        total:
          type: number
          minimum: 0
        currency:
          type: string
        model:
          type: string
    ModelProvenance:
      type: object
      additionalProperties: false
      required:
        - provider
        - requested_model
        - served_model
        - credential_source
      properties:
        provider:
          type: string
        requested_model:
          type: string
        served_model:
          type: string
        credential_source:
          type: string
          enum:
            - caller_ephemeral
            - app_byok
            - tenant_byok
            - platform
        provider_credential_id:
          $ref: "#/components/schemas/ProviderCredentialID"
        credential_version_id:
          $ref: "#/components/schemas/ProviderCredentialVersionID"
      description: Reusable stored-key sources include safe credential and version IDs. Caller-supplied and platform-funded sources omit them. Secret material is never returned.
    ProviderCredentialID:
      type: string
      pattern: ^pcrd_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `pcrd_` prefix.
    ProviderCredentialVersionID:
      type: string
      pattern: ^pcvr_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `pcvr_` prefix.
    StructuredOutputProvenance:
      type: object
      additionalProperties: false
      required:
        - source
        - schema_sha256
      properties:
        source:
          type: string
          enum:
            - tool_call
          x-enum-varnames:
            - SourceToolCall
        tool_call_id:
          type: string
          pattern: ^tcal_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
        schema_sha256:
          type: string
          pattern: ^[0-9a-f]{64}$
      description: |
        Immutable proof that output equals the accepted reserved durable
        ToolCall request under the admitted schema digest.
    Metadata:
      type: object
      additionalProperties:
        type: string
        maxLength: 512
      maxProperties: 16
      propertyNames:
        pattern: ^[A-Za-z0-9_.:-]+$
        maxLength: 64
      description: |
        Opaque host correlation data. nvoken stores it, returns it verbatim,
        and never interprets it — it exists so a support engineer holding an
        Invocation id can get back to the board, ticket, or surface the turn
        came from, which nvoken deliberately knows nothing about.

        At most 16 entries. Keys are 1–64 bytes of letters, digits, `_`, `.`,
        `:`, and `-`; values are at most 512 bytes of UTF-8.

        There is no `title` field on a Session by design. A title is one of
        these entries (`{"title": "Refund policy"}`), which keeps nvoken from
        forming opinions about a string only the host renders.
    ResolvedLimits:
      type: object
      additionalProperties: false
      required:
        - total_timeout_seconds
        - active_timeout_seconds
        - waiting_timeout_seconds
        - max_iterations
      properties:
        total_timeout_seconds:
          type: integer
          minimum: 1
        active_timeout_seconds:
          type: integer
          minimum: 1
        waiting_timeout_seconds:
          oneOf:
            - type: integer
              minimum: 1
            - type: "null"
          description: |
            The explicit cumulative external-waiting SLA, or null when waits
            are durable and unbounded.
        max_output_tokens:
          type: integer
          minimum: 1
        max_estimated_cost_usd:
          type: number
          exclusiveMinimum: 0
          multipleOf: 0.000001
          description: |
            Resolved USD list-price guardrail. It is present only when the host
            requested a cost limit; unknown price metadata fails closed.
        max_iterations:
          type: integer
          minimum: 1
    PendingHostToolCall:
      type: object
      additionalProperties: false
      required:
        - id
        - name
        - input
        - deadline_at
      properties:
        id:
          $ref: "#/components/schemas/ToolCallID"
        name:
          type: string
        input: {}
        deadline_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
          description: |
            The Invocation's explicit waiting deadline, or null when external
            waiting is unbounded.
    ToolCallID:
      type: string
      pattern: ^tcal_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
    CreateInvocationRequest:
      type: object
      additionalProperties: false
      required:
        - agent_key
        - idempotency_key
        - input
        - spec
      allOf:
        - not:
            required:
              - session_id
              - session_key
        - not:
            required:
              - session_id
              - session_options
      properties:
        agent_key:
          type: string
          minLength: 1
          maxLength: 255
          description: Stable caller-controlled Agent key, unique within the App. The resulting Agent anchor stores identity only and can be shared across tenant partitions.
        tenant_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional tenant partition. For Session-key resolution or a new
            Session, precedence is credential constraint, this explicit value,
            then the default partition. For Session-ID resolution, an
            App-wide caller may omit it and use the stored partition.
        user_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional host-owned end-user label recorded on the Session this
            Invocation opens. It exists so invocations and Sessions can be
            filtered by end user, and is deliberately not an isolation
            boundary: no credential is constrained to it and no request is
            refused because of it. Ignored when the Invocation joins a Session
            that already exists.
        session_id:
          $ref: "#/components/schemas/SessionID"
          description: Existing Session to continue. Mutually exclusive with session_key.
        session_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Caller key resolved within (effective tenant partition,
            Agent, session_key). Mutually exclusive with session_id.
        session_options:
          $ref: "#/components/schemas/SessionOptions"
          description: |
            Creation-only options for the Session this admission creates.
            Applied when admission creates the Session and ignored when a
            `session_key` already resolves to one; rejected outright alongside
            `session_id`, which never creates a Session. They are not part of
            the Invocation execution spec or its idempotency comparison.
        metadata:
          $ref: "#/components/schemas/Metadata"
          description: |
            Opaque host correlation data recorded on this Invocation. It is
            part of the admitted input, so it is immutable and material to
            idempotency: a replay carrying different metadata conflicts rather
            than updating it. A genuine retry describes the same originating
            request and so carries the same values by construction.

            Session metadata is separate and mutable — see
            `session_options.metadata` and `PATCH /v1/sessions/{session_id}`.
        idempotency_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Stable admission identity scoped to (effective tenant
            partition, agent_key). Reuse an unchanged request after any 5xx,
            timeout, disconnect, or missing acknowledgement. Deduplication is
            guaranteed while the original Invocation is retained.
        if_active:
          type: string
          enum:
            - reject
            - supersede
            - interrupt
          default: reject
          description: |
            Policy when the resolved Session already has a nonterminal
            Invocation. `reject` returns `session_invocation_active`.
            `supersede` atomically applies the ordinary durable cancellation
            transition to that Invocation before admitting this replacement,
            dropping its work from later turns. `interrupt` stops it
            gracefully and admits only after it has settled `completed`, so
            the replacement builds on what the stopped turn produced: it is
            "stop and redo" where supersede is "discard and redo". Omission
            and explicit `reject` compare equal on replay.
        input:
          $ref: "#/components/schemas/InvocationInput"
        notify:
          $ref: "#/components/schemas/InvocationNotificationTarget"
        spec:
          $ref: "#/components/schemas/ExecutionSpec"
        provider_credentials:
          type: array
          minItems: 1
          maxItems: 1
          items:
            $ref: "#/components/schemas/InvocationProviderCredentialSelection"
          description: Choose a nonsecret credential source for the model provider. Omit this field to use the App's stored provider key when one exists, or platform funding when it is available. Use `caller_ephemeral` (or the matching provider API-key header) to supply a key for this Invocation. The selected source is bound when the Invocation is admitted and an idempotent replay never replaces it.
    SessionOptions:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        compaction:
          $ref: "#/components/schemas/ContextCompactionSpec"
        retention:
          $ref: "#/components/schemas/SessionRetentionSpec"
        metadata:
          $ref: "#/components/schemas/Metadata"
      description: |
        Creation-only durable Session options. Every member is optional and at
        least one must be present.

        Options configure a Session as it is created, so they are accepted on
        any request that can create one — `POST /v1/sessions`, or an admission
        that resolves to a new Session — and rejected outright on a request
        naming an existing `session_id`, rather than silently ignored.

        `compaction` additionally requires an Invocation, because the policy is
        validated against that Invocation's model: an `auto` trigger is derived
        from the model's context window, and later admissions are refused if
        their provider disagrees with the stored policy. Set it on the first
        invocation of a Session rather than on `POST /v1/sessions`.
    ContextCompactionSpec:
      type: object
      additionalProperties: false
      required:
        - trigger_tokens
      properties:
        trigger_tokens:
          oneOf:
            - type: integer
              minimum: 4096
              maximum: 1000000
            - type: string
              enum:
                - auto
              x-enum-varnames:
                - Auto
          description: |
            Estimated retained-context size that triggers compaction. The
            Session stores "auto" as an exact value materialized at creation to
            60% of the creating primary model's context window, clamped to the
            supported integer range.
        model:
          $ref: "#/components/schemas/Model"
      description: |
        Durable Session context-compaction policy. Omission of model uses the
        creating Invocation's primary model. An explicit model must be
        cataloged and use the same provider. Later Invocations automatically
        use this resolved policy and the latest Session summary.
    SessionRetentionSpec:
      type: object
      additionalProperties: false
      required:
        - ttl_seconds
      properties:
        ttl_seconds:
          type: integer
          minimum: 3600
          maximum: 2592000
          description: |
            Idle window, from one hour to thirty days.
      description: |
        Bounds how long an idle Session is retained. Past the window nvoken
        erases the Session and its whole subtree, exactly as
        `DELETE /v1/sessions/{session_id}` would.

        The window measures idle time, not lifetime: it restarts on every
        Invocation admission and every settlement, so a turn that runs longer
        than the window cannot expire underneath itself.

        **Automatic expiry never cancels running work.** A Session holding a
        queued, running, or waiting Invocation is skipped and reconsidered on
        the next sweep. An explicit `DELETE` may still stop a running turn,
        because a person asked for it; a clock may not.

        Omitting retention retains the Session until it is deleted explicitly,
        which stays the default.
    InvocationInput:
      description: |
        A plain string is shorthand for one text block; an array is ordered
        multi-block input mixing text, images, and documents. Both normalize
        to the same stored message and are therefore equal for idempotency.
        At most 8 blocks may carry media, and their decoded payloads must
        total at most 16777216 bytes.
      oneOf:
        - type: string
          minLength: 1
        - type: array
          minItems: 1
          maxItems: 64
          items:
            $ref: "#/components/schemas/InputBlock"
    InputBlock:
      oneOf:
        - $ref: "#/components/schemas/TextInputBlock"
        - $ref: "#/components/schemas/ImageInputBlock"
        - $ref: "#/components/schemas/DocumentInputBlock"
      description: |
        One caller-input block. A block carries text, an image, or a document;
        the members of one type are never valid on another.
    TextInputBlock:
      type: object
      additionalProperties: false
      required:
        - type
        - text
      properties:
        type:
          type: string
          enum:
            - text
          x-enum-varnames:
            - InputTypeText
        text:
          type: string
          minLength: 1
    ImageInputBlock:
      type: object
      additionalProperties: false
      required:
        - type
        - source
      properties:
        type:
          type: string
          enum:
            - image
          x-enum-varnames:
            - InputTypeImage
        source:
          $ref: "#/components/schemas/ImageInputSource"
      description: |
        An inline image. Admission decodes the payload, requires the sniffed
        format to match the declared media type, bounds it to 5242880 decoded
        bytes and 8000 by 8000 pixels, and requires the selected model to
        declare the image modality and that exact media type.
    ImageInputSource:
      type: object
      additionalProperties: false
      required:
        - media_type
        - data
      properties:
        media_type:
          type: string
          enum:
            - image/gif
            - image/jpeg
            - image/png
            - image/webp
          x-enum-varnames:
            - ImageGIF
            - ImageJPEG
            - ImagePNG
            - ImageWEBP
        data:
          type: string
          minLength: 1
          maxLength: 6990508
          description: |
            Standard padded base64 with no whitespace. URL and provider file
            sources are deliberately unsupported.
    DocumentInputBlock:
      type: object
      additionalProperties: false
      required:
        - type
        - source
      properties:
        type:
          type: string
          enum:
            - document
          x-enum-varnames:
            - InputTypeDocument
        source:
          $ref: "#/components/schemas/DocumentInputSource"
        title:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional filename supplied to providers that require one. Omission
            uses the adapter default and does not change acceptance.
      description: |
        An inline document. Admission applies the same decode and sniff rules
        as an image with a 16777216 decoded-byte bound. Page limits are the
        provider's, so an oversized page count settles as
        input_media_rejected.
    DocumentInputSource:
      type: object
      additionalProperties: false
      required:
        - media_type
        - data
      properties:
        media_type:
          type: string
          enum:
            - application/pdf
          x-enum-varnames:
            - DocumentPDF
        data:
          type: string
          minLength: 1
          maxLength: 22369624
          description: Standard padded base64 with no whitespace.
    InvocationNotificationTarget:
      type: object
      additionalProperties: false
      required:
        - url
      description: |
        Optional endpoint nvoken posts a signed notification to when this
        Invocation parks awaiting host tool results or reaches a terminal
        status. It sits beside `spec` because the execution spec is the
        immutable input to the model and is what the snapshot preserves, while
        this only says where to report. Two otherwise identical turns that
        differ only in endpoint therefore admit the same spec.

        Delivery is at least once and ordered within one Invocation, and the
        notification ID is repeated in `Idempotency-Key` on every attempt. The
        payload carries identifiers and status only, so authoritative state is
        still read through this API. A notification never changes, delays, or
        fails the Invocation it describes.

        The endpoint is part of the request, so replaying an idempotency key
        with a changed endpoint or event set is a conflict rather than a
        silent adoption of either value. An service without a configured
        notification signing key rejects this field instead of accepting it
        and never notifying.
      properties:
        url:
          type: string
          format: uri
          minLength: 1
          maxLength: 2048
          description: |
            Absolute HTTPS endpoint with no userinfo and no fragment. Private,
            loopback, and link-local destinations are refused by the same
            guarded public egress the callback transport uses.
        events:
          type: array
          minItems: 1
          maxItems: 2
          uniqueItems: true
          items:
            $ref: "#/components/schemas/InvocationNotificationEvent"
          description: |
            Events to deliver. Omission selects every event, because the
            harmful default is the one that quietly drops
            `invocation.waiting` and leaves a parked tool loop with nobody
            listening. Order does not affect replay comparison.
    InvocationNotificationEvent:
      type: string
      enum:
        - invocation.waiting
        - invocation.settled
      x-enum-varnames:
        - NotificationEventWaiting
        - NotificationEventSettled
      description: |
        `invocation.waiting` fires when the Invocation parks with at least one
        unresolved host ToolCall the caller must satisfy; a wait made entirely
        of callback tools emits nothing, because nvoken delivers those itself.
        `invocation.settled` fires once when the Invocation reaches
        `completed`, `failed`, or `cancelled`.
    InvocationProviderCredentialSelection:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - provider
            - source
            - credential
          properties:
            provider:
              $ref: "#/components/schemas/ModelProvider"
            source:
              type: string
              enum:
                - caller_ephemeral
              x-enum-varnames:
                - SourceCallerEphemeral
            credential:
              $ref: "#/components/schemas/ProviderStaticCredential"
        - type: object
          additionalProperties: false
          required:
            - provider
            - source
          properties:
            provider:
              $ref: "#/components/schemas/ModelProvider"
            source:
              type: string
              enum:
                - app_byok
                - tenant_byok
                - platform
      description: Select one credential source for the model provider. Omit `provider_credentials` to use the App's stored provider key when one exists, or platform funding when it is available.
    ProviderStaticCredential:
      type: object
      additionalProperties: false
      required:
        - api_key
      properties:
        api_key:
          type: string
          minLength: 1
          maxLength: 65536
          writeOnly: true
          description: Encrypted before persistence and never returned.
    InvocationAcknowledgement:
      type: object
      additionalProperties: false
      required:
        - agent_id
        - session_id
        - invocation_id
        - status
        - deduplicated
        - deadline_at
        - limits
      properties:
        agent_id:
          $ref: "#/components/schemas/AgentID"
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        status:
          $ref: "#/components/schemas/InvocationStatus"
        deduplicated:
          type: boolean
        deadline_at:
          type: string
          format: date-time
        limits:
          allOf:
            - $ref: "#/components/schemas/ResolvedLimits"
          description: |
            The effective budgets this Invocation was admitted with, after
            service defaults and feature floors were applied. Echoed here
            so a host sees what it actually got without a second read — an
            omitted `max_iterations` in particular resolves to a value the
            request never named.
    InvocationStreamEvent:
      description: |
        Typed event from one Invocation stream. A minimal consumer prints
        `output_text.delta` and finishes on `invocation.result`.
      oneOf:
        - $ref: "#/components/schemas/InvocationAcceptedEvent"
        - $ref: "#/components/schemas/OutputTextDeltaEvent"
        - $ref: "#/components/schemas/ThinkingDeltaEvent"
        - $ref: "#/components/schemas/InvocationUpdateEvent"
        - $ref: "#/components/schemas/InvocationResultEvent"
        - $ref: "#/components/schemas/StreamResyncEvent"
        - $ref: "#/components/schemas/StreamEndEvent"
    InvocationAcceptedEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - agent_id
        - session_id
        - invocation_id
        - status
        - deduplicated
        - deadline_at
        - limits
      properties:
        type:
          type: string
          enum:
            - invocation.accepted
          x-enum-varnames:
            - EventInvocationAccepted
        agent_id:
          $ref: "#/components/schemas/AgentID"
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        status:
          $ref: "#/components/schemas/InvocationStatus"
        deduplicated:
          type: boolean
        deadline_at:
          type: string
          format: date-time
        limits:
          allOf:
            - $ref: "#/components/schemas/ResolvedLimits"
          description: |
            The effective budgets this Invocation was admitted with, after
            service defaults and feature floors were applied. Echoed here
            so a host sees what it actually got without a second read — an
            omitted `max_iterations` in particular resolves to a value the
            request never named.
    OutputTextDeltaEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - attempt
        - iteration
        - content_index
        - text
        - emitted_at
      properties:
        type:
          type: string
          enum:
            - output_text.delta
          x-enum-varnames:
            - EventOutputTextDelta
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        attempt:
          type: integer
          format: int64
          minimum: 1
          description: |
            Execution attempt that emitted this preview. Discard provisional
            output from earlier attempts when this value increases.
        iteration:
          type: integer
          minimum: 1
          description: Model iteration that owns the assistant message.
        content_index:
          type: integer
          minimum: 0
        text:
          type: string
          minLength: 1
        emitted_at:
          type: string
          format: date-time
    ThinkingDeltaEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - attempt
        - iteration
        - content_index
        - thinking
        - emitted_at
      properties:
        type:
          type: string
          enum:
            - thinking.delta
          x-enum-varnames:
            - EventThinkingDelta
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        attempt:
          type: integer
          format: int64
          minimum: 1
          description: |
            Execution attempt that emitted this preview. Discard provisional
            output from earlier attempts when this value increases.
        iteration:
          type: integer
          minimum: 1
          description: Model iteration that owns the assistant message.
        content_index:
          type: integer
          minimum: 0
        thinking:
          type: string
          minLength: 1
        emitted_at:
          type: string
          format: date-time
    InvocationUpdateEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - invocation
        - new_messages
      properties:
        type:
          type: string
          enum:
            - invocation.update
          x-enum-varnames:
            - EventInvocationUpdate
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        invocation:
          $ref: "#/components/schemas/Invocation"
        new_messages:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
    SessionMessage:
      type: object
      additionalProperties: false
      required:
        - id
        - session_id
        - agent_id
        - invocation_id
        - sequence
        - role
        - content
        - created_at
      properties:
        id:
          type: string
          pattern: ^smsg_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
        session_id:
          $ref: "#/components/schemas/SessionID"
        agent_id:
          $ref: "#/components/schemas/AgentID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        sequence:
          type: integer
          format: int64
          minimum: 1
        role:
          $ref: "#/components/schemas/SessionMessageRole"
        content:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/SessionContentBlock"
        phase:
          allOf:
            - $ref: "#/components/schemas/MessagePhase"
          description: |
            What this assistant message was in its turn. Absent on user and
            tool messages, which have no phase.

            Derived at read time from the settling facts the Invocation
            already carries, so it costs no migration and takes no host input.
            One consequence: a message read from the live stream before its
            turn settles reads `commentary`, because nothing had settled yet.
            The same message read after settlement reads its final phase, and
            the stream's `invocation_changes` are what announce the settlement
            in between.
        created_at:
          type: string
          format: date-time
    SessionMessageRole:
      type: string
      enum:
        - user
        - assistant
        - tool
    SessionContentBlock:
      type: object
      additionalProperties: true
      required:
        - type
      properties:
        type:
          type: string
      description: |
        A canonical provider-neutral content block. Stored media bytes are
        projected on reads as `type`, `media_type`, optional `title`, decoded
        `bytes`, and a `sha256:` digest; other block members are preserved.
    MessagePhase:
      type: string
      enum:
        - commentary
        - final_answer
      description: |
        `final_answer` is the assistant message that settled its turn
        `completed` with `stop_reason: end_turn` — the reply. `commentary` is
        everything else: narration between tool calls, and every message of an
        interrupted or `incomplete` turn, none of which was ever an answer. A
        turn with no `final_answer` is visibly unfinished, which is what makes
        `incomplete` directly renderable.
    InvocationResultEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - result
      properties:
        type:
          type: string
          enum:
            - invocation.result
          x-enum-varnames:
            - EventInvocationResult
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        result:
          $ref: "#/components/schemas/InvocationResult"
    InvocationResult:
      type: object
      additionalProperties: false
      required:
        - invocation
        - messages
        - output_text
      properties:
        invocation:
          $ref: "#/components/schemas/Invocation"
        messages:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
          description: |
            Every canonical SessionMessage owned by this Invocation, all
            roles, ascending sequence, composed at read time from the
            canonical transcript. Nothing is stored twice.
        output_text:
          oneOf:
            - type: string
            - type: "null"
          description: |
            The text content blocks of this Invocation's assistant-role
            messages concatenated in transcript order without separators.
            Non-null only when the Invocation is completed and at least one
            assistant text block exists. Failed and cancelled Invocations
            keep their messages readable as evidence while output_text stays
            null.
    StreamResyncEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - reason
      properties:
        type:
          type: string
          enum:
            - stream.resync
          x-enum-varnames:
            - EventStreamResync
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          oneOf:
            - $ref: "#/components/schemas/InvocationID"
            - type: "null"
        reason:
          type: string
          enum:
            - live_delivery_gap
          x-enum-varnames:
            - ReasonLiveDeliveryGap
    StreamEndEvent:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - invocation_id
        - reason
        - resume_cursor
      properties:
        type:
          type: string
          enum:
            - stream.end
          x-enum-varnames:
            - EventStreamEnd
        session_id:
          $ref: "#/components/schemas/SessionID"
        invocation_id:
          oneOf:
            - $ref: "#/components/schemas/InvocationID"
            - type: "null"
        reason:
          type: string
          enum:
            - terminal
            - rotate
          description: |
            `rotate` means reconnect with `resume_cursor`. `terminal` means
            the scoped Invocation settled, or the Session became idle for a
            Session-scoped stream.
        resume_cursor:
          type: string
          minLength: 1
    NudgeInvocationRequest:
      type: object
      additionalProperties: false
      required:
        - content
      properties:
        content:
          allOf:
            - $ref: "#/components/schemas/InvocationInput"
          description: |
            Text guidance for the running turn: a string, or an array of text
            blocks. Media blocks are refused here.
        idempotency_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Per-Invocation retry key. The same key with the same content
            returns the original acknowledgement with `deduped: true`; the
            same key with different content is refused, so a key is never
            reused with different words.
    NudgeAcknowledgement:
      type: object
      additionalProperties: false
      required:
        - pending_input_id
        - state
        - deduped
        - after_sequence
      properties:
        pending_input_id:
          $ref: "#/components/schemas/PendingInputID"
        state:
          $ref: "#/components/schemas/PendingInputStatus"
        deduped:
          type: boolean
        after_sequence:
          type: integer
          format: int64
          description: |
            Transcript position to watch from for the promoted message. It is
            the Session cursor as of this call for input that has not been
            drained, and the position immediately before the promoted message
            for input that already has, so reading after it finds the message
            either way.
    PendingInputID:
      type: string
      pattern: ^npin_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: UUIDv7 with the public `npin_` prefix.
    PendingInputStatus:
      type: string
      enum:
        - pending
        - drained
        - expired
        - cancelled
      description: |
        `pending` is the only state the turn consumes. `drained` means the
        executor promoted the content into the transcript, `expired` that the
        Invocation settled without taking it, and `cancelled` that the caller
        withdrew it first.
    PendingInputList:
      type: object
      additionalProperties: false
      required:
        - items
        - has_more
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/PendingInput"
        has_more:
          type: boolean
        next_cursor:
          oneOf:
            - type: string
            - type: "null"
    PendingInput:
      type: object
      additionalProperties: false
      required:
        - id
        - invocation_id
        - status
        - content
        - created_at
      properties:
        id:
          $ref: "#/components/schemas/PendingInputID"
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        status:
          $ref: "#/components/schemas/PendingInputStatus"
        content:
          $ref: "#/components/schemas/InvocationInput"
        idempotency_key:
          type: string
        created_at:
          type: string
          format: date-time
        drained_at:
          type: string
          format: date-time
        drained_message_sequence:
          type: integer
          format: int64
          description: |
            Transcript sequence the content was promoted into. Present only on
            a drained input, and the receipt that the model saw it.
        settled_at:
          type: string
          format: date-time
    SubmitHostToolResultsRequest:
      type: object
      additionalProperties: false
      required:
        - results
      properties:
        results:
          type: array
          minItems: 1
          maxItems: 32
          items:
            type: object
            additionalProperties: false
            required:
              - tool_call_id
              - content
            properties:
              tool_call_id:
                $ref: "#/components/schemas/ToolCallID"
              content: {}
              is_error:
                type: boolean
    SubmitHostToolResultsResponse:
      type: object
      additionalProperties: false
      required:
        - invocation_id
        - session_id
        - status
        - results
        - pending_tool_calls
      properties:
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        session_id:
          $ref: "#/components/schemas/SessionID"
        status:
          $ref: "#/components/schemas/InvocationStatus"
        results:
          type: array
          minItems: 1
          maxItems: 32
          items:
            $ref: "#/components/schemas/HostToolResultAcceptance"
        pending_tool_calls:
          type: array
          items:
            $ref: "#/components/schemas/PendingHostToolCall"
    HostToolResultAcceptance:
      type: object
      additionalProperties: false
      required:
        - tool_call_id
        - status
        - deduplicated
      properties:
        tool_call_id:
          $ref: "#/components/schemas/ToolCallID"
        status:
          type: string
          enum:
            - completed
            - failed
        deduplicated:
          type: boolean
    DailyUsage:
      type: object
      additionalProperties: false
      required:
        - start_date
        - end_date
        - items
      properties:
        start_date:
          type: string
          format: date
          description: Inclusive first UTC day of the served window.
        end_date:
          type: string
          format: date
          description: Inclusive last UTC day of the served window.
        items:
          type: array
          items:
            $ref: "#/components/schemas/DailyUsageBucket"
          description: Buckets ordered by day, app, provider, and model. Days with no usage produce no bucket.
    DailyUsageBucket:
      type: object
      additionalProperties: false
      required:
        - day
        - app_id
        - provider
        - model
        - invocations
        - model_calls
        - input_tokens
        - output_tokens
        - cache_creation_input_tokens
        - cache_read_input_tokens
        - reasoning_tokens
        - estimated_cost
      properties:
        day:
          type: string
          format: date
          description: UTC calendar day of the bucket.
        app_id:
          type: string
          description: The app whose tenants produced the usage.
        provider:
          type: string
          description: Provider from checkpoint provenance; empty when provenance omitted it.
        model:
          type: string
          description: Served model from provenance, falling back to the requested model.
        invocations:
          type: integer
          minimum: 0
          description: Distinct Invocations with at least one model call in the bucket. Compaction calls carry no Invocation.
        model_calls:
          type: integer
          minimum: 0
          description: Individual provider calls in the bucket.
        input_tokens:
          type: integer
          minimum: 0
        output_tokens:
          type: integer
          minimum: 0
        cache_creation_input_tokens:
          type: integer
          minimum: 0
        cache_read_input_tokens:
          type: integer
          minimum: 0
        reasoning_tokens:
          type: integer
          minimum: 0
        estimated_cost:
          type: number
          description: Summed list-price estimate in USD; zero when every call in the bucket was unpriced.
    ModelList:
      type: object
      additionalProperties: false
      required:
        - items
        - catalog_version
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ModelDescriptor"
        catalog_version:
          type: string
          minLength: 1
          description: Opaque version of the complete unfiltered catalog and pricing snapshot.
    ModelDescriptor:
      type: object
      additionalProperties: false
      required:
        - provider
        - id
        - cataloged
        - pricing
      properties:
        provider:
          $ref: "#/components/schemas/ModelProvider"
        id:
          type: string
          minLength: 1
          maxLength: 255
        cataloged:
          type: boolean
          description: Whether nvoken advertises and maintains metadata for this selection.
        display_name:
          type: string
          minLength: 1
        description:
          type: string
          minLength: 1
        context_window_tokens:
          type: integer
          minimum: 1
        max_output_tokens:
          type: integer
          minimum: 1
        input_modalities:
          type: array
          minItems: 1
          items:
            type: string
            pattern: ^[a-z][a-z0-9_]*$
          description: |
            Input modalities admission accepts for this model selection. Media
            modalities also publish their exact media types under
            controls.input.media.
        recommended:
          type: boolean
          description: nvoken's suggested general-purpose starting point for this provider.
        deprecated:
          type: boolean
        controls:
          $ref: "#/components/schemas/ModelControlCapabilities"
        pricing:
          $ref: "#/components/schemas/ModelPricing"
      description: |
        Cataloged descriptors include all maintained metadata fields.
        Uncataloged descriptors include only provider, id, cataloged, and
        pricing; absent metadata is unknown rather than false or zero.
        A cataloged descriptor's controls are the same qualified capabilities
        used by admission; omitted controls on an uncataloged descriptor mean
        unknown, not unsupported.
    ModelControlCapabilities:
      type: object
      additionalProperties: false
      required:
        - sampling
        - reasoning
        - tools
        - input
      properties:
        sampling:
          $ref: "#/components/schemas/ModelSamplingCapabilities"
        reasoning:
          $ref: "#/components/schemas/ModelReasoningCapabilities"
        tools:
          $ref: "#/components/schemas/ModelToolCapabilities"
        input:
          $ref: "#/components/schemas/ModelInputCapabilities"
    ModelSamplingCapabilities:
      type: object
      additionalProperties: false
      required:
        - temperature
      properties:
        temperature:
          type: boolean
          description: Whether nvoken can guarantee an explicit temperature reaches this exact model path.
    ModelReasoningCapabilities:
      type: object
      additionalProperties: false
      required:
        - effort
        - budget_tokens
        - effort_budget_compatible
      properties:
        effort:
          $ref: "#/components/schemas/ModelReasoningEffortCapabilities"
        budget_tokens:
          $ref: "#/components/schemas/ModelReasoningBudgetCapabilities"
        effort_budget_compatible:
          type: boolean
    ModelReasoningEffortCapabilities:
      type: object
      additionalProperties: false
      required:
        - supported
        - values
        - with_temperature
      properties:
        supported:
          type: boolean
        values:
          type: array
          items:
            $ref: "#/components/schemas/ReasoningEffort"
        with_temperature:
          type: boolean
    ModelReasoningBudgetCapabilities:
      type: object
      additionalProperties: false
      required:
        - supported
        - with_temperature
      properties:
        supported:
          type: boolean
        with_temperature:
          type: boolean
        minimum:
          type: integer
          minimum: 1024
        maximum:
          type: integer
          minimum: 1024
    ModelToolCapabilities:
      type: object
      additionalProperties: false
      required:
        - choice
        - web_search
      properties:
        choice:
          $ref: "#/components/schemas/ModelToolChoiceCapabilities"
        web_search:
          type: boolean
          description: |
            Whether this exact model supports the provider's server-side web
            search. Admission fails closed on it, so `spec.provider_tools` is
            refused on a model that reports false — including any uncataloged
            model, which reports nothing.
    ModelToolChoiceCapabilities:
      type: object
      additionalProperties: false
      required:
        - modes
        - with_reasoning
      properties:
        modes:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: "#/components/schemas/ModelToolChoiceMode"
        with_reasoning:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: "#/components/schemas/ModelToolChoiceMode"
          description: Modes that remain exact when explicit reasoning is active.
    ModelInputCapabilities:
      type: object
      additionalProperties: false
      required:
        - media
      properties:
        media:
          $ref: "#/components/schemas/ModelMediaCapabilities"
    ModelMediaCapabilities:
      type: object
      additionalProperties: false
      required:
        - image
        - document
      properties:
        image:
          $ref: "#/components/schemas/ModelMediaKindCapabilities"
        document:
          $ref: "#/components/schemas/ModelMediaKindCapabilities"
    ModelMediaKindCapabilities:
      type: object
      additionalProperties: false
      required:
        - supported
        - media_types
      properties:
        supported:
          type: boolean
        media_types:
          type: array
          uniqueItems: true
          items:
            type: string
          description: |
            Exact media types admission accepts for this model. An empty list
            means the modality is unsupported.
    ModelPricing:
      type: object
      additionalProperties: false
      required:
        - status
        - pricing_version
      description: |
        Standard local price evidence used by nvoken's estimated-cost
        guardrail. A `priced` object also carries currency, unit, input, output,
        and update date. `unpriced` and `unknown` carry only status and the
        opaque pricing version. Clients compare versions for equality only.
      properties:
        status:
          type: string
          enum:
            - priced
            - unpriced
            - unknown
        currency:
          type: string
          enum:
            - USD
        unit:
          type: string
          enum:
            - per_million_tokens
        input:
          type: string
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
        output:
          type: string
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
        cache_read:
          type: string
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
        cache_write:
          type: string
          pattern: ^(0|[1-9][0-9]*)(\.[0-9]+)?$
        updated_at:
          type: string
          format: date
        pricing_version:
          type: string
          minLength: 1
          description: |
            Opaque nvoken-owned identifier for the local pricing snapshot.
            Values never encode an upstream dependency name or version.
    CreateSessionRequest:
      type: object
      additionalProperties: false
      dependentRequired:
        session_key:
          - agent_key
      properties:
        agent_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional stable caller-controlled Agent key, resolved or created
            exactly as invocation admission does. Omitting it creates an
            unbound Session whose agent is set by its first admitted
            Invocation.
        tenant_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional tenant partition. Precedence is credential constraint,
            this explicit value, then the default partition.
        user_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional host-owned end-user label recorded on the Session.
            Filtering only; not an isolation boundary.
        session_key:
          type: string
          minLength: 1
          maxLength: 255
          description: |
            Optional caller key resolved within (effective tenant partition,
            Agent, session_key). Requires agent_key. Makes creation an
            upsert: an existing keyed Session is returned unchanged.
        session_options:
          $ref: "#/components/schemas/SessionOptions"
    Session:
      type: object
      additionalProperties: false
      required:
        - id
        - agent_id
        - tenant_key
        - session_key
        - user_key
        - compaction
        - retention
        - expires_at
        - metadata
        - active_invocation_id
        - active_invocation_status
        - usage
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/SessionID"
        agent_id:
          oneOf:
            - $ref: "#/components/schemas/AgentID"
            - type: "null"
          description: |
            Null only for a Session created ahead of its first Invocation.
            The first admitted Invocation binds it; once set the binding is
            immutable.
        tenant_key:
          oneOf:
            - type: string
            - type: "null"
          description: Immutable effective tenant partition reference.
        session_key:
          oneOf:
            - type: string
            - type: "null"
        user_key:
          oneOf:
            - type: string
            - type: "null"
          description: |
            Host-owned end-user label recorded when this Session was opened.
            Filtering only; not an isolation boundary.
        compaction:
          oneOf:
            - $ref: "#/components/schemas/ContextCompactionSpec"
            - type: "null"
          description: |
            The automatic compaction policy this Session actually applies, or
            null when it compacts nothing. It is echoed resolved: a request
            that asked for `trigger_tokens: auto` reads back the integer that
            resolved to, and a request that named no model reads back the
            model the policy bound. Nothing here is ever the unresolved
            request.
        retention:
          oneOf:
            - $ref: "#/components/schemas/SessionRetentionSpec"
            - type: "null"
          description: |
            The idle retention window this Session was created with, or null
            when it is retained until deleted explicitly. A window outside the
            supported range is refused at creation rather than clamped, so
            what is read back is always exactly what applies.
        expires_at:
          oneOf:
            - type: string
              format: date-time
            - type: "null"
          description: |
            When this Session becomes eligible for automatic erasure, or null
            when it has no retention window. It moves forward on every
            Invocation admission and settlement, so a Session in use stays
            ahead of it.
        metadata:
          oneOf:
            - $ref: "#/components/schemas/Metadata"
            - type: "null"
          description: |
            Host correlation data, returned verbatim. Set at creation through
            `session_options.metadata` and changed with
            `PATCH /v1/sessions/{session_id}`.
        active_invocation_id:
          oneOf:
            - $ref: "#/components/schemas/InvocationID"
            - type: "null"
          description: The queued, running, or waiting Invocation, if one exists.
        active_invocation_status:
          oneOf:
            - type: string
              enum:
                - queued
                - running
                - waiting
            - type: "null"
          description: Status of active_invocation_id; null exactly when that ID is null.
        usage:
          oneOf:
            - $ref: "#/components/schemas/ModelUsage"
            - type: "null"
          description: |
            Read-time sum of this Session's non-null Invocation usage and
            committed private compaction usage. Null until either exists. This
            normalized estimate is not a billing ledger.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        pending_tool_calls:
          type: array
          items:
            $ref: "#/components/schemas/PendingHostToolCall"
          description: Pending host calls for the active waiting Invocation.
    SessionList:
      type: object
      additionalProperties: false
      required:
        - items
        - has_more
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Session"
        has_more:
          type: boolean
        next_cursor:
          oneOf:
            - type: string
            - type: "null"
    UpdateSessionRequest:
      type: object
      additionalProperties: false
      required:
        - metadata
      properties:
        metadata:
          type: object
          additionalProperties:
            oneOf:
              - type: string
                maxLength: 512
              - type: "null"
          maxProperties: 16
          propertyNames:
            pattern: ^[A-Za-z0-9_.:-]+$
            maxLength: 64
          description: |
            Metadata merge patch. A string value sets the key, `null` deletes
            it, and an absent key is left alone. Bounds are enforced on the
            merged result.
    SessionMessageList:
      type: object
      additionalProperties: false
      required:
        - items
        - has_more
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
        has_more:
          type: boolean
        next_cursor:
          oneOf:
            - type: string
            - type: "null"
    TranscriptSnapshot:
      type: object
      additionalProperties: false
      required:
        - messages
        - invocation_changes
        - has_more
        - resume_cursor
        - next_page_token
      properties:
        messages:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
        invocation_changes:
          type: array
          items:
            $ref: "#/components/schemas/InvocationChange"
        has_more:
          type: boolean
        resume_cursor:
          type: string
          minLength: 1
          description: Delivered composite message-sequence and lifecycle-revision watermark.
        next_page_token:
          oneOf:
            - type: string
            - type: "null"
    InvocationChange:
      type: object
      additionalProperties: false
      required:
        - invocation_id
        - revision
        - status
        - through_message_sequence
        - error
        - usage
        - provenance
        - structured_output
        - structured_output_provenance
        - occurred_at
      properties:
        invocation_id:
          $ref: "#/components/schemas/InvocationID"
        revision:
          type: integer
          format: int64
          minimum: 1
        status:
          $ref: "#/components/schemas/InvocationStatus"
        through_message_sequence:
          oneOf:
            - type: integer
              format: int64
              minimum: 1
            - type: "null"
        error:
          oneOf:
            - $ref: "#/components/schemas/InvocationFailure"
            - type: "null"
        usage:
          oneOf:
            - $ref: "#/components/schemas/ModelUsage"
            - type: "null"
        provenance:
          oneOf:
            - $ref: "#/components/schemas/ModelProvenance"
            - type: "null"
        structured_output:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
        structured_output_provenance:
          oneOf:
            - $ref: "#/components/schemas/StructuredOutputProvenance"
            - type: "null"
        occurred_at:
          type: string
          format: date-time
    TranscriptStreamEvent:
      description: |
        The JSON value carried by one Session-stream SSE `data:` field.
        Durable `transcript.update` frames carry the resume cursor as both
        payload data and SSE `id`; preview and control frames never carry IDs.
      oneOf:
        - $ref: "#/components/schemas/TranscriptUpdate"
        - $ref: "#/components/schemas/OutputTextDeltaEvent"
        - $ref: "#/components/schemas/ThinkingDeltaEvent"
        - $ref: "#/components/schemas/StreamResyncEvent"
        - $ref: "#/components/schemas/StreamEndEvent"
    TranscriptUpdate:
      type: object
      additionalProperties: false
      required:
        - type
        - session_id
        - messages
        - invocation_changes
        - resume_cursor
      properties:
        type:
          type: string
          enum:
            - transcript.update
          x-enum-varnames:
            - EventTranscriptUpdate
        session_id:
          $ref: "#/components/schemas/SessionID"
        messages:
          type: array
          items:
            $ref: "#/components/schemas/SessionMessage"
        invocation_changes:
          type: array
          items:
            $ref: "#/components/schemas/InvocationChange"
        resume_cursor:
          type: string
          minLength: 1
          description: Delivered composite message-sequence and lifecycle-revision watermark.
    CurrentIdentity:
      type: object
      required:
        - authentication
      properties:
        authentication:
          type: object
          required:
            - credential_id
            - effective_profile
            - operations
            - method
            - assurance
          properties:
            credential_id:
              type: string
            effective_profile:
              $ref: "#/components/schemas/Profile"
            tenant_key:
              type:
                - string
                - "null"
            session_id:
              type:
                - string
                - "null"
            operations:
              type: array
              items:
                $ref: "#/components/schemas/Operation"
            method:
              type: string
              enum:
                - api_key
            assurance:
              type: string
              const: bearer
    Profile:
      type: string
      enum:
        - Runtime
        - Viewer
    Operation:
      type: string
      enum:
        - create_invocation
        - create_session
        - get_agent
        - list_agents
        - get_invocation
        - submit_tool_results
        - cancel_invocation
        - list_invocations
        - get_session
        - list_sessions
        - list_session_messages
        - get_session_transcript
        - get_identity
  responses:
    InvalidRequest:
      description: The request is invalid or contains a deferred field.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: invalid_request
            message: spec.tools[0].mode must be host.
            request_id: req_01K0NV9K7Z9WR3Y3Q3RMV2G7H0
            details:
              path: /spec/tools/0/mode
    Unauthenticated:
      description: Authentication is missing or invalid.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: unauthenticated
            message: A valid API credential is required.
            request_id: req_01K0NVA1K5T8Y1MBVX7TZBM21H
    Forbidden:
      description: The authenticated credential is not permitted to make this request.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: forbidden
            message: The requested tenant_key conflicts with the credential constraint.
            request_id: req_01K0NVAE24H917QNE9P1QMX95Y
    RateLimited:
      description: The request is rate limited.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 0
          description: Seconds before the caller should retry.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: rate_limited
            message: Too many requests.
            request_id: req_01K0NVB0QFQP0YNSVD3CB7E97S
    Internal:
      description: An unexpected server error occurred before acknowledgement.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: internal
            message: The request could not be completed.
            request_id: req_01K0NVB8MJPBPQY0WXFJGCXEH3
    Unavailable:
      description: The service cannot currently complete admission or the read.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: unavailable
            message: The service is temporarily unavailable.
            request_id: req_01K0NVBGWQ1V6JJBVH0Q4WZRS1
    NotFound:
      description: The resource is missing or cannot be disclosed in this scope.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: not_found
            message: The requested resource was not found.
            request_id: req_01K0NVASB4CF29K41VZA271H23
    MCPDiscoveryFailed:
      description: The remote MCP server could not be safely discovered or projected.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            code: mcp_discovery_failed
            message: The remote MCP server could not be discovered.
            request_id: req_01K0NVCPKYGE1D64K9DXMZ7J64
  headers:
    ETag:
      description: Strong validator for this exact JSON representation.
      schema:
        type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: nvoken API key
      description: Use a Runtime or Viewer API key from the App's API keys page in the nvoken dashboard.
