openapi: 3.1.0
info:
  title: Minara Agent Gateway
  version: 0.0.0
  description: REST + SSE surface in front of the Minara Agent runtime (`npm run serve`). Every route lives on `/v1/...` except the unauthenticated `/healthz` liveness probe. See https://docs.minara.ai/docs/reference/api/openapi for download instructions and tooling integration tips.
  license:
    name: Apache-2.0
servers:
  - url: http://localhost:8080
    description: Local dev (`npm run serve` default)
  - url: https://your-deployment.example.com
    description: Replace with your deployment URL
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: "Set `GATEWAY_AUTH_TOKEN` server-side; pass `Authorization: Bearer <token>` on every `/v1/...` route."
security:
  - bearer: []
tags:
  - name: health
  - name: chat
  - name: state
  - name: auth
  - name: voice
  - name: files
  - name: research
  - name: config
  - name: workspace
  - name: stats
  - name: sessions
  - name: portfolio
  - name: perps
  - name: autopilot
  - name: personalization
  - name: learning
  - name: preferences
  - name: notifications
  - name: migrate
  - name: gateway
  - name: llm
  - name: market
  - name: skills
  - name: theme
  - name: admin
  - name: workflows
paths:
  /healthz:
    get:
      operationId: healthz
      tags:
        - health
      summary: Health check
      description: "Liveness probe. Returns 200 with `{\"ok\": true}` if the gateway is up."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
      security: []
  /v1/chat:
    post:
      operationId: chat
      tags:
        - chat
      summary: Synchronous chat
      description: |-
        Single agent turn. The request body is queued, the agent runs to completion (or error), and the final assistant message is returned.
        
        Long-running. For interactive UIs, prefer `/v1/chat/stream` to see intermediate tool calls as they happen.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              message: buy $50 of SOL
      responses:
        "200":
          description: "{ \"reply\": \"...\", \"toolCalls\": [ ... ] }"
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Missing or invalid bearer token
  /v1/chat/stream:
    post:
      operationId: chat_stream
      tags:
        - chat
      summary: Streaming chat (SSE)
      description: |-
        Streaming agent turn over Server-Sent Events. Each SSE event is a JSON payload describing an intermediate step: LLM delta, tool call, tool result, or final assistant message.
        
        Mid-turn steering events: a `user_interjection` event (`{ "message": "...", "ts_ms": 1747000000000 }`) is emitted when a message queued via `POST /v1/chat/interject` is injected into the running turn. The final `done` event data carries two optional fields: `interrupted: true` when the turn was stopped via `POST /v1/chat/interrupt`, and `pending_interjections: string[]` listing interjected messages that arrived too late to inject; clients re-send those as normal messages.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              message: analyze BTC momentum
      responses:
        "200":
          description: text/event-stream — see the SSE envelope defined in `apps/agent/src/gateway/api.ts`.
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/assist/system-prompt:
    post:
      operationId: assist_system_prompt
      tags:
        - chat
      summary: Assist — generate Custom Agent system prompt (SSE)
      description: "Streams a Custom Agent system prompt for the Web UI wizard's Generate-with-AI panel. Body: `{ goal, name?, description? }`. Uses Anthropic Haiku with a 30s server-side timeout and 2000 max output tokens. Rate-limited to 10 requests per minute per token."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              goal: monitor SOL volatility, alert if 1h move > 5%
              name: SOL watcher
              description: alerting
      responses:
        "200":
          description: text/event-stream — `chunk` events carry `{ text }`; `done` and `error` mark stream end.
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/status:
    get:
      operationId: status
      tags:
        - state
      summary: Agent status
      description: Kill switch state, daily spend, tool count, review engine state. Equivalent to the `/status` REPL slash command.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/system-metrics:
    get:
      operationId: system_metrics
      tags:
        - state
      summary: System resource metrics
      description: "Process + system snapshot for the Dashboard System status row: CPU percent, memory RSS, agent workspace size (recursive walk of `MINARA_DATA_DIR` — not host-filesystem usage) + sandbox folder size, gateway network throughput (bytes in/out per second), and recency-weighted LLM token throughput (averaged across the most recently active sessions, not lifetime). Each metric ships a 60-sample history for an inline sparkline. Returns `{ disabled: true }` when `MINARA_SYSTEM_METRICS_DISABLED=1`."
      responses:
        "200":
          description: "{ \"ts\": 1747000000000, \"cpu\": { \"process_percent\": 18.4, \"system_load_1m\": 1.2, \"cores\": 8, \"history\": […] }, \"memory\": { \"process_rss_mb\": 642.1, \"system_used_mb\": 12480, \"system_total_mb\": 16384, \"h"
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Missing or invalid bearer token
  /v1/dashboard/achievements:
    get:
      operationId: dashboard_achievements
      tags:
        - state
      summary: Dashboard achievements
      description: "Marketing-style stats for the web-ui Dashboard banner: activation timestamp, distinct tickers researched, lifetime realized profit (positive deltas only — losses skipped), and estimated hours of research saved. Server caches the response for 60s; a successful sell / swap / close trade busts the cache."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio:
    get:
      operationId: portfolio
      tags:
        - state
      summary: Current portfolio
      description: Current portfolio snapshot from the Minara backend, formatted for agent consumption.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/methodologies:
    get:
      operationId: methodologies
      tags:
        - state
      summary: Trading methodologies
      description: List of registered methodologies with their pass/fail counts and recent outcomes.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learning/audit/reports:
    get:
      operationId: audit_reports
      tags:
        - state
      summary: Methodology audit reports
      description: Trend + history of methodology audit health reports. Query `days` (default 30) bounds the look-back; returns lightweight summaries newest-first for the Learning Health trend chart.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learning/audit/reports/latest:
    get:
      operationId: audit_report_latest
      tags:
        - state
      summary: Latest methodology audit report
      description: "The most recent methodology audit report in full: composite health score, per-dimension scores, findings, and advisory actions. Returns `{ report, readiness }`. `report` is null until the first report exists; `readiness` carries the warm-up progress — the first report needs self-learning on plus a 7-day warm-up."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learning/audit/reports/{passId}:
    get:
      operationId: audit_report_detail
      tags:
        - state
      summary: Methodology audit report by id
      description: One methodology audit report in full, looked up by its pass id.
      parameters:
        - in: path
          name: passId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learning/audit/run:
    post:
      operationId: audit_run
      tags:
        - state
      summary: Run a methodology audit
      description: Run one audit pass on demand and persist it. Optional body `{ windowDays }` (clamped 1-365) overrides the configured look-back. Returns `{ report, readiness }`; the first report is gated on the self-learning warm-up, so `report` is null (nothing persisted) until `readiness.eligible`. Returns 409 when an audit is already running.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows:
    get:
      operationId: workflows
      tags:
        - state
      summary: Workflows
      description: List local workflow definitions and executions managed by the workflow engine.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                definitions_count: 1
                executions_count: 2
                definitions:
                  - id: wf_local_123
                    name: daily-brief
                    backend: local
                    publish_state: published
                    publish_health: up_to_date
                    active: true
                    has_unpublished_changes: false
                    last_execution_at: 2026-04-23T00:00:00.000Z
                    last_execution_status: success
                    next_run_at: 2026-04-24T00:00:00.000Z
                executions:
                  - id: inst_123
                    definition_id: wf_local_123
                    name: daily-brief
                    execution_mode: production
                    run_purpose: null
                    is_dry_run: false
                    status: success
                    waiting_reason: null
                    cancel_reason: null
                    error_reason: null
                    iterations: 1
                    current_step: null
                    started_at: 2026-04-23T00:00:00.000Z
                    ended_at: 2026-04-23T00:00:03.000Z
        "401":
          description: Missing or invalid bearer token
  /v1/kill:
    post:
      operationId: kill
      tags:
        - state
      summary: Activate kill switch
      description: |-
        Blocks all tier ≥ 2 tool calls until `/v1/unkill` is called. Intended for emergency halting.
        
        Logged to the audit trail.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/unkill:
    post:
      operationId: unkill
      tags:
        - state
      summary: Deactivate kill switch
      description: Clears the kill switch flag.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/elevenlabs/api-key:
    post:
      operationId: auth_elevenlabs_api_key
      tags:
        - auth
      summary: Save an ElevenLabs voice key
      description: "Save an ElevenLabs key for speech synthesis and transcription. Validated against the ElevenLabs /v1/user endpoint before persisting; pass `saveAnyway: true` to skip validation. Not an LLM provider — powers the voice endpoints only."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              apiKey: ...
              saveAnyway: false
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                validated: true
        "401":
          description: Missing or invalid bearer token
  /v1/voice/status:
    get:
      operationId: voice_status
      tags:
        - voice
      summary: Voice service status
      description: "Whether speech services are configured: `stt_configured` (a transcription provider is available) and `tts_configured` (a synthesis provider is available). The web UI checks this before arming the microphone."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                stt_configured: true
                tts_configured: true
        "401":
          description: Missing or invalid bearer token
  /v1/voice/voices:
    get:
      operationId: voice_voices
      tags:
        - voice
      summary: List selectable voices
      description: "The voice-picker option list: official preset voices (ElevenLabs + OpenAI, always available) plus the user's own ElevenLabs voices fetched live when a key is configured. Each item carries `id`, `name`, `provider`, and `group` (`preset` or `custom`). The chosen id is saved separately to the `voice.ttsVoice` runtime preference."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                voices:
                  - id: EpMOHcveL15wHOy6xt7T
                    name: Minara
                    provider: elevenlabs
                    group: preset
                  - id: alloy
                    name: Alloy
                    provider: openai
                    group: preset
        "401":
          description: Missing or invalid bearer token
  /v1/voice/settings:
    get:
      operationId: voice_settings_get
      tags:
        - voice
      summary: Get speech settings
      description: Current user-tunable speech-synthesis settings (stability, similarity_boost, style, speaker boost, speed), merged onto the defaults. Drives the Settings -> Voice models sliders.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                settings:
                  stability: 0.5
                  similarityBoost: 0.75
                  style: 0.3
                  speakerBoost: true
                  speed: 0.7
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: voice_settings_put
      tags:
        - voice
      summary: Update speech settings
      description: Persist user-tuned speech-synthesis settings. Accepts a partial object; every field is clamped to its valid range (0-1 for stability/similarity/style, 0.7-1.2 for speed). Returns the normalized result.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              stability: 0.5
              style: 0.3
              speed: 0.7
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                settings:
                  stability: 0.5
                  similarityBoost: 0.75
                  style: 0.3
                  speakerBoost: true
                  speed: 0.7
        "401":
          description: Missing or invalid bearer token
  /v1/voice/cleanup:
    post:
      operationId: voice_cleanup
      tags:
        - voice
      summary: Clear old voice files
      description: Delete persisted voice audio (spoken replies + mic recordings) on chat messages older than `older_than_days` (default 30, range 0-3650). The message rows stay; only the audio file + its metadata key are removed, so an old reply read aloud again re-synthesizes on demand. Returns counts + bytes freed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              older_than_days: 30
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                older_than_days: 30
                deleted_files: 12
                freed_bytes: 345678
                messages_affected: 9
        "401":
          description: Missing or invalid bearer token
  /v1/voice/transcribe:
    post:
      operationId: voice_transcribe
      tags:
        - voice
      summary: Transcribe speech to text
      description: Transcribe an uploaded audio recording via the configured voice provider (ElevenLabs Scribe preferred, OpenAI fallback). Optional `language` (ISO-639-1 hint) and `persist` (store the original recording, returning its `file_key`) form fields.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: multipart/form-data
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                text: ...
                file_key: chat/files/...
        "401":
          description: Missing or invalid bearer token
  /v1/voice/tts:
    post:
      operationId: voice_tts
      tags:
        - voice
      summary: Synthesize speech
      description: "Stream synthesized speech for up to 4096 chars of text. The provider body is piped through unbuffered, so first audio bytes arrive before synthesis completes. `format` is one of mp3 (default), opus, wav; opus routes to the OpenAI provider (Telegram-style voice notes). Sentence-by-sentence callers can pass `previous_text` / `next_text` (each truncated to 600 chars) so ElevenLabs keeps prosody continuous across requests, and `first_chunk: true` on a reply's first sentence to let the gateway swap in the fastest model when the faster-start setting is on."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              text: ...
              voice: optional
              format: mp3
              first_chunk: false
              previous_text: optional
              next_text: optional
      responses:
        "200":
          description: audio bytes (audio/mpeg | audio/ogg | audio/wav)
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Missing or invalid bearer token
  /v1/sessions/{id}/voice-audio:
    post:
      operationId: session_voice_audio
      tags:
        - voice
      summary: Attach spoken-reply audio to a message
      description: Attach a previously-uploaded audio file (POST /v1/files) to an assistant message's metadata so the session history can replay the exact spoken reply. `message_id` optional; defaults to the latest assistant message.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              file_key: chat/files/...
              message_id: 123
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                message_id: 123
        "401":
          description: Missing or invalid bearer token
  /v1/files:
    post:
      operationId: files_upload
      tags:
        - files
      summary: Upload a file
      description: Upload a file into the sandboxed workspace. Returns a key that can be referenced by tools via `read_file`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: multipart/form-data
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                key: ...
                size: 1234
        "401":
          description: Missing or invalid bearer token
  /v1/files/{key}:
    get:
      operationId: files_fetch
      tags:
        - files
      summary: Fetch a file
      description: Stream a previously-uploaded sandboxed file back to the caller.
      parameters:
        - in: path
          name: key
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/artifacts/{id}:
    get:
      operationId: artifacts_fetch
      tags:
        - files
      summary: Fetch an artifact
      description: Fetch an agent-generated artifact (chart, spreadsheet, report payload) by its id. Artifacts are produced by document / visualization tools.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/artifacts:
    get:
      operationId: artifacts_list
      tags:
        - files
      summary: List report artifacts
      description: List completed report artifacts for the web UI's Files pages. Filter by `kind=chat|institution` to scope to one module's sessions, or pass `chat_id` for a single session. Each row includes the on-disk files plus pre-built `preview_url` (`/v1/sandbox/files/...?inline=1`) and `download_url` so the UI can join them with the gateway base.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/research:
    post:
      operationId: research
      tags:
        - research
      summary: Deep research pipeline
      description: Run the deep-research pipeline directly from the gateway without going through the chat loop. Equivalent to `minara research <topic>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"topic\": \"...\", \"mode\": \"light\" | \"heavy\" }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/config/financial-profile:
    get:
      operationId: config_financial_profile
      tags:
        - config
      summary: Get effective financial-profile config
      description: Current resolved `FinancialProfileConfig` — useful when debugging why a rebuild gate fired (or didn't).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/status:
    get:
      operationId: auth_status
      tags:
        - auth
      summary: Auth status
      description: List all configured auth profiles (OpenAI / Anthropic / OpenRouter / Minara).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/openai/init:
    post:
      operationId: auth_openai_init
      tags:
        - auth
      summary: OpenAI device-code init
      description: Start an OpenAI Codex device-code login flow. Returns `{ flowId, userCode, verificationUrl, intervalSec }`. The web UI presents `userCode` + a button to open `verificationUrl`, then polls `/v1/auth/oauth/openai/poll?flowId=…` every `intervalSec`. Replaces the previous PKCE-loopback flow, which OpenAI's public Codex client_id rejects with `unknown_error` (only the device-code endpoints accept third-party integrations).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/openai/poll:
    get:
      operationId: auth_openai_poll
      tags:
        - auth
      summary: OpenAI device-code poll
      description: "Poll an in-flight OpenAI device-code flow by `flowId` (from `/v1/auth/oauth/openai/init`). Returns `{ status: \"pending\" }` until the user finishes sign-in, then `{ status: \"success\", accountId, expiresAt }` (tokens persisted) or `{ status: \"failed\", message }` on terminal upstream errors. Caches the success answer for the flow's 15-min TTL so a delayed re-poll after success still resolves cleanly."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/openai/callback:
    get:
      operationId: auth_openai_callback
      tags:
        - auth
      summary: OpenAI OAuth callback (retired)
      description: Retired in 2026-05. The previous PKCE-loopback redirect target now returns 410 Gone with a hint pointing at the new device-code flow. Kept only so stale browser tabs opened before the cutover surface a clear message instead of "Unknown or expired state".
      responses:
        "200":
          description: Success
      security: []
  /v1/auth/oauth/openrouter/init:
    post:
      operationId: auth_openrouter_init
      tags:
        - auth
      summary: OpenRouter OAuth init
      description: Start an OpenRouter OAuth PKCE flow.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/openrouter/callback:
    get:
      operationId: auth_openrouter_callback
      tags:
        - auth
      summary: OpenRouter OAuth callback
      description: OAuth redirect target for OpenRouter.
      responses:
        "200":
          description: Success
      security: []
  /v1/auth/openrouter/api-key:
    post:
      operationId: auth_openrouter_api_key
      tags:
        - auth
      summary: OpenRouter direct API key
      description: Skip the OAuth flow and register an OpenRouter API key directly.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              apiKey: sk-or-...
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/anthropic/init:
    post:
      operationId: auth_anthropic_init
      tags:
        - auth
      summary: Anthropic OAuth init
      description: Start the Anthropic device-flow reuse path (reuses an existing Claude CLI login).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/anthropic/exchange:
    post:
      operationId: auth_anthropic_exchange
      tags:
        - auth
      summary: Anthropic OAuth exchange
      description: Complete the Anthropic device flow by exchanging the verifier for credentials.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/{provider}:
    delete:
      operationId: auth_clear
      tags:
        - auth
      summary: Clear auth profile
      description: Remove a stored auth profile. `:provider` is `openai` | `openai-api-key` | `anthropic` | `anthropic-api-key` | `openrouter` | `xai` | `ollama`.
      parameters:
        - in: path
          name: provider
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/xai/init:
    post:
      operationId: auth_xai_init
      tags:
        - auth
      summary: xAI OAuth init
      description: Start a SuperGrok-style PKCE flow against auth.x.ai. The gateway binds the loopback callback port (56121) for the flow's lifetime via the in-process OAuth orchestrator; the web UI polls /v1/auth/status to detect completion.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/oauth/flows/{flowId}:
    get:
      operationId: auth_oauth_flow_status
      tags:
        - auth
      summary: Web OAuth flow status
      description: Query the in-process orchestrator for the status of an in-flight web-OAuth flow. Returns `pending` / `success` / `failed` / `unknown`. Web UI uses this to surface precise failure reasons (token exchange errors, OAuth provider denials) before the 5-minute poll timeout.
      parameters:
        - in: path
          name: flowId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: auth_oauth_flow_cancel
      tags:
        - auth
      summary: Cancel web OAuth flow
      description: Cancel an in-flight web-OAuth flow — closes the bound loopback port immediately so the user (or a concurrent CLI login) can retry without waiting for the 5-minute timeout. Called by the web UI when the user closes the re-authorize modal.
      parameters:
        - in: path
          name: flowId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/openai/api-key:
    post:
      operationId: auth_openai_api_key
      tags:
        - auth
      summary: OpenAI direct API key
      description: "Save an OpenAI platform API key (api.openai.com). Validates via GET /v1/models before persisting; pass `saveAnyway: true` to skip validation when offline. Requires gateway bearer auth (returns 503 when MINARA_HTTP_TOKEN is unset)."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"apiKey\": \"sk-...\", \"saveAnyway\"?: boolean }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
      security:
        - bearer: []
  /v1/auth/anthropic/api-key:
    post:
      operationId: auth_anthropic_api_key
      tags:
        - auth
      summary: Anthropic direct API key
      description: Save an Anthropic platform API key. Validates via GET /v1/models before persisting. Lives in a dedicated `anthropicApiKey` profile slot — survives gateway restart without depending on $dataDir/env being sourced.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"apiKey\": \"sk-ant-...\", \"saveAnyway\"?: boolean }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
      security:
        - bearer: []
  /v1/auth/xai/api-key:
    post:
      operationId: auth_xai_api_key
      tags:
        - auth
      summary: xAI direct API key
      description: Save an xAI (Grok) API key. Validates via GET /v1/models before persisting.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"apiKey\": \"xai-...\", \"saveAnyway\"?: boolean }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
      security:
        - bearer: []
  /v1/auth/ollama/api-key:
    post:
      operationId: auth_ollama_api_key
      tags:
        - auth
      summary: Ollama Cloud API key
      description: "Save an Ollama Cloud API key. Validates the key against the configured Ollama host's `/api/tags` before persisting; pass `saveAnyway: true` to store an unverified key."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"apiKey\": \"...\", \"saveAnyway\"?: boolean }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
      security:
        - bearer: []
  /v1/auth/preferred-provider:
    get:
      operationId: auth_preferred_provider_get
      tags:
        - auth
      summary: Get preferred provider
      description: Read the user's explicit "Use this provider" override from auth-profiles.json. select-provider.ts honors this BEFORE its normal precedence chain.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: auth_preferred_provider_put
      tags:
        - auth
      summary: Set preferred provider (hot-swap)
      description: |-
        Set or clear the preferred provider override. Live-probes the selected credential before persisting; returns 400 `reauth_required` when the profile is configured but the credential is stale.
        
        On success, ALSO performs an in-process hot-swap: rebuilds the LLM client for the new provider via `selectProvider` + `createLLMClient` and atomically updates `LLMRouter` (the single source of truth). Long-lived consumers (AgentLoop, ScenarioClassifier, ChartBuilder, …) receive a live Proxy and pick up the new client on next createMessage. AgentLoop snapshots the client at turn entry, so a turn in flight when the swap lands finishes on the OLD provider (no split conversation); the NEXT turn uses the new provider.
        
        Model: auto-picks `defaultModelForProvider(newKind)` since model ids are provider-scoped (`grok-4` only works on xAI, `gpt-5` only on OpenAI). The new model is persisted under the `defaultModel` slot keyed by the new provider, so the next boot starts on the same pair.
        
        Response: `{ ok, preferredProvider, activeModel, swapped, inflightOnOldProvider }`. `swapped: false` indicates the preference was persisted but the in-process rebuild failed (e.g. credentials disappeared between probe and rebuild); the next gateway boot still picks up the persisted preference.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"provider\": ProviderKind | null }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
      security:
        - bearer: []
  /v1/workspace/files:
    get:
      operationId: workspace_files_list
      tags:
        - workspace
      summary: List workspace files
      description: "Enumerate every editable workspace md file with size, mtime, and sha256. Files that don't exist on disk show `exists: false`. The name set is whitelisted in `apps/agent/src/workspace/seed.ts` (`EDITABLE_WORKSPACE_FILES`)."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  workspace_dir: /home/user/.minara/workspace
                  files:
                    - name: SOUL.md
                      exists: true
                      size: 1982
                      mtime: 1714492800000
                      sha256: deadbeef...
                    - name: HEARTBEAT.md
                      exists: false
                      size: 0
                      mtime: null
                      sha256: null
        "401":
          description: Missing or invalid bearer token
  /v1/workspace/files/{name}:
    get:
      operationId: workspace_file_get
      tags:
        - workspace
      summary: Read a workspace file
      description: Return the contents of a single workspace md file plus its sha256. `:name` must be a whitelisted filename (SOUL.md / AGENTS.md / IDENTITY.md / USER.md / MEMORY.md / BOOTSTRAP.md / TOOLS.md / HEARTBEAT.md). Path traversal attempts (`..`, absolute paths, non-whitelisted names) return 404.
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  name: SOUL.md
                  exists: true
                  content: |-
                    # SOUL.md - Minara
                    ...
                  sha256: deadbeef...
                  mtime: 1714492800000
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: workspace_file_put
      tags:
        - workspace
      summary: Write a workspace file (sha256 OCC)
      description: |-
        Atomically replace a workspace md file. `expected_sha256` is REQUIRED — pass the value returned by the previous GET (string), or `null` to create a brand-new file. Mismatch returns 409 with the live sha so the UI can offer overwrite / reload. Body cap is 256 KB; oversize requests return 413.
        
        Optimistic concurrency: the gateway compares `expected_sha256` to the on-disk sha BEFORE writing. A miss yields 409. The atomic write goes through `apps/agent/src/workspace/seed.ts::atomicWriteFile`, which writes to a tempfile and renames into place, with a single-slot `.bak` backup written next to the target.
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              content: |-
                # SOUL.md - Minara
                ...
              expected_sha256: deadbeef...
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  name: SOUL.md
                  sha256: cafef00d...
                  size: 2048
                  mtime: 1714493000000
        "401":
          description: Missing or invalid bearer token
  /v1/workspace/files/{name}/restore-template:
    post:
      operationId: workspace_file_restore_template
      tags:
        - workspace
      summary: Reset a workspace file to its shipped template
      description: Overwrite the workspace file with the shipped template from `src/workspace/templates/`. Useful when an operator wants to discard local edits and restart from the default. Files without a shipped template (HEARTBEAT.md is runtime-only) return 422.
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  name: SOUL.md
                  restored: true
        "401":
          description: Missing or invalid bearer token
  /v1/healthz:
    get:
      operationId: v1_healthz
      tags:
        - health
      summary: Versioned health check
      description: "Versioned mirror of `/healthz`. Returns `{\"ok\": true, \"ts\": ...}` without auth so external monitors can probe behind a `GATEWAY_AUTH_TOKEN`."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                ts: 1747333200000
      security: []
  /v1/chat/stream/resume:
    get:
      operationId: chat_stream_resume
      tags:
        - chat
      summary: Resume a streaming chat
      description: Reattach to an in-flight `/v1/chat/stream` SSE buffer that was interrupted by a network blip. Pass the original `correlation_id` as a query parameter; the gateway replays the buffered events from the last delivered cursor.
      parameters:
        - in: query
          name: correlation_id
          schema:
            type: string
          required: true
        - in: query
          name: cursor
          schema:
            type: string
          required: false
      responses:
        "200":
          description: text/event-stream — same envelope as `/v1/chat/stream`.
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/chat/interject:
    post:
      operationId: chat_interject
      tags:
        - chat
      summary: Interject into a running turn
      description: |-
        Queue a steering message for the session's in-flight turn. The gateway injects it into the running turn at the next step boundary, and the model folds it into its current work instead of starting a fresh turn. The live `/v1/chat/stream` connection echoes the injection as a `user_interjection` event (`{ message, ts_ms }`).
        
        Use this when the user types a follow-up while a long turn is still streaming and the message should steer the work in progress (narrow the scope, add a constraint, correct a wrong assumption) rather than abort it.
        
        Responses: `200` with `{ "status": "injected" }` when the message was queued for the in-flight turn; `409` with `{ "status": "no_active_turn" }` when no turn is running for the session, in which case the client sends the message as a normal `POST /v1/chat/stream` request instead; `400` when `session_id` or `message` is missing or empty.
        
        A message that is queued but arrives too late to inject comes back on the stream's final `done` event under `pending_interjections`; clients re-send those as normal messages.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              session_id: sess_abc
              message: skip the 1h chart, focus on funding rates
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                status: injected
        "401":
          description: Missing or invalid bearer token
  /v1/chat/interrupt:
    post:
      operationId: chat_interrupt
      tags:
        - chat
      summary: Interrupt a running turn
      description: |-
        Stop the session's in-flight turn. The turn ends gracefully: partial work is kept, and the SSE stream finishes with a `done` event carrying `interrupted: true`.
        
        Backs a user-facing Stop button. The turn does not error out: text and tool results produced before the interrupt persist to the session transcript, and the stream still closes with a normal `done` event (flagged `interrupted: true`).
        
        Idempotent: returns `200` with `{ "status": "interrupted" }` when a turn was stopped, and `200` with `{ "status": "no_active_turn" }` when nothing was running. `400` when `session_id` is missing.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              session_id: sess_abc
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                status: interrupted
        "401":
          description: Missing or invalid bearer token
  /v1/stats:
    get:
      operationId: stats
      tags:
        - stats
      summary: Per-session usage stats
      description: Token + cost + tool-call counters bucketed by chat session.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/telemetry/block-action:
    post:
      operationId: telemetry_block_action
      tags:
        - stats
      summary: UI Block click telemetry
      description: Fire-and-forget click record for a `ui_block` event (UBP v1). Posted by the web-ui when a user clicks a CTA inside a rendered block such as `minara.feature_recommendation@1`. The gateway logs the structured payload and returns `204 No Content`; analytics pipelines attach to the log line. Failure is silent client-side — the user-visible action runs regardless.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              blockId: b_abc123
              itemId: open_deposit_modal
              action:
                kind: modal
                name: deposit
      responses:
        "200":
          description: (empty — 204 No Content)
          content:
            application/json:
              schema:
                type: object
        "401":
          description: Missing or invalid bearer token
  /v1/sessions:
    get:
      operationId: sessions_list
      tags:
        - sessions
      summary: List chat sessions
      description: List the user's chat sessions with last-activity timestamp and turn count. Used by the web-UI session sidebar.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 50
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: sessions_create
      tags:
        - sessions
      summary: Create a chat session
      description: Provision a new session id; the agent loop scopes per-session state (frozen snapshot, scenario cache, shadow recorder) to this id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              title: BTC analysis
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  id: sess_abc
                  created_at: ...
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/status:
    get:
      operationId: portfolio_status
      tags:
        - portfolio
      summary: Portfolio connectivity status
      description: "Quick readiness probe: whether Minara is authenticated and which venues are reachable. Cheap; safe to poll."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/spot:
    get:
      operationId: portfolio_spot
      tags:
        - portfolio
      summary: Spot balances
      description: Aggregate spot balances across every connected wallet, normalized to the gateway's `PortfolioAsset` shape.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/spot/positions:
    get:
      operationId: portfolio_spot_positions
      tags:
        - portfolio
      summary: Spot positions
      description: Per-asset spot positions including avg cost basis when known.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/perps:
    get:
      operationId: portfolio_perps
      tags:
        - portfolio
      summary: Perps positions
      description: Open perp positions across every Minara perp sub-wallet. Use `?subAccountId=` to scope to one sub.
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/perps/summary:
    get:
      operationId: portfolio_perps_summary
      tags:
        - portfolio
      summary: Perps account summary
      description: Aggregated equity / margin / unrealized PnL across perp subs.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/history:
    get:
      operationId: portfolio_history
      tags:
        - portfolio
      summary: Portfolio P&L history
      description: Timeseries of total portfolio value bucketed for chart display.
      parameters:
        - in: query
          name: type
          schema:
            type: string
            example: 1d
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/portfolio/spot/activity:
    get:
      operationId: portfolio_spot_activity
      tags:
        - portfolio
      summary: Recent spot activity
      description: Cross-chain spot activity feed (swaps + transfers) for the user's primary wallet, sourced from Minara `/v1/tx/cross-chain/activities`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/account-state:
    get:
      operationId: perps_account_state
      tags:
        - perps
      summary: Hyperliquid account state
      description: "Live state for one Hyperliquid sub: equity, margin usage, withdrawable, open orders count."
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/sub-accounts:
    get:
      operationId: perps_sub_accounts
      tags:
        - perps
      summary: List perp sub-accounts
      description: "All perp sub-wallets under the user's Minara perp wallet, with the multi-exchange binding (`exchange`: hyperliquid | lighter), lifecycle `status` (active | creating | error), default flag, and a `schema_source` marker that callers MUST consult before making safety decisions on the bound exchange."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: perp_wallets_create_alias
      tags:
        - perps
      summary: Create a perp sub-account (alias)
      description: Agent-native alias of `POST /v1/perp-wallets`. Same body, same response shape.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perp-wallets:
    get:
      operationId: perp_wallets_list_alias
      tags:
        - perps
      summary: List perp sub-accounts (alias)
      description: Same handler as `/v1/perps/sub-accounts`; aligned with the upstream OpenAPI path so future SDKs / direct API consumers can use the canonical name. Web-UI continues to call the agent-native path; new clients should target this one.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: perp_wallets_create
      tags:
        - perps
      summary: Create a perp sub-account
      description: "Create a new perp sub-account bound to one exchange (`hyperliquid` | `lighter`). Body: `{ name: string (max 20), exchange?: 'hyperliquid' | 'lighter' }`. Returns the new sub-account in `creating` status — callers MUST poll `GET /v1/perp-wallets` until it reaches `active` before issuing trading-gateway calls against it. Also accepts the `/v1/perps/sub-accounts` alias."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perp-wallets/rename:
    post:
      operationId: perp_wallets_rename
      tags:
        - perps
      summary: Rename a perp sub-account
      description: "Rename an existing perp sub-account. Body: `{ subAccountId: string, name: string (max 20) }`. Read-name-only change; does not affect the on-chain address or the bound exchange."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/sub-accounts/rename:
    post:
      operationId: perp_wallets_rename_alias
      tags:
        - perps
      summary: Rename a perp sub-account (alias)
      description: Agent-native alias of `POST /v1/perp-wallets/rename`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/place-orders:
    post:
      operationId: trading_gateway_place_orders
      tags:
        - perps
      summary: Place perp orders
      description: "Batch place perp orders against the sub-account's bound exchange. Each order uses an action-based DTO (`open_long` | `open_short` | `close_long` | `close_short` | `set_stop_loss` | `set_take_profit`) with optional nested `stop_loss_orders` / `take_profit_orders` arrays for first-class TpSL. Body: `{ orders: BatchOrderItem[], subAccountId?: string }`. Returns `BatchPlaceOrderResult[]` — one per input order with per-order success / failure so a partially-rejected batch doesn't fail the whole call."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              orders:
                - action: open_long
                  symbol: BTC
                  quantity: 0.01
                  leverage: 5
                  stop_loss_orders:
                    - price: 55000
                  take_profit_orders:
                    - price: 75000
              subAccountId: sub_hl_default
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                - success: true
                  order:
                    order_id: ord_abc
                    symbol: BTC
                    side: buy
                    price: 62000
                    quantity: 0.01
                    status: open
                    created_at: 1716796800000
                    raw_data: {}
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/cancel-orders:
    post:
      operationId: trading_gateway_cancel_orders
      tags:
        - perps
      summary: Cancel perp orders
      description: "Batch cancel by `{ symbol, orderId }`. Body: `{ cancels: { symbol: string, orderId: string }[], subAccountId?: string }`. Returns the list of cancelled order ids plus the upstream `raw_data` for audit."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              cancels:
                - symbol: BTC
                  orderId: ord_abc
              subAccountId: sub_hl_default
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                cancelled_order_ids:
                  - ord_abc
                raw_data: {}
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/modify-order:
    post:
      operationId: trading_gateway_modify_order
      tags:
        - perps
      summary: Modify an active order
      description: "Modify an active order's price, quantity, or trigger price. Body: `{ symbol: string, orderId: string, price?: string, quantity?: string, triggerPrice?: string, subAccountId?: string }` — at least one of `price` / `quantity` / `triggerPrice` must be supplied or the call is a no-op. Returns the updated `PlaceOrderResult`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              symbol: BTC
              orderId: ord_abc
              price: 63500
              subAccountId: sub_hl_default
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/update-leverage:
    post:
      operationId: trading_gateway_update_leverage
      tags:
        - perps
      summary: Update leverage + margin mode
      description: "Change leverage and margin mode (cross vs isolated) on a symbol in one round-trip. Body: `{ symbol: string, leverage: number, isCross: boolean, subAccountId?: string }`. Returns `{ leverage, is_cross, raw_data }`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              symbol: BTC
              leverage: 5
              isCross: true
              subAccountId: sub_hl_default
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                leverage: 5
                is_cross: true
                raw_data: {}
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/update-isolated-margin:
    post:
      operationId: trading_gateway_update_isolated_margin
      tags:
        - perps
      summary: Add or remove isolated margin
      description: "Adjust the isolated-margin amount on an existing position. Body: `{ symbol: string, isAdd: boolean, amount: string, subAccountId?: string }`. Set `isAdd: true` to top up, `false` to claw back. Returns `{ amount, is_add, raw_data }`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              symbol: BTC
              isAdd: true
              amount: 100
              subAccountId: sub_hl_default
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                amount: 100
                is_add: true
                raw_data: {}
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/withdraw:
    post:
      operationId: trading_gateway_withdraw
      tags:
        - perps
      summary: Withdraw USDC from a perp sub-account
      description: "Withdraw USDC to an external address. Body: `{ amount: string, toAddress: string, subAccountId?: string, totpCode: string }`. `totpCode` is required by the upstream `UserTOTPGuard`; users with TOTP disabled may pass an empty string. Returns `{ amount, to, raw_data }`. Tx hash is extracted from `raw_data.tx_hash` (or nested under `raw_data.receipt.txHash`) by the agent's withdraw watcher."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              amount: 50
              toAddress: 0xabcd...
              subAccountId: sub_hl_default
              totpCode: 123456
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                amount: 50
                to: 0xabcd...
                raw_data: {}
        "401":
          description: Missing or invalid bearer token
  /v1/trading-gateway/summary:
    get:
      operationId: trading_gateway_summary
      tags:
        - perps
      summary: Per-sub equity + positions + open orders
      description: One-shot read for a single sub-wallet's equity, open positions, and open orders. `subAccountId` is optional — when omitted the user's default sub is used. Routes per the sub's bound exchange. Prefer this over the legacy `/v1/perp-wallets/summary` (kept for backwards compat); the legacy path requires `subAccountId` and historically only spoke to Hyperliquid's default dex.
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: false
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                total_equity: 175.52
                available_balance: 90.18
                margin_used: 85.34
                positions: []
                open_orders: []
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/status:
    get:
      operationId: security_totp_status
      tags:
        - auth
      summary: TOTP status
      description: Projection of `/auth/me` through the agent's `normalizeTotpStatus` adapter. Returns the three boolean TOTP toggles (master enabled, withdraw-required, new-device-required) plus a `schema_source` provenance marker. Safety-critical callers (withdraw preview, login flow) MUST fail-closed when `schema_source === "indeterminate"`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/user/wallet-addresses:
    get:
      operationId: user_wallet_addresses
      tags:
        - auth
      summary: User wallet addresses
      description: Verbatim pass-through of `/auth/me`'s `wallets` object, filtered to string-valued entries. Returns a flat record keyed by `<purpose>-<chain>` (e.g. `spot-evm`, `spot-solana`, `abstraction-evm`, `earn-evm`, `perpetual-evm`). The Portfolio Spot Value card surfaces `spot-evm` + `spot-solana` as click-to-copy chips; other keys are forwarded for future surfaces. The nested `whitelist` object on the upstream payload is dropped (only addresses come through). New purposes / chains may extend the key set; clients must tolerate unknown keys. **Always returns HTTP 200**, even on upstream failure (the UI polls this every 5 minutes and `usePolling` would otherwise preserve the last successful payload across an error, leaking a logged-out user's previous-session addresses).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/user/profile:
    get:
      operationId: user_profile
      tags:
        - auth
      summary: User profile
      description: Display identity for the account menu. Projects the signed-in Minara account's `/auth/me` into `{ avatar, name, subscription }` — the avatar URL, display name, and subscription standing (plan, a paid flag, remaining/total credits, and expiry). Each field is omitted when the upstream payload lacks it. **Always returns HTTP 200**, even on upstream failure, so the UI falls back to an initials avatar instead of surfacing an error.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/generate:
    post:
      operationId: security_totp_generate
      tags:
        - auth
      summary: Generate TOTP secret
      description: Mints a fresh TOTP secret + QR code URL + backup codes for the user to add to an authenticator app. Step 2 of the Settings → Security enable wizard.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/enable:
    post:
      operationId: security_totp_enable
      tags:
        - auth
      summary: Enable TOTP
      description: Activates TOTP after the user proves they configured the authenticator by submitting a fresh 6-digit code. Step 3 of the enable wizard.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/disable:
    post:
      operationId: security_totp_disable
      tags:
        - auth
      summary: Disable TOTP
      description: Turns TOTP off without removing the device binding. Requires both a fresh TOTP code AND a fresh email verification code so a stolen authenticator alone cannot disable 2FA.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/verify:
    post:
      operationId: security_totp_verify
      tags:
        - auth
      summary: Verify TOTP code
      description: Agent-internal verifier used by the Settings page to confirm the user can read a current TOTP code (e.g. before unbinding).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/unbind:
    post:
      operationId: security_totp_unbind
      tags:
        - auth
      summary: Unbind TOTP device
      description: Irrevocably removes the authenticator binding — re-enabling requires a fresh scan + verify cycle. Same dual-factor (TOTP + email) requirement as disable.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/email-code:
    post:
      operationId: security_email_code
      tags:
        - auth
      summary: Send email verification code
      description: Triggers `POST /auth/email/code` upstream so the user can receive a fresh verification code. Disable/unbind TOTP flows pair the authenticator code with this email code; other account changes that need a fresh email code can also call this endpoint. Body accepts an optional `emailType` string to scope the template; omit for the generic verification.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/security/totp/settings:
    put:
      operationId: security_totp_settings
      tags:
        - auth
      summary: Update TOTP settings
      description: Independently toggle new-device-login and withdraw TOTP requirements. The withdraw toggle gates the perps withdraw TOTP prompt; the new-device toggle gates first-login TOTP on a fresh browser / CLI. Requires a fresh TOTP code to authenticate the settings change itself.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/deposits:
    get:
      operationId: wallet_deposits
      tags:
        - perps
      summary: Deposit options (spot + perps)
      description: One envelope with spot + perps deposit addresses. Spot maps from the user's cross-chain account (one address, many chains); each perps target is a Hyperliquid sub-account with the USDC-on-Arbitrum constraint hard-coded. Read-only — no fund-moving gate. Used by the Portfolio DepositModal.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/supported-chains:
    get:
      operationId: wallet_supported_chains
      tags:
        - perps
      summary: Wallet-supported chains
      description: Proxy of the upstream Minara `/tokens/supported-chains` endpoint. Returns the list of chains the wallet backend can credit deposits on (EVM family + Solana). Used by the Portfolio Deposit modal to filter the chain picker so users can't pick a chain that would result in lost funds. Response is stable across releases; the gateway caches in-process for 24h and the web UI loads once at app startup. On upstream failure the endpoint returns an empty list rather than stale data — the wallet picker prefers to show nothing over showing the wrong thing.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/deposits/watch:
    post:
      operationId: wallet_deposits_watch
      tags:
        - perps
      summary: Watch a perps deposit address (SSE)
      description: Server-sent-events stream tracking real-time deposit detection for one perps USDC/Arbitrum address. Emits `watching → detected_on_chain → confirming → credited / failed / timeout`. Behind the `WALLET_DEPOSIT_WATCH_ENABLED` env flag. Transport auto-selects Alchemy SDK when `ALCHEMY_API_KEY` is set, else public Arbitrum RPC.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              address: 0xabc...
              asset: USDC
              chain: arbitrum
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/withdraw/spot:
    post:
      operationId: wallet_withdraw_spot
      tags:
        - perps
      summary: Spot withdraw — preview / execute
      description: "Preview a cross-chain spot withdraw (`confirm: false`) or execute it (`confirm: true`, with `quoteHash` + `idempotencyKey` echoed from the preview). Execute currently returns 501 `spot_execute_not_yet_supported` — a per-(symbol, chain) token contract registry is still pending. Preview works fully."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              confirm: false
              token: USDC
              chain: arbitrum
              address: 0x...
              amount: 10.5
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/withdraw/perps:
    post:
      operationId: wallet_withdraw_perps
      tags:
        - perps
      summary: Perps withdraw — preview / execute
      description: "Preview a perps USDC/Arbitrum withdraw (`confirm: false`) or execute it (`confirm: true`, with `quoteHash` + `idempotencyKey`). Execute currently returns 501 `perps_execute_not_yet_supported` because upstream `/v1/tx/perps/withdraw` has no sub-account or destination field. Preview works fully and binds the source sub-account into the quote hash."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              confirm: false
              walletId: 0x...
              address: 0x...
              amount: 10.5
              asset: USDC
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/withdrawals/{operationId}:
    get:
      operationId: wallet_withdrawals_status
      tags:
        - perps
      summary: Get withdraw operation status
      description: Durable status lookup for a withdraw operation. Returns the latest known `{operationId, txHash?, status, broadcastAt, confirmedAt?, failedReason?}` from the local `withdraw_operations` table. Lets the UI reconnect to an in-flight withdraw after the modal closes.
      parameters:
        - in: path
          name: operationId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/wallet/withdrawals/{operationId}/watch:
    post:
      operationId: wallet_withdrawals_watch
      tags:
        - perps
      summary: Watch a withdraw operation (SSE)
      description: Server-sent-events stream tracking outbound tx finality for one withdraw operationId. Emits `broadcast → confirming → credited / failed / timeout`. Terminal events persist back to the durable status table so the status endpoint reflects the real outcome.
      parameters:
        - in: path
          name: operationId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/trades:
    get:
      operationId: perps_trades
      tags:
        - perps
      summary: Recent perps fills
      description: Hyperliquid fill history for one sub-wallet (live, not the local mirror). Mirrors the upstream `userFills` payload through the gateway's `normalizeFill`.
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: true
        - in: query
          name: limit
          schema:
            type: integer
            example: 100
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/funding:
    get:
      operationId: perps_funding
      tags:
        - perps
      summary: Funding history
      description: Funding payments received / paid per perp position.
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/order-history:
    get:
      operationId: perps_order_history
      tags:
        - perps
      summary: Order history
      description: Recently filled / cancelled orders.
      parameters:
        - in: query
          name: subAccountId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/perps/lighter-pnl:
    get:
      operationId: perps_lighter_pnl
      tags:
        - perps
      summary: Lighter sub-wallet realized PnL (24h / 7d / 30d)
      description: "Rolling realized PnL for a Lighter sub-wallet, fetched from Lighter's first-class `/api/v1/pnl` endpoint (1h + 1d resolutions). The handler owns the encrypted-bearer handshake against Minara's `/v1/tx/perps/lighter-authorization` (RSA-OAEP-256 + AES-256-GCM) so the web UI calls one clean endpoint and gets back `{ d1, d7, d30, connected }`. Each window value is `null` when Lighter has no data inside it — the Portfolio Perps Value card renders `—` for nulls. Fail-open: any step (sub-account lookup, authorization fetch, decrypt, Lighter HTTP call) degrades to per-window `null` with a warn log. HL-bound sub-wallets aren't this endpoint's audience — they use the trade + funding feeds client-side; calling this with an HL `?wallet=` returns the empty envelope."
      parameters:
        - in: query
          name: wallet
          schema:
            type: string
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/orders:
    get:
      operationId: orders_open
      tags:
        - perps
      summary: Open orders
      description: Currently resting orders across every perp sub-wallet.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/strategies:
    get:
      operationId: autopilot_strategies
      tags:
        - autopilot
      summary: Autopilot strategies
      description: List user-deployed autopilot strategies + their on/off state.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/records:
    get:
      operationId: autopilot_records
      tags:
        - autopilot
      summary: Autopilot execution records
      description: Recent autopilot decision + execution log. Useful for the Autopilot tab in the web UI.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 50
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/{strategyId}/{action}:
    post:
      operationId: autopilot_action
      tags:
        - autopilot
      summary: Toggle / pause an autopilot strategy
      description: Enable, disable, pause, or resume one strategy. Fund-moving toggles are gated on the standard two-step confirm workflow on the agent side.
      parameters:
        - in: path
          name: strategyId
          schema:
            type: string
          required: true
        - in: path
          name: action
          schema:
            type: string
            enum:
              - enable
              - disable
              - pause
              - resume
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/catalog:
    get:
      operationId: autopilot_managed_catalog
      tags:
        - autopilot
      summary: Managed strategy catalog
      description: "List the managed (fully-managed) strategy types the picker can offer — Sharpe Guard and Futures Grid — each with default config and a backtest summary. `source` tells the UI how to render: `static-fallback` while the upstream catalog is pending, `upstream` once live, `schema-drift` when an upstream payload can't be parsed."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                catalog:
                  - strategyType: sharpe-guard
                    name: Sharpe Guard
                    tags: []
                    description: …
                    defaultConfig: {}
                    backtestSummary:
                      estAprPct: 300.58
                      maxDrawdownPct: -38.9
                source: static-fallback
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/catalog/{strategyType}/backtest:
    get:
      operationId: autopilot_managed_backtest
      tags:
        - autopilot
      summary: Managed strategy backtest
      description: "Backtest preview for one managed strategy type (equity curve + headline stats). Returns `{ available: false, reason }` when the current environment doesn't expose a backtest for that type."
      parameters:
        - in: path
          name: strategyType
          schema:
            type: string
            enum:
              - sharpe-guard
              - futures-grid
          required: true
        - in: query
          name: symbol
          schema:
            type: string
            example: BTC
          required: false
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                available: true
                equityCurve:
                  - t: 1779900000000
                    v: 100
                stats:
                  totalReturnPct: 300.58
                  sharpe: 2.1
                  maxDrawdownPct: -38.9
                  winRatePct: 61
                  totalTrades: 420
                  profitFactor: 1.8
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/strategies:
    get:
      operationId: autopilot_managed_strategies
      tags:
        - autopilot
      summary: Managed strategies (flat list)
      description: Every managed strategy with its bound sub-account and normalized status. The /autopilot page derives a sub-account → status map from this single fetch so each wallet row can show Running / Idle without an N+1 per-row poll.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                strategies:
                  - strategyId: str_…
                    subAccountId: sub_…
                    strategyType: sharpe-guard
                    name: Sharpe Guard
                    symbols:
                      - BTC
                    status: running
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: autopilot_managed_create
      tags:
        - autopilot
      summary: Create a managed strategy (two-step confirm)
      description: "Create a managed strategy on a sub-wallet. Fund-moving: omit `confirm` (or pass false) to get `{ confirmed: false, preview }`; pass `confirm: true` with the same `idempotencyKey` to actually create. 409 when the sub-wallet already has a strategy or a create is already in flight. The strategy starts DISABLED — enabling autonomous trading is a separate manual step routed through chat."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              subAccountId: sub_…
              strategyType: sharpe-guard
              symbols:
                - BTC
              strategyConfig: {}
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  subAccountId: sub_…
                  strategyType: sharpe-guard
                  symbols:
                    - BTC
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/aggregated-summary:
    get:
      operationId: autopilot_managed_aggregated_summary
      tags:
        - autopilot
      summary: Managed autopilot aggregated summary
      description: "Single aggregate source shared by the /autopilot KPI bar and the Dashboard managed card: total equity, total unrealized PnL, running vs total counts, and a trailing 30-day return sparkline. Fanned out once and cached 60s server-side to avoid an N+1 across the two consumers."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                totalEquity: 1234.56
                totalUnrealizedPnl: 12.34
                runningCount: 2
                totalCount: 3
                return30D:
                  pct: 12.5
                  sparkline:
                    - t: 1779900000000
                      v: 0
                lastUpdatedAt: 1779986400000
                connected: true
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/xstrategy/deployments:
    get:
      operationId: autopilot_managed_xstrategy_deployments
      tags:
        - autopilot
      summary: The caller's XStrategy deployments (picker 'My' tab)
      description: Lists the user's live XStrategy deployments (any status) so the Autopilot strategy picker's 'My' tab can show them next to their Strategy Studio strategies. Read-only; upstream is untyped so the gateway projects each row defensively.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                deployments:
                  - deploymentId: dep_…
                    strategyId: str_…
                    name: …
                    status: RUNNING
                    symbols:
                      - BTC
                    subAccountId: sub_…
                    backtestId: bt_…
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/xstrategy/backtest/{backtestId}:
    get:
      operationId: autopilot_managed_xstrategy_backtest
      tags:
        - autopilot
      summary: A deployed XStrategy's backtest report (picker 'My' detail)
      description: Full multi-asset backtest report for a deployment (add `/curves` for the equity curve). Namespaced under autopilot so the picker detail doesn't depend on the Top Strategies surface; reads the same upstream `/xstrategy/backtests/:id`.
      parameters:
        - in: path
          name: backtestId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                backtestId: bt_…
                netPnlPercent: 458.98
                annualizedReturnPercent: 99.72
                maxEquityDrawdownPercent: -26.27
                sharpeRatio: 1.981
                totalTrades: 2847
                symbols:
                  - NVDA
                  - TSLA
                  - MSFT
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/by-sub-account/{subAccountId}:
    get:
      operationId: autopilot_managed_by_sub_account
      tags:
        - autopilot
      summary: Managed autopilot status for one sub-wallet
      description: "Per-sub-account snapshot the detail panel consumes: the bound strategy (or null), an account summary (equity / available / unrealized PnL), and a stale-data envelope (`connected` + `reason`) so the UI can show a degraded banner without clearing data."
      parameters:
        - in: path
          name: subAccountId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                subAccountId: sub_…
                strategy: null
                summary: null
                lastUpdatedAt: 1779986400000
                connected: true
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/run:
    post:
      operationId: autopilot_managed_run
      tags:
        - autopilot
      summary: Run a marketplace or studio strategy as autopilot (two-step confirm)
      description: "Run a strategy from another source as autopilot. `marketplace` subscribes the published strategy to the chosen wallet and starts it; `studio` starts an already-deployed strategy on its own wallet. Fund-moving: omit `confirm` (or pass false) for `{ confirmed: false, preview }`; pass `confirm: true` with the same `idempotencyKey` to execute. The built-in catalog keeps the `/strategies` create path."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              source: marketplace
              strategyRef: pub_…
              subAccountId: sub_…
              settings: {}
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  source: marketplace
                  strategyRef: pub_…
                  subAccountId: sub_…
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/{strategyId}/disable:
    post:
      operationId: autopilot_managed_disable
      tags:
        - autopilot
      summary: Disable (stop) a managed strategy (two-step confirm)
      description: Stop a running managed strategy. Fund-affecting, so it follows the same preview → confirm two-step as create. Stopping does not close open positions or cancel resting orders — the preview warning says so.
      parameters:
        - in: path
          name: strategyId
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  strategyId: str_…
                  subAccountId: sub_…
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/xstrategy/{strategyId}/stop:
    post:
      operationId: autopilot_managed_xstrategy_stop
      tags:
        - autopilot
      summary: Stop a self-authored XStrategy deployment (two-step confirm)
      description: Stop a live XStrategy deployment the user authored (Autopilot surfaces it alongside managed + Strategy Studio strategies). Keyed by strategy id. Fund-affecting, so it follows the same preview → confirm two-step as disable; stopping does not close open positions.
      parameters:
        - in: path
          name: strategyId
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  strategyId: str_…
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/autopilot/managed/xstrategy/{strategyId}/deploy:
    post:
      operationId: autopilot_managed_xstrategy_deploy
      tags:
        - autopilot
      summary: Run a self-authored XStrategy on a wallet (two-step confirm)
      description: Deploy an XStrategy onto the selected sub-wallet by cloning the existing deployment's config (version / universe / interval / policy) and re-binding the wallet. Fund-moving, two-step preview → confirm. One active deployment per strategy — an already-running strategy returns a conflict.
      parameters:
        - in: path
          name: strategyId
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              deploymentId: dep_…
              subAccountId: sub_…
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  strategyId: str_…
                  subAccountId: sub_…
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/perps/transfer:
    post:
      operationId: perps_transfer
      tags:
        - perps
      summary: Transfer USDC between perp sub-wallets (two-step confirm)
      description: "Move idle USDC from one perp sub-wallet to another. USDC only. Fund-moving: preview → confirm two-step keyed by `idempotencyKey`. 409 `insufficient_balance` when the amount exceeds the source wallet's idle USDC; 400 on a self-transfer."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              fromSubAccountId: sub_a
              toSubAccountId: sub_b
              amount: 100
              asset: USDC
              confirm: false
              idempotencyKey: <uuid-v4>
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                confirmed: false
                preview:
                  fromSubAccountId: sub_a
                  toSubAccountId: sub_b
                  amount: 100
                  asset: USDC
                  availableBefore: 126.71
                  warning: …
        "401":
          description: Missing or invalid bearer token
  /v1/perps/sub-account/{subAccountId}/idle-usdc:
    get:
      operationId: perps_idle_usdc
      tags:
        - perps
      summary: Idle USDC available to transfer
      description: "The single 'available to transfer' number for a perp sub-wallet: idle USDC after subtracting margin in open positions, locked open-order balance, and venue reserve. Backs the Transfer dialog's Max button."
      parameters:
        - in: path
          name: subAccountId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                subAccountId: sub_…
                idleUsdc: 126.71
                lastUpdatedAt: 1779986400000
        "401":
          description: Missing or invalid bearer token
  /v1/profile:
    get:
      operationId: profile
      tags:
        - personalization
      summary: Personalization snapshot
      description: "Read the rebuilt personalization snapshot the agent injects into every system prompt: trading summary text, behavioural tags, personalization-class memories, custom instructions, and visibility settings. Mirrors the REPL `/profile` command."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/profile/onboarding:
    get:
      operationId: profile_onboarding_get
      tags:
        - personalization
      summary: Onboarding status
      description: Whether the user has completed onboarding, plus their raw saved answers. Used by the web UI to decide whether to show the onboarding flow.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  hasCompleted: true
                  responses:
                    financeKnowledge: level_2
                    risk: balanced
                    markets:
                      - crypto_majors
                      - stocks
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: profile_onboarding_submit
      tags:
        - personalization
      summary: Submit onboarding answers
      description: "Apply onboarding answers: maps finance knowledge / frequency / risk / markets to user-sourced behavioural tags, writes one personalization memory per answered dimension (first completion only), and records the self-reported capital as a memory (never a tag). Idempotent."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              financeKnowledge: level_2
              frequency: weekly
              risk: balanced
              markets:
                - crypto_majors
                - stocks
              capital: $1k-5k
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  hasCompleted: true
                  tagsSet:
                    - finance_knowledge
                    - frequency
                    - risk
                    - markets
                  memoriesWritten: 5
        "401":
          description: Missing or invalid bearer token
  /v1/financial-profile:
    get:
      operationId: financial_profile_snapshot
      tags:
        - personalization
      summary: Financial profile snapshot
      description: "Latest persisted `financial_profile` row: rebuilt platform wallet summary (composite of in-session trades + perps fills + spot activities), reference wallets, custom prompt, visibility flags, and rebuild cursors."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/profile/prompt:
    put:
      operationId: profile_prompt_set
      tags:
        - personalization
      summary: Set custom prompt
      description: Replace the user's custom system-prompt addendum (max 2000 chars). Surfaces in the personalization snapshot on the next agent turn.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              text: Always quote prices in CNY alongside USD.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  custom_prompt: Always quote prices in CNY alongside USD.
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: profile_prompt_clear
      tags:
        - personalization
      summary: Clear custom prompt
      description: Drop the addendum. UI surfaces a hard-confirm before calling.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  custom_prompt: null
        "401":
          description: Missing or invalid bearer token
  /v1/profile/tags/{name}:
    put:
      operationId: profile_tag_set
      tags:
        - personalization
      summary: Set a behavioural tag
      description: "Set or clear one of the 10 behavioural-tag dimensions (e.g. `risk`, `markets`, `FOMO Index`). Pass a string for single-value dimensions; pass an array for the multi-select `markets` dimension. `value: null` clears the tag — the agent re-infers it from chat + trade history on the next rebuild."
      parameters:
        - in: path
          name: name
          schema:
            type: string
            example: risk
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              value: balanced
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                data:
                  name: risk
                  value: balanced
                  source: user
                  updated_at: ...
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: profile_tag_delete
      tags:
        - personalization
      summary: Reset a behavioural tag
      description: "Equivalent to PUT with `value: null` — the tag is cleared and the agent re-infers it from history."
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory:
    get:
      operationId: memory_list
      tags:
        - personalization
      summary: List or search memories
      description: Read recent memories filtered by `category`, or run a hybrid (BM25 + vector) search via `query`. Soft-deleted rows (`deleted_at IS NOT NULL`) are excluded.
      parameters:
        - in: query
          name: category
          schema:
            type: string
            example: personalization
          required: false
        - in: query
          name: query
          schema:
            type: string
          required: false
        - in: query
          name: limit
          schema:
            type: integer
            example: 50
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: memory_create
      tags:
        - personalization
      summary: Create a user memory
      description: Hand-write a memory the agent should always remember. Server stamps `category=personalization` + `source=user_manual`; `fact_type` and `tickers` are stored in metadata.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              statement: I do not trade meme coins.
              fact_type: constraint.hard
              tickers: []
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                id: 142
        "401":
          description: Missing or invalid bearer token
  /v1/memory/trading-cases:
    get:
      operationId: memory_trading_cases
      tags:
        - personalization
      summary: List free-form trading-case notes
      description: Legacy free-form notes feed (memories table where `category = 'trading-cases'`). For the methodology audit feed, see `/v1/memory/methodology-cases`.
      parameters:
        - in: query
          name: ticker
          schema:
            type: string
          required: false
        - in: query
          name: limit
          schema:
            type: integer
            example: 50
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/research-cases:
    get:
      operationId: memory_research_cases
      tags:
        - personalization
      summary: List research-case notes
      description: Memories from `/research` runs filtered by `topic`. Used by the Research retrospect cards.
      parameters:
        - in: query
          name: topic
          schema:
            type: string
          required: false
        - in: query
          name: limit
          schema:
            type: integer
            example: 50
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/{id}:
    patch:
      operationId: memory_update
      tags:
        - personalization
      summary: Edit a user memory
      description: Edit `statement`, `fact_type`, or `tickers` on a `source=user_manual` memory. Other source values return 403 — agent-authored facts are immutable from the gateway.
      parameters:
        - in: path
          name: id
          schema:
            type: integer
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              statement: I do not trade pre-launch meme coins.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                updated: true
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: memory_delete
      tags:
        - personalization
      summary: Soft-delete a user memory
      description: Mark `deleted_at = now()`. Read paths (FTS, listings, snapshot) filter the row out. A 30-day retention cron physically deletes it later. Restore via `POST /v1/memory/:id/restore`.
      parameters:
        - in: path
          name: id
          schema:
            type: integer
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                deleted: true
        "401":
          description: Missing or invalid bearer token
  /v1/memory/{id}/restore:
    post:
      operationId: memory_restore
      tags:
        - personalization
      summary: Restore a soft-deleted memory
      description: Clears `deleted_at`. Wired to the web-UI undo toast (5 second window) but works at any time before the 30-day purge.
      parameters:
        - in: path
          name: id
          schema:
            type: integer
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                restored: true
        "401":
          description: Missing or invalid bearer token
  /v1/profile/refresh:
    post:
      operationId: profile_refresh
      tags:
        - personalization
      summary: Force a personalization rebuild
      description: Run all three personalization rebuilds (trading_summary / tags / memories) with `force=true`. Also kicks off `MinaraHistorySync.runOnce()` first so external perps + spot history is current before the rebuild reads it. Throttle gates do not apply on the force path.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                results:
                  trading_summary:
                    changed: true
                  tags: {}
                  memories: {}
        "401":
          description: Missing or invalid bearer token
  /v1/profile/trade-history-breakdown:
    get:
      operationId: profile_trade_history_breakdown
      tags:
        - personalization
      summary: Trade history breakdown
      description: "Three-source breakdown feeding the Financial Profile dashboard: local trade_history count, recent perps fills + per-symbol aggregate, recent spot activities + per-pair aggregate, last sync timestamp."
      parameters:
        - in: query
          name: perps_limit
          schema:
            type: integer
            example: 30
          required: false
        - in: query
          name: spot_limit
          schema:
            type: integer
            example: 20
          required: false
        - in: query
          name: window_days
          schema:
            type: integer
            example: 90
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/profile/reference-wallets:
    get:
      operationId: profile_reference_wallets_get
      tags:
        - personalization
      summary: List reference wallets
      description: User's watchlist of external wallet addresses.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                wallets:
                  - 0xabc...
                  - 9XYZ...
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: profile_reference_wallets_set
      tags:
        - personalization
      summary: Replace reference wallet list
      description: Replace the watchlist atomically. Server validates EVM (`0x` + 40 hex) and Solana / generic base58 (32-44 chars) addresses; rejects anything else.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              wallets:
                - 0xabc...
                - 9XYZ...
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                wallets:
                  - 0xabc...
                  - 9XYZ...
        "401":
          description: Missing or invalid bearer token
  /v1/memory/trading-cases/stats:
    get:
      operationId: trading_cases_stats
      tags:
        - learning
      summary: Methodology cases snapshot
      description: "4-number snapshot for the Trading-cases dashboard: total cases, last 30 days, winning ratio, avg alpha, top methodology id."
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/trading-cases/methodology-trend:
    get:
      operationId: methodology_trend
      tags:
        - learning
      summary: Top-N methodology alpha trend
      description: Per-week timeseries of `outcome_alpha_return` for the top-N methodologies over the requested window. Powers the SVG line chart on the Trading-cases page.
      parameters:
        - in: query
          name: weeks
          schema:
            type: integer
            example: 12
          required: false
        - in: query
          name: top
          schema:
            type: integer
            example: 5
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/methodologies:
    get:
      operationId: methodologies_list
      tags:
        - learning
      summary: Top learned methodologies
      description: Top-N methodologies ranked by Wilson-lower confidence, with case count, asset classes, last-seen, and active / quarantined / insufficient_data status. Drives the 'Top patterns' cards.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 8
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/methodology-cases:
    get:
      operationId: methodology_cases_list
      tags:
        - learning
      summary: Raw methodology cases ledger
      description: Read the `methodology_cases` table directly. Powers the audit ledger drawer; row ids share the same id space as `/v1/memory/trading-cases/:id`.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 200
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/memory/trading-cases/{id}:
    get:
      operationId: trading_case_detail
      tags:
        - learning
      summary: Single methodology case
      description: Full row JSON for one `methodology_cases.id`. Used by the right-side Drawer in the Trading-cases page.
      parameters:
        - in: path
          name: id
          schema:
            type: integer
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learned-preferences:
    get:
      operationId: learned_preferences_list
      tags:
        - preferences
      summary: List learned preferences
      description: Inferred behavioural preferences awaiting your call (proposed / active / deprecated). Drives the Personalization page's 'Learned preferences' card.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learned-preferences/{id}/{action}:
    post:
      operationId: learned_preferences_action
      tags:
        - preferences
      summary: Approve / reject / deprecate a preference
      description: Transition one learned preference. Approve makes it active and brings it into the next agent system prompt; reject or deprecate hides it.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: action
          schema:
            type: string
            enum:
              - approve
              - reject
              - deprecate
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/learned-preferences/{id}:
    get:
      operationId: learned_preference_detail
      tags:
        - preferences
      summary: Get one learned preference
      description: Full record for one learned preference id (description, signal strength, history).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/watchlist:
    get:
      operationId: watchlist_get
      tags:
        - preferences
      summary: Get web-UI watchlist
      description: User-curated symbol watchlist persisted on the profile.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: watchlist_set
      tags:
        - preferences
      summary: Replace web-UI watchlist
      description: Replace the watchlist (max 100 symbols).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              symbols:
                - ticker: BTC
                  kind: crypto
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/runtime-preferences:
    get:
      operationId: runtime_preferences_get
      tags:
        - preferences
      summary: Get runtime preferences
      description: Returns the sub-feature toggle schema, current overrides, and the resolved values. Backs the Settings → Preferences page.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: runtime_preferences_put
      tags:
        - preferences
      summary: Apply runtime preference overrides
      description: Batch apply user overrides with all-or-nothing validation. Supports If-Match concurrency via header or body. Critical-tier toggles require a signed ack token.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              if_match: <rev>
              overrides:
                thinking.enabled: true
              ack_token: <optional>
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/runtime-preferences/critical-unlock-status:
    get:
      operationId: runtime_preferences_critical_unlock_status
      tags:
        - preferences
      summary: Critical-unlock state
      description: Read whether critical-tier toggles are currently unlocked (via the CLI `minara settings unlock-critical` or POST /v1/runtime-preferences/critical-unlock).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/runtime-preferences/critical-unlock:
    post:
      operationId: runtime_preferences_critical_unlock
      tags:
        - preferences
      summary: Issue a critical-unlock
      description: Issue a short-lived unlock that lets a subsequent PUT /v1/runtime-preferences flip a critical-tier toggle into its danger direction. Optional `ttlMs` body sets the lifetime (default 5 minutes). The typed-confirm ack on the PUT is still required.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/runtime-preferences/sign-ack:
    post:
      operationId: runtime_preferences_sign_ack
      tags:
        - preferences
      summary: Server-signed ack token for critical confirms
      description: Mint a short-lived signed ack token consumed by the next PUT /v1/runtime-preferences call that touches a critical-tier toggle.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/runtime-preferences/events:
    get:
      operationId: runtime_preferences_events
      tags:
        - preferences
      summary: SSE preferences events
      description: Server-sent stream emitting `saved { revision, lifecycleEffects }` whenever runtime preferences change. Used for cross-tab sync in the Settings page.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/notifications:
    get:
      operationId: notifications_list
      tags:
        - notifications
      summary: List notifications
      description: Recent in-app notifications (newest first) plus an `unread_count`. A notification is recorded each time a custom agent fires on its own (scheduled / event / once) and its in-app config opts into the outcome. Optional `?limit=` (default 50).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: notifications_clear
      tags:
        - notifications
      summary: Clear all notifications
      description: Delete every notification. Returns `{ ok, cleared }`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/notifications/stream:
    get:
      operationId: notifications_stream
      tags:
        - notifications
      summary: SSE notification stream
      description: Server-sent stream emitting a `notification` event whenever a new notification is recorded. Powers the live toast + unread badge. Accepts `?token=` for EventSource (which can't set an Authorization header).
      responses:
        "200":
          description: Server-Sent Events stream
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/notifications/read-all:
    post:
      operationId: notifications_read_all
      tags:
        - notifications
      summary: Mark all read
      description: Mark every unread notification as read. Returns `{ ok, updated }`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/notifications/{id}/read:
    post:
      operationId: notifications_read
      tags:
        - notifications
      summary: Mark one read
      description: "Mark a single notification read (idempotent). Returns `{ ok: true, updated }` — `updated` is false if it was already read. 404 only if the id is missing."
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/notifications/{id}:
    delete:
      operationId: notifications_dismiss
      tags:
        - notifications
      summary: Dismiss a notification
      description: Delete a single notification by id.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/credentials/families:
    get:
      operationId: credentials_families
      tags:
        - preferences
      summary: List credential families
      description: Returns the provider-family registry (web search, market data, embeddings, cloud compute, etc.) along with each field's set / display / source status. Backs the Settings → API Keys page.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/credentials/families/{id}:
    put:
      operationId: credentials_family_put
      tags:
        - preferences
      summary: Upsert credential family overrides
      description: Writes overrides for a single provider family into `~/.minara/runtime-credentials.json` (atomic + file-locked). .env files are never touched.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              fields:
                COINGECKO_API_KEY: cg-...
                GLASSNODE_API_KEY: ""
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/credentials/families/{id}/field/{envVar}:
    delete:
      operationId: credentials_family_field_delete
      tags:
        - preferences
      summary: Clear a credential override
      description: Remove a single field's override (reverts to env-derived fallback).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: envVar
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/migrate/manifest:
    get:
      operationId: migrate_manifest
      tags:
        - migrate
      summary: Export manifest
      description: List the data sets that can be exported (memories, profile, prefs, workflows). Pre-flight for `/v1/migrate/export`.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/migrate/export:
    post:
      operationId: migrate_export
      tags:
        - migrate
      summary: Export user data
      description: Bundle the requested manifest entries into a portable JSON blob for backup / migration to another deployment.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              include:
                - memories
                - profile
                - preferences
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/migrate/import:
    post:
      operationId: migrate_import
      tags:
        - migrate
      summary: Import user data
      description: Restore from a previously exported bundle. Idempotent on stable ids (memories, preferences); replaces watchlist + profile.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"bundle\": { /* output of /v1/migrate/export */ } }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/auth/minara/oauth/start:
    post:
      operationId: auth_minara_oauth_start
      tags:
        - auth
      summary: Begin Minara OAuth PKCE flow
      description: Kick off the Minara platform OAuth 2.0 + PKCE login. Binds a loopback callback server, returns the authorize URL the user opens in a browser. On consent, the agent persists a minara-oauth profile.
      responses:
        "200":
          description: Success
      security: []
  /v1/auth/minara/oauth/poll:
    post:
      operationId: auth_minara_oauth_poll
      tags:
        - auth
      summary: Poll Minara OAuth flow status
      description: Poll the in-flight OAuth flow by flow_id. Returns pending until the callback fires, then completed (profile saved) or error / expired.
      responses:
        "200":
          description: Success
      security: []
  /v1/auth/oauth/minara/init:
    post:
      operationId: auth_oauth_minara_init
      tags:
        - auth
      summary: Begin Minara reauthorization (shared provider flow)
      description: Alias of the Minara OAuth start under the shared provider-reauth namespace. Runs the same PKCE flow and returns the authorize URL and flow id in the camelCase shape the provider-reauth UI expects. Completion is polled via /v1/auth/oauth/flows/:state.
      responses:
        "200":
          description: Success
      security: []
  /v1/auth/minara:
    delete:
      operationId: auth_minara_clear
      tags:
        - auth
      summary: Disconnect Minara
      description: Forget the stored Minara token; subsequent calls become unauthenticated.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/gateway/providers:
    get:
      operationId: gateway_providers
      tags:
        - gateway
      summary: Messaging providers
      description: Inbound messaging providers registered with the gateway (Slack, Lark, etc).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/gateway/providers/{id}/fields/{envVar}/reveal:
    post:
      operationId: gateway_provider_field_reveal
      tags:
        - gateway
      summary: Reveal one credential field
      description: "Return the plaintext value of a single masked credential field (e.g. `TELEGRAM_BOT_TOKEN`) for the Settings → Messaging \"Show\" button. The implicit `GET /v1/gateway/providers` payload always masks; this endpoint narrowly widens to plaintext on explicit user request, one field at a time. Auth: same-origin / loopback / missing-Origin requests pass through (the single-user local-dev case); cross-origin callers must present the gateway bearer token or receive 503 `gateway_auth_required`. Non-masked, unknown, or unset fields return 404."
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: envVar
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                value: 85123abcdef...
        "401":
          description: Missing or invalid bearer token
  /v1/gateway/providers/{id}/oauth/init:
    post:
      operationId: gateway_provider_oauth_init
      tags:
        - gateway
      summary: Start Gmail OAuth connect
      description: Begin a Google OAuth consent flow for an OAuth-style messaging provider (`email-gmail`). The gateway binds a loopback callback, returns the Google `authorizeUrl` for the browser to open, then on the callback exchanges the code and persists the connection (`GMAIL_REFRESH_TOKEN` + `GMAIL_SENDER_EMAIL`). The web UI polls `GET /v1/auth/oauth/flows/:state` until done. Returns 400 `google_client_not_configured` when the operator's Google client id/secret are unset, 404 `provider_not_oauth` for non-OAuth providers, and 409 `port_in_use` when the callback port is busy.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                authorizeUrl: https://accounts.google.com/o/oauth2/v2/auth?...
                state: ...
                flowId: ...
                redirectUri: http://127.0.0.1:1457/callback
                expiresAt: 1730000000000
        "401":
          description: Missing or invalid bearer token
  /v1/llm/default-model:
    get:
      operationId: llm_default_model_get
      tags:
        - llm
      summary: Get default agent model
      description: The model id the agent loop currently uses for new turns.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    put:
      operationId: llm_default_model_set
      tags:
        - llm
      summary: Set default agent model
      description: Switch the agent loop to a different model id. Validated against the live `available-models` list.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              model: claude-opus-4-7
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/llm/available-models:
    get:
      operationId: llm_available_models
      tags:
        - llm
      summary: Available models
      description: Models the gateway can dispatch to, grouped by provider. Reflects the user's active auth profiles.
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/llm/available-models/refresh:
    post:
      operationId: llm_available_models_refresh
      tags:
        - llm
      summary: Refresh available models
      description: Force a live re-fetch of the active provider's model catalog, bypassing the in-memory and 24h disk caches, then return the refreshed list. Lets the UI surface a model released after the agent booted without a restart.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                provider: anthropic-oauth
                current: claude-opus-4-7
                models:
                  - id: claude-opus-4-8
                    label: Claude Opus 4.8
                source: live
        "401":
          description: Missing or invalid bearer token
  /v1/shortcut-questions:
    get:
      operationId: shortcut_questions
      tags:
        - llm
      summary: Shortcut questions (Chat landing)
      description: "Hand-authored fixed lists plus LLM-generated dynamic questions per category, used by the Chat landing page chip-row. Categories: Trending, Crypto (research / trading / DeFi / airdrop / meme), Stock (research / trading), Macro, Sentiment, Learn. Dynamic questions are regenerated on a 6-hour cadence by default (SHORTCUT_QUESTIONS_TTL_MS)."
      parameters:
        - in: query
          name: locale
          schema:
            type: string
            example: en
          required: false
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                locale: en
                leaves:
                  - topLevel: trending
                    sub: null
                    fixed:
                      - ...
                    dynamic:
                      - What's pumping $BTC this week?
                    generatedAt: 1746000000000
        "401":
          description: Missing or invalid bearer token
  /v1/quote/batch:
    get:
      operationId: quote_batch
      tags:
        - market
      summary: Batch ticker quotes
      description: Spot/perp quotes for many tickers in one round trip — used by the Ticker Board to populate ~20 cells per tick.
      parameters:
        - in: query
          name: tickers
          schema:
            type: string
            example: BTC,ETH,SOL
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/quote/{ticker}:
    get:
      operationId: quote_single
      tags:
        - market
      summary: Single ticker quote
      description: "Spot price + 24h change + traditional metrics (P/E, EPS, dividend yield, 52-week high/low, beta, market cap) for one non-crypto ticker. Source chain: Minara `stocks/v2/get-stock-info` for price + fundamentals; Yahoo `quoteSummary` in parallel for the rich metrics. Falls back to Yahoo as primary for symbols Minara 404s (commodities, forex, indices)."
      parameters:
        - in: path
          name: ticker
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/quote/crypto/{symbol}:
    get:
      operationId: quote_crypto
      tags:
        - market
      summary: Single crypto token stats
      description: "Market cap, FDV, 24h volume (USD), 24h % change, name, logo, and chain for one crypto symbol. Sourced from Minara `searchTokens` by picking the first row whose symbol matches exactly (case-insensitive). Used by the markets-detail Crypto header. Returns `supported: false` when Minara has no exact-match row for the symbol."
      parameters:
        - in: path
          name: symbol
          schema:
            type: string
            example: BTC
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                supported: true
                symbol: BTC
                name: Bitcoin
                chain: btc
                logo_url: https://...
                price: 78000.12
                price_change_24h_pct: 1.23
                volume_24h: 32500000000
                market_cap: 1540000000000
                fdv: 1640000000000
        "401":
          description: Missing or invalid bearer token
  /v1/market/candles:
    get:
      operationId: market_candles
      tags:
        - market
      summary: Candle (K-line) series
      description: OHLC bars for one ticker + interval. Routes to the appropriate provider (Hyperliquid / Yahoo Finance) based on asset class.
      parameters:
        - in: query
          name: ticker
          schema:
            type: string
          required: true
        - in: query
          name: interval
          schema:
            type: string
            example: 1h
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/market/sparkline:
    get:
      operationId: market_sparkline
      tags:
        - market
      summary: Sparkline series
      description: Compact close-only series for inline sparkline rendering in the web UI.
      parameters:
        - in: query
          name: ticker
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/market/search:
    get:
      operationId: market_search
      tags:
        - market
      summary: Multi-asset symbol search
      description: "Fuzzy-match ticker symbols + names across crypto, stocks, ETFs, commodities, forex, and indices. Powers the typeahead on the web UI's `/markets` page. Source chain: Minara token + stock search first; Yahoo Finance fills commodities, forex, indices, and long-tail equities. Pass `?source=minara` or `?source=yahoo` to fan one tier out independently for progressive UX rendering — the default `source=all` waits for both and returns the merged set."
      parameters:
        - in: query
          name: q
          schema:
            type: string
            example: bit
          required: true
          description: Search query (ticker or partial name). Max 64 chars.
        - in: query
          name: limit
          schema:
            type: integer
            example: 10
          description: Max results returned (default 10, max 50).
        - in: query
          name: source
          schema:
            type: string
            enum:
              - all
              - minara
              - yahoo
            example: minara
          description: Restrict to a single upstream tier. `all` (default) merges Minara + Yahoo on the server. `minara` returns only Minara token+stock hits (fast). `yahoo` returns only Yahoo Finance hits (covers commodities, forex, indices, long-tail equities). The web UI fires `minara` + `yahoo` in parallel for progressive rendering.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                results:
                  - symbol: BTC
                    name: Bitcoin
                    category: crypto
                    exchange: Binance
                    logoUrl: https://...
                    source: minara
        "401":
          description: Missing or invalid bearer token
  /v1/markets/movers:
    get:
      operationId: markets_movers
      tags:
        - market
      summary: Top gainers / losers / most active
      description: Biggest movers for the markets overview board. `class=stocks` (default) returns equity gainers, losers, and most-active from Yahoo Finance's public predefined screeners, with Minara trending stocks as a fallback. `class=crypto` returns a Minara-trending fallback only — the board's primary crypto movers come from the Binance public WebSocket stream client-side. Cached for 60s.
      parameters:
        - in: query
          name: class
          schema:
            type: string
            enum:
              - stocks
              - crypto
            example: stocks
          description: "Asset class: `stocks` (default) or `crypto`."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                gainers:
                  - symbol: NVDA
                    name: NVIDIA
                    price: 123.4
                    changePct: 5.2
                    volume: 1000000
                losers: []
                active: []
        "401":
          description: Missing or invalid bearer token
  /v1/markets/snapshot:
    get:
      operationId: markets_snapshot
      tags:
        - market
      summary: Macro market snapshot
      description: "The markets overview board's macro bundle in one call: major index quotes (with intraday sparklines), commodity futures, major forex pairs, treasury yields, and the crypto Fear & Greed reading. All values come from keyless public providers (Yahoo Finance quotes, alternative.me sentiment, Minara as the sentiment fallback). Each symbol degrades on its own, so a single failed fetch never empties the bundle. Cached for 30s."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                indices:
                  - symbol: ^GSPC
                    label: S&P 500
                    price: 5400
                    change: 12
                    changePct: 0.2
                    spark:
                      - 5388
                      - 5400
                futures: []
                forex: []
                treasury:
                  - label: 10Y
                    yield: 4.45
                    change: -0.02
                fearGreed:
                  value: 54
                  label: Neutral
        "401":
          description: Missing or invalid bearer token
  /v1/markets/sectors:
    get:
      operationId: markets_sectors
      tags:
        - market
      summary: Sector performance heatmap
      description: Daily performance of the 11 GICS sectors for the board's heatmap, proxied by the SPDR sector ETFs (keyless Yahoo Finance quotes). Each sector always appears, even when its quote fails, so the heatmap keeps all tiles. Cached for 5 minutes.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                sectors:
                  - name: Technology
                    changePct: 1.2
                  - name: Energy
                    changePct: -0.8
        "401":
          description: Missing or invalid bearer token
  /v1/markets/news:
    get:
      operationId: markets_news
      tags:
        - market
      summary: Market news headlines
      description: "A market headline feed for the board, parsed from Yahoo Finance's keyless RSS. `class=general` (default) covers the broad market; `class=crypto` covers major coins. Best-effort: an unreachable feed returns an empty list. Cached for 5 minutes."
      parameters:
        - in: query
          name: class
          schema:
            type: string
            enum:
              - general
              - crypto
            example: general
          description: "Feed: `general` (default) or `crypto`."
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                items:
                  - title: Stocks close higher
                    site: Yahoo Finance
                    url: https://...
                    publishedAt: 2026-06-20T20:00:00.000Z
        "401":
          description: Missing or invalid bearer token
  /v1/markets/calendar:
    get:
      operationId: markets_calendar
      tags:
        - market
      summary: Upcoming earnings
      description: The next session's earnings reports from Nasdaq's keyless calendar, forward-filled past weekends so it's never blank. Cached for 15 minutes.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                earnings:
                  - symbol: NVDA
                    name: NVIDIA
                    time: time-after-hours
                    epsEstimate: 1.23
        "401":
          description: Missing or invalid bearer token
  /v1/skills:
    get:
      operationId: skills_list
      tags:
        - skills
      summary: List registered skills
      description: Every registered domain skill the agent can activate. Each entry carries activation policy, risk tier, and a `source` of `builtin` or `external` (vendored SKILL.md packages added via the CLI).
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                skills:
                  - id: minara.core
                    name: ...
                    description: ...
                    activation: always
                    tags: []
                    tool_names:
                      - ...
                    requires_env: []
                    env_satisfied: true
                    risk_tier: 2
                    source: builtin
        "401":
          description: Missing or invalid bearer token
  /v1/skills/reload:
    post:
      operationId: skills_reload
      tags:
        - skills
      summary: Reload external skill packages
      description: Re-mirror vendored external skill packages into the sandbox and re-register them on the in-process registry. The `minara skills add / remove / upgrade` CLI calls this on a running gateway so the change takes effect without restart.
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                added:
                  - external.foo
                reregistered: []
                removed: []
        "401":
          description: Missing or invalid bearer token
  /v1/theme:
    get:
      operationId: theme_get
      tags:
        - theme
      summary: Get user theme
      description: Web-UI theme preference (light/dark/system + accent).
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: theme_set
      tags:
        - theme
      summary: Set user theme
      description: Update the persisted theme preference.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              mode: dark
              accent: primary
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/admin/script-risk/decisions:
    get:
      operationId: admin_script_risk_decisions
      tags:
        - admin
      summary: List script-risk decisions
      description: Audit log of the script-risk gate's decisions in front of `execute_code` / `terminal` / `write_file` / `patch`. Shows accepted and rejected commands with the operator-visible reason.
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            example: 100
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/admin/script-risk/decisions/{id}:
    get:
      operationId: admin_script_risk_decision_detail
      tags:
        - admin
      summary: Get one script-risk decision
      description: Full audit record for one script-risk gate decision.
      parameters:
        - in: path
          name: id
          schema:
            type: integer
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/consent/grants:
    get:
      operationId: consent_grants_list
      tags:
        - admin
      summary: List tool-consent grants
      description: List authoring-time consent grants for Tier-3 / Tier-4 tools running from cron / workflow / agent sources. Each grant binds (scope, tool_name, args_constraints). Filter by `scope_kind` + `scope_id`, `user_id`, or include revoked rows via `include_revoked=true`.
      parameters:
        - in: query
          name: scope_kind
          schema:
            type: string
            enum:
              - workflow_definition
              - cron
              - agent
          required: false
        - in: query
          name: scope_id
          schema:
            type: string
          required: false
        - in: query
          name: user_id
          schema:
            type: string
          required: false
        - in: query
          name: include_revoked
          schema:
            type: boolean
          required: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: consent_grants_create
      tags:
        - admin
      summary: Create a tool-consent grant
      description: Record operator authorization for a Tier-3 / Tier-4 tool call scheduled to run from an autonomous source. At least one constraint inside `args_constraints` MUST be set (`args_sha256`, `to_address`, `chain`, or `max_per_call_usdc`); an unconstrained grant is rejected.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              scope_kind: cron
              scope_id: cron_7f2
              tool_name: transfer_token
              args_constraints:
                to_address: 0xABCD...
                chain: base
                max_per_call_usdc: 50
              note: weekly USDC sweep
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/consent/grants/{id}:
    get:
      operationId: consent_grants_show
      tags:
        - admin
      summary: Get one tool-consent grant
      description: Return the full record for one consent grant.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: consent_grants_revoke
      tags:
        - admin
      summary: Revoke a tool-consent grant
      description: Mark a grant revoked. Subsequent autonomous calls to the tool under the same scope will block again until a fresh grant is issued.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/consent/grants/requirements:
    post:
      operationId: consent_grants_requirements
      tags:
        - admin
      summary: Compute grant requirements for a draft definition
      description: Dry-run scan of a draft cron / workflow / agent definition for Tier-3 / Tier-4 tool calls that need an authoring-time consent grant. The wizard calls this before save, surfaces one prompt per returned requirement, then POSTs the approved grants to `/v1/consent/grants`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"scope_kind\": \"cron\", \"definition\": { /* WorkflowDefinition draft */ } }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/batch/actions:
    post:
      operationId: workflow_batch_actions
      tags:
        - workflows
      summary: Batch workflow actions
      description: Apply the same action (activate / deactivate / pause / delete) to many workflows in one request.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              ids:
                - wf_1
                - wf_2
              action: activate
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/import/n8n:
    post:
      operationId: workflow_import_n8n
      tags:
        - workflows
      summary: Import a workflow from n8n
      description: "Convert an n8n workflow JSON into a local Minara workflow definition. Idempotent: the same import id collapses on conflict."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example: "{ \"n8n\": { /* n8n export */ } }"
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}:
    get:
      operationId: workflow_detail
      tags:
        - workflows
      summary: Get workflow detail
      description: Full workflow definition by `<backend>:<rawId>` (or legacy unprefixed id, defaulting to local).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    patch:
      operationId: workflow_update
      tags:
        - workflows
      summary: Update a workflow
      description: Edit a workflow's draft / metadata / steps.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: workflow_delete
      tags:
        - workflows
      summary: Delete a workflow
      description: Soft-delete a workflow definition; running instances continue.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/run:
    post:
      operationId: workflow_run
      tags:
        - workflows
      summary: Run a workflow
      description: Kick off a one-shot execution of the workflow.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              execution_mode: production
              dry_run: false
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/pause:
    post:
      operationId: workflow_pause
      tags:
        - workflows
      summary: Pause a workflow
      description: Pause the running instance(s) of a workflow.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/activate:
    post:
      operationId: workflow_activate
      tags:
        - workflows
      summary: Activate a workflow
      description: Set the workflow's `active` flag to true (eligible for triggers).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/deactivate:
    post:
      operationId: workflow_deactivate
      tags:
        - workflows
      summary: Deactivate a workflow
      description: Set the workflow's `active` flag to false (no new trigger fires).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/validate:
    post:
      operationId: workflow_validate
      tags:
        - workflows
      summary: Validate a workflow draft
      description: Static + simulated validation of a workflow draft before publish.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/publish:
    post:
      operationId: workflow_publish
      tags:
        - workflows
      summary: Publish a workflow draft
      description: Promote the draft revision to the active definition.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/test:
    post:
      operationId: workflow_test
      tags:
        - workflows
      summary: Test a workflow
      description: Dry-run a workflow with operator-supplied input for previewing.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/from-template:
    post:
      operationId: workflow_from_template
      tags:
        - workflows
      summary: Create a workflow from a template
      description: Render one of the curated landing-page templates into a real WorkflowDefinition via operator-supplied params. The gateway picks the matching template, runs the param-substitution pass, and saves the resulting draft like a hand-authored create.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              template_id: crypto-price-alert
              params:
                token: BTC
                threshold_pct: -5
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/generate:
    post:
      operationId: workflow_generate
      tags:
        - workflows
      summary: Generate a workflow from a prompt
      description: "LLM authoring: turns a plain-language description into a validated WorkflowDefinition. Runs the workflow-authoring subagent with up to 3 attempts; each retry receives a structured error locator describing why the previous attempt failed validation. Streams events in attempt-bounded groups; the canvas updates on the terminal `workflow.commit` only. On validation failure after MAX_ATTEMPTS, emits `cluster.rollback{reason:'validation_failed'}` and skips the row creation."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              prompt: Alert me when BTC drops 5% in 24h via Telegram.
      responses:
        "200":
          description: |-
            text/event-stream. Events fire in attempt-bounded groups:
              cluster.attempt_start { attempt, total_budget }
              cluster.node_code_delta { step_name, delta }
              cluster.node_add { step_name, kind, definition }
              workflow.patch  (preview only — does NOT mutate the canvas)
              cluster.attempt_discard { attempt, errors } OR cluster.validate { ok }
              workflow.commit { definition, attempt }  (only on the validated attempt)
              cluster.rollback { reason: 'messaging_gate' | 'persist_failed' | 'validation_failed' | 'stream_error' }
              assistant.message { text }  (delta-streamed)
              workflow.created { workflow, validation, ignored_fields, source }
              error { message, code }
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/refine:
    post:
      operationId: workflow_refine
      tags:
        - workflows
      summary: Refine a workflow via chat
      description: Open an SSE stream that streams cluster events as the LLM edits the workflow definition in response to the user's plain-language message. The workflow-authoring subagent retries up to 3 times when validation fails; the chat panel renders each attempt as a collapsible section and the canvas only updates on the validated attempt's terminal `workflow.commit`. Persists user + assistant chat turns; the assistant row stays `status='in_progress'` while live and finalizes on completion. Returns 409 if another refine session is already active for this workflow — clients should attach to the existing session via `GET /v1/workflows/:id/refine/stream` instead of opening a parallel run. On validation failure after MAX_ATTEMPTS the definition is NOT persisted; the existing definition stays as-is.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              message: Send me an alert when BTC drops 5%.
      responses:
        "200":
          description: |-
            text/event-stream. Events fire in attempt-bounded groups:
              cluster.attempt_start { attempt, total_budget }
              cluster.node_code_delta { step_name, delta }
              cluster.node_add { step_name, kind, definition }
              workflow.patch  (preview only — does NOT mutate the canvas)
              cluster.attempt_discard { attempt, errors } OR cluster.validate { ok }
              workflow.commit { definition, attempt }  (only on the validated attempt)
              cluster.rollback { reason: 'messaging_gate' | 'persist_failed' | 'validation_failed' | 'stream_error' }
              assistant.message { text }  (delta-streamed)
              error { message, code }
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/refine/stream:
    get:
      operationId: workflow_refine_stream
      tags:
        - workflows
      summary: Attach to an in-flight refine session
      description: SSE endpoint that replays the active refine session's event buffer in order, then tails live events until the session finishes. Used by the web UI to resume the chat stream after a mid-stream refresh or navigation. Replays the SAME attempt-bounded event vocabulary the POST /refine endpoint streams. Discarded attempts from before the attach reach the client through the persisted chat history (chat_messages.metadata.events), not via this live attach. Returns 204 No Content when no session is active for this workflow.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: text/event-stream — same envelope as POST /refine, OR HTTP 204 when no active session.
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/executions:
    get:
      operationId: workflow_executions_list
      tags:
        - workflows
      summary: List workflow executions
      description: Recent execution instances for one workflow.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/export/n8n:
    get:
      operationId: workflow_export_n8n
      tags:
        - workflows
      summary: Export workflow to n8n
      description: Convert a Minara workflow back to the n8n JSON format.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/executions/{instanceId}:
    get:
      operationId: workflow_execution_get
      tags:
        - workflows
      summary: Get one execution
      description: Full state for one workflow execution instance.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: instanceId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
    delete:
      operationId: workflow_execution_delete
      tags:
        - workflows
      summary: Delete an execution record
      description: Remove the persisted record of one execution instance.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: instanceId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/instances/{instanceId}/resume:
    post:
      operationId: workflow_instance_resume
      tags:
        - workflows
      summary: Resume a paused instance
      description: Resume an instance that's currently waiting on a confirm step.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: instanceId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/workflows/{id}/instances/{instanceId}/cancel:
    post:
      operationId: workflow_instance_cancel
      tags:
        - workflows
      summary: Cancel a running instance
      description: Cancel a running execution. The cancellation reason is logged.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: instanceId
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              reason: operator cancelled
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/sandbox/files/{name}:
    get:
      operationId: sandbox_files_fetch
      tags:
        - files
      summary: Fetch a sandbox file
      description: Read a file the agent wrote into its per-session sandbox. Cannot escape the sandbox root.
      parameters:
        - in: path
          name: name
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /v1/interactions/{id}/answer:
    post:
      operationId: interactions_answer
      tags:
        - state
      summary: Answer an interaction prompt
      description: Submit the user's answer to a paused agent prompt (the agent loop stops on `interaction_required` events; the web UI / CLI calls this to deliver the response).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              value: yes
      responses:
        "200":
          description: Success
        "401":
          description: Missing or invalid bearer token
  /studio/p/{pageId}:
    get:
      operationId: studio_page_host
      tags:
        - gateway
      summary: Hosted Data Studio dashboard
      description: Serve a generated Data Studio dashboard as a standalone HTML page on its own link. Public (no auth) so the page can be shared; rendered inside a sandboxed cross-origin iframe with a strict CSP. Returns `text/html`, not JSON.
      parameters:
        - in: path
          name: pageId
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
      security: []
  /v1/chat/sessions/{id}/goal:
    get:
      operationId: session_goal_get
      tags:
        - chat
      summary: Get the session's standing goal
      description: "Return the session's standing goal, or `null` when none is set. The agent pursues a standing goal turn after turn: after each turn a judge decides whether the goal is met, and if not the client sends the next turn automatically (the chat stream carries the decision)."
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                goal:
                  goalText: ship the report
                  status: active
                  turnsUsed: 3
                  maxTurns: 20
                  subgoals: []
                  lastVerdict: continue
                  lastReason: draft not finished
                  pausedReason: null
        "401":
          description: Missing or invalid bearer token
    post:
      operationId: session_goal_set
      tags:
        - chat
      summary: Set the session's standing goal
      description: Set a standing goal for the session. Rejected with 400 when a goal is already active or paused — clear it first. `maxTurns` is the turn budget for one run (defaults to the configured value).
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              goalText: ship the report
              maxTurns: 20
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal:
                  goalText: ship the report
                  status: active
                  turnsUsed: 0
                  maxTurns: 20
                  subgoals: []
                  lastVerdict: null
                  lastReason: null
                  pausedReason: null
        "401":
          description: Missing or invalid bearer token
  /v1/chat/sessions/{id}/goal/pause:
    post:
      operationId: session_goal_pause
      tags:
        - chat
      summary: Pause the standing goal
      description: Pause the standing goal's continuation. Manual turns still work; the goal can be resumed later.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal:
                  status: paused
                  pausedReason: user
        "401":
          description: Missing or invalid bearer token
  /v1/chat/sessions/{id}/goal/resume:
    post:
      operationId: session_goal_resume
      tags:
        - chat
      summary: Resume the standing goal
      description: Resume a paused goal. Resets the turn budget so the goal gets a fresh run of turns.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal:
                  status: active
                  turnsUsed: 0
        "401":
          description: Missing or invalid bearer token
  /v1/chat/sessions/{id}/goal/clear:
    post:
      operationId: session_goal_clear
      tags:
        - chat
      summary: Clear the standing goal
      description: Clear the standing goal. Terminal — the goal is gone.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal: null
        "401":
          description: Missing or invalid bearer token
  /v1/chat/sessions/{id}/goal/subgoals:
    post:
      operationId: session_goal_subgoal_add
      tags:
        - chat
      summary: Add an acceptance criterion
      description: Append an acceptance criterion to the standing goal. The judge and the next-turn prompt both carry the criteria. Works on an active or paused goal.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
            example:
              criterion: all tests pass
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal:
                  subgoals:
                    - all tests pass
        "401":
          description: Missing or invalid bearer token
  /v1/chat/sessions/{id}/goal/subgoals/{n}:
    delete:
      operationId: session_goal_subgoal_drop
      tags:
        - chat
      summary: Drop an acceptance criterion
      description: Remove the acceptance criterion at position `n` (1-based). Rejected with 400 when `n` is out of range.
      parameters:
        - in: path
          name: id
          schema:
            type: string
          required: true
        - in: path
          name: n
          schema:
            type: integer
            example: 1
          required: true
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
              example:
                ok: true
                goal:
                  subgoals: []
        "401":
          description: Missing or invalid bearer token
