openapi: 3.1.0
info:
  title: Travelmode Agent Runs API
  version: "1.0.0"
  description: >
    Create, observe, and control AI planning runs. A run is an asynchronous
    execution of the planning agent over one trip. Each run moves through a
    set of phases, emits a sequenced stream of events, and produces typed
    artifacts (candidates, entities, conflicts, draft operations, proposals).


    Like the rest of the user-facing API, these endpoints use the **end-user
    auth model** (session cookie or mobile / extension bearer token) — not a
    `tm_` developer key. **Runs are owner-scoped**: every run belongs to the
    user who created it, and only that user may read, stream, or control it.
    Creating a run additionally requires that you own the target trip.


    Responses are resource objects, not a `{ data, meta }` envelope. Errors are
    returned as `{ "error": <string | array> }`.
servers:
  - url: https://api.travelmode.ai
    description: Production
  - url: https://{host}
    description: Self-hosted / Replit deployment. Replace `{host}` with your deployed domain.
    variables:
      host:
        default: your-app-domain.replit.app
security:
  - BearerToken: []
  - SessionCookie: []
tags:
  - name: Runs
    description: Create, list, read, and control runs.
  - name: Streaming
    description: Live event stream and incremental polling.
  - name: Artifacts
    description: Typed outputs produced during a run.
paths:
  /v1/agent-runs:
    get:
      tags: [Runs]
      summary: List runs
      description: >
        Lists agent runs. When `tripId` is supplied, returns runs for that
        trip; otherwise returns the authenticated user's runs. Results are
        newest-first.
      parameters:
        - name: tripId
          in: query
          required: false
          description: Restrict the listing to a single trip.
          schema:
            type: integer
            minimum: 1
        - name: limit
          in: query
          required: false
          description: Maximum number of runs to return.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: The matching runs.
          content:
            application/json:
              schema:
                type: object
                required: [runs]
                properties:
                  runs:
                    type: array
                    items:
                      $ref: "#/components/schemas/Run"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      tags: [Runs]
      summary: Create a run
      description: >
        Starts a new planning run for a trip you own. The run is created in the
        `pending` state; drive it forward with the control endpoint or observe
        it via the stream. Requires `trip.userId === <caller>` (trip owner).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRunRequest"
            example:
              tripId: 42
              triggerMessage: "Plan 3 days in Rome"
      responses:
        "201":
          description: The run was created.
          content:
            application/json:
              schema:
                type: object
                required: [run]
                properties:
                  run:
                    $ref: "#/components/schemas/Run"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The caller does not own the target trip.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Forbidden
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
  /v1/agent-runs/{runId}:
    get:
      tags: [Runs]
      summary: Get a run
      description: >
        Returns the run, its artifacts, and whether the trip has advanced past
        the run's input snapshot (`revisionDrift`). Owner-only.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: The run with artifacts and drift status.
          content:
            application/json:
              schema:
                type: object
                required: [run]
                properties:
                  run:
                    $ref: "#/components/schemas/Run"
                  artifacts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Artifact"
                  revisionDrift:
                    $ref: "#/components/schemas/RevisionDrift"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
  /v1/agent-runs/{runId}/control:
    post:
      tags: [Runs]
      summary: Control a run
      description: >
        Drives a run through its lifecycle. Valid `action` values are `start`,
        `stop`, `retry`, and `cancel`. An action that is invalid for the run's
        current state returns `400`. Owner-only.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [start, stop, retry, cancel]
                reason:
                  type: string
                  description: Optional reason recorded on cancel/stop.
            example:
              action: start
      responses:
        "200":
          description: The updated run.
          content:
            application/json:
              schema:
                type: object
                required: [run]
                properties:
                  run:
                    $ref: "#/components/schemas/Run"
        "400":
          description: The action is invalid for the run's current state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Cannot start a run that is already running
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
  /v1/agent-runs/{runId}/artifacts:
    get:
      tags: [Artifacts]
      summary: List a run's artifacts
      description: Returns all artifacts produced by the run. Owner-only.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: The run's artifacts.
          content:
            application/json:
              schema:
                type: object
                required: [artifacts]
                properties:
                  artifacts:
                    type: array
                    items:
                      $ref: "#/components/schemas/Artifact"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
    patch:
      tags: [Artifacts]
      summary: Pin or unpin an artifact
      description: >
        Sets the `isPinned` flag on one of the run's artifacts so it survives
        re-runs and is surfaced in the UI. Owner-only.
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [artifactId, isPinned]
              properties:
                artifactId:
                  type: integer
                isPinned:
                  type: boolean
            example:
              artifactId: 5012
              isPinned: true
      responses:
        "200":
          description: The updated artifact.
          content:
            application/json:
              schema:
                type: object
                required: [artifact]
                properties:
                  artifact:
                    $ref: "#/components/schemas/Artifact"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: The artifact was not found on this run.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Artifact not found
        "500":
          $ref: "#/components/responses/ServerError"
  /v1/agent-runs/{runId}/events:
    get:
      tags: [Streaming]
      summary: Poll a run's events
      description: >
        Returns the run's events, the current status, and the current phase.
        Pass `since` (the last sequence number you have seen) to fetch only
        newer events — the standard way to incrementally catch up without the
        SSE stream. Owner-only.
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: since
          in: query
          required: false
          description: Return only events with a sequence greater than this value.
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: The (incremental) events plus run status and phase.
          content:
            application/json:
              schema:
                type: object
                required: [events, runStatus]
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/RunEvent"
                  runStatus:
                    $ref: "#/components/schemas/RunStatus"
                  currentPhase:
                    $ref: "#/components/schemas/RunPhase"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
  /v1/agent-runs/{runId}/stream:
    get:
      tags: [Streaming]
      summary: Stream a run's events (SSE)
      description: >
        Opens a Server-Sent Events stream of the run's progress. The stream
        emits `connected`, then `run_event` frames for each new event, periodic
        `status` frames, and a terminal `run_finished` (or `error`) frame.
        Pass `since` to resume after a known sequence number so no events are
        missed across reconnects.


        **Authentication note:** because EventSource cannot send custom
        headers, this endpoint authenticates with the **session cookie only** —
        a bearer token in the `Authorization` header is not read here. Owner-only.
      security:
        - SessionCookie: []
      parameters:
        - $ref: "#/components/parameters/RunId"
        - name: since
          in: query
          required: false
          description: Resume after this sequence number.
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: An SSE stream of run events.
          content:
            text/event-stream:
              schema:
                type: string
                description: >
                  A stream of SSE frames. Each `data:` line is a JSON object
                  whose `type` is one of `connected`, `run_event`, `status`,
                  `run_finished`, or `error`.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
components:
  securitySchemes:
    BearerToken:
      type: http
      scheme: bearer
      description: >
        A mobile access token or browser-extension session token, sent as
        `Authorization: Bearer <token>`. This is the credential the
        reference's Try It console uses. Not accepted on the SSE stream
        endpoint, which is cookie-only.
    SessionCookie:
      type: apiKey
      in: cookie
      name: session_token
      description: >
        The first-party web session cookie set at login. Required for the SSE
        stream endpoint; accepted everywhere else.
  parameters:
    RunId:
      name: runId
      in: path
      required: true
      description: The run's UUID.
      schema:
        type: string
  responses:
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Invalid request body
    Unauthorized:
      description: No valid session cookie or bearer token was supplied.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Authentication required
    Forbidden:
      description: The run belongs to another user.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Forbidden
    NotFound:
      description: The run or trip was not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Run not found
    ServerError:
      description: An unexpected error occurred.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: Internal server error
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          description: >
            A human-readable error message, or — for validation failures — an
            array of structured Zod issues.
          oneOf:
            - type: string
            - type: array
              items:
                type: object
                additionalProperties: true
    RunStatus:
      type: string
      enum: [pending, running, completed, failed, cancelled]
    RunPhase:
      type: [string, "null"]
      enum: [understand, plan, retrieve, reason, act, compose, deliver, done, null]
      description: The run's current phase, or null before it starts.
    Run:
      type: object
      required: [id, userId, status]
      properties:
        id:
          type: string
        tripId:
          type: [integer, "null"]
        userId:
          type: string
        conversationId:
          type: [integer, "null"]
        status:
          $ref: "#/components/schemas/RunStatus"
        phase:
          $ref: "#/components/schemas/RunPhase"
        inputSnapshot:
          type: [object, "null"]
          additionalProperties: true
        triggerMessage:
          type: [string, "null"]
        errorMessage:
          type: [string, "null"]
        cancelReason:
          type: [string, "null"]
        lastEventSequence:
          type: integer
        startedAt:
          type: [string, "null"]
          format: date-time
        completedAt:
          type: [string, "null"]
          format: date-time
        createdAt:
          type: string
          format: date-time
      additionalProperties: true
    CreateRunRequest:
      type: object
      required: [tripId]
      properties:
        tripId:
          type: integer
          minimum: 1
        conversationId:
          type: integer
        triggerMessage:
          type: string
        inputSnapshot:
          type: object
          additionalProperties: true
    RunEvent:
      type: object
      properties:
        id:
          type: integer
        runId:
          type: string
        sequence:
          type: integer
          description: Monotonic per-run sequence number; use as the `since` cursor.
        eventType:
          type: string
          description: "e.g. RUN_STARTED, PHASE_STARTED, NOTE_ADDED, ARTIFACT_CREATED, RUN_FINISHED."
        actor:
          type: [string, "null"]
        payload:
          type: [object, "null"]
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
      additionalProperties: true
    Artifact:
      type: object
      properties:
        id:
          type: integer
        runId:
          type: string
        artifactType:
          type: string
          enum: [candidates, entities, conflicts, draft_ops, proposals]
        schemaVersion:
          type: string
        validationStatus:
          type: string
          enum: [pending, valid, invalid]
        validationErrors:
          type: [array, "null"]
          items:
            type: object
            additionalProperties: true
        data:
          type: object
          additionalProperties: true
        isPartial:
          type: boolean
        isPinned:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      additionalProperties: true
    RevisionDrift:
      type: object
      description: Whether the trip advanced past the run's frozen input snapshot.
      properties:
        hasDrift:
          type: boolean
        snapshotRevision:
          type: [integer, "null"]
        currentRevision:
          type: [integer, "null"]
      additionalProperties: true
