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: '#/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:
        - 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
        - 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 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: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/tools/{tool_id}:
    get:
      tags:
        - Tools
      summary: Get a tool
      description: Returns a single tool by ID.
      operationId: getTool
      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: '#/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:
        - Tools
      summary: Update a tool
      description: Updates an existing tool.
      operationId: updateTool
      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: '#/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:
        - Tools
      summary: Delete a tool
      description: Deletes a tool by ID.
      operationId: deleteTool
      parameters:
        - name: tool_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/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.
      operationId: callTool
      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: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/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: '#/components/schemas/ErrorResponse'
        '404':
          description: Tool not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Unprocessable — tool type cannot be invoked server-side
          content:
            application/json:
              schema:
                $ref: '#/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`.
          content:
            application/json:
              schema:
                $ref: '#/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`, and `body_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).


            `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.


            `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.
        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 — the behavior of every tool created
            before this field existed. 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.


            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.
        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> }`,
            so `{ "var": "output.text" }` extracts a bare scalar field instead
            of requiring a wrapping `pipeline` tool, and `{ "var": "input.title" }`
            echoes back a field of the request that produced the response. 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
      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:
          type: object
          description: >
            Execution config for http tools. Supported fields: `url` (required),
            `method` (default `POST`), `headers`, and `body_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).


            `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.


            `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.
        mcp:
          type: object
          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.
        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 — the behavior of every tool created
            before this field existed. 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.


            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
      properties:
        name:
          type: string
        type:
          type: string
          enum:
            - http
            - client
            - mcp
            - builtin
            - pipeline
        description:
          type: string
          nullable: true
        parameters:
          type: object
          nullable: true
        execute:
          type: object
          nullable: true
          description: >
            Execution config for http tools. Supported fields: `url` (required),
            `method` (default `POST`), `headers`, and `body_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).


            `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.


            `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.
        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.
        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 — the behavior of every tool created
            before this field existed. 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.


            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

    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.
