> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ishlabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Session

> The full session state + a cursor page of the decision trace.

`after_index` returns turns strictly after that turn_index; `limit`
(default 50, max 200) caps the page. `has_more` in the body signals
whether to page again.



## OpenAPI

````yaml /api/openapi.public.json get /api/v1/sessions/{session_id}
openapi: 3.1.0
info:
  contact:
    name: ish developer platform
    url: https://docs.ishlabs.io/
  description: >
    The ish Developer API: the entry point for getting human feedback on
    whatever you're building, in whichever format you need.


    Its first surface is **sessions**: you open a session for one of your people
    inside *your own* environment, submit what that person perceives
    (human-sensory observations: a rendered screen, an error message) along with
    the set of actions your environment currently offers, and they decide one
    action per turn and tell you how it felt. Your code executes the chosen
    action and loops. **The loop is decide-only: ish never locates, drives, or
    executes anything inside your environment.** You stay in control of what
    runs; we tell you what a person would do next and why.


    ## Vocabulary


    The public vocabulary is **workspace**, **person**, **environment**, and
    **session**, and the wire uses these names directly:


    - `workspace_id`: the workspace that owns the person and is billed for the  
    session (the create path is `/workspaces/{workspace_id}/sessions`).

    - `person_id`: the person the session drives. Two kinds are available to
    you:   the ones your workspace owns, and the ones published in the shared
    ish   library. Both are listed and searchable under
    `/v1/workspaces/{workspace_id}/people`, and each carries an `owner` saying
    which it is.

    - `environment_id`: a registered environment in that workspace: a named
    target   with its own operating notes and default action vocabulary.
    Register one and   reference it, or describe an ad-hoc one inline on the
    session; supply exactly   one of the two.


    **"Session" here means one API session: a single person working toward one
    task in your environment, turn by turn.** It is not the ish product's notion
    of a study session (someone sitting down with a study), and it is unrelated
    to a browser session. Sessions live under `/v1/sessions`.


    ## Authentication


    All requests use a bearer token: `Authorization: Bearer <token>`. The token
    is a **workspace API key** (prefix `ish_sk_live_`), a workspace-scoped
    machine principal minted in Settings > Developers and shown exactly once at
    mint. Scopes: `sessions:run` (create, submit turns, close), `sessions:read`
    (read the session and trace), `environments:read` / `environments:write` for
    the environment registry, `tasks:read` / `tasks:write` for the task
    registry, `people:read` / `people:write` for the people you can run a
    session for, and `usage:read` for this workspace's consumption, limits and
    rate-limit budgets. Keys are minted with `sessions:run`, `sessions:read`,
    and `tasks:read`; ask for any `:write` scope, and for `people:read` and
    `usage:read`, explicitly. `usage:read` is never implied by another scope: it
    covers the shape of the whole workspace's operation, not just the lane a key
    was minted for. An ish user access token (JWT) also works on the same header
    for personal scripts; server integrations should use keys.


    ## Versioning & forward-compatibility


    The API is versioned in the URL path (`/api/v1`). Several enum-typed and
    block-typed fields are **open unions**: new values may be added over time,
    and clients MUST tolerate values they do not recognize rather than failing
    on them. The field descriptions call these out.


    ## Correlation & errors


    Every response carries an **`X-Request-Id`** header; quote it in support
    requests. Error responses additionally echo it in the body as `request_id`.


    Two error-body shapes exist; parse defensively, they differ:


    - **`ValidationError`**: the request-validation layer (HTTP 422): a
    malformed   body (wrong types, missing fields, an invalid observation
    `type`, a bad enum   value, `parameters` that don't fit the model). A
    field-level envelope   (`error_code`, `errors[]`, `suggestions[]`;
    `suggestions` is CLI-oriented   boilerplate; ignore it for API use).

    - **`Error`**: everything else, a `{ detail, request_id }` envelope. This  
    covers the handler layer's business-rule 422s (e.g. `exactly one image  
    observation block is required`; `person has no renderable background`) as
    well   as 401 / 402 / 403 / 404 / 409 / 429 / 502.


    That is why 422 is documented on the responses below as either shape
    (`oneOf`).


    **Branch on `detail.error_kind`, not on the status code.** On every refusal
    this API classifies, `detail` is an object rather than a string:


    ```json

    {
      "detail": {
        "error_kind": "turn_index_mismatch",
        "message": "expected turn_index 4, got 3",
        "expected_turn_index": 4
      },
      "request_id": "9f2c...",
      "docs_url": "https://docs.ishlabs.io/api/errors#turn_index_mismatch"
    }

    ```


    `error_kind` is the stable, machine-readable classification; `message` is
    for humans and may be reworded; any further keys carry the specifics you
    would act on. One status often covers several kinds with different remedies.
    A 402 is either "top up" or "raise your cap"; a 429 is either "slow down" or
    "you are out of sessions for today". The status alone is not enough to
    decide what to do. `docs_url` links to that kind's entry in the error
    catalog.


    The set of kinds is an **open union**: new ones are added as new refusals
    are classified, so treat an unfamiliar `error_kind` as "an error of this
    status class" rather than failing on it. `message` is always present.


    ## Rate limits


    API keys carry a per-key request budget and, on session creates, a per-key
    daily session quota. Exceeding either is a `429` with a `Retry-After` header
    (also echoed as `detail.retry_after_seconds`) and a distinct `error_kind`:
    `rate_limited` resets within the minute, `session_quota_exceeded` on a day
    boundary. Both are transient and neither affects sessions that are already
    open.


    You do not have to wait for the `429` to find out. Every key-authed
    response, including the `429` itself, carries the budget it just spent:


    - **`x-ratelimit-limit`**: the ceiling for that window.

    - **`x-ratelimit-remaining`**: what is left in it after this request.

    - **`x-ratelimit-reset`**: **seconds from now** until the window resets. A  
    delta, not a Unix timestamp.


    When a request charges more than one budget (a session create spends both),
    the headers describe the one closest to refusing you, so pacing off
    `remaining` is always pacing off the binding constraint. Treat all three as
    optional: they are absent when nothing was charged, and `reset` alone can be
    omitted if the limit backend cannot be read at that moment.


    User access tokens are deliberately not rate limited, so their responses
    carry no `x-ratelimit-*` headers. The per-key budgets exist because a key is
    a machine principal that can loop unattended; an interactive session is
    bounded by the person driving it.
  license:
    identifier: LicenseRef-Proprietary
    name: Proprietary
  title: ish Developer API
  version: 1.6.1
servers:
  - description: Production
    url: https://api.ishlabs.io
security:
  - bearerAuth: []
tags:
  - description: >-
      Open a session for one of your people inside your own environment, submit
      what they perceive turn by turn, and read back the decision they made and
      how it felt.
    name: sessions
  - description: >-
      Register the targets your sessions run against, and the declared versions
      of each one. A registered environment carries its own operating notes and
      default action vocabulary, so a session only has to name it.
    name: environments
  - description: >-
      Register the intent a session works toward, so runs stay comparable across
      environments and over time. A session can also describe its instructions
      inline and skip the registry entirely.
    name: tasks
  - description: >-
      Find the person a session should run as. Read and search the pool you can
      draw on: the people your workspace owns, plus the ones published in the
      shared ish library. Read-only at this version.
    name: people
  - description: >-
      What this workspace has consumed, and what bounds it: the session usage
      series over time, the spend cap and the credits drawn against it, and the
      live rate-limit budgets this credential is spending. Read-only.
    name: usage
paths:
  /api/v1/sessions/{session_id}:
    get:
      tags:
        - sessions
      summary: Get Session
      description: |-
        The full session state + a cursor page of the decision trace.

        `after_index` returns turns strictly after that turn_index; `limit`
        (default 50, max 200) caps the page. `has_more` in the body signals
        whether to page again.
      operationId: getSession
      parameters:
        - in: path
          name: session_id
          required: true
          schema:
            format: uuid
            title: Session Id
            type: string
        - in: query
          name: after_index
          required: false
          schema:
            anyOf:
              - minimum: 0
                type: integer
              - type: 'null'
            title: After Index
        - in: query
          name: limit
          required: false
          schema:
            default: 50
            maximum: 200
            minimum: 1
            title: Limit
            type: integer
      responses:
        '200':
          content:
            application/json:
              examples:
                default:
                  summary: An open session after one turn
                  value:
                    accrued_credits: 1
                    created_at: '2026-07-28T09:14:02.117000Z'
                    created_by_api_key_id: 5e8f3a21-64c7-4e0b-9a3d-77dd88ee99ff
                    decision_mode: intent
                    ended_at: null
                    ended_reason: null
                    environment:
                      kind: web
                      name: Checkout (staging)
                    environment_id: 3c2a1f80-7e64-4b19-8d55-11aa22bb33cc
                    environment_revision: 4
                    environment_version_id: null
                    environment_version_label: null
                    has_more: false
                    id: 7d1e4c09-53af-4d2b-9f60-8c7b6a5e4321
                    max_turns: 20
                    observation_retention: 30d
                    pacing: pause_on_decide
                    person_id: 9f1c7b52-0d3a-4a1e-9c4f-2b8e5a6d1234
                    person_snapshot:
                      city: Manchester
                      country: GB
                      id: 9f1c7b52-0d3a-4a1e-9c4f-2b8e5a6d1234
                      name: Dana Whitfield
                      occupation: Paediatric nurse
                      snapshotted_at: '2026-07-28T09:14:02.101000Z'
                      type: ai
                    reasoning_effort: low
                    status: open
                    task:
                      id: null
                      instructions: Find the refund policy and start a return.
                      name: null
                      revision: null
                    turn_count: 1
                    turns:
                      - created_at: '2026-07-28T09:14:19.402000Z'
                        decision:
                          action_name: continue_to_payment
                          comment: >-
                            The total matches what was in my cart, so I am ready
                            to pay.
                          felt_intensity: moderate
                          sentiment: Confident
                          status: continue
                        frame_age_ms: 312
                        input_tokens: 1204
                        latency_ms: 2841
                        observation_kinds:
                          - image
                        observation_url: null
                        output_tokens: 96
                        turn_index: 0
                        validation_retries: 0
                    updated_at: '2026-07-28T09:14:19.480000Z'
                    workspace_id: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed
              schema:
                $ref: '#/components/schemas/SessionDetailResponse'
          description: Successful Response
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            The bearer token is missing, malformed, unknown, or revoked
            (`detail.error_kind = 'invalid_api_key'`). One kind covers all four
            deliberately: distinguishing them would confirm which guesses are
            real keys.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            The API key lacks the `sessions:read` scope (`detail.error_kind =
            'insufficient_scope'`, with `detail.required_scope =
            'sessions:read'`). Mint a key that includes it; an ish user access
            token is unconstrained by scopes.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            The session was not found, or is not reachable with this credential.
            `detail.error_kind` is `session_not_found`.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '422':
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ValidationError'
                  - $ref: '#/components/schemas/Error'
          description: >-
            Validation error. A request-shape failure uses the `ValidationError`
            envelope; a handler-raised domain-validation refusal uses the
            `Error` envelope.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            The API key's request rate limit is exceeded (`detail.error_kind =
            'rate_limited'`). Transient: retry after the `Retry-After` interval,
            also echoed as `detail.retry_after_seconds`. Not applied to user
            access tokens.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            An unexpected server-side failure. `detail.error_kind` is
            `session_actor_missing`. Not a state to handle: quote `request_id`
            and the kind in a bug report.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
            x-ratelimit-limit:
              $ref: '#/components/headers/XRateLimitLimit'
            x-ratelimit-remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            x-ratelimit-reset:
              $ref: '#/components/headers/XRateLimitReset'
      security:
        - bearerAuth: []
components:
  schemas:
    SessionDetailResponse:
      allOf:
        - $ref: '#/components/schemas/SessionResponse'
        - properties:
            has_more:
              description: >-
                True when more turns exist beyond this page (page with
                after_index).
              title: Has More
              type: boolean
            turns:
              items:
                $ref: '#/components/schemas/SessionTurnView'
              title: Turns
              type: array
          required:
            - turns
            - has_more
          type: object
      description: |-
        Full session state + a page of the ordered decision trace (GET).

        Exactly the create/close body plus the trace, so a caller never has to
        reconcile two differently-shaped views of one session.
      title: SessionDetailResponse
    Error:
      description: >-
        Generic error envelope for 4xx/5xx responses other than request-shape
        validation.
      properties:
        detail:
          anyOf:
            - type: string
            - additionalProperties: true
              type: object
          description: >-
            A human-readable message, OR a structured object carrying a
            machine-readable `error_kind` (e.g. `idempotency_conflict`,
            `turn_index_mismatch`, `decision_invalid`).
          title: Detail
        docs_url:
          description: >-
            Link to this error's entry in the error catalog. Present whenever
            `detail` carries a registered `error_kind`; absent on unclassified
            errors.
          title: Docs Url
          type: string
        request_id:
          description: >-
            Correlation id; identical to the `X-Request-Id` response header.
            Quote it in support requests.
          title: Request Id
          type: string
      required:
        - detail
        - request_id
      title: Error
      type: object
    ValidationError:
      description: >-
        Request body / parameter validation failure (HTTP 422). A field-level
        envelope so callers can pinpoint the offending field, value, and allowed
        values without parsing prose.
      properties:
        detail:
          title: Detail
          type: string
        error:
          title: Error
          type: string
        error_code:
          description: Stable machine code, e.g. `validation_error`.
          title: Error Code
          type: string
        errors:
          items:
            $ref: '#/components/schemas/FieldError'
          title: Errors
          type: array
        retryable:
          title: Retryable
          type: boolean
        status:
          title: Status
          type: integer
        suggestions:
          items:
            type: string
          title: Suggestions
          type: array
      required:
        - error
        - error_code
        - status
        - retryable
        - detail
        - errors
      title: ValidationError
      type: object
    SessionResponse:
      allOf:
        - $ref: '#/components/schemas/SessionBase'
        - properties:
            observation_retention:
              description: >-
                Retention window resolved at open: `none`, `30d`, `90d`, or
                `indefinite`. Open union: new values may be added over time;
                clients must tolerate unknown values rather than failing on
                them.
              title: Observation Retention
              type: string
            pacing:
              $ref: '#/components/schemas/PacingContract'
            person_id:
              anyOf:
                - format: uuid
                  type: string
                - type: 'null'
              title: Person Id
            person_snapshot:
              $ref: '#/components/schemas/PersonSnapshot'
            reasoning_effort:
              description: >-
                Deliberation budget the session was opened with: `low`, `medium`
                or `high`. Open union: new values may be added over time;
                clients must tolerate unknown values rather than failing on
                them.
              title: Reasoning Effort
              type: string
            workspace_id:
              format: uuid
              title: Workspace Id
              type: string
          required:
            - person_id
            - workspace_id
            - pacing
            - observation_retention
            - reasoning_effort
            - person_snapshot
          type: object
      description: >-
        The full session, without its decision trace.


        What create and close return: a caller that opened a session holds the
        same

        object a read would give it, so nothing has to be fetched twice to learn
        what

        was actually frozen. The TRACE is deliberately absent: turns are a paged

        subresource of the detail read, and a create has none yet.
      title: SessionResponse
    SessionTurnView:
      description: >-
        One recorded turn of the session's decision trace.


        The observation is always recorded by reference (its block kinds and a
        sha256). Whether the FRAME itself survives is the session's retention
        window: when it does, `observation_url` carries a short-lived link to
        it.
      properties:
        created_at:
          format: date-time
          title: Created At
          type: string
        decision:
          $ref: '#/components/schemas/TurnDecisionView'
        frame_age_ms:
          anyOf:
            - type: integer
            - type: 'null'
          title: Frame Age Ms
        input_tokens:
          anyOf:
            - type: integer
            - type: 'null'
          title: Input Tokens
        latency_ms:
          title: Latency Ms
          type: integer
        observation_height:
          anyOf:
            - type: integer
            - type: 'null'
          title: Observation Height
        observation_kinds:
          items:
            type: string
          title: Observation Kinds
          type: array
        observation_url:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Short-lived signed URL for this turn's retained observation frame,
            or null when no frame was retained (see the session's
            `observation_retention`). Expires shortly after issue, so do not
            store it.
          title: Observation Url
        observation_width:
          anyOf:
            - type: integer
            - type: 'null'
          title: Observation Width
        output_tokens:
          anyOf:
            - type: integer
            - type: 'null'
          title: Output Tokens
        turn_index:
          title: Turn Index
          type: integer
        validation_retries:
          title: Validation Retries
          type: integer
      required:
        - turn_index
        - decision
        - observation_kinds
        - latency_ms
        - validation_retries
        - input_tokens
        - output_tokens
        - frame_age_ms
        - created_at
      title: SessionTurnView
      type: object
    FieldError:
      description: One field-level entry in a ValidationError envelope.
      properties:
        allowed_values:
          description: For enum/literal failures, the accepted values.
          items: {}
          title: Allowed Values
          type: array
        input:
          anyOf:
            - type: string
            - type: number
            - type: boolean
            - type: 'null'
          description: Offending value (truncated); omitted for containers.
          title: Input
        loc:
          description: Path to the offending field.
          items:
            anyOf:
              - type: string
              - type: integer
          title: Location
          type: array
        msg:
          title: Message
          type: string
        type:
          title: Error Type
          type: string
      required:
        - loc
        - msg
        - type
      title: FieldError
      type: object
    SessionBase:
      description: >-
        Everything every session read carries, wherever the session is served.


        The one place the shared fields are declared: the list row, the
        create/close

        body and the detail read all extend this, so an SDK generator sees ONE

        Session family instead of three types that happen to overlap.
      properties:
        accrued_credits:
          description: This session's spend in credits (0 if it had no billable turns).
          title: Accrued Credits
          type: integer
        created_at:
          format: date-time
          title: Created At
          type: string
        created_by_api_key_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Created By Api Key Id
        decision_mode:
          $ref: '#/components/schemas/DecisionMode'
        ended_at:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          description: >-
            When the session left `open` (UTC), stamped on the first terminal
            transition and never moved. Null while the session is open. This is
            the timestamp `sessions_ended` usage buckets on.
          title: Ended At
        ended_reason:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Why the session ended, when that is finer than `status`:
            `credits_exhausted` or `plan_allowance_exhausted` (both end the
            session as `balance_exhausted`), or `spend_cap_reached`. Null for an
            ordinary ending. Open union: new values may be added over time;
            clients must tolerate unknown values rather than failing on them.
          title: Ended Reason
        environment:
          $ref: '#/components/schemas/EnvironmentDescriptor'
          description: The environment as frozen at create (name + kind snapshot).
        environment_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Environment Id
        environment_revision:
          anyOf:
            - type: integer
            - type: 'null'
          title: Environment Revision
        environment_version_id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Environment Version Id
        environment_version_label:
          anyOf:
            - type: string
            - type: 'null'
          title: Environment Version Label
        id:
          format: uuid
          title: Id
          type: string
        max_turns:
          title: Max Turns
          type: integer
        status:
          $ref: '#/components/schemas/SessionStatus'
          description: >-
            Session status. Open union: new values may be added over time;
            clients must tolerate unknown values rather than failing on them.
        task:
          $ref: '#/components/schemas/TaskSnapshot'
          description: >-
            The session's intent, frozen when it was created. A later edit to a
            registered task never changes it.
        turn_count:
          title: Turn Count
          type: integer
        updated_at:
          format: date-time
          title: Updated At
          type: string
      required:
        - id
        - status
        - task
        - environment
        - environment_id
        - environment_revision
        - environment_version_id
        - environment_version_label
        - decision_mode
        - turn_count
        - max_turns
        - created_by_api_key_id
        - accrued_credits
        - ended_reason
        - created_at
        - updated_at
        - ended_at
      title: SessionBase
      type: object
    PacingContract:
      enum:
        - pause_on_decide
        - frame_on_demand
        - free_running
      title: PacingContract
      type: string
    PersonSnapshot:
      additionalProperties: true
      description: >-
        The person as they were when the session was created, frozen onto the
        session.


        This, not the live person, is what the session was run against: it stays
        readable and unchanged after the person is edited or deleted, which is
        what makes an old trace still mean something. `id` is the person's id at
        create time.


        Additional keys may be present and more may be added over time; treat
        anything not listed here as unspecified rather than failing on it.
      properties:
        avatar_url:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Avatar Url
        bio:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Bio
        city:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: City
        country:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Country
        date_of_birth:
          anyOf:
            - format: date
              type: string
            - type: 'null'
          default: null
          title: Date Of Birth
        education_level:
          anyOf:
            - $ref: '#/components/schemas/EducationLevel'
            - type: 'null'
          default: null
        employment_status:
          anyOf:
            - $ref: '#/components/schemas/EmploymentStatus'
            - type: 'null'
          default: null
        gender:
          anyOf:
            - $ref: '#/components/schemas/Gender'
            - type: 'null'
          default: null
        household:
          anyOf:
            - $ref: '#/components/schemas/Household'
            - type: 'null'
          default: null
        id:
          format: uuid
          title: Id
          type: string
        income_level:
          anyOf:
            - $ref: '#/components/schemas/IncomeLevel'
            - type: 'null'
          default: null
        languages:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          default: null
          title: Languages
        locale_type:
          anyOf:
            - $ref: '#/components/schemas/LocaleType'
            - type: 'null'
          default: null
        name:
          title: Name
          type: string
        occupation:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Occupation
        snapshot_version:
          default: '2'
          enum:
            - '1'
            - '2'
          title: Snapshot Version
          type: string
        snapshotted_at:
          format: date-time
          title: Snapshotted At
          type: string
        timezone:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Timezone
        type:
          $ref: '#/components/schemas/IterationType'
      required:
        - id
        - name
        - type
        - snapshotted_at
      title: PersonSnapshot
      type: object
    TurnDecisionView:
      description: >-
        What the person decided this turn, and how it felt.


        `action_name` names the action to execute, and is null when there is
        nothing to run: the person found no action that fits, or judged that
        none was needed. `comment`, `sentiment` and `felt_intensity` are the
        experiential half and are always present, including on a turn with no
        action. `status` says whether they intend to keep going.


        `intent`, `resolution` and `unmet_expectation` are populated only when
        the session runs in `intent` decision mode; they are null in `direct`
        mode.
      properties:
        action_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Action Name
        arguments:
          additionalProperties: true
          title: Arguments
          type: object
        comment:
          title: Comment
          type: string
        felt_intensity:
          $ref: '#/components/schemas/FeltIntensity'
        intent:
          anyOf:
            - type: string
            - type: 'null'
          title: Intent
        resolution:
          anyOf:
            - enum:
                - matched
                - no_match
                - none_needed
              type: string
            - type: 'null'
          description: >-
            Resolver outcome (None in direct mode). Open union: new values may
            be added over time; clients must tolerate unknown values rather than
            failing on them.
          title: Resolution
        sentiment:
          $ref: '#/components/schemas/Sentiment'
        status:
          enum:
            - continue
            - done
            - gave_up
          title: Status
          type: string
        unmet_expectation:
          anyOf:
            - type: string
            - type: 'null'
          title: Unmet Expectation
      required:
        - action_name
        - comment
        - sentiment
        - felt_intensity
        - status
      title: TurnDecisionView
      type: object
    DecisionMode:
      enum:
        - direct
        - intent
      title: DecisionMode
      type: string
    EnvironmentDescriptor:
      description: >-
        An ad-hoc environment, described inline on the session that uses it.


        Also the shape of the FROZEN SNAPSHOT a session carries after it was
        opened

        against a REGISTERED environment (`environment_id`), so `kind` means the

        same thing on both paths and on the environments surface: one noun for
        one

        concept, rather than a wire that says `platform` where the registry says

        `kind`.
      properties:
        kind:
          description: >-
            The kind of surface (e.g. `web`, `device`, `world`). Open union: new
            values may be added over time; clients must tolerate unknown values
            rather than failing on them.
          maxLength: 40
          minLength: 1
          title: Kind
          type: string
        name:
          maxLength: 120
          minLength: 1
          title: Name
          type: string
      required:
        - name
        - kind
      title: EnvironmentDescriptor
      type: object
    SessionStatus:
      enum:
        - open
        - completed
        - gave_up
        - closed
        - max_turns
        - spend_cap_reached
        - balance_exhausted
      title: SessionStatus
      type: string
    TaskSnapshot:
      description: |-
        The session's intent as frozen at create.

        `instructions` is always present (the snapshot). `id`/`name`/
        `revision` are null for a session opened with an inline descriptor;
        `name` reads the LIVE registry row (display only) while `instructions`
        and `revision` stay frozen, so a rename shows through but content never
        drifts.
      properties:
        id:
          anyOf:
            - format: uuid
              type: string
            - type: 'null'
          title: Id
        instructions:
          title: Instructions
          type: string
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        revision:
          anyOf:
            - type: integer
            - type: 'null'
          description: >-
            The registry row's revision at bind time. Two runs of one task are
            comparable within a revision of its content.
          title: Revision
      required:
        - id
        - name
        - revision
        - instructions
      title: TaskSnapshot
      type: object
    EducationLevel:
      enum:
        - less_than_secondary
        - secondary
        - some_post_secondary
        - vocational_or_associate
        - bachelor
        - graduate
      title: EducationLevel
      type: string
    EmploymentStatus:
      enum:
        - employed_full_time
        - employed_part_time
        - self_employed
        - unemployed_seeking
        - student
        - homemaker
        - retired
        - unable_to_work
        - other
      title: EmploymentStatus
      type: string
    Gender:
      description: Gender options for people.
      enum:
        - male
        - female
        - non-binary
        - prefer_not_to_say
      title: Gender
      type: string
    Household:
      enum:
        - single
        - couple_no_kids
        - couple_with_kids
        - single_parent
        - shared_housing
        - adult_with_parents
        - multi_generational
      title: Household
      type: string
    IncomeLevel:
      enum:
        - lower
        - lower_middle
        - middle
        - upper_middle
        - upper
        - prefer_not_to_say
      title: IncomeLevel
      type: string
    LocaleType:
      enum:
        - urban
        - suburban
        - small_town
        - rural
      title: LocaleType
      type: string
    IterationType:
      description: Iteration execution type.
      enum:
        - ai
        - human
      title: IterationType
      type: string
    FeltIntensity:
      description: >-
        How strongly the person feels the labelled sentiment: `mild`, `moderate`
        or `strong`. An ordinal magnitude, reported alongside `sentiment` on
        every decision.


        Open union: new values may be added over time; clients must tolerate
        unknown values rather than failing on them.
      enum:
        - mild
        - moderate
        - strong
      title: FeltIntensity
      type: string
    Sentiment:
      description: >-
        How the person felt while making this turn's decision.


        The labels span the positive/negative and active/passive range, so
        disengagement reads differently from frustration. Reported with an
        ordinal `felt_intensity`.


        VALUES ARE CAPITALISED exactly as listed (`Confident`, not `confident`);
        match on the literal string. Open union: new labels may be added over
        time; clients must tolerate an unknown label rather than failing on it.
      enum:
        - Delighted
        - Excited
        - Confident
        - Satisfied
        - Relieved
        - Neutral
        - Confused
        - Uncertain
        - Anxious
        - Frustrated
        - Disengaged
      title: Sentiment
      type: string
  headers:
    XRequestId:
      description: >-
        Per-response correlation id, present on EVERY response (success and
        error). Error bodies echo it as `request_id`.
      schema:
        type: string
    XRateLimitLimit:
      description: >-
        Ceiling for the rate-limit window this request charged. Present on
        responses made with an API key against a rate-limited operation,
        including the `429` itself; absent for user access tokens (which carry
        no per-key budget) and when the rate-limit backend could not be read.
      schema:
        type: integer
    XRateLimitRemaining:
      description: >-
        Requests left in that window AFTER this one was counted. Pace off this
        rather than waiting for a `429`.
      schema:
        type: integer
    XRateLimitReset:
      description: >-
        **Seconds from now** until that window resets (a delta, NOT a Unix
        timestamp). Measured when the response is written. Omitted on the rare
        occasion the reset instant could not be read, while `limit` and
        `remaining` are still published.
      schema:
        type: integer
  securitySchemes:
    bearerAuth:
      description: >-
        Workspace API key as a bearer token: `Authorization: Bearer
        ish_sk_live_...`. Keys are workspace-scoped machine principals minted in
        Settings > Developers (shown once at mint). Scopes: `sessions:run`
        (create a session, submit turns, close), `sessions:read` (read the
        session and its decision trace), `environments:read` and
        `environments:write` (manage the workspace's registered environments),
        `tasks:read` and `tasks:write` (manage the workspace's registered
        tasks), `people:read` and `people:write` (the people the workspace can
        run a session for), and `usage:read` (the workspace's own consumption,
        spend limits and rate-limit budgets). A key is minted with
        `sessions:run`, `sessions:read`, and `tasks:read` by default; every
        other scope must be requested, `people:read` and `usage:read` included.
        An ish user access token also works on the same header for personal
        scripts.
      scheme: bearer
      type: http

````