openapi: 3.0.3
info:
  title: SOAT Triggers API
  version: 1.0.0
  description: API for managing triggers that activate orchestrations, agents, tools, and evals
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
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: Triggers
    description: Manage triggers and inspect firings
security:
  - bearerAuth: []
paths:
  /api/v1/triggers:
    get:
      description: Lists triggers. Filter by project, starter type, or target type.
      tags:
        - Triggers
      summary: List triggers
      operationId: listTriggers
      parameters:
        - name: project_id
          in: query
          required: false
          schema:
            type: string
        - name: type
          in: query
          required: false
          schema:
            type: string
            enum:
              - manual
              - webhook
              - schedule
              - event
        - name: target_type
          in: query
          required: false
          schema:
            type: string
            enum:
              - orchestration
              - agent
              - tool
              - eval
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: A list of triggers
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                  - limit
                  - offset
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Trigger'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
    post:
      description: Creates a new trigger for a project
      tags:
        - Triggers
      summary: Create a trigger
      operationId: createTrigger
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTriggerRequest'
      responses:
        '201':
          description: Trigger created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerWithSecret'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden

  /api/v1/triggers/{trigger_id}:
    get:
      description: Retrieves the details of a specific trigger
      tags:
        - Triggers
      summary: Get a trigger
      operationId: getTrigger
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Trigger details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Trigger'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found
    patch:
      description: >
        Updates an existing trigger's configuration. The type is immutable.
        Changing `target_type` or `target_id` re-checks the target-start action
        (`orchestrations:StartRun`, `agents:CreateAgentGeneration` or
        `tools:CallTool`) against the resulting target, because a firing runs with
        the trigger creator's authority rather than the updater's. A caller who
        could not start the new target is answered `403` and the trigger keeps the
        target it had.
      tags:
        - Triggers
      summary: Update a trigger
      operationId: updateTrigger
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTriggerRequest'
      responses:
        '200':
          description: Trigger updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Trigger'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found
    delete:
      description: Deletes a trigger
      tags:
        - Triggers
      summary: Delete a trigger
      operationId: deleteTrigger
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Trigger deleted
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found

  /api/v1/triggers/{trigger_id}/fire:
    post:
      description: >-
        Fires a trigger synchronously and returns the terminal firing record.
        The firing itself always settles here; an `eval` target's run is queued
        rather than executed inline, so the record names a `queued` run to poll.
      tags:
        - Triggers
      summary: Fire a trigger
      operationId: fireTrigger
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FireTriggerRequest'
      responses:
        '200':
          description: Terminal firing record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerFiring'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found
        '409':
          description: Trigger inactive or creator unavailable

  /api/v1/triggers/{trigger_id}/secret:
    get:
      description: Retrieves the signing secret for a webhook trigger
      tags:
        - Triggers
      summary: Get trigger secret
      operationId: getTriggerSecret
      x-soat-agent-exclude: true
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Trigger secret
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerSecretResponse'
        '400':
          description: Trigger is not a webhook trigger
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found

  /api/v1/triggers/{trigger_id}/rotate-secret:
    post:
      description: Rotates the signing secret for a webhook trigger
      tags:
        - Triggers
      summary: Rotate trigger secret
      operationId: rotateTriggerSecret
      x-soat-agent-exclude: true
      parameters:
        - name: trigger_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Secret rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerWithSecret'
        '400':
          description: Trigger is not a webhook trigger
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found

  /api/v1/trigger-firings:
    get:
      description: Lists firings for a trigger (trigger_id is required).
      tags:
        - Triggers
      summary: List trigger firings
      operationId: listTriggerFirings
      parameters:
        - name: trigger_id
          in: query
          required: true
          description: Trigger to list firings for (trg_...)
          schema:
            type: string
        - $ref: './pagination.yaml#/components/parameters/Limit'
        - $ref: './pagination.yaml#/components/parameters/Offset'
      responses:
        '200':
          description: A list of firings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerFiringListResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Trigger not found

  /api/v1/trigger-firings/{firing_id}:
    get:
      description: Retrieves the details of a specific trigger firing
      tags:
        - Triggers
      summary: Get a trigger firing
      operationId: getTriggerFiring
      parameters:
        - name: firing_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Firing details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerFiring'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden
        '404':
          description: Firing not found

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    Trigger:
      type: object
      properties:
        id:
          type: string
        project_id:
          x-soat-ref: projects
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        type:
          type: string
          enum:
            - manual
            - webhook
            - schedule
            - event
        target_type:
          type: string
          enum:
            - orchestration
            - agent
            - tool
            - eval
        target_id:
          type: string
          description: Public ID of the target resource (orchestration, agent, tool, or eval)
        action:
          type: string
          nullable: true
          description: Tool targets only — the action for builtin/mcp tools
        input:
          type: object
          nullable: true
          description: >-
            Static input, shallow-merged under fire-time input. For `eval`
            targets the effective input may carry `agent_version` and
            `baseline_run_id`, which are passed to the queued run.
        cron:
          type: string
          nullable: true
          description: 5-field cron expression (UTC). Present only for schedule triggers
        event_pattern:
          type: string
          nullable: true
          description: >-
            Internal-event subscription pattern. Present only for event
            triggers: `*`, `prefix.*`, or an exact event name
        active:
          type: boolean
        policy_id:
          x-soat-ref: policies
          type: string
          nullable: true
          description: Optional boundary policy that further restricts firings
        next_fire_at:
          type: string
          format: date-time
          nullable: true
          description: Read-only, schedule triggers only. Server-computed next fire time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TriggerWithSecret:
      allOf:
        - $ref: '#/components/schemas/Trigger'
        - type: object
          properties:
            secret:
              type: string
              description: Webhook triggers only. Returned only on create and rotate

    CreateTriggerRequest:
      type: object
      required:
        - name
        - type
        - target_type
        - target_id
      additionalProperties: false
      properties:
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project. Optional when authenticating with a project-scoped API key, which defaults to the key's project; required otherwise.
        name:
          type: string
        description:
          type: string
        type:
          type: string
          enum:
            - manual
            - webhook
            - schedule
            - event
        target_type:
          type: string
          enum:
            - orchestration
            - agent
            - tool
            - eval
        target_id:
          type: string
        action:
          type: string
          description: Tool targets only — the action for builtin/mcp tools
        input:
          type: object
        tool_context:
          type: object
          additionalProperties:
            type: string
          description: >
            Caller context every firing forwards to the run it starts, so an
            agent whose tools authorize through `{{context:<key>}}` can be put
            on a schedule — a firing has no request to carry one. Each key is
            forwarded as one `X-Soat-Context-<key>` header.


            **Write-only.** It is accepted here and never returned on a read,
            so the record cannot be used to recover a value.


            A value may be a `{{secret:sec_...}}` reference, which keeps the
            credential in the [secret](/docs/modules/secrets) store and leaves
            only its id on the trigger; it is resolved at fire time, so
            rotating the secret changes the next firing without touching the
            trigger. A reference is refused here rather than at fire time when
            it is not in that id form (a secret's name does not resolve) or
            names a secret that does not exist in this project.
        cron:
          type: string
          description: 5-field cron expression (UTC). Required when type is schedule
        event_pattern:
          type: string
          description: >-
            Internal-event subscription pattern. Required when type is event,
            rejected otherwise. `*` matches every event, `prefix.*` a namespace,
            or give an exact event name such as `documents.ingested`
        active:
          type: boolean
          default: true
        policy_id:
          x-soat-ref: policies
          type: string

    UpdateTriggerRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        target_type:
          type: string
          enum:
            - orchestration
            - agent
            - tool
            - eval
        target_id:
          type: string
        action:
          type: string
          nullable: true
        input:
          type: object
          nullable: true
        tool_context:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: >
            Replaces the stored bag; `null` clears it. Write-only and resolved
            at fire time — see `CreateTriggerRequest.tool_context`.
        cron:
          type: string
          nullable: true
        event_pattern:
          type: string
          nullable: true
        active:
          type: boolean
        policy_id:
          x-soat-ref: policies
          type: string
          nullable: true

    FireTriggerRequest:
      type: object
      additionalProperties: false
      properties:
        input:
          type: object
          description: >-
            Fire-time input, shallow-merged over the trigger's static input.
            For `eval` targets it may carry `agent_version` and
            `baseline_run_id`.
        tool_context:
          type: object
          additionalProperties:
            type: string
          description: >
            Fire-time caller context, shallow-merged per key over the trigger's
            stored `tool_context`. A manual fire has a caller, so this is the
            one path that can supply a value without storing it.


            Forwarded exactly as written: a `{{secret:...}}` reference is
            resolved only in the trigger's stored bag, never in one supplied
            here.

    TriggerSecretResponse:
      type: object
      properties:
        secret:
          type: string

    TriggerFiring:
      type: object
      properties:
        id:
          type: string
        trigger_id:
          x-soat-ref: triggers
          type: string
        project_id:
          x-soat-ref: projects
          type: string
        source:
          type: string
          enum:
            - manual
            - webhook
            - schedule
            - event
        status:
          type: string
          enum:
            - queued
            - running
            - completed
            - failed
        input:
          type: object
          nullable: true
          description: >-
            Effective input, after the trigger's static input is merged under
            the fire-time input. For an `event` firing the fire-time input is
            the event envelope a webhook delivery carries (`id`, `event`,
            `project_id`, `resource_type`, `resource_id`, `data`, `timestamp`).
        result:
          type: object
          nullable: true
          description: '{ target_type, result_id, status, output } — output truncated'
        error:
          allOf:
            - $ref: './errors.yaml#/components/schemas/StoredError'
          nullable: true
          description: Why the firing failed, when it did.
        idempotency_key:
          type: string
          nullable: true
          description: >-
            `<event_id>:<trigger_id>` for an `event` firing, where `event_id`
            is the `id` in `input`; null for every other source. The same event reaching the same trigger twice
            produces one firing under this key, so a consumer reading firings
            can recognise a redelivery.
        attempts:
          type: integer
          description: >-
            Dispatch attempts started. Above 1 means an earlier attempt was
            interrupted before it recorded a result and the firing was
            redelivered — not that the target refused it, which is recorded
            terminally in `error` and never retried.
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TriggerFiringListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TriggerFiring'
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
