openapi: 3.0.3
info:
  title: Tools API
  version: 1.0.0
  description: >
    Standalone tool definitions that can be attached to agents. Supports HTTP,
    MCP, client, and SOAT platform tools.
  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: Tools
    description: Manage tools

security:
  - bearerAuth: []

paths:
  /api/v1/tools:
    post:
      tags:
        - Tools
      summary: Create a tool
      description: Creates a new tool in the project.
      operationId: createTool
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateToolRequest'
            examples:
              http:
                summary: HTTP tool
                value:
                  name: get-weather
                  type: http
                  description: Fetches current weather for a city
                  parameters:
                    type: object
                    properties:
                      city:
                        type: string
                    required:
                      - city
                  execute:
                    url: https://api.weather.example/v1/current?city={city}
              http_with_output_mapping:
                summary: HTTP tool reshaping its result with output_mapping
                value:
                  name: transcribe-audio
                  type: http
                  description: Transcribes an audio file and returns the bare text
                  parameters:
                    type: object
                    properties:
                      file:
                        type: string
                    required:
                      - file
                  execute:
                    url: https://api.x.ai/v1/stt
                    method: POST
                    body_mode: multipart
                  output_mapping:
                    var: output.text
              http_aws_sigv4:
                summary: HTTP tool signed with AWS Signature Version 4
                value:
                  name: get-s3-object
                  type: http
                  description: Reads an object from an S3 bucket
                  parameters:
                    type: object
                    properties:
                      key:
                        type: string
                    required:
                      - key
                  execute:
                    url: https://my-bucket.s3.us-east-1.amazonaws.com/{key}
                    method: GET
                    auth:
                      type: aws_sigv4
                      region: us-east-1
                      service: s3
                      access_key_id: '{{secret:sec_awsKeyId}}'
                      secret_access_key: '{{secret:sec_awsSecret}}'
              http_gcp_service_account:
                summary: HTTP tool authenticated as a GCP service account
                value:
                  name: create-bigquery-job
                  type: http
                  description: Submits a BigQuery job
                  parameters:
                    type: object
                    properties:
                      query:
                        type: string
                    required:
                      - query
                  execute:
                    url: https://bigquery.googleapis.com/bigquery/v2/projects/my-gcp-project/jobs
                    method: POST
                    auth:
                      type: gcp_service_account
                      credentials: '{{secret:sec_gcpServiceAccount}}'
                      scopes:
                        - https://www.googleapis.com/auth/bigquery
              client:
                summary: Client tool
                value:
                  name: show-dialog
                  type: client
                  description: Displays a confirmation dialog to the user
                  parameters:
                    type: object
                    properties:
                      message:
                        type: string
              builtin:
                summary: SOAT platform tool
                value:
                  name: builtin-files
                  type: builtin
                  actions:
                    - files:ListFiles
                    - files:GetFile
              mcp_scoped:
                summary: Read-only MCP tool (allowlisted to a subset of actions)
                value:
                  name: oneclick
                  type: mcp
                  mcp:
                    url: https://mcp.oneclick.example/sse
                  actions:
                    - list_campaigns
                    - get_campaign
              mcp_denylist:
                summary: Read-only MCP tool (whole surface minus write actions)
                value:
                  name: oneclick
                  type: mcp
                  mcp:
                    url: https://mcp.oneclick.example/sse
                  denied_actions:
                    - create_optimization
                    - update_optimization
                    - deactivate_all_optimizations
              pipeline:
                summary: Pipeline tool (compute → persist)
                value:
                  name: compute-and-save
                  type: pipeline
                  description: Computes a sum and persists the result
                  parameters:
                    type: object
                    properties:
                      x:
                        type: number
                      y:
                        type: number
                    required:
                      - x
                      - y
                  pipeline:
                    steps:
                      - id: compute
                        tool_id: tool_calc
                        action: add
                        input:
                          a:
                            var: input.x
                          b:
                            var: input.y
                      - id: persist
                        tool_id: tool_save_record
                        input:
                          value:
                            var: steps.compute.sum
                    output:
                      saved_id:
                        var: steps.persist.id
      responses:
        '201':
          description: Tool created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
    get:
      tags:
        - Tools
      summary: List tools
      description: Returns all tools in the project.
      operationId: listTools
      parameters:
        - name: project_id
          in: query
          required: false
          schema:
            type: string
          description: Project public ID to filter by
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: List of tools
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Tool'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/tools/{tool_id}:
    get:
      tags:
        - Tools
      summary: Get a tool
      description: >
        Returns a single tool by ID. A credential scoped to a project the tool
        is shared with, through an accepted share, reads its `id`, `name`,
        `description` and `parameters` only.
      operationId: getTool
      x-soat-resource:
        kind: tool
        from: tool_id
      parameters:
        - name: tool_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Tool
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Tools
      summary: Update a tool
      description: Updates an existing tool.
      operationId: updateTool
      x-soat-resource:
        kind: tool
        from: tool_id
      parameters:
        - name: tool_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateToolRequest'
      responses:
        '200':
          description: Tool updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tool'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Tools
      summary: Delete a tool
      description: >-
        Deletes a tool by ID. A tool that is a decider's backend is refused with
        `409 TOOL_HAS_DEPENDENTS`, and so is one another project has accepted a
        share of, or one of its own project's ingestion rules converts with,
        until `force=true`. Every share of the tool is revoked when it is
        deleted; an ingestion rule keeps its `tool_id` and fails the documents
        it matches with `CONVERTER_FAILED` until repointed.
      operationId: deleteTool
      x-soat-resource:
        kind: tool
        from: tool_id
      parameters:
        - name: tool_id
          in: path
          required: true
          schema:
            type: string
        - name: force
          in: query
          required: false
          description: >-
            Delete the tool even when another project has accepted a share of
            it, revoking those shares, or an ingestion rule converts with it. A
            decider backend still refuses.
          schema:
            type: boolean
            default: false
      responses:
        '204':
          description: Deleted
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The tool is a decider's backend, or has accepted shares and `force`
            is not set
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

  /api/v1/tools/{tool_id}/call:
    post:
      tags:
        - Tools
      summary: Call a tool
      description: >
        Directly invokes a tool and returns its output. Supported for `http`,
        `builtin`, `mcp`, and `pipeline` tools. `client` tools cannot be invoked
        server-side and will return 422. A `pipeline` tool runs its declared
        steps in order and returns the mapped `output` (or the last step's
        output); `action` is ignored and `input` is the pipeline input.

        For `builtin` and `mcp` tools the `action` field is required and identifies
        which action (SOAT) or tool name (MCP) to invoke. For `http` tools
        `action` is ignored. When a `builtin` or `mcp` tool declares an `actions`
        allowlist, an action outside it is rejected with `400 VALIDATION_FAILED`
        ("not available on this tool") before any outbound request is made.

        `preset_parameters` stored on the tool are pinned over the caller-supplied
        `input` before execution: a key the tool presets keeps its preset value
        even when `input` sets it. Keys the presets do not name are taken from
        `input` as sent.

        Guardrails attached to the tool or to its project adjudicate the call
        before dispatch, composing project + tool scope. A call this route
        cannot await a decision on — class C (human sign-off), class D, or a
        class-B tripwire — is refused with `422 TOOL_DISPATCH_FAILED`, whose
        `meta` carries the `tool_id` and the `outcome`. A `pipeline` tool is
        adjudicated before its first step runs, and every step is adjudicated as
        the call of that tool it is.


        A credential scoped to a project the tool is shared with, through an
        accepted share, calls it in that project: the calling project's
        guardrails adjudicate it, it is metered there with
        `publisher_project_id`, and the tool receives the calling project as
        the `calling_project_id` tool context key.
      operationId: callTool
      x-soat-resource:
        kind: tool
        from: tool_id
      parameters:
        - name: tool_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CallToolRequest'
            examples:
              builtin_list_documents:
                summary: Call a SOAT list-documents action
                value:
                  action: list-documents
                  input:
                    project_id: proj_abc123
              http_tool:
                summary: Call an HTTP tool
                value:
                  input:
                    city: London
              mcp_tool:
                summary: Call an MCP tool
                value:
                  action: get_weather
                  input:
                    location: Paris
      responses:
        '200':
          description: Tool output
          content:
            application/json:
              schema:
                description: >-
                  The raw output returned by the tool — any JSON value (object,
                  array, string, number, boolean). `null` when the tool
                  produced no output — a `builtin` action answering `204 No
                  Content`, for instance.
        '400':
          description: Bad Request — invalid input or unknown action
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '403':
          description: >
            Forbidden — the caller lacks permission, or the tool's target is
            blocked by the deployment's egress policy (TOOL_EGRESS_BLOCKED).
            An `http`/`mcp` tool may only reach publicly routable addresses
            unless the destination is listed in the server's
            TOOL_EGRESS_ALLOWED_HOSTS; the check runs against the resolved
            address and on every redirect hop, so `meta.tool_address` names
            the address that was refused.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '404':
          description: Tool not found
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '422':
          description: Unprocessable — tool type cannot be invoked server-side
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '429':
          description: >-
            `SHARE_CAP_EXCEEDED`: the tool is another project's, reached
            through a share whose `cap` is spent for the window. Carries a
            `Retry-After` header and `error.meta.retry_after`.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'
        '502':
          description: >
            Upstream tool target error (TOOL_HTTP_ERROR). Returned when an
            `http`-type tool's target responds with a non-2xx status. The
            error `meta` carries the real upstream `tool_status_code`,
            `tool_response_body`, `tool_url`, and `tool_method`. A target that
            never answers (`http` or `mcp`) is `TOOL_UNREACHABLE`, its message
            naming the network failure and its `meta` the `tool_url` and
            `tool_method`.
          content:
            application/json:
              schema:
                $ref: './errors.yaml#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    Tool:
      type: object
      properties:
        id:
          type: string
          description: Public ID of the tool
          example: tool_V1StGXR8Z5jdHi6B
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the owning project
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          description: Tool name
          example: get-weather
        type:
          type: string
          enum:
            - http
            - client
            - mcp
            - builtin
            - pipeline
          description: Tool type
          example: http
        description:
          type: string
          nullable: true
          description: What the tool does (sent to the model)
        parameters:
          type: object
          nullable: true
          description: JSON Schema for tool input
        execute:
          type: object
          nullable: true
          description: >
            Execution config for http tools. Supported fields: `url` (required),
            `method` (default `POST`), `headers`, `body_mode` and `response_mode`. The `url`
            may contain `{paramName}` placeholders (e.g. `/users/{userId}`)
            that are replaced at call time with the corresponding tool argument
            value (URL-encoded). Arguments consumed as path parameters are
            excluded from the query string and request body. `body_mode` is
            `json` (default) or `multipart`. In `multipart` mode the merged tool
            arguments are sent as a `multipart/form-data` body: scalar fields
            become plain form fields and a field shaped like
            `{ content_type, filename, data_base64 }` is decoded from base64
            and attached as a file part (the hardcoded
            `Content-Type: application/json` is dropped so `fetch` sets the
            multipart boundary itself). `response_mode` is `json` (default: the
            body parsed as JSON, else returned as text) or `base64`, which returns
            a 2xx body as `{ content_type, filename, data_base64 }` — the same
            file shape `multipart` accepts, so a pipeline step can relay a
            downloaded file. `filename` comes from `Content-Disposition` and is
            omitted when the target names none; a body over
            `TOOL_RESPONSE_MAX_BYTES` is `502 TOOL_RESPONSE_TOO_LARGE`.


            `auth` adds a computed request credential, for targets whose
            `Authorization` value cannot be expressed as a static header.
            Supported `auth.type` values:


            - `aws_sigv4` — signs the request with AWS Signature Version 4.
            Requires `region`, `service`, `access_key_id` and
            `secret_access_key`; `session_token` is optional (temporary
            credentials). Incompatible with `body_mode: multipart`, whose body
            bytes are not known at signing time.

            - `gcp_service_account` — mints a Google OAuth 2.0 access token from
            a signed service account assertion and sends it as a bearer token.
            Requires `credentials` (the service account key file JSON, as a
            string) and `scopes` (a non-empty array). Tokens are cached per
            service account and scope set until shortly before they expire.


            Credential fields accept `{{secret:...}}` references and should use
            them — a tool is readable by anyone who can `GET /tools`, and the
            stored reference is what is echoed back, never the resolved value.


            A credential slot takes a reference, never a literal: a literal
            `secret_access_key`, `session_token` or `credentials` under `auth`, a
            literal credential-named `headers` value (`Authorization`, `Cookie`, or
            a name containing `api-key`/`token`/`secret`/`password`), or a `url`
            carrying a password or a literal credential-named query parameter
            (those names, plus `key` and `sig`/`signature`) is refused with `400
            LITERAL_CREDENTIAL`, `meta.paths` naming each one. A stored literal reads
            back as `{"no_echo": true}` (a `url` holding one is masked whole), an
            object rather than a string so a read-edit-write round trip fails the
            schema check instead of writing the placeholder in as the credential.
            A value carrying a `{{secret:...}}` reference is the wiring, not the
            credential, and stays readable.


            `headers` values additionally accept `{{context:<key>}}` references,
            resolved per call from the caller's `tool_context`, so a per-user
            credential can be placed in the real header the target expects
            (`Authorization: Bearer {{context:ocaToken}}`) instead of only in a
            prefixed context header. Valid **only** inside `headers` — a
            context value is caller-supplied, so it may not steer the `url` — and
            a key missing from the `tool_context` at call time fails the tool call
            with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty
            credential. See the Tool Context reference.
        mcp:
          type: object
          nullable: true
          description: >-
            MCP server config (`url`, `headers`). `headers` values accept
            `{{secret:...}}` and `{{context:<key>}}` references, resolved right
            before the outbound MCP request; `url` accepts `{{secret:...}}` only.
            A literal credential in `headers` or `url` is refused, exactly as in
            `execute`.
        actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Allowlist of actions to expose. For `builtin` tools: SOAT platform
            action names (required). For `mcp` tools: an optional allowlist of
            MCP tool names — when set, only those tools are exposed to the model
            and callable via `/call`; when `null`, the entire MCP server surface
            is exposed. Ignored for other tool types.
        denied_actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            For `mcp` tools: an optional denylist of MCP tool names to hide.
            Applied after `actions` and taking precedence over it — a name in
            both lists is denied. This is the ergonomic way to scope a
            read+write MCP server read-only: deny just the write tools instead
            of enumerating every read tool in `actions`. Names not listed are
            exposed. `null` (default) denies nothing. Ignored for other tool
            types.
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Optional allowlist of `tool_context` keys that may be forwarded to
            this tool as prefixed context headers (`X-Soat-Context-<key>` by
            default). When `null`, every key in the caller's
            `tool_context` is forwarded. When set, only the listed keys are, so
            a per-user credential in `tool_context` can be confined to the tools
            that need it; `[]` forwards none. The server-pinned identity keys
            (`session_id`, `actor_id`, `actor_external_id`) are always forwarded.
            A key consumed by a `{{context:<key>}}` token in this tool's own
            headers is substituted regardless of this list — the tool declared
            that header itself.
        preset_parameters:
          type: object
          nullable: true
          description: >-
            Fixed parameters pinned on every call this tool makes, whatever its
            type. Keys matching fields in the input schema are removed from the
            schema shown to the model, and a pinned value wins over one the
            model or a direct caller supplies for the same key. On an `mcp`
            tool a preset reaches each action whose input schema declares it;
            an action whose schema declares no properties receives every one.


            Values accept `{{context:<key>}}` references, resolved per call from
            the caller's `tool_context`, so a pin can be the run's own value —
            the one account this run may act on — rather than one fixed when the
            tool was created. A resolved value is retyped to the parameter's
            declared schema type; a key missing from the call's `tool_context`
            fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending
            the literal placeholder. `{{secret:...}}` is not resolved here. See
            the Tool Context reference.
        pipeline:
          type: object
          nullable: true
          description: >-
            Pipeline definition for `pipeline` tools: an ordered `steps` array,
            each step invoking a tool (optional `action`) and building its
            `input` from earlier results via JSON Logic evaluated over
            `{ input, steps }`. A step references its tool either by `tool_id`
            (an existing, persisted tool) or by an inline `tool` definition —
            the same shape as `CreateToolRequest` minus `project_id` —
            executed directly without a Tool row, but never both. An inline
            step `tool` cannot itself be of type `pipeline`. An optional
            `output` maps the final result; `steps` may be empty when
            `output` is set, which computes `output` over the input alone. A literal credential in an inline
            step `tool` is refused, exactly as in `execute`.
        output_mapping:
          type: object
          nullable: true
          description: >-
            Universal JSON Logic mapping applied to the tool's raw result,
            for every tool type (`http`, `mcp`, `builtin`, `pipeline`, `client`).
            Evaluated over `{ output: <raw result>, input: <merged input>,
            action: <action> }`, so `{ "var": "output.text" }` extracts a bare
            scalar field instead of requiring a wrapping `pipeline` tool,
            `{ "var": "input.title" }` echoes back a field of the request that
            produced the response, and `{ "var": "action" }` reads the `mcp` or
            `builtin` action that ran (`null` on other types). For
            `pipeline` tools this runs *after* the pipeline's own `output`
            mapping, over that mapping's result.
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Guardrails attached at the tool scope, governing this tool wherever
            it is used, by any agent.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CreateToolRequest:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project
        name:
          type: string
          description: Tool name
        type:
          type: string
          enum:
            - http
            - client
            - mcp
            - builtin
            - pipeline
          description: Tool type (default http)
        description:
          type: string
          description: What the tool does
        parameters:
          type: object
          description: JSON Schema for tool input
        execute:
          $ref: '#/components/schemas/ToolExecuteConfig'
        mcp:
          $ref: '#/components/schemas/ToolMcpConfig'
        actions:
          type: array
          items:
            type: string
          description: >-
            Allowlist of actions. For `builtin` tools: SOAT platform action names
            (required). For `mcp` tools: an optional allowlist of MCP tool names
            to scope the server surface — omit or set `null` to expose every
            tool the MCP server offers. Ignored for other tool types.
        denied_actions:
          type: array
          items:
            type: string
          description: >-
            For `mcp` tools: an optional denylist of MCP tool names to hide.
            Applied after `actions` and taking precedence over it. Use it to
            scope a read+write MCP server read-only by denying just the write
            tools. Omit or set `null` to deny nothing. Ignored for other tool
            types.
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Optional allowlist of `tool_context` keys that may be forwarded to
            this tool as prefixed context headers (`X-Soat-Context-<key>` by
            default). When `null` or omitted, every key in the caller's
            `tool_context` is forwarded. When set, only the listed keys are, so
            a per-user credential in `tool_context` can be confined to the tools
            that need it; `[]` forwards none. The server-pinned identity keys
            (`session_id`, `actor_id`, `actor_external_id`) are always forwarded.
            A key consumed by a `{{context:<key>}}` token in this tool's own
            headers is substituted regardless of this list — the tool declared
            that header itself.
        preset_parameters:
          type: object
          description: >-
            Fixed parameters pinned on every call this tool makes, whatever its
            type. Keys matching fields in the input schema are removed from the
            schema shown to the model, and a pinned value wins over one the
            model or a direct caller supplies for the same key. On an `mcp`
            tool a preset reaches each action whose input schema declares it;
            an action whose schema declares no properties receives every one.


            Values accept `{{context:<key>}}` references, resolved per call from
            the caller's `tool_context`, so a pin can be the run's own value —
            the one account this run may act on — rather than one fixed when the
            tool was created. A resolved value is retyped to the parameter's
            declared schema type; a key missing from the call's `tool_context`
            fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending
            the literal placeholder. `{{secret:...}}` is not resolved here. See
            the Tool Context reference.
        pipeline:
          type: object
          description: >-
            Pipeline definition for `pipeline` tools. See the `pipeline` field
            on the Tool schema for the full structure.
        output_mapping:
          type: object
          description: >-
            Universal JSON Logic mapping applied to the tool's raw result. See
            the `output_mapping` field on the Tool schema for details.
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the tool scope.

    UpdateToolRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        type:
          type: string
          enum:
            - http
            - client
            - mcp
            - builtin
            - pipeline
        description:
          type: string
          nullable: true
        parameters:
          type: object
          nullable: true
        execute:
          $ref: '#/components/schemas/ToolExecuteConfig'
        mcp:
          $ref: '#/components/schemas/ToolMcpConfig'
        actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Allowlist of actions. For `builtin` tools: SOAT platform action names.
            For `mcp` tools: an optional allowlist of MCP tool names to scope
            the server surface (`null` exposes every tool). Ignored for other
            tool types.
        denied_actions:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            For `mcp` tools: an optional denylist of MCP tool names to hide.
            Applied after `actions` and taking precedence over it. Use it to
            scope a read+write MCP server read-only by denying just the write
            tools. `null` denies nothing. Ignored for other tool types.
        context_keys:
          type: array
          nullable: true
          items:
            type: string
          description: >-
            Optional allowlist of `tool_context` keys that may be forwarded to
            this tool as prefixed context headers (`X-Soat-Context-<key>` by
            default). When `null` or omitted, every key in the caller's
            `tool_context` is forwarded. When set, only the listed keys are, so
            a per-user credential in `tool_context` can be confined to the tools
            that need it; `[]` forwards none. The server-pinned identity keys
            (`session_id`, `actor_id`, `actor_external_id`) are always forwarded.
            A key consumed by a `{{context:<key>}}` token in this tool's own
            headers is substituted regardless of this list — the tool declared
            that header itself.
        preset_parameters:
          type: object
          nullable: true
          description: >-
            Fixed parameters pinned on every call this tool makes, whatever its
            type. Keys matching fields in the input schema are removed from the
            schema shown to the model, and a pinned value wins over one the
            model or a direct caller supplies for the same key. On an `mcp`
            tool a preset reaches each action whose input schema declares it;
            an action whose schema declares no properties receives every one.


            Values accept `{{context:<key>}}` references, resolved per call from
            the caller's `tool_context`, so a pin can be the run's own value —
            the one account this run may act on — rather than one fixed when the
            tool was created. A resolved value is retyped to the parameter's
            declared schema type; a key missing from the call's `tool_context`
            fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending
            the literal placeholder. `{{secret:...}}` is not resolved here. See
            the Tool Context reference.
        pipeline:
          type: object
          nullable: true
          description: >-
            Pipeline definition for `pipeline` tools. See the `pipeline` field
            on the Tool schema for the full structure.
        output_mapping:
          type: object
          nullable: true
          description: >-
            Universal JSON Logic mapping applied to the tool's raw result. See
            the `output_mapping` field on the Tool schema for details.
        guardrail_ids:
          x-soat-ref: guardrails
          type: array
          nullable: true
          items:
            type: string
          description: Guardrails attached at the tool scope.

    CallToolRequest:
      type: object
      properties:
        action:
          type: string
          description: >
            For `builtin` tools: the action name (must be in the tool's `actions`
            list). For `mcp` tools: the MCP tool name to invoke (must be in the
            tool's `actions` allowlist when one is set, and must not be in its
            `denied_actions` denylist). Ignored for `http` tools.
        input:
          type: object
          description: >
            Input parameters for the tool call. These are merged with the
            tool's `preset_parameters` before execution; a preset value wins
            over the same key sent here.
          additionalProperties: true
        tool_context:
          type: object
          additionalProperties:
            type: string
          description: >
            Key/value context for this call, forwarded to the tool as
            `X-Soat-Context-<key>` request headers and resolving any
            `{{context:<key>}}` token in the tool's `execute.headers`,
            `mcp.headers` or `preset_parameters`. Narrowed by the tool's
            `context_keys` allowlist when it sets one.

            This route has no session, so it stamps no server-derived identity:
            the reserved keys `session_id`, `actor_id` and `actor_external_id` are
            dropped from this bag (in any casing) rather than forwarded, so a
            downstream tool can still trust that a context header naming one is
            server-derived. Every other key becomes an HTTP header name and must
            match that grammar, or the call fails with
            `INVALID_TOOL_CONTEXT_KEY`.
          example:
            tenantId: acme
            userToken: tok_abc123
    ToolExecuteConfig:
      type: object
      nullable: true
      additionalProperties: false
      description: >
        Execution config for http tools. Supported fields: `url` (required),
        `method` (default `POST`), `headers`, `body_mode` and `response_mode`. The `url`
        may contain `{paramName}` placeholders (e.g. `/users/{userId}`)
        that are replaced at call time with the corresponding tool argument
        value (URL-encoded). Arguments consumed as path parameters are
        excluded from the query string and request body. `body_mode` is
        `json` (default) or `multipart`. In `multipart` mode the merged tool
        arguments are sent as a `multipart/form-data` body: scalar fields
        become plain form fields and a field shaped like
        `{ content_type, filename, data_base64 }` is decoded from base64
        and attached as a file part (the hardcoded
        `Content-Type: application/json` is dropped so `fetch` sets the
        multipart boundary itself). `response_mode` is `json` (default: the
        body parsed as JSON, else returned as text) or `base64`, which returns
        a 2xx body as `{ content_type, filename, data_base64 }` — the same
        file shape `multipart` accepts, so a pipeline step can relay a
        downloaded file. `filename` comes from `Content-Disposition` and is
        omitted when the target names none; a body over
        `TOOL_RESPONSE_MAX_BYTES` is `502 TOOL_RESPONSE_TOO_LARGE`.


        `auth` adds a computed request credential, for targets whose
        `Authorization` value cannot be expressed as a static header.
        Supported `auth.type` values:


        - `aws_sigv4` — signs the request with AWS Signature Version 4.
        Requires `region`, `service`, `access_key_id` and
        `secret_access_key`; `session_token` is optional (temporary
        credentials). Incompatible with `body_mode: multipart`, whose body
        bytes are not known at signing time.

        - `gcp_service_account` — mints a Google OAuth 2.0 access token from
        a signed service account assertion and sends it as a bearer token.
        Requires `credentials` (the service account key file JSON, as a
        string) and `scopes` (a non-empty array). Tokens are cached per
        service account and scope set until shortly before they expire.


        Credential fields accept `{{secret:...}}` references and should use
        them — a tool is readable by anyone who can `GET /tools`, and the
        stored reference is what is echoed back, never the resolved value.


        A credential slot takes a reference, never a literal: a literal
        `secret_access_key`, `session_token` or `credentials` under `auth`, a
        literal credential-named `headers` value (`Authorization`, `Cookie`, or
        a name containing `api-key`/`token`/`secret`/`password`), or a `url`
        carrying a password or a literal credential-named query parameter
        (those names, plus `key` and `sig`/`signature`) is refused with `400
        LITERAL_CREDENTIAL`, `meta.paths` naming each one. A stored literal reads
        back as `{"no_echo": true}` (a `url` holding one is masked whole), an
        object rather than a string so a read-edit-write round trip fails the
        schema check instead of writing the placeholder in as the credential.
        A value carrying a `{{secret:...}}` reference is the wiring, not the
        credential, and stays readable.


        `headers` values additionally accept `{{context:<key>}}` references,
        resolved per call from the caller's `tool_context`, so a per-user
        credential can be placed in the real header the target expects
        (`Authorization: Bearer {{context:ocaToken}}`) instead of only in an
        `X-Soat-Context-<key>` header. Valid **only** inside `headers` — a
        context value is caller-supplied, so it may not steer the `url` — and
        a key missing from the `tool_context` at call time fails the tool call
        with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty
        credential. See the Tool Context reference.
      properties:
        url:
          type: string
          description: >-
            Endpoint URL. May carry `{paramName}` placeholders resolved from
            the tool arguments at call time.
        method:
          type: string
          nullable: true
          description: 'HTTP method (default: `POST`)'
        headers:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: >-
            Static headers sent on every request. Values accept
            `{{secret:...}}` and `{{context:<key>}}` references.
        body_mode:
          type: string
          nullable: true
          enum:
            - json
            - multipart
            - null
          description: >-
            Request body encoding. Incompatible with
            `auth.type: aws_sigv4`.
        response_mode:
          type: string
          nullable: true
          enum:
            - json
            - base64
            - null
          description: >-
            How a 2xx response body is returned. `base64` returns
            `{ content_type, filename, data_base64 }` for binary targets.
        auth:
          $ref: '#/components/schemas/ToolExecuteAuthConfig'
    ToolExecuteAuthConfig:
      type: object
      nullable: true
      additionalProperties: false
      description: >-
        Computed request credential, for a target whose `Authorization`
        value cannot be expressed as a static header. `aws_sigv4` requires
        `region`, `service`, `access_key_id` and `secret_access_key`;
        `gcp_service_account` requires `credentials` and `scopes`. Every
        credential field takes a `{{secret:...}}` reference; a literal is
        refused with `400 LITERAL_CREDENTIAL`.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - aws_sigv4
            - gcp_service_account
          description: Which credential is computed.
        region:
          type: string
          nullable: true
          description: "`aws_sigv4`: the signing region, e.g. `us-east-1`."
        service:
          type: string
          nullable: true
          description: "`aws_sigv4`: the signing service, e.g. `execute-api`."
        access_key_id:
          type: string
          nullable: true
          description: "`aws_sigv4`: the access key id."
        secret_access_key:
          type: string
          nullable: true
          description: >-
            `aws_sigv4`: the secret access key, as a `{{secret:...}}`
            reference; a literal is refused.
        session_token:
          type: string
          nullable: true
          description: >-
            `aws_sigv4`: the session token of a temporary credential, as a
            reference, like `secret_access_key`.
        credentials:
          type: string
          nullable: true
          description: >-
            `gcp_service_account`: the service account key file JSON, as a
            reference to a secret holding it, like `secret_access_key`.
        scopes:
          type: array
          nullable: true
          items:
            type: string
          description: "`gcp_service_account`: the OAuth scopes to mint the token for."
    ToolMcpConfig:
      type: object
      nullable: true
      additionalProperties: false
      description: >-
        MCP server config (`url`, `headers`). `headers` values accept
        `{{secret:...}}` and `{{context:<key>}}` references, resolved right
        before the outbound MCP request; `url` accepts `{{secret:...}}` only.
        A literal credential in `headers` or `url` is refused, exactly as in
        `execute`.
      properties:
        url:
          type: string
          description: MCP server URL. Accepts a `{{secret:...}}` reference.
        headers:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: >-
            Headers sent on every MCP request. Values accept
            `{{secret:...}}` and `{{context:<key>}}` references.
