openapi: 3.0.3
info:
  title: Transient Intelligence TI-01 Developer API
  version: 1.0.0-beta
  description: |
    Frozen beta contract for the API-first TI-01 surface.

    Active endpoints:
    - POST /api/models/v1/upload
    - POST /api/models/v1/ask
    - GET /api/models/v1/runs/{id}
    - GET /api/models/v1/runs/{id}/results

servers:
  - url: https://{host}
    variables:
      host:
        default: api.transientintelligence.com

security:
  - ApiKeyAuth: []

paths:
  /api/models/v1/answer:
    post:
      summary: One-call upload + ask orchestrator
      description: |
        Universal API-side workflow wrapper (similar to MCP `ti_run_workflow`).
        Accepts either:
        - multipart file upload(s), or
        - JSON payload (`input`, `input_url`, `text`, `json_payload`), or
        - an existing `session_id` (ask-only path)

        Default behavior:
        - Evidence-first output (citations/locators) via the same response shape as `/v1/ask`
        - Wait/poll up to ~10 seconds for ingestion/indexing, then return `mode=answer_wait_then_retry`
      operationId: v1Answer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V1AnswerJsonRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/V1AnswerMultipartRequest'
      responses:
        '200':
          description: Completed answer or retry-needed response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1AnswerResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server-side processing failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/models/v1/upload:
    post:
      summary: Upload and ingest source data
      description: |
        Ingest-only endpoint. Accepts JSON payloads and file uploads.
        Returns deterministic upload state fields for agent orchestration:
        - `run_id` is always present (nullable for sync paths)
        - `mode` is `async` when polling is required, otherwise `sync`
        - `retrieval_ready` indicates whether `/v1/ask` can immediately retrieve evidence
        `batchId` remains for backward compatibility and maps to `run_id` when present.
        Guardrails:
        - Max request payload: 30MB (default, env-tunable)
        - Max document count: 100 (default, env-tunable)
      operationId: v1Upload
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V1UploadJsonRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/V1UploadMultipartRequest'
      responses:
        '200':
          description: Upload accepted or processed response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1UploadResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server-side processing failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/models/v1/ask:
    post:
      summary: Retrieve evidence for a question
      description: |
        Evidence-first query endpoint. Requires session_id and question.
        Returns verifiable citations/locators and no synthesis by default.
        `top_k` controls retrieval depth (how many ranked evidence items are returned):
        - lower values favor precision and concise review
        - higher values favor recall and broader coverage
        Guardrails:
        - top_k must be 1..50 (default max, env-tunable)
        - limit must be 1..1000 (default max, env-tunable)
        - inline input/data/inputs payload max: 8MB (default, env-tunable)
      operationId: v1Ask
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/V1AskRequest'
      responses:
        '200':
          description: Evidence retrieval response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1AskEvidenceResponse'
        '400':
          description: Missing or invalid request fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LegacySimpleErrorResponse'
        '503':
          description: Service unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LegacySimpleErrorResponse'

  /api/models/v1/runs/{id}:
    get:
      summary: Get run status
      operationId: v1GetRun
      parameters:
        - name: id
          in: path
          required: true
          description: Batch/run identifier returned by upload
          schema:
            type: string
      responses:
        '200':
          description: Current run status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1RunStatusResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Batch not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Status lookup failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/models/v1/runs/{id}/results:
    get:
      summary: Get run results
      operationId: v1GetRunResults
      parameters:
        - name: id
          in: path
          required: true
          description: Batch/run identifier returned by upload
          schema:
            type: string
      responses:
        '200':
          description: Completed run results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/V1RunResultsResponse'
        '400':
          description: Batch exists but is not completed yet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Batch not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Results retrieval failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

  schemas:
    V1UploadJsonRequest:
      type: object
      additionalProperties: true
      properties:
        model:
          type: string
          default: TI-01
        sessionId:
          type: string
          description: Optional caller-provided session identifier
        wait_for_completion:
          type: boolean
          default: false
        retention_hours:
          type: integer
          minimum: 1
          maximum: 72
        input:
          description: JSON/text payload or storage URL(s)
          oneOf:
            - type: string
            - type: array
              maxItems: 100
              items:
                type: string
            - type: object
              additionalProperties: true
        input_url:
          type: string
          description: Optional HTTPS URL shortcut for remote file ingest (mapped to input when input is omitted)
        data:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
        inputs:
          type: object
          additionalProperties: true

    V1UploadMultipartRequest:
      type: object
      properties:
        files:
          type: array
          description: One or more files for ingest
          maxItems: 100
          items:
            type: string
            format: binary
        sessionId:
          type: string
        wait_for_completion:
          type: boolean
        retention_hours:
          type: integer
          minimum: 1
          maximum: 72
      required:
        - files

    V1UploadResponse:
      type: object
      additionalProperties: true
      properties:
        model:
          type: string
          example: TI-01
        batchId:
          type: string
          description: Backward-compatible alias for async run polling (same value as run_id when present)
        run_id:
          type: string
          nullable: true
          description: Deterministic run identifier for polling; null for sync/no-run upload paths
        mode:
          type: string
          enum: [sync, async]
          description: Upload execution mode; async indicates polling via /v1/runs endpoints
        retrieval_ready:
          type: boolean
          description: True when evidence retrieval is ready for /v1/ask
        status:
          type: string
          example: processing
        message:
          type: string
        session_id:
          type: string
        metadata:
          type: object
          additionalProperties: true
        retention_policy:
          type: string
        retention_hours:
          type: integer
        expires_at:
          type: string
          format: date-time
        burn_status:
          type: string
        retention_enforced_cap_hours:
          type: integer
        timestamp:
          type: string
          format: date-time

    V1AskRequest:
      type: object
      additionalProperties: true
      required:
        - session_id
        - question
      properties:
        session_id:
          type: string
        question:
          type: string
          minLength: 1
        include_normative:
          type: boolean
          default: true
          description: When true (default), include normative_check and factual_confidence blocks in the response.
        top_k:
          type: integer
          minimum: 1
          maximum: 50
          description: |
            Number of top-ranked evidence items to return.
            Guidance:
            - 1..5: precision-first (quick checks, less noise)
            - 6..15: balanced default for most workflows
            - 16..50: recall-first (wider sweep, more review effort)
        limit:
          type: integer
          minimum: 1
          maximum: 1000
        filters:
          type: object
          additionalProperties: true
        context:
          type: object
          additionalProperties: true
        retention_hours:
          type: integer
          minimum: 1
          maximum: 72
        input:
          description: Optional inline payload fallback for evidence retrieval
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
        data:
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
        inputs:
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true

    Citation:
      type: object
      additionalProperties: true
      properties:
        entity:
          type: string
        doc_id:
          type: string
        locator:
          type: string
        program_locator:
          oneOf:
            - type: object
              additionalProperties: true
            - type: 'null'
        human_locator:
          type: string
        snippet:
          type: string
        quote:
          type: string

    Refund:
      type: object
      additionalProperties: true
      properties:
        applied:
          type: boolean
        reason:
          oneOf:
            - type: string
            - type: 'null'
        message:
          oneOf:
            - type: string
            - type: 'null'
        refundsUsedThisSession:
          type: integer
        refundCapRemaining:
          type: integer

    V1AskEvidenceResponse:
      type: object
      additionalProperties: true
      required:
        - success
        - sessionId
        - query
        - totalMatches
        - citations
      properties:
        success:
          type: boolean
        sessionId:
          type: string
        query:
          type: string
        totalMatches:
          type: integer
        citations:
          type: array
          items:
            $ref: '#/components/schemas/Citation'
        creditsUsed:
          type: number
        processingTime:
          type: number
        source:
          type: string
        rawJSON:
          type: array
          items:
            type: object
            additionalProperties: true
        retention_policy:
          type: string
        retention_hours:
          type: integer
        expires_at:
          type: string
          format: date-time
        burn_status:
          type: string
        retention_enforced_cap_hours:
          type: integer
        refund:
          $ref: '#/components/schemas/Refund'
        timestamp:
          type: string
          format: date-time
        normative_check:
          $ref: '#/components/schemas/NormativeCheck'
        factual_confidence:
          $ref: '#/components/schemas/FactualConfidence'

    NormativeCheck:
      type: object
      additionalProperties: true
      properties:
        normative_drift_risk:
          type: string
          enum: [none, low, medium, high]
        evidence_coverage:
          type: string
          enum: [strong, partial, insufficient]
        possible_pipeline_gap:
          type: boolean
        missing_expected_evidence:
          type: array
          items:
            type: string
        recommended_next_action:
          type: string
        requested_scope_signals:
          type: array
          items:
            type: string
        human_summary:
          type: string

    FactualConfidence:
      type: object
      additionalProperties: true
      properties:
        label:
          type: string
          enum: [Strong, Partial, Insufficient]
        message:
          type: string

    V1AnswerJsonRequest:
      type: object
      additionalProperties: true
      required:
        - question
      properties:
        session_id:
          type: string
          description: Optional session identifier. If omitted, server generates a valid session id.
        question:
          type: string
          minLength: 1
        include_normative:
          type: boolean
          default: true
        wait_max_ms:
          type: integer
          default: 10000
          minimum: 0
          maximum: 60000
        retry_after_seconds:
          type: integer
          default: 10
          minimum: 1
          maximum: 60
        input_url:
          type: string
          description: Optional HTTPS URL shortcut for remote file ingest.
        input:
          description: Inline payload for ingest (string/object/array); forwarded to /v1/upload.
          oneOf:
            - type: string
            - type: array
              maxItems: 100
              items:
                type: string
            - type: object
              additionalProperties: true
        text:
          type: string
          description: Optional extracted text (mapped to input for ingest).
        json_payload:
          description: Optional extracted JSON payload (mapped to input for ingest).
        top_k:
          type: integer
          minimum: 1
          maximum: 50
        limit:
          type: integer
          minimum: 1
          maximum: 1000
        filters:
          type: object
          additionalProperties: true
        context:
          type: object
          additionalProperties: true

    V1AnswerMultipartRequest:
      type: object
      properties:
        files:
          type: array
          description: One or more files for ingest
          maxItems: 100
          items:
            type: string
            format: binary
        session_id:
          type: string
        question:
          type: string
        include_normative:
          type: boolean
        wait_max_ms:
          type: integer
        retry_after_seconds:
          type: integer
        top_k:
          type: integer
        limit:
          type: integer
        retention_hours:
          type: integer
          minimum: 1
          maximum: 72
      required:
        - files
        - question

    V1AnswerResponse:
      type: object
      additionalProperties: true
      description: |
        When mode=answer_completed, response includes the full /v1/ask evidence-first payload plus a workflow block.
        When mode=answer_wait_then_retry, response includes session_id/run_id and retry guidance.
      properties:
        mode:
          type: string
          enum: [answer_completed, answer_wait_then_retry]
        workflow:
          type: object
          additionalProperties: true

    V1RunStatusResponse:
      type: object
      additionalProperties: true
      required:
        - model
        - batch_id
        - status
        - progress
        - session_id
      properties:
        model:
          type: string
        batch_id:
          type: string
        status:
          type: string
        progress:
          type: integer
        files_processed:
          type: integer
        total_files:
          type: integer
        session_id:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        retention_policy:
          type: string
        retention_hours:
          type: integer
        expires_at:
          type: string
          format: date-time
        burn_status:
          type: string
        retention_enforced_cap_hours:
          type: integer
        results:
          type: object
          additionalProperties: true
        error:
          type: object
          additionalProperties: true
        timestamp:
          type: string
          format: date-time

    V1RunResultsResponse:
      type: object
      additionalProperties: true
      required:
        - model
        - session_id
      properties:
        model:
          type: string
        intelligence:
          type: object
          additionalProperties: true
        metadata:
          type: object
          additionalProperties: true
        results:
          type: object
          additionalProperties: true
        session_id:
          type: string
        retention_policy:
          type: string
        retention_hours:
          type: integer
        expires_at:
          type: string
          format: date-time
        burn_status:
          type: string
        retention_enforced_cap_hours:
          type: integer
        timestamp:
          type: string
          format: date-time

    ErrorObject:
      type: object
      additionalProperties: true
      required:
        - type
        - code
        - message
      properties:
        type:
          type: string
        code:
          type: string
          description: |
            Stable machine-readable error code.
            Common values include:
            - missing_session_id
            - missing_question
            - ask_upload_fields_not_supported
            - top_k_out_of_range
            - limit_out_of_range
            - too_many_documents
            - payload_too_large
            - inline_payload_too_large
            - url_timeout
            - url_forbidden
            - url_expired
            - url_not_found
            - url_payload_too_large
            - url_fetch_failed
        message:
          type: string

    ErrorResponse:
      type: object
      additionalProperties: true
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorObject'

    LegacySimpleErrorResponse:
      type: object
      additionalProperties: true
      properties:
        error:
          type: string
        message:
          type: string
