openapi: 3.1.0
info:
  title: AMBIE API
  description: |
    Audio transcription, intelligence, text translation, TTS, sentiment analysis,
    summarization, embeddings, reranking, content moderation, image generation,
    vision/image analysis, and voice end-of-utterance detection.

    > **Send a `User-Agent` header.** A missing or generic library User-Agent (for
    > example Python's default `python-urllib`) can be blocked at the edge with `403`
    > (Cloudflare error `1010`) before the request reaches the API. Send any descriptive
    > User-Agent, such as your application name or the stock `curl`/SDK agent, and the
    > request passes. The official SDKs set this for you.

    > **⚠ Platform preview.** This API is the developer-platform preview for AMBIE.
    > The endpoint shapes, auth, retry, webhook signatures, SDKs, and billing are stable.
    > Behind the scenes, every endpoint currently runs on commodity off-the-shelf models
    > (Deepgram Nova-3, OpenAI Whisper, Llama 3.1 70B, BGE family, BART, DistilBERT,
    > Llama Guard 3) served via Cloudflare Workers AI and Deepgram passthrough.
    >
    > **The proprietary AMBIE acoustic-intelligence models — what gives the platform its
    > 90-95% noisy-environment accuracy — are in active development and will replace the
    > transcription and TTS engines in 2027.** When that happens, the same API surface
    > stays; you'll get the upgrade with no code change. Translation, embeddings, rerank,
    > sentiment, summarize, moderate, and detect-language remain as utility wrappers
    > around well-understood OSS models — those won't get an "AMBIE" proprietary swap.
    >
    > See https://ambie.ai/preview/ for the full disclosure and 2027 roadmap. Email
    > cisco@ambie.ai if you're an early-access candidate (high volume, noisy-environment
    > use case, willingness to feedback during beta).
    >
    > Every API response carries `X-AMBIE-Preview: true` while the platform is in this
    > state, so SDKs and integrations can detect the preview-vs-GA transition programmatically.

    ## Response Envelope

    All native `/api/v1/*` endpoints (except the `/api/v1/openai/*` facade and binary
    responses like audio/images which return raw bytes) wrap their payload in a standard
    envelope:

    **Success:**
    ```json
    { "ok": true, "request_id": "<uuid>", "data": { ...endpoint payload... } }
    ```

    **Error:**
    ```json
    { "ok": false, "request_id": "<uuid>", "error": { "code": "<string>", "message": "<string>" } }
    ```

    **Error codes:** `bad_request`, `unauthorized`, `payment_required`, `forbidden`,
    `not_found`, `payload_too_large`, `rate_limited`, `upstream_error`,
    `service_unavailable`, `internal_error`, `invalid_model`.

    The per-endpoint result objects documented below are the contents of `data` on success.
    The `/api/v1/openai/*` facade preserves the OpenAI response shape unchanged.

    **Transcribe:** Two ASR engines (Deepgram Nova-3, OpenAI Whisper), four output formats,
    LLM-powered audio intelligence (summarization, key phrases, action items, chapters),
    speaker diarization, audio-to-English translation, and async webhook delivery.

    **Translate:** LLM-powered text translation with auto language detection, formality control,
    and domain-specific context hints. Powered by Llama 3.1 70B.

    **TTS:** Text-to-speech with Aura 2 (English/Spanish), Aura 1, and MeloTTS (6 languages).
    Multiple voices, encodings, and audio containers.

    **Sentiment:** Binary sentiment analysis (POSITIVE/NEGATIVE) with batch support.

    **Summarize:** Extractive text summarization with URL support. Free during beta.

    **Embeddings:** Text embeddings for semantic search with 7 model options (384-1024 dims).

    **Rerank:** Rerank search results by relevance using BGE Reranker. Pairs with embeddings.

    **Moderate:** Content safety screening with Llama Guard 3 (14 hazard categories).

    **Detect Language:** Standalone language detection with ISO 639-1 codes and confidence scores.

    **Imagine:** Image generation from text prompts (FLUX family + Leonardo models).

    **Edit Image:** Image editing and inpainting — transform or selectively edit an existing image with a
    text prompt (Stable Diffusion img2img + inpainting).

    **Vision:** Image-to-text description and visual question answering (Llama 4 Scout, LLaVA, Gemma 4).

    **Classify:** ImageNet image classification (ResNet-50, 1,000 categories).

    **Detect:** COCO object detection with bounding boxes (DETR ResNet-50, 80 categories).

    **Turn:** Voice end-of-utterance detection for real-time voice agents (Smart Turn v2).
  version: 2.3.0
  contact:
    name: AMBIE
    url: https://ambie.ai
    email: contact@ambie.ai
  license:
    name: Proprietary

servers:
  - url: https://ambie.ai/api/v1
    description: Production

security:
  - BearerAuth: []

tags:
  - name: Transcription
    description: Audio transcription and intelligence
  - name: Translation
    description: Text translation between languages
  - name: TTS
    description: Text-to-speech synthesis
  - name: Sentiment
    description: Sentiment analysis
  - name: Summarization
    description: Text summarization
  - name: Embeddings
    description: Text embeddings for semantic search
  - name: Reranking
    description: Search result reranking
  - name: Moderation
    description: Content safety screening
  - name: Language Detection
    description: Identify text language
  - name: Chat
    description: General-purpose chat / text generation across frontier LLMs
  - name: Image Generation
    description: Text-to-image generation (FLUX family, Leonardo)
  - name: Vision
    description: Image-to-text description and visual question answering
  - name: Image Analysis
    description: Image classification and object detection
  - name: Voice
    description: Voice end-of-utterance detection for real-time voice agents
  - name: Jobs
    description: Async job status polling
  - name: Health
    description: Service health and capabilities

paths:
  /transcribe:
    get:
      tags: [Health]
      summary: Health check and API capabilities
      description: Returns service status, available engines, features, formats, and limits. No authentication required.
      security: []
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

    post:
      tags: [Transcription]
      summary: Transcribe audio
      description: |
        Transcribe an audio file with optional intelligence features. Supports synchronous
        (wait for result) and asynchronous (webhook callback) modes.

        **Sync mode:** Returns full result in response body.

        **Async mode:** When `callback_url` is provided, returns `202 Accepted` immediately
        with a `request_id` and `poll_url`. The full result is POSTed to `callback_url` when
        processing completes. Poll status at `/transcribe/{request_id}`.
      operationId: transcribeAudio
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TranscribeRequest"
      responses:
        "200":
          description: Transcription complete (sync mode, format=json)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranscriptionResult"
            text/plain:
              schema:
                type: string
                description: Plain text transcript (format=text)
            application/x-subrip:
              schema:
                type: string
                description: SRT subtitle file (format=srt)
            text/vtt:
              schema:
                type: string
                description: WebVTT subtitle file (format=vtt)
        "202":
          description: Job accepted for async processing
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missingAudio:
                  summary: No audio file
                  value:
                    error: "Missing 'audio' file field"
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                invalidEngine:
                  summary: Bad engine value
                  value:
                    error: 'Invalid engine: use "deepgram" or "whisper"'
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                invalidFormat:
                  summary: Bad format value
                  value:
                    error: 'Invalid format: use "json", "text", "srt", or "vtt"'
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                emptyFile:
                  summary: Zero-byte file
                  value:
                    error: "Audio file is empty"
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                invalidCallback:
                  summary: Malformed callback URL
                  value:
                    error: "Invalid callback_url"
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        "401":
          description: Missing authentication
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Audio file too large (max 100 MB)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded (30 req/min)
          headers:
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-RateLimit-Remaining:
              schema:
                type: string
                example: "0"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Transcription failed or timed out
          headers:
            Retry-After:
              $ref: "#/components/headers/Retry-After"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Service unavailable (API key not configured or AI binding missing)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /transcribe/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async job status
      description: |
        Check the status of an async transcription job. Returns `202` while processing,
        `200` when completed or failed, `404` if the job doesn't exist.
      operationId: getJobStatus
      parameters:
        - name: request_id
          in: path
          required: true
          description: The request_id returned from the async POST
          schema:
            type: string
            format: uuid
            example: cc3300a2-69f0-408e-bb61-13febf246dba
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
              example:
                request_id: cc3300a2-69f0-408e-bb61-13febf246dba
                status: processing
                created_at: "2026-03-28T07:00:00.000Z"
                updated_at: "2026-03-28T07:00:00.000Z"
        "400":
          description: Invalid request_id format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing authentication
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Job tracking not available (D1 not bound)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /translate:
    get:
      tags: [Health]
      summary: Translation health check and capabilities
      description: Returns translation service status, model, features, and limits. No authentication required.
      security: []
      operationId: getTranslateHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranslateHealthResponse"

    post:
      tags: [Translation]
      summary: Translate text
      description: |
        Translate text between languages with optional formality control and domain context.
        Supports synchronous (wait for result) and asynchronous (webhook callback) modes.

        **Auto-detection:** Source language is automatically detected when not specified.
        Detection runs in parallel with translation, adding no extra latency.

        **Formality:** Control register with `formal`, `informal`, or `auto`.

        **Context:** Provide a domain hint (e.g. "legal document", "casual chat") to guide
        terminology and tone.

        **Async mode:** When `callback_url` is provided, returns `202 Accepted` immediately.
      operationId: translateText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TranslateRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TranslateRequest"
      responses:
        "200":
          description: Translation complete (sync mode, format=json)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranslationResult"
            text/plain:
              schema:
                type: string
                description: Translated text only (format=text)
        "202":
          description: Job accepted for async processing
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranslateAsyncResponse"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missingText:
                  summary: No text provided
                  value:
                    error: "Missing required field: text"
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                missingTarget:
                  summary: No target language
                  value:
                    error: "Missing required field: target_lang"
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                invalidFormality:
                  summary: Bad formality value
                  value:
                    error: 'Invalid formality: use "auto", "formal", or "informal"'
                    request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        "401":
          description: Missing authentication
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Text too long (max 50,000 chars)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded (30 req/min)
          headers:
            Retry-After:
              $ref: "#/components/headers/Retry-After"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Translation failed or timed out
          headers:
            Retry-After:
              $ref: "#/components/headers/Retry-After"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Service unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /translate/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async translation job status
      description: |
        Check the status of an async translation job. Returns `202` while processing,
        `200` when completed or failed, `404` if the job doesn't exist.
      operationId: getTranslateJobStatus
      parameters:
        - name: request_id
          in: path
          required: true
          description: The request_id returned from the async POST
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranslateJobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TranslateJobStatusResponse"
        "400":
          description: Invalid request_id format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing authentication
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Job tracking not available (D1 not bound)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  # --- TTS ---

  /tts:
    get:
      tags: [Health]
      summary: TTS health check and capabilities
      security: []
      operationId: getTtsHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TtsHealthResponse"

    post:
      tags: [TTS]
      summary: Convert text to speech
      description: |
        Synthesize speech from text using Aura 2 (English/Spanish) or MeloTTS (6 languages).

        **Models:** `aura-2-en` (default, 40+ voices), `aura-2-es` (10+ voices),
        `aura-1` (12 voices), `melotts` (6 langs).

        **Output:** Raw audio bytes (default) or JSON with base64-encoded audio.
        Note: the Deepgram **aura** models emit **WAV** PCM — the Workers AI
        binding ignores the `encoding: mp3` hint — so aura responses are
        `Content-Type: audio/wav` (the endpoint decodes the model's base64 to
        real bytes and sets the Content-Type from the actual container). melotts
        emits MP3. Transcode client-side if you need a different format.

        **Async mode:** When `callback_url` is provided, returns `202 Accepted` immediately.
      operationId: synthesizeSpeech
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TtsRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TtsRequest"
      responses:
        "200":
          description: Audio synthesized successfully
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            audio/wav:
              schema:
                type: string
                format: binary
                description: Raw WAV bytes (aura models, format=audio)
            audio/mpeg:
              schema:
                type: string
                format: binary
                description: Raw MP3 bytes (melotts, format=audio)
            application/json:
              schema:
                $ref: "#/components/schemas/TtsResult"
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 10,000 chars)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /tts/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async TTS job status
      operationId: getTtsJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Sentiment ---

  /sentiment:
    get:
      tags: [Health]
      summary: Sentiment health check and capabilities
      security: []
      operationId: getSentimentHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SentimentHealthResponse"

    post:
      tags: [Sentiment]
      summary: Analyze text sentiment
      description: |
        Analyze sentiment of text using DistilBERT. Returns POSITIVE or NEGATIVE with confidence score.

        **Batch:** Send a JSON array of strings to analyze multiple texts in one request (max 100).
      operationId: analyzeSentiment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SentimentRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SentimentRequest"
      responses:
        "200":
          description: Sentiment analysis complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SentimentResult"
            text/plain:
              schema:
                type: string
                description: One result per line (format=text)
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 50,000 chars total)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /sentiment/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async sentiment job status
      operationId: getSentimentJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Summarize ---

  /summarize:
    get:
      tags: [Health]
      summary: Summarization health check and capabilities
      security: []
      operationId: getSummarizeHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SummarizeHealthResponse"

    post:
      tags: [Summarization]
      summary: Summarize text
      description: |
        Summarize text or web page content using BART Large CNN.
        Free during beta — no per-request cost.

        **URL input:** Provide a `url` to fetch and summarize a web page directly.
      operationId: summarizeText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SummarizeRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SummarizeRequest"
      responses:
        "200":
          description: Summarization complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SummarizeResult"
            text/plain:
              schema:
                type: string
                description: Summary text only (format=text)
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 50,000 chars)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /summarize/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async summarization job status
      operationId: getSummarizeJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Chat / Text Generation ---

  /chat:
    get:
      tags: [Health]
      summary: Chat health check and model menu
      security: []
      operationId: getChatHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatHealthResponse"

    post:
      tags: [Chat]
      summary: Generate a chat completion
      description: |
        General-purpose chat / text generation across the most capable
        Cloudflare Workers AI models. Pick a model per task with the `model`
        field (default `gpt-oss-120b`):

        - `gpt-oss-120b` — 120B, high reasoning + agentic (default)
        - `kimi-k2.6` — 1T MoE, 262k context, vision + tool calling (flagship)
        - `nemotron-3-120b` — 120B MoE, 256k context, long documents + tools
        - `qwq-32b` — reasoning specialist for hard problems
        - `llama-3.3-70b` — balanced general quality, fast (no reasoning step)
        - `llama-3.1-8b-fast` — fastest + cheapest, high throughput
        - `glm-4.7-flash` — GLM-4.7 Flash, fast inference
        - `qwen-coder-32b` — Qwen2.5 Coder 32B, code generation specialist
        - `deepseek-r1-32b` — DeepSeek R1 distill Qwen 32B, strong reasoning
        - `qwen3-30b` — Qwen3 30B-A3B FP8, efficient MoE
        - `mistral-small-3.1-24b` — Mistral Small 3.1 24B, 128k context, function calling
        - `llama-3.2-3b` — Llama 3.2 3B, micro-tier, low cost
        - `llama-3.2-1b` — Llama 3.2 1B, edge/cheapest
        - `granite-4-micro` — IBM Granite 4.0 Micro, agentic + function calling
        - `llama-4-scout` — Llama 4 Scout 17B MoE, multimodal (text + vision)

        gpt-oss, kimi, nemotron, qwq, and deepseek-r1 are reasoning models (they
        think before answering) — give them enough `max_tokens` (default 2048).

        Vision: the vision-capable models (`kimi-k2.6`, `llama-4-scout`) accept
        OpenAI image content — a `messages` entry whose `content` is an array
        with `{type:"image_url", image_url:{url:"data:image/...;base64,..."}}`
        plus a `{type:"text",...}` part. Web-search grounding auto-disables when
        an image is present.

        Provide either an OpenAI-style `messages` array or a single `prompt`
        string. Set `stream: true` for a Server-Sent Events token stream. Set
        `web_search: true` to ground the answer in live Brave web results
        (returns `sources`, cites inline). Also reachable via the
        OpenAI-compatible `POST /openai/chat/completions`.
      operationId: chatCompletion
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ChatRequest"
      responses:
        "200":
          description: Completion generated (or an SSE stream when stream=true)
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatResult"
            text/plain:
              schema:
                type: string
                description: Completion text only (format=text)
            text/event-stream:
              schema:
                type: string
                description: OpenAI-style SSE chunks (stream=true)
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Input too long (max 600,000 chars)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /chat/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async chat-completion job status
      operationId: getChatJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Embeddings ---

  /embeddings:
    get:
      tags: [Health]
      summary: Embeddings health check and capabilities
      security: []
      operationId: getEmbeddingsHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingsHealthResponse"

    post:
      tags: [Embeddings]
      summary: Generate text embeddings
      description: |
        Generate vector embeddings for semantic search, clustering, and similarity.

        **Models:** `bge-base-en-v1.5` (default, 768d), `bge-large-en-v1.5` (1024d),
        `bge-small-en-v1.5` (384d), `bge-m3` (multilingual, 60K tokens), `embeddinggemma` (100+ langs),
        `qwen3-embedding` (multilingual, 8K tokens), `plamo-embedding-ja` (Japanese specialist).

        **Batch:** Send a JSON array of strings to embed multiple texts (max 100).
      operationId: generateEmbeddings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingsRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/EmbeddingsRequest"
      responses:
        "200":
          description: Embeddings generated successfully
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingsResult"
            text/plain:
              schema:
                type: string
                description: Tab-separated vectors, one per line (format=text)
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 100,000 chars total)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /embeddings/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async embeddings job status
      operationId: getEmbeddingsJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Rerank ---

  /rerank:
    get:
      tags: [Health]
      summary: Rerank health check and capabilities
      security: []
      operationId: getRerankHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RerankHealthResponse"

    post:
      tags: [Reranking]
      summary: Rerank search results
      description: |
        Rerank a set of text contexts by relevance to a query using BGE Reranker.
        Returns sigmoid-scored results (0-1) sorted by relevance.

        **Use case:** After retrieving candidates via embeddings or keyword search,
        rerank them for precision before presenting to users.
      operationId: rerankTexts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RerankRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/RerankRequest"
      responses:
        "200":
          description: Reranking complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RerankResult"
            text/plain:
              schema:
                type: string
                description: "Tab-separated score and text per line (format=text)"
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Query or contexts too long
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /rerank/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async rerank job status
      operationId: getRerankJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Moderate ---

  /moderate:
    get:
      tags: [Health]
      summary: Moderation health check and capabilities
      security: []
      operationId: getModerateHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModerateHealthResponse"

    post:
      tags: [Moderation]
      summary: Screen text for safety
      description: |
        Screen text for safety using Llama Guard 3. Returns a safe/unsafe verdict
        with flagged hazard categories (S1-S14).

        **Categories:** Violent Crimes, Non-Violent Crimes, Sex-Related Crimes,
        Child Sexual Exploitation, Defamation, Specialized Advice, Privacy,
        Intellectual Property, Indiscriminate Weapons, Hate, Suicide & Self-Harm,
        Sexual Content, Elections, Code Interpreter Abuse.
      operationId: moderateText
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ModerateRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ModerateRequest"
      responses:
        "200":
          description: Moderation complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModerateResult"
            text/plain:
              schema:
                type: string
                description: "SAFE or UNSAFE with categories (format=text)"
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 50,000 chars)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /moderate/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async moderation job status
      operationId: getModerateJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Detect Language ---

  /detect-lang:
    get:
      tags: [Health]
      summary: Language detection health check and capabilities
      security: []
      operationId: getDetectLangHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetectLangHealthResponse"

    post:
      tags: [Language Detection]
      summary: Detect text language
      description: |
        Detect the language of text, returning ISO 639-1 codes with confidence scores.

        **Batch:** Send a JSON array of strings to detect multiple texts (max 50).
      operationId: detectLanguage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DetectLangRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/DetectLangRequest"
      responses:
        "200":
          description: Detection complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetectLangResult"
            text/plain:
              schema:
                type: string
                description: "Tab-separated code, name, confidence, text per line"
        "202":
          description: Job accepted for async processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AsyncAcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Text too long (max 50,000 chars total)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  /detect-lang/{request_id}:
    get:
      tags: [Jobs]
      summary: Poll async language detection job status
      operationId: getDetectLangJobStatus
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Job completed or failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "202":
          description: Job still processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Image Generation ---

  /imagine:
    get:
      tags: [Health]
      summary: Image generation health check and capabilities
      security: []
      operationId: getImagineHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Image Generation API
                  default_model:
                    type: string
                    example: flux-1-schnell
                  models:
                    type: object
                    additionalProperties: true
                  ai_available:
                    type: boolean

    post:
      tags: [Image Generation]
      summary: Generate an image from a text prompt
      description: |
        Generate images from text prompts using FLUX and Leonardo models.

        **Models:**
        - `flux-1-schnell` (default) — FLUX.1 schnell, 12B, 4 steps, fast + cheap
        - `flux-2-klein-4b` — FLUX.2 Klein 4B, compact
        - `flux-2-klein-9b` — FLUX.2 Klein 9B, higher detail
        - `flux-2-dev` — FLUX.2 dev, highest quality multi-reference
        - `lucid-origin` — Leonardo Lucid Origin
        - `phoenix` — Leonardo Phoenix 1.0

        **Output:** Raw JPEG bytes by default (`format=image`). Set `format=json` for an
        enveloped response with `image_base64`.
      operationId: generateImage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImagineRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ImagineRequest"
      responses:
        "200":
          description: Image generated successfully
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            image/jpeg:
              schema:
                type: string
                format: binary
                description: Raw image bytes (format=image, default)
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Enveloped response with image_base64 (format=json)
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Edit Image ---

  /edit-image:
    get:
      tags: [Health]
      summary: Edit-image health check and capabilities
      security: []
      operationId: getEditImageHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Image Editing API
                  default_model:
                    type: string
                    example: img2img
                  models:
                    type: object
                    additionalProperties: true
                  ai_available:
                    type: boolean

    post:
      tags: [Image Generation]
      summary: Edit or inpaint an image
      description: |
        Edit an existing image guided by a text prompt using Stable Diffusion
        img2img or inpainting. The input image and an optional mask are passed
        alongside the prompt.

        **Models:**
        - `img2img` (default) — SD 1.5 img2img, transforms the full image
        - `inpainting` — SD 1.5 inpainting, edits only the masked region

        **Image input:** Provide `image` (base64 string, data URI, or raw bytes)
        OR `image_url` (HTTP/HTTPS URL or data URI).

        **Mask input:** For inpainting, provide `mask` or `mask_url` — white
        pixels mark areas to regenerate, black pixels are preserved.

        **Output:** Raw PNG bytes by default (`format=image`). Set `format=json`
        for an enveloped response with `image_base64`.
      operationId: editImage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EditImageRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/EditImageRequest"
      responses:
        "200":
          description: Edited image generated successfully
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            image/png:
              schema:
                type: string
                format: binary
                description: Raw PNG bytes (format=image, default)
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Enveloped response with image_base64 (format=json)
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Image too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Vision ---

  /vision:
    get:
      tags: [Health]
      summary: Vision health check and capabilities
      security: []
      operationId: getVisionHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Vision API
                  default_model:
                    type: string
                    example: llama-4-scout
                  models:
                    type: object
                    additionalProperties: true
                  ai_available:
                    type: boolean

    post:
      tags: [Vision]
      summary: Describe or analyze an image
      description: |
        Generate a text description or answer questions about an image using
        multimodal vision models.

        **Models:**
        - `llama-4-scout` (default) — Llama 4 Scout 17B, state-of-the-art multimodal
        - `llama-3.2-11b` — Llama 3.2 11B Vision, compact multimodal
        - `gemma-4-vision` — Gemma 4 26B-A4B-IT (MoE vision)
        - `llava-1.5` — LLaVA 1.5 7B, lightweight visual QA

        **Image input:** Provide `image` as a base64 string, data URI, or raw bytes,
        OR `image_url` as an HTTP/HTTPS URL or data URI.
      operationId: describeImage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VisionRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/VisionRequest"
      responses:
        "200":
          description: Vision analysis complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Envelope with VisionResult in data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          description: Image too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Classify ---

  /classify:
    get:
      tags: [Health]
      summary: Image classification health check and capabilities
      security: []
      operationId: getClassifyHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Image Classification API
                  model:
                    type: string
                    example: "@cf/microsoft/resnet-50"
                  ai_available:
                    type: boolean

    post:
      tags: [Image Analysis]
      summary: Classify an image (ImageNet categories)
      description: |
        Classify an image using ResNet-50 trained on ImageNet (1,000 categories).
        Returns top-N predictions with confidence scores.

        **Image input:** Provide `image` as base64/data URI, or `image_url`.
      operationId: classifyImage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClassifyRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ClassifyRequest"
      responses:
        "200":
          description: Classification complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Envelope with ClassifyResult in data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Detect ---

  /detect:
    get:
      tags: [Health]
      summary: Object detection health check and capabilities
      security: []
      operationId: getDetectHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Object Detection API
                  model:
                    type: string
                    example: "@cf/facebook/detr-resnet-50"
                  ai_available:
                    type: boolean

    post:
      tags: [Image Analysis]
      summary: Detect objects in an image (COCO categories)
      description: |
        Detect objects in an image using DETR ResNet-50 trained on COCO 2017
        (80 categories). Returns bounding boxes, labels, and confidence scores.

        **Image input:** Provide `image` as base64/data URI, or `image_url`.
      operationId: detectObjects
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DetectRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/DetectRequest"
      responses:
        "200":
          description: Detection complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Envelope with DetectResult in data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- Turn Detection ---

  /turn:
    get:
      tags: [Health]
      summary: Turn detection health check and capabilities
      security: []
      operationId: getTurnHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: AMBIE Turn Detection API
                  model:
                    type: string
                    example: "@cf/pipecat-ai/smart-turn-v2"
                  ai_available:
                    type: boolean

    post:
      tags: [Voice]
      summary: Detect voice end-of-utterance
      description: |
        Classify whether a speaker has finished their turn using Smart Turn v2.
        Designed for real-time voice agents — call after each audio chunk to know
        when to hand off to the bot.

        **Audio input:** Provide `audio` as a base64 string in JSON, or as a file
        in multipart/form-data. `dtype` sets the sample format expected by the model.
      operationId: detectTurn
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TurnRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TurnRequest"
      responses:
        "200":
          description: Turn detection complete
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
                description: Envelope with TurnResult in data
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

  # --- OpenAI Compatibility Facade ---

  /openai/images/generations:
    post:
      tags: [Image Generation]
      summary: OpenAI Images API compatibility facade
      description: |
        Maps the OpenAI Images API shape onto `/imagine`, letting any client
        that speaks the OpenAI SDK generate images via AMBIE without code changes.
        Works with Open WebUI's image-generation backend and similar tools.

        Send `{prompt, model, size, n}` — the facade maps `size` to `width`/`height`,
        selects the requested model (defaults to `flux-1-schnell`), and returns
        `{created, data:[{b64_json}]}`.

        **Supported size strings:** `256x256`, `512x512`, `1024x1024` (default),
        `1024x1792`, `1792x1024`.
      operationId: openaiImagesGenerations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OpenAIImagesRequest"
      responses:
        "200":
          description: Image(s) generated successfully
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAIImagesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unavailable"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        DB-issued API key (format `amb_live_…`) as a Bearer token, e.g.
        `Authorization: Bearer amb_live_…`. Keys are minted at signup / by the
        operator and resolved against the per-user key store. (The legacy single
        env-var key was retired in the 2026-06 auth migration.)

  headers:
    X-Request-Id:
      description: Unique request identifier for log correlation
      schema:
        type: string
        format: uuid
    X-RateLimit-Remaining:
      description: Requests remaining in current rate limit window
      schema:
        type: string
    Retry-After:
      description: Seconds to wait before retrying
      schema:
        type: string

  schemas:
    TranscribeRequest:
      type: object
      description: "Provide either 'audio' (file upload) or 'url' (remote fetch)."
      properties:
        audio:
          type: string
          format: binary
          description: "Audio file (mp3, mp4, mp2, aac, wav, flac, pcm, m4a, ogg, opus, webm). Max 100 MB. Provide either audio or url."
        url:
          type: string
          format: uri
          description: "URL to audio file (alternative to upload). CF fetches server-side. Max 100 MB. Large files auto-chunked."
          example: https://cdn.example.com/recordings/meeting.mp3
        engine:
          type: string
          enum: [deepgram, whisper]
          default: deepgram
          description: "ASR engine. Deepgram for accuracy/diarization, Whisper for multilingual/translation."
        language:
          type: string
          description: "BCP-47 language code hint (e.g. en, es, fr-CA). Auto-detected if omitted."
          example: en
        format:
          type: string
          enum: [json, text, srt, vtt]
          default: json
          description: "Output format. Non-json formats return raw content with appropriate Content-Type."
        translate:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Translate audio to English (Whisper engine only)."
        callback_url:
          type: string
          format: uri
          description: "Webhook URL. When provided, API returns 202 immediately and POSTs result to this URL."
          example: https://yourserver.com/webhooks/transcription
        client_id:
          type: string
          description: "Your correlation ID. Echoed in all responses, webhook deliveries, and poll results."
          example: meeting-2026-03-28
        punctuate:
          type: string
          enum: ["true", "false"]
          default: "true"
          description: Add punctuation and capitalization.
        smart_format:
          type: string
          enum: ["true", "false"]
          default: "true"
          description: Apply formatting for improved readability.
        diarize:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Detect and label speakers (Deepgram only). Adds diarized_text and speaker_count."
        detect_entities:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Extract named entities (Deepgram only)."
        paragraphs:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Split transcript into paragraphs (Deepgram only)."
        utterances:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Segment into semantic utterances (Deepgram only)."
        filler_words:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: 'Include filler words like "uh" and "um" (Deepgram only).'
        profanity_filter:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Replace profanity with asterisks (Deepgram only)."
        numerals:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Convert spoken numbers to digits (Deepgram only)."
        measurements:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Abbreviate spoken measurements (Deepgram only)."
        multichannel:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Transcribe each audio channel independently (Deepgram only)."
        vocabulary:
          type: string
          description: "Comma-separated terms to boost recognition (Whisper only). Helps with names, acronyms, and domain-specific jargon."
          example: "AMBIE, Abundera, Trivlet, ASR, diarization"
        prefix:
          type: string
          description: "Guide the start of the transcription output (Whisper only)."
          example: "Meeting notes:"
        summarize:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Generate LLM summary of the transcript."
        key_phrases:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Extract key phrases, terms, names, and topics via LLM."
        action_items:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Extract action items, tasks, and commitments via LLM."
        chapters:
          type: string
          enum: ["true", "false"]
          default: "false"
          description: "Split transcript into timestamped topic chapters via LLM."

    Word:
      type: object
      required: [word, start, end]
      properties:
        word:
          type: string
          example: Americans,
        start:
          type: number
          format: float
          description: Start time in seconds
          example: 2.4
        end:
          type: number
          format: float
          description: End time in seconds
          example: 3.36
        confidence:
          type: number
          format: float
          description: "Confidence score (0-1)"
          example: 0.984
        speaker:
          type: integer
          description: "Speaker number (present when diarize=true)"
          example: 0
        channel:
          type: integer
          description: "Audio channel (present when multichannel=true)"

    Utterance:
      type: object
      properties:
        text:
          type: string
        start:
          type: number
          format: float
        end:
          type: number
          format: float
        confidence:
          type: number
          format: float
        speaker:
          type: integer

    ActionItem:
      type: object
      properties:
        action:
          type: string
          description: Description of the action item
          example: Follow up with vendor on pricing
        assignee:
          type: string
          nullable: true
          description: "Who is responsible (Speaker N or null)"
          example: Speaker 1
        deadline:
          type: string
          nullable: true
          description: "When it's due (if mentioned)"
          example: Friday

    Chapter:
      type: object
      properties:
        title:
          type: string
          description: "Concise chapter title (3-8 words)"
          example: Budget Discussion
        summary:
          type: string
          description: "1-2 sentence summary"
          example: Team reviews Q2 numbers and discusses budget allocation.
        start_phrase:
          type: string
          description: "First few words of this section in the transcript"
          example: Let's look at the numbers

    FileInfo:
      type: object
      properties:
        name:
          type: string
          nullable: true
          example: meeting.mp3
        size_bytes:
          type: integer
          example: 352078
        type:
          type: string
          nullable: true
          example: audio/mpeg
        source:
          type: string
          enum: [upload, url]
          description: "How the audio was provided"

    TranscriptionResult:
      type: object
      required: [text, language, word_count, words, model, engine, processing_ms, request_id, processed_at, file]
      properties:
        text:
          type: string
          description: Full transcript text
        language:
          type: string
          description: Detected or specified language
          example: en
        language_probability:
          type: number
          format: float
          nullable: true
          description: "Language detection confidence (Whisper only)"
        duration_seconds:
          type: number
          format: float
          nullable: true
          description: Audio duration in seconds (parsed from file headers)
          example: 63.66
        transcript_confidence:
          type: number
          format: float
          nullable: true
          description: "Average word confidence (0-1)"
          example: 0.983
        word_count:
          type: integer
          example: 152
        words:
          type: array
          items:
            $ref: "#/components/schemas/Word"
        srt:
          type: string
          description: SRT subtitle content generated from word timestamps
        vtt:
          type: string
          description: WebVTT subtitle content generated from word timestamps
        model:
          type: string
          example: "@cf/deepgram/nova-3"
        engine:
          type: string
          enum: [deepgram, whisper]
        processing_ms:
          type: integer
          description: ASR processing time in milliseconds
          example: 2405
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        file:
          $ref: "#/components/schemas/FileInfo"
        client_id:
          type: string
          description: "Echoed from request (if provided)"
        translated:
          type: boolean
          description: "True if translation was applied (Whisper only)"
        diarized_text:
          type: string
          nullable: true
          description: "Transcript with [Speaker N] labels (when diarize=true)"
        speaker_count:
          type: integer
          nullable: true
          description: "Number of speakers detected (when diarize=true)"
        utterances:
          type: array
          items:
            $ref: "#/components/schemas/Utterance"
          description: "Semantic utterances (when utterances=true)"
        paragraphs:
          type: object
          nullable: true
          description: "Paragraph structure (when paragraphs=true)"
        entities:
          type: object
          nullable: true
          description: "Named entities (when detect_entities=true)"
        channels:
          type: array
          description: "Per-channel transcripts (when multichannel=true, stereo+ audio)"
          items:
            type: object
            properties:
              channel:
                type: integer
              transcript:
                type: string
              confidence:
                type: number
                format: float
                nullable: true
        segments:
          type: array
          description: "Whisper segments with logprobs (Whisper engine only)"
          items:
            type: object
            properties:
              text:
                type: string
              start:
                type: number
                format: float
              end:
                type: number
                format: float
              avg_logprob:
                type: number
                format: float
              no_speech_prob:
                type: number
                format: float
              words:
                type: array
                items:
                  $ref: "#/components/schemas/Word"
        summary:
          type: string
          nullable: true
          description: "LLM-generated summary (when summarize=true)"
        summary_model:
          type: string
          description: "Model used for summarization"
        summary_error:
          type: string
          description: "Error message if summarization failed"
        key_phrases:
          type: array
          nullable: true
          items:
            type: string
          description: "Extracted key phrases (when key_phrases=true)"
          example: ["fellow Americans", "civic duty", "country"]
        key_phrases_error:
          type: string
        action_items:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/ActionItem"
          description: "Extracted action items (when action_items=true)"
        action_items_error:
          type: string
        chapters:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/Chapter"
          description: "Topic chapters (when chapters=true)"
        chapters_error:
          type: string
        intelligence_ms:
          type: integer
          description: "Total LLM processing time for all intelligence features"
          example: 1200
        chunks_processed:
          type: integer
          description: "Number of chunks (present when large file was auto-chunked)"
          example: 11

    AsyncAcceptedResponse:
      type: object
      required: [request_id, status, poll_url, callback_url]
      properties:
        request_id:
          type: string
          format: uuid
          example: cc3300a2-69f0-408e-bb61-13febf246dba
        status:
          type: string
          enum: [processing]
        poll_url:
          type: string
          format: uri
          example: https://ambie.ai/api/v1/transcribe/cc3300a2-69f0-408e-bb61-13febf246dba
        callback_url:
          type: string
          format: uri
        client_id:
          type: string
          description: "Echoed from request (if provided)"

    JobStatusResponse:
      type: object
      required: [request_id, status, created_at, updated_at]
      properties:
        request_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [processing, completed, failed]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        result:
          $ref: "#/components/schemas/TranscriptionResult"
          description: "Full result (when status=completed)"
        error:
          type: string
          description: "Error message (when status=failed)"

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Transcribe API
        version:
          type: string
          example: 1.0.0
        engines:
          type: object
          properties:
            deepgram:
              type: object
              properties:
                model:
                  type: string
                features:
                  type: array
                  items:
                    type: string
            whisper:
              type: object
              properties:
                model:
                  type: string
                features:
                  type: array
                  items:
                    type: string
        intelligence:
          type: array
          items:
            type: string
          example: [summarize, key_phrases, action_items, chapters]
        formats:
          type: array
          items:
            type: string
          example: [json, text, srt, vtt]
        supported_audio:
          type: string
          example: "mp3, mp4, mp2, aac, wav, flac, pcm, m4a, ogg, opus, webm"
        max_file_size_mb:
          type: integer
          example: 100
        rate_limit:
          type: string
          example: "30 requests per 60s"
        ai_available:
          type: boolean

    TranslateRequest:
      type: object
      required: [target_lang]
      description: "Provide either 'text' (direct input) or 'url' (fetch and extract from web page)."
      properties:
        text:
          type: string
          description: "Text to translate (max 50,000 characters). Provide either text or url."
          example: "Hello, how are you? I hope you are having a wonderful day."
        url:
          type: string
          format: uri
          description: "URL to fetch and translate. HTML text is extracted automatically. Max 5 MB page size, 50,000 chars after extraction."
          example: https://example.com/article
        target_lang:
          type: string
          description: "Target language — name or BCP-47 code (e.g. 'Spanish', 'ja', 'fr-CA', 'zh-CN')"
          example: Spanish
        source_lang:
          type: string
          description: "Source language (auto-detected if omitted)"
          example: en
        engine:
          type: string
          enum: [llm, m2m100]
          default: llm
          description: "Translation engine. 'llm' = Llama 3.1 70B (formality, context, auto-detect). 'm2m100' = dedicated NMT (~10x faster, no formality/context)."
        formality:
          type: string
          enum: [auto, formal, informal]
          default: auto
          description: "Register control. 'formal' uses polite/professional forms, 'informal' uses casual tone."
        context:
          type: string
          description: "Domain hint to guide terminology and tone"
          example: "business email"
        format:
          type: string
          enum: [json, text]
          default: json
          description: "Output format. 'text' returns only the translated string."
        callback_url:
          type: string
          format: uri
          description: "Webhook URL. When provided, API returns 202 and POSTs result when done."
          example: https://yourserver.com/webhooks/translation
        client_id:
          type: string
          description: "Your correlation ID. Echoed in all responses and webhooks."
          example: doc-2026-04-02

    TranslationResult:
      type: object
      required: [translated_text, source_lang, target_lang, formality, input_chars, output_chars, model, processing_ms, request_id, processed_at]
      properties:
        translated_text:
          type: string
          description: The translated text
          example: "Hola, ¿cómo estás? Espero que estés teniendo un día maravilloso."
        source_lang:
          type: string
          description: "Detected or specified source language code"
          example: en
        source_lang_name:
          type: string
          nullable: true
          description: "Full name of detected source language"
          example: English
        source_lang_confidence:
          type: number
          format: float
          nullable: true
          description: "Language detection confidence (0-1)"
          example: 0.99
        target_lang:
          type: string
          example: Spanish
        formality:
          type: string
          enum: [auto, formal, informal]
        input_chars:
          type: integer
          description: Character count of input text
          example: 58
        output_chars:
          type: integer
          description: Character count of translated text
          example: 64
        model:
          type: string
          example: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
        served_by:
          type: string
          description: The model that actually served (equals `model`; differs only on fallback).
        engine:
          type: string
          description: '"llm" — or "llm (fallback)" when the 8B fallback served.'
        fallback:
          type: object
          nullable: true
          description: >
            Non-null when the primary model failed and the 8B fallback served the
            translation (the failure is also logged at error level).
          properties:
            requested_model:
              type: string
            reason:
              type: string
        processing_ms:
          type: integer
          description: Processing time in milliseconds
          example: 1374
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string
          description: "Echoed from request (if provided)"
        source:
          type: object
          nullable: true
          description: "Present when url parameter was used"
          properties:
            url:
              type: string
              format: uri
            content_type:
              type: string
              nullable: true
            extracted_chars:
              type: integer
              description: "Characters extracted from the page"

    TranslateAsyncResponse:
      type: object
      required: [request_id, status, poll_url, callback_url]
      properties:
        request_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [processing]
        poll_url:
          type: string
          format: uri
          example: https://ambie.ai/api/v1/translate/cc3300a2-69f0-408e-bb61-13febf246dba
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    TranslateJobStatusResponse:
      type: object
      required: [request_id, status, created_at, updated_at]
      properties:
        request_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [processing, completed, failed]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        result:
          $ref: "#/components/schemas/TranslationResult"
          description: "Full result (when status=completed)"
        error:
          type: string
          description: "Error message (when status=failed)"

    TranslateHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Translate API
        version:
          type: string
          example: 1.0.0
        model:
          type: string
          example: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
        features:
          type: object
          properties:
            auto_detect:
              type: string
            formality:
              type: array
              items:
                type: string
            context:
              type: string
            async:
              type: object
        formats:
          type: array
          items:
            type: string
          example: [json, text]
        max_text_length:
          type: integer
          example: 50000
        rate_limit:
          type: string
          example: "30 requests per 60s"
        ai_available:
          type: boolean

    # --- TTS Schemas ---

    TtsRequest:
      type: object
      required: [text]
      properties:
        text:
          type: string
          description: Text to synthesize (max 10,000 chars)
          maxLength: 10000
          example: "Hello, welcome to Ambie."
        model:
          type: string
          enum: [aura-2-en, aura-2-es, aura-1, melotts]
          default: aura-2-en
          description: TTS model to use
        voice:
          type: string
          description: >
            Voice name. Model-specific; an unknown voice returns 400 with the valid list.
            Call `GET /api/v1/tts` for the live catalog. MeloTTS ignores this (language-only).

            aura-2-en (40): amalthea, andromeda, apollo, arcas, aries, asteria, athena, atlas,
            aurora, callista, cora, cordelia, delia, draco, electra, harmonia, helena, hera,
            hermes, hyperion, iris, janus, juno, jupiter, luna, mars, minerva, neptune, odysseus,
            ophelia, orion, orpheus, pandora, phoebe, pluto, saturn, thalia, theia, vesta, zeus.

            aura-2-es (10): alvaro, aquila, carina, celeste, diana, estrella, javier, nestor,
            selena, sirio.

            aura-1 (12): angus, arcas, asteria, athena, helios, hera, luna, orion, orpheus,
            perseus, stella, zeus.
          example: luna
        lang:
          type: string
          enum: [en, es, fr, zh, ja, ko]
          default: en
          description: Language for MeloTTS model
        encoding:
          type: string
          enum: [mp3, linear16, flac, mulaw, alaw, opus, aac]
          default: mp3
        container:
          type: string
          enum: [none, wav, ogg]
          default: none
        sample_rate:
          type: integer
          description: Sample rate in Hz
          example: 24000
        format:
          type: string
          enum: [audio, json]
          default: audio
          description: "audio = raw bytes, json = base64-encoded"
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    TtsResult:
      type: object
      properties:
        audio_base64:
          type: string
          description: Base64-encoded audio data (format=json only)
        model:
          type: string
          example: "@cf/deepgram/aura-2-en"
        model_name:
          type: string
          example: aura-2-en
        voice:
          type: string
          example: luna
        lang:
          type: string
        input_chars:
          type: integer
        encoding:
          type: string
          description: The encoding the caller requested (a hint; see output_format for what was actually produced).
        output_format:
          type: string
          enum: [wav, mp3, ogg, flac, unknown]
          description: The container the model ACTUALLY produced, sniffed from the bytes. Aura emits wav.
        served_by:
          type: string
          description: Which engine actually produced the audio. Equals the requested model unless a fallback occurred.
          example: aura-2-en
        fallback:
          type: object
          nullable: true
          description: >
            Present (non-null) when the requested model failed and a different engine served the
            audio. Aura failures fall back to MeloTTS; the response also sets X-TTS-Fallback: true
            and X-TTS-Served-By headers so audio-mode callers see it too.
          properties:
            requested_model:
              type: string
              example: aura-2-en
            reason:
              type: string
              description: The underlying error that caused the fallback.
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    TtsHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Text-to-Speech API
        version:
          type: string
        models:
          type: object
        features:
          type: object
        max_text_length:
          type: integer
          example: 10000
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Sentiment Schemas ---

    SentimentRequest:
      type: object
      required: [text]
      properties:
        text:
          oneOf:
            - type: string
              description: Single text to analyze
            - type: array
              items:
                type: string
              description: Batch of texts (max 100)
          example: "This product is absolutely amazing!"
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    SentimentResult:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              text:
                type: string
              label:
                type: string
                enum: [POSITIVE, NEGATIVE]
              score:
                type: number
                example: 0.9997
              scores:
                type: array
                items:
                  type: object
                  properties:
                    label:
                      type: string
                    score:
                      type: number
        model:
          type: string
          example: "@cf/huggingface/distilbert-sst-2-int8"
        total_items:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    SentimentHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Sentiment Analysis API
        model:
          type: string
        features:
          type: object
        max_batch_size:
          type: integer
          example: 100
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Summarize Schemas ---

    ChatMessage:
      type: object
      required: [role, content]
      properties:
        role:
          type: string
          enum: [system, user, assistant, tool]
        content:
          type: string

    ChatRequest:
      type: object
      description: Provide `messages` OR `prompt`.
      properties:
        messages:
          type: array
          description: OpenAI-style conversation. Provide this OR `prompt`.
          items:
            $ref: "#/components/schemas/ChatMessage"
        prompt:
          type: string
          description: Single user message. Provide this OR `messages`.
        system:
          type: string
          description: System prompt (prepended when using `prompt`)
        model:
          type: string
          enum:
            - gpt-oss-120b
            - kimi-k2.6
            - nemotron-3-120b
            - qwq-32b
            - llama-3.3-70b
            - llama-3.1-8b-fast
            - glm-4.7-flash
            - qwen-coder-32b
            - deepseek-r1-32b
            - qwen3-30b
            - mistral-small-3.1-24b
            - llama-3.2-3b
            - llama-3.2-1b
            - granite-4-micro
            - llama-4-scout
          default: gpt-oss-120b
        max_tokens:
          type: integer
          default: 2048
          maximum: 8192
          description: Maximum output tokens
        temperature:
          type: number
          default: 0.7
          minimum: 0
          maximum: 2
        top_p:
          type: number
          minimum: 0
          maximum: 1
          description: Nucleus sampling (model default if omitted)
        stream:
          type: boolean
          default: false
          description: Return a Server-Sent Events token stream
        web_search:
          type: boolean
          default: true
          description: >
            ON by default. Grounds the answer in live web results via Brave
            Search: returns a `sources` array, injects the current UTC time, and
            instructs the model to cite inline as [1], [2]. Set false to disable.
            In stream mode the answer is grounded but `sources` are not returned
            in-band (an `X-AMBIE-Web-Search` header is set instead).
        search_query:
          type: string
          description: Override the search query (defaults to the last user message)
        search_count:
          type: integer
          default: 5
          maximum: 10
          description: Number of web results to retrieve
        search_country:
          type: string
          description: 2-letter country code to bias results (e.g. "us")
        search_freshness:
          type: string
          enum: [pd, pw, pm, py]
          description: Restrict to results from the past day / week / month / year
        timezone:
          type: string
          description: >
            IANA timezone (e.g. "America/Cancun"). The current local time and
            exact UTC offset are computed server-side and given to the model so
            it never performs timezone math from memory. Falls back to the
            caller's Cloudflare-detected timezone when omitted.
          example: America/Cancun
        convert_times:
          type: boolean
          description: >
            Deterministic time conversion. When a timezone is resolved and the
            question is time-related, the model only extracts each source-stated
            time with its IANA zone and code does all offset math (via Intl),
            returning authoritative `event_times`. Auto-runs on time-intent; set
            true to force, false to disable.
        verify:
          type: boolean
          default: true
          description: >
            ON by default for grounded answers. Runs an independent fast model
            that fact-checks the answer against the sources and returns a
            `verification` object. Set false to skip (saves a call). No-op when
            there are no sources or in stream / text-format modes.
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    ChatSource:
      type: object
      properties:
        title:
          type: string
        url:
          type: string
          format: uri
        description:
          type: string
        age:
          type: string
          nullable: true
          description: Human-readable publish date when known

    ChatVerification:
      type: object
      description: Independent fact-check of the answer against the retrieved sources.
      properties:
        verified:
          type: boolean
          nullable: true
          description: True if the verifier judged the answer supported by the sources (null if the check was unavailable)
        confidence:
          type: number
          nullable: true
          description: Verifier confidence 0.0-1.0
        unsupported_claims:
          type: array
          items:
            type: string
          description: Claims the verifier could not support from the sources
        notes:
          type: string
        model:
          type: string
          example: "@cf/meta/llama-3.1-8b-instruct-fast"

    ChatUsage:
      type: object
      properties:
        prompt_tokens:
          type: integer
        completion_tokens:
          type: integer
        total_tokens:
          type: integer

    ChatResult:
      type: object
      properties:
        text:
          type: string
          description: The generated completion
        model:
          type: string
          example: "@cf/openai/gpt-oss-120b"
        model_name:
          type: string
          example: gpt-oss-120b
        usage:
          $ref: "#/components/schemas/ChatUsage"
        web_search:
          type: boolean
          description: Present and true when the answer was grounded in web results
        search_query:
          type: string
          description: The query sent to web search (when web_search was used)
        sources:
          type: array
          description: Web results used to ground the answer (when web_search was used)
          items:
            $ref: "#/components/schemas/ChatSource"
        search_error:
          type: string
          description: Set when web search was requested but failed (answer is ungrounded)
        verification:
          $ref: "#/components/schemas/ChatVerification"
        event_times:
          type: array
          description: >
            Code-computed event times in the user's timezone (offset math done
            via Intl, not the model). Present when time conversion ran.
          items:
            type: object
            properties:
              label:
                type: string
              stated:
                type: string
                description: The source-stated time + its IANA zone (e.g. "13:00 America/Mexico_City")
              utc:
                type: string
                format: date-time
              local:
                type: string
                description: The event time formatted in the user's timezone
              local_tz:
                type: string
        cf_cost_estimate:
          type: number
          description: Estimated Cloudflare compute cost in USD (informational)
        processing_ms:
          type: integer
        request_id:
          type: string
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    ChatHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Chat / Text Generation API
        version:
          type: string
        default_model:
          type: string
          example: gpt-oss-120b
        models:
          type: object
          additionalProperties: true
          description: Map of model name → capabilities, context window, and CF price
        ai_available:
          type: boolean

    SummarizeRequest:
      type: object
      properties:
        text:
          type: string
          description: "Text to summarize (max 50,000 chars). Provide text OR url."
          maxLength: 50000
        url:
          type: string
          format: uri
          description: URL to fetch and summarize
          example: https://en.wikipedia.org/wiki/Artificial_intelligence
        max_length:
          type: integer
          default: 1024
          maximum: 1024
          description: Maximum summary length in tokens
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    SummarizeResult:
      type: object
      properties:
        summary:
          type: string
          description: The generated summary
        model:
          type: string
          example: "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
        served_by:
          type: string
          description: The model that actually served (equals `model`; differs only on fallback).
        fallback:
          type: object
          nullable: true
          description: Non-null when the primary model failed and the 8B fallback served.
          properties:
            requested_model:
              type: string
            reason:
              type: string
        input_chars:
          type: integer
        output_chars:
          type: integer
        max_length:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        source:
          type: object
          description: Present when url was used
          properties:
            url:
              type: string
            content_type:
              type: string
            extracted_chars:
              type: integer
        client_id:
          type: string

    SummarizeHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Summarization API
        model:
          type: string
        features:
          type: object
        max_text_length:
          type: integer
          example: 50000
        max_summary_length:
          type: integer
          example: 1024
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Embeddings Schemas ---

    EmbeddingsRequest:
      type: object
      required: [text]
      properties:
        text:
          oneOf:
            - type: string
              description: Single text to embed
            - type: array
              items:
                type: string
              description: Batch of texts (max 100)
          example: "Semantic search query"
        model:
          type: string
          enum: [bge-base-en-v1.5, bge-large-en-v1.5, bge-small-en-v1.5, bge-m3, embeddinggemma, qwen3-embedding, plamo-embedding-ja]
          default: bge-base-en-v1.5
        format:
          type: string
          enum: [json, text]
          default: json
          description: "json = full response, text = tab-separated vectors"
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    EmbeddingsResult:
      type: object
      properties:
        data:
          type: array
          items:
            type: array
            items:
              type: number
          description: Array of embedding vectors
        shape:
          type: array
          items:
            type: integer
          example: [1, 768]
        model:
          type: string
          example: "@cf/baai/bge-base-en-v1.5"
        model_name:
          type: string
          example: bge-base-en-v1.5
        dimensions:
          type: integer
          example: 768
        total_items:
          type: integer
        total_chars:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    EmbeddingsHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Embeddings API
        default_model:
          type: string
        models:
          type: object
        max_batch_size:
          type: integer
          example: 100
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Rerank Schemas ---

    RerankRequest:
      type: object
      required: [query, contexts]
      properties:
        query:
          type: string
          description: The search query to rank against (max 1,000 chars)
          maxLength: 1000
          example: "best restaurant in Las Vegas"
        contexts:
          type: array
          items:
            type: string
          description: Texts to rerank by relevance (max 100)
          example:
            - "Fine dining at Bellagio with a prix fixe menu"
            - "Pizza place on the strip open late"
            - "Weather forecast for Tuesday"
        top_k:
          type: integer
          description: Return only the top K most relevant results
          example: 5
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    RerankResult:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
                description: Original index in the contexts array
              score:
                type: number
                description: Relevance score (0-1, sigmoid)
                example: 0.8923
              text:
                type: string
                description: The original context text
        query:
          type: string
        model:
          type: string
          example: "@cf/baai/bge-reranker-base"
        total_contexts:
          type: integer
        returned:
          type: integer
        top_k:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    RerankHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Rerank API
        model:
          type: string
        features:
          type: object
        max_contexts:
          type: integer
          example: 100
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Moderate Schemas ---

    ModerateRequest:
      type: object
      required: [text]
      properties:
        text:
          type: string
          description: Text to screen for safety (max 50,000 chars)
          maxLength: 50000
          example: "How do I bake a chocolate cake?"
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    ModerateResult:
      type: object
      properties:
        safe:
          type: boolean
          description: Whether the text is safe
          example: true
        flagged:
          type: boolean
          description: Inverse of safe (true = unsafe)
          example: false
        categories:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
                example: S10
              name:
                type: string
                example: Hate
          description: Flagged hazard categories (empty if safe)
        category_count:
          type: integer
          example: 0
        model:
          type: string
          example: "@cf/meta/llama-guard-3-8b"
        input_chars:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    ModerateHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Content Moderation API
        model:
          type: string
        features:
          type: object
        categories:
          type: object
          description: Map of S1-S14 hazard category codes to names
        max_text_length:
          type: integer
          example: 50000
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Detect Language Schemas ---

    DetectLangRequest:
      type: object
      required: [text]
      properties:
        text:
          oneOf:
            - type: string
              description: Single text to analyze
            - type: array
              items:
                type: string
              description: Batch of texts (max 50)
          example: "Bonjour, comment allez-vous?"
        format:
          type: string
          enum: [json, text]
          default: json
        callback_url:
          type: string
          format: uri
        client_id:
          type: string

    DetectLangResult:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              text:
                type: string
                description: Input text (truncated to 100 chars)
              language:
                type: string
                description: ISO 639-1 language code
                example: fr
              language_name:
                type: string
                example: French
              confidence:
                type: number
                example: 0.98
        model:
          type: string
        total_items:
          type: integer
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time
        client_id:
          type: string

    DetectLangHealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: AMBIE Language Detection API
        model:
          type: string
        features:
          type: object
        max_batch_size:
          type: integer
          example: 50
        rate_limit:
          type: string
        ai_available:
          type: boolean

    # --- Envelope Schemas ---

    Envelope:
      type: object
      description: Standard success envelope for all native /api/v1/* endpoints (except OpenAI facade and binary responses).
      required: [ok, request_id, data]
      properties:
        ok:
          type: boolean
          enum: [true]
          description: Always true on success
        request_id:
          type: string
          format: uuid
          description: Unique request identifier for log correlation
        data:
          type: object
          description: The endpoint-specific payload

    ErrorEnvelope:
      type: object
      description: Standard error envelope for all native /api/v1/* endpoints.
      required: [ok, request_id, error]
      properties:
        ok:
          type: boolean
          enum: [false]
          description: Always false on error
        request_id:
          type: string
          format: uuid
          description: Unique request identifier for log correlation
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - bad_request
                - unauthorized
                - payment_required
                - forbidden
                - not_found
                - payload_too_large
                - rate_limited
                - upstream_error
                - service_unavailable
                - internal_error
                - invalid_model
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message

    # --- Image Generation Schemas ---

    ImagineRequest:
      type: object
      required: [prompt]
      properties:
        prompt:
          type: string
          description: Text prompt describing the image to generate (max 2048 chars)
          maxLength: 2048
          example: "a neon-lit cityscape at night, cyberpunk style"
        model:
          type: string
          enum:
            - flux-1-schnell
            - flux-2-klein-4b
            - flux-2-klein-9b
            - flux-2-dev
            - lucid-origin
            - phoenix
          default: flux-1-schnell
          description: Image generation model to use
        width:
          type: integer
          description: Image width in pixels
          example: 1024
        height:
          type: integer
          description: Image height in pixels
          example: 1024
        steps:
          type: integer
          description: Number of diffusion steps (model-dependent maximum)
          example: 4
        guidance:
          type: number
          description: Guidance scale (prompt adherence vs. creativity)
          example: 7.5
        seed:
          type: integer
          description: Random seed for reproducible outputs
          example: 42
        negative_prompt:
          type: string
          description: Things to exclude from the generated image
          example: "blurry, low quality, watermark"
        format:
          type: string
          enum: [image, json]
          default: image
          description: "image = raw JPEG bytes (default), json = enveloped response with image_base64"

    ImagineResult:
      type: object
      description: Returned inside the `data` envelope when format=json
      properties:
        image_base64:
          type: string
          description: Base64-encoded JPEG image
        format:
          type: string
          example: jpeg
        model:
          type: string
          example: "@cf/black-forest-labs/flux-1-schnell"
        model_name:
          type: string
          example: flux-1-schnell
        width:
          type: integer
        height:
          type: integer
        steps:
          type: integer
        seed:
          type: integer
          nullable: true
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- Edit Image Schemas ---

    EditImageRequest:
      type: object
      required: [prompt]
      description: Provide `image` OR `image_url`. For inpainting, also provide `mask` or `mask_url`.
      properties:
        prompt:
          type: string
          description: Text prompt describing the desired edit (max 2048 chars)
          maxLength: 2048
          example: "replace the sky with a dramatic sunset"
        image:
          type: string
          description: Source image as base64 string, data URI, or raw bytes (provide this OR image_url)
        image_url:
          type: string
          description: HTTP/HTTPS URL or data URI of the source image (provide this OR image)
          example: "https://example.com/photo.jpg"
        mask:
          type: string
          description: Mask image as base64 string or data URI — white = regenerate, black = preserve (inpainting model)
        mask_url:
          type: string
          description: HTTP/HTTPS URL or data URI of the mask image (provide this OR mask)
        model:
          type: string
          enum:
            - img2img
            - inpainting
          default: img2img
          description: |
            `img2img` (default) — @cf/runwayml/stable-diffusion-v1-5-img2img, full-image transform.
            `inpainting` — @cf/runwayml/stable-diffusion-v1-5-inpainting, mask-guided region edit.
        strength:
          type: number
          minimum: 0
          maximum: 1
          description: How strongly the prompt overrides the source image (0 = no change, 1 = full regeneration)
          example: 0.75
        guidance:
          type: number
          description: Guidance scale (prompt adherence vs. creativity)
          example: 7.5
        num_steps:
          type: integer
          maximum: 20
          description: Number of diffusion steps (max 20)
          example: 20
        negative_prompt:
          type: string
          description: Things to exclude from the output
          example: "blurry, low quality, watermark"
        height:
          type: integer
          minimum: 256
          maximum: 2048
          description: Output height in pixels
          example: 512
        width:
          type: integer
          minimum: 256
          maximum: 2048
          description: Output width in pixels
          example: 512
        seed:
          type: integer
          description: Random seed for reproducible outputs
          example: 42
        format:
          type: string
          enum: [image, json]
          default: image
          description: "image = raw PNG bytes (default), json = enveloped response with image_base64"

    EditImageResult:
      type: object
      description: Returned inside the `data` envelope when format=json
      properties:
        image_base64:
          type: string
          description: Base64-encoded PNG image
        format:
          type: string
          example: png
        model:
          type: string
          example: "@cf/runwayml/stable-diffusion-v1-5-img2img"
        model_name:
          type: string
          example: img2img
        width:
          type: integer
        height:
          type: integer
        seed:
          type: integer
          nullable: true
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- OpenAI Compat Schemas ---

    OpenAIImagesRequest:
      type: object
      required: [prompt]
      properties:
        prompt:
          type: string
          description: Text prompt describing the image to generate
          example: "a photorealistic cat sitting on a cloud"
        model:
          type: string
          description: AMBIE model name (maps to the /imagine model enum; defaults to flux-1-schnell)
          example: flux-1-schnell
        size:
          type: string
          enum: ["256x256", "512x512", "1024x1024", "1024x1792", "1792x1024"]
          default: "1024x1024"
          description: Output image dimensions
        n:
          type: integer
          minimum: 1
          maximum: 1
          default: 1
          description: Number of images to generate (only 1 supported)

    OpenAIImagesResponse:
      type: object
      required: [created, data]
      properties:
        created:
          type: integer
          description: Unix timestamp of the generation
          example: 1749600000
        data:
          type: array
          items:
            type: object
            required: [b64_json]
            properties:
              b64_json:
                type: string
                description: Base64-encoded JPEG image

    # --- Vision Schemas ---

    VisionRequest:
      type: object
      description: Provide `image` (base64/data URI) OR `image_url` (HTTP URL or data URI).
      properties:
        image:
          type: string
          description: Base64-encoded image, data URI, or raw bytes (provide this OR image_url)
        image_url:
          type: string
          description: HTTP/HTTPS URL or data URI of the image (provide this OR image)
          example: "https://example.com/photo.jpg"
        prompt:
          type: string
          description: Question or instruction for the model
          default: "Describe this image in detail."
          example: "What objects are visible in this image?"
        model:
          type: string
          enum:
            - llama-4-scout
            - llama-3.2-11b
            - gemma-4-vision
            - llava-1.5
          default: llama-4-scout
          description: Vision model to use
        max_tokens:
          type: integer
          default: 512
          maximum: 2048
          description: Maximum number of tokens to generate

    VisionResult:
      type: object
      description: Returned inside the `data` envelope
      properties:
        text:
          type: string
          description: The model's textual response
        model:
          type: string
          example: "@cf/meta/llama-4-scout-17b-16e-instruct"
        model_name:
          type: string
          example: llama-4-scout
        image_bytes:
          type: integer
          description: Size of the input image in bytes
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- Classify Schemas ---

    ClassifyRequest:
      type: object
      description: Provide `image` (base64/data URI) OR `image_url`.
      properties:
        image:
          type: string
          description: Base64-encoded image or data URI (provide this OR image_url)
        image_url:
          type: string
          description: HTTP/HTTPS URL or data URI of the image
          example: "https://example.com/dog.jpg"
        top_k:
          type: integer
          description: Return only the top K predictions
          example: 5

    ClassifyResult:
      type: object
      description: Returned inside the `data` envelope
      properties:
        results:
          type: array
          items:
            type: object
            required: [label, score]
            properties:
              label:
                type: string
                example: golden retriever
              score:
                type: number
                example: 0.9512
        model:
          type: string
          example: "@cf/microsoft/resnet-50"
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- Object Detection Schemas ---

    DetectRequest:
      type: object
      description: Provide `image` (base64/data URI) OR `image_url`.
      properties:
        image:
          type: string
          description: Base64-encoded image or data URI (provide this OR image_url)
        image_url:
          type: string
          description: HTTP/HTTPS URL or data URI of the image
          example: "https://example.com/street.jpg"
        min_score:
          type: number
          description: Minimum confidence threshold to include a detection (0-1)
          example: 0.5

    DetectResult:
      type: object
      description: Returned inside the `data` envelope
      properties:
        objects:
          type: array
          items:
            type: object
            required: [score, label, box]
            properties:
              score:
                type: number
                example: 0.9743
              label:
                type: string
                example: cat
              box:
                type: object
                required: [xmin, ymin, xmax, ymax]
                properties:
                  xmin:
                    type: integer
                    example: 100
                  ymin:
                    type: integer
                    example: 50
                  xmax:
                    type: integer
                    example: 400
                  ymax:
                    type: integer
                    example: 350
        count:
          type: integer
          description: Total number of objects detected
        model:
          type: string
          example: "@cf/facebook/detr-resnet-50"
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- Turn Detection Schemas ---

    TurnRequest:
      type: object
      description: Provide `audio` as base64 in JSON, or as a file field in multipart/form-data.
      properties:
        audio:
          type: string
          description: Base64-encoded audio data (JSON) or binary file (multipart)
        dtype:
          type: string
          enum: [float32, uint8, float64]
          default: float32
          description: Sample data type expected by the model

    TurnResult:
      type: object
      description: Returned inside the `data` envelope
      properties:
        is_complete:
          type: boolean
          description: True if the speaker has finished their turn (hand off to the bot)
        probability:
          type: number
          description: Confidence that the utterance is complete (0-1)
          example: 0.93
        model:
          type: string
          example: "@cf/pipecat-ai/smart-turn-v2"
        processing_ms:
          type: integer
        request_id:
          type: string
          format: uuid
        processed_at:
          type: string
          format: date-time

    # --- Shared Schemas ---

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Human-readable error message
        detail:
          type: string
          description: "Additional error context (on 502 errors)"
        request_id:
          type: string
          format: uuid

  parameters:
    RequestId:
      name: request_id
      in: path
      required: true
      description: The request_id returned from the async POST
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Unauthorized:
      description: Missing authentication
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Forbidden:
      description: Invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    UpstreamError:
      description: Upstream AI model failed or timed out
      headers:
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    Unavailable:
      description: Service unavailable
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
