openapi: 3.0.3
info:
  title: Guardrails API
  version: 1.0.0
  description: >
    First-class, versioned action-class policies. A guardrail classifies each
    agent tool call into an action class (A/B/C/D) with a non-LLM JSON Logic
    guard, and is attached to projects, agents, or tools. This API manages the
    guardrail resource and its archived document versions.
  contact:
    name: SOAT API Support

servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047

tags:
  - name: Guardrails
    description: Manage guardrails

security:
  - bearerAuth: []

paths:
  /api/v1/guardrails:
    post:
      tags:
        - Guardrails
      summary: Create a guardrail
      description: >
        Creates a new guardrail in the project, archiving its document as
        version 1. The `document` is validated on write: `class` must be a
        literal (A/B/C/D) or a JSON Logic expression, and every variable it (and
        `guard`) reference must resolve to the `args.*` / `context.*` / `runtime.*`
        namespaces — an out-of-catalog `runtime.*` key is rejected with 400.
      operationId: createGuardrail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateGuardrailRequest'
            examples:
              always_approve:
                summary: Always require human sign-off
                value:
                  name: Sign-off Guardrail
                  document:
                    class: C
              budget_threshold:
                summary: Class B below a threshold, C at or above, guarded by 24h spend
                value:
                  name: Budget Update Guardrail
                  document:
                    default_class: C
                    class:
                      if:
                        - '<':
                            - var: args.amount
                            - 500
                        - B
                        - C
                    guard:
                      '<':
                        - var: runtime.usage.cost_usd_24h
                        - 1000
      responses:
        '201':
          description: Guardrail created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guardrail'
        '400':
          description: Bad Request — invalid document or variable reference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    get:
      tags:
        - Guardrails
      summary: List guardrails
      description: Returns all guardrails in the project.
      operationId: listGuardrails
      parameters:
        - name: project_id
          in: query
          required: false
          schema:
            type: string
          description: Project public ID to filter by
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of guardrails
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Guardrail'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/guardrails/{guardrail_id}:
    get:
      tags:
        - Guardrails
      summary: Get a guardrail
      description: Returns a single guardrail by ID.
      operationId: getGuardrail
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Guardrail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guardrail'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Guardrails
      summary: Update a guardrail
      description: >
        Updates an existing guardrail. A `document` write that actually changes
        the policy increments `version` and archives the new document as a
        GuardrailVersion; metadata-only edits (name / description / context), and
        re-writing the document the guardrail already holds, leave the version
        untouched.
      operationId: updateGuardrail
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateGuardrailRequest'
      responses:
        '200':
          description: Guardrail updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guardrail'
        '400':
          description: Bad Request — invalid document or variable reference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Guardrails
      summary: Delete a guardrail
      description: Deletes a guardrail and its archived versions by ID.
      operationId: deleteGuardrail
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/guardrails/{guardrail_id}/versions:
    get:
      tags:
        - Guardrails
      summary: List a guardrail's config versions
      description: >
        Returns the guardrail's archived configurations, newest first. A version
        is written on create and on every subsequent write that changes the
        policy `document` — through the REST API or a formation apply alike.
        Metadata-only edits (name, description, context binding) do not archive a
        version. See [Versioning](/docs/modules/guardrails#versioning).
      operationId: listGuardrailVersions
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of guardrail versions, newest first
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/GuardrailVersion'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Guardrail not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/guardrails/{guardrail_id}/versions/{version}:
    get:
      tags:
        - Guardrails
      summary: Fetch an archived guardrail version
      description: >
        Returns the exact configuration — and so the exact `document` — that
        governed at a given version. Approval items, activity entries, and
        exceptions record the version that governed them, so the audit chain
        survives edits.
      operationId: getGuardrailVersion
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
        - name: version
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Archived guardrail version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GuardrailVersion'
        '400':
          description: Bad Request — version is not a positive integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/guardrails/{guardrail_id}/versions/{version}/restore:
    post:
      tags:
        - Guardrails
      summary: Restore an archived guardrail config
      description: >
        Writes an archived version's `document` back as the guardrail's live
        policy, which archives it again as a **new** version rather than rewinding
        the counter — so an approval item or exception citing any version in
        between still resolves.


        The restore runs through the ordinary update path, so the archived
        document is re-validated; restoring the policy the guardrail already
        holds is a no-op and archives nothing.
      operationId: restoreGuardrailVersion
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
        - name: version
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RestoreGuardrailVersionRequest'
      responses:
        '200':
          description: The guardrail, at its new version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guardrail'
        '400':
          description: Bad Request — invalid version, or the archived document no longer validates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Guardrail or version not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/guardrails/{guardrail_id}/evaluate:
    post:
      tags:
        - Guardrails
      summary: Dry-run evaluate a guardrail
      description: >
        Runs the full evaluation pipeline — the `class` expression, the guard,
        the context tool per `context_mode`, live `runtime.*` resolution — against
        caller-supplied `args` and `guardrail_context`, and returns the exact
        `guardrail_evaluation` record a real call would produce. Nothing
        executes, no approval item is filed, and no activity entry is written.
        This is the adoption path: preview a document's decisions against
        production-shaped calls before attaching it — or before editing a
        widely-attached one. Pass an optional `tool_id` to resolve `runtime.tool.*`.
      operationId: evaluateGuardrail
      parameters:
        - name: guardrail_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                args:
                  type: object
                  additionalProperties: true
                  description: The proposed call's arguments (the `args.*` namespace).
                guardrail_context:
                  type: object
                  additionalProperties: true
                  description: >
                    The caller-supplied guardrail context (the `context.*`
                    namespace), combined with the context tool per `context_mode`.
                tool_id:
                  type: string
                  description: Optional tool to resolve `runtime.tool.*` against.
      responses:
        '200':
          description: The would-be evaluation record (nothing executed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GuardrailEvaluation'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    GuardrailDocument:
      type: object
      required:
        - class
      # Free-form: the inner keys are the contract fields plus author-authored
      # JSON Logic operators/vars, so the request-validation middleware must not
      # walk this object (its snake_case `default_class` would be mis-flagged
      # against the camelCase allowlist). `additionalProperties: true` marks it
      # open so validation is delegated entirely to `validateGuardrailDocument`.
      additionalProperties: true
      description: >
        The action-class document. `class` maps a call to an action class;
        `guard` gates class-B autonomy. Both are single JSON Logic expressions
        over the `args.*` / `context.*` / `runtime.*` namespaces.
      properties:
        class:
          description: >
            A class literal (`A` / `B` / `C` / `D`) or a JSON Logic expression
            returning one. An invalid result resolves to `default_class`.
          oneOf:
            - type: string
              enum:
                - A
                - B
                - C
                - D
            - type: object
        default_class:
          type: string
          enum:
            - A
            - B
            - C
            - D
          description: >
            Applied when the `class` expression returns anything other than a
            valid class. Defaults to `C` (fail-closed).
        guard:
          type: object
          description: >
            A single JSON Logic expression; when the call classifies as `B` it
            must evaluate truthy to execute autonomously. Compose multiple
            conditions with `{ "and": [...] }`.
        escalate:
          type: boolean
          description: >
            When `true`, a failing guard routes to approval instead of tripping
            fail-closed.
        expires_in:
          type: integer
          minimum: 1
          description: >
            Default approval window in seconds for a class-C approval this
            guardrail files. Omitted → the platform's 24h default. When several
            guardrails apply, the governing (strictest-matching) one's value is
            used.
    Guardrail:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the guardrail
          example: guard_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the owning project
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          description: Human-readable name
          example: Budget Update Guardrail
        description:
          type: string
          nullable: true
          description: Optional description
        version:
          type: integer
          description: Incremented on every document write; prior versions are archived
          example: 1
        document:
          $ref: '#/components/schemas/GuardrailDocument'
        context_tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
          description: >
            Optional tool the platform calls at evaluation time to fetch fresh
            guardrail context.
        context_mode:
          type: string
          nullable: true
          enum:
            - merge
            - replace
            - null
          description: >
            How tool-fetched context combines with the caller-supplied context.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    GuardrailEvaluation:
      type: object
      description: >
        The record produced by evaluating a guardrail against one call — written
        to the audit trail at dispatch time (one per applying guardrail) and
        returned verbatim by the dry-run endpoint.
      properties:
        kind:
          type: string
          description: Always `guardrail_evaluation`.
        guardrail_id:
          x-soat-ref: guardrails
          type: string
          example: guard_V1StGXR8Z5jdHi6B
        guardrail_version:
          type: integer
          nullable: true
          description: The governing version; null for a dangling reference (fail-closed C).
        scope:
          type: string
          enum:
            - project
            - agent
            - tool
        tool:
          type: string
          nullable: true
        action:
          type: string
          nullable: true
        class:
          type: string
          description: The resolved class (or the applied default_class).
          enum:
            - A
            - B
            - C
            - D
        decision:
          type: string
          enum:
            - execute
            - route_to_approval
            - blocked
            - tripwire
        guard_result:
          type: boolean
          nullable: true
          description: The guard outcome; null when the call did not classify as B.
        context_source:
          type: string
          enum:
            - caller
            - tool
            - merged
            - none
        context_snapshot:
          type: object
          additionalProperties: true
          description: >
            Flat map of only the vars the class/guard expressions referenced,
            keyed by fully-qualified path, frozen at evaluation-time values.
        agent_id:
          type: string
          nullable: true
        orchestration_run_id:
          type: string
          nullable: true
        generation_id:
          type: string
          nullable: true
    GuardrailVersion:
      type: object
      description: >-
        An immutable archive of a guardrail's configuration at one version.
      properties:
        id:
          type: string
          description: Public ID of the archived version
          example: guard_ver_V1StGXR8Z5jdHi6B
        guardrail_id:
          x-soat-ref: guardrails
          type: string
          description: Public ID of the guardrail this version belongs to
          example: guard_V1StGXR8Z5jdHi6B
        version:
          type: integer
          description: The archived version number
          example: 1
        config:
          type: object
          additionalProperties: true
          description: >-
            The guardrail's versioned surface as it stood at this version. Today
            that is the policy `document` and nothing else: name, description and
            the context binding are metadata, and bumping the version when one of
            them changes would make two version numbers denote the same policy —
            which is exactly what an evaluation record cites.


            Deliberately open rather than a fixed schema: an archive written by an
            earlier release of SOAT reflects the guardrail surface **of its own
            time**, so it may carry fields the current API no longer documents.
          properties:
            document:
              $ref: '#/components/schemas/GuardrailDocument'
        label:
          type: string
          nullable: true
          description: >-
            Optional human tag for this version, e.g. `pre-tightening`. Set from
            the `label` field of a restore, or generated for one.
          example: restored from v2
        created_by:
          x-soat-ref: users
          type: string
          nullable: true
          description: >-
            Public ID of the user whose action produced this version. Null for
            writes with no request user behind them.
        created_at:
          type: string
          format: date-time
    RestoreGuardrailVersionRequest:
      type: object
      properties:
        label:
          type: string
          description: >-
            Optional tag for the version the restore creates. Defaults to
            `restored from v<version>`.
          example: rollback to pre-incident policy
    CreateGuardrailRequest:
      type: object
      required:
        - name
        - document
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project
        name:
          type: string
          description: Human-readable name
          example: Budget Update Guardrail
        description:
          type: string
          nullable: true
        document:
          $ref: '#/components/schemas/GuardrailDocument'
        context_tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
        context_mode:
          type: string
          enum:
            - merge
            - replace
        version_label:
          type: string
          description: >-
            Optional tag for the config version this write archives (e.g.
            `initial`). Annotates the version only — it is not stored on the
            guardrail and is not part of the config, so labelling a change is
            never itself a change.
          example: initial
    UpdateGuardrailRequest:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        document:
          $ref: '#/components/schemas/GuardrailDocument'
        context_tool_id:
          x-soat-ref: tools
          type: string
          nullable: true
        context_mode:
          type: string
          nullable: true
          enum:
            - merge
            - replace
            - null
        version_label:
          type: string
          description: >-
            Optional tag for the config version this write archives (e.g.
            `pre-tightening`). Annotates the version only — it is not stored on
            the guardrail and is not part of the config, so labelling a change is
            never itself a change. Ignored when the write changes no policy, since
            no version is created.
          example: pre-tightening
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape — 401, 403
            and the 500 catch-all included — so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
            - hint
            - docs_url
          properties:
            code:
              type: string
              description: A key from the server's ERROR_CODES registry.
              example: RESOURCE_NOT_FOUND
            message:
              type: string
              example: 'Resource not found'
            hint:
              type: string
              description: >-
                What to do about this error. Resolved per code, so a caller that
                has never seen the code before can act on the response without
                leaving it.
              example: >-
                Check the id, and check that the credential can see the project
                that owns the resource.
            docs_url:
              type: string
              format: uri
              description: The reference-page anchor documenting this code.
              example: >-
                https://soat.ttoss.dev/docs/error-codes#resource_not_found
            meta:
              type: object
              description: Optional structured context for the error.
