Marcel Routing API documentation

This is the public, JavaScript-free documentation for the Marcel router. The endpoint reference and schemas below are generated from the same OpenAPI source used by Marcel's SDKs. They contain only public routing endpoints, not private customer or administration endpoints.

Base URL: https://marcel-agent.com/api/v1

Authenticate

All /api/v1 operations require a Marcel API key in the Authorization header. Reading /api/catalog and these guides is free and unauthenticated. Authentication is not a bot block: a 401 from an operational endpoint means the caller needs a valid key.

Authorization: Bearer YOUR_MARCEL_API_KEY
Content-Type: application/json

Send a chat completion

from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ["MARCEL_API_KEY"],
    base_url="https://marcel-agent.com/api/v1",
)
response = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[{"role": "user", "content": "Summarize this business objective."}],
    max_completion_tokens=1024,
)
print(response.choices[0].message.content)

Use the live catalog for available model IDs. max_tokens and max_completion_tokens are aliases; if both are present, their values must agree. The accepted limit is 1 through 32768.

Function calls and worker fallback

On supported models, provide function definitions in tools. When an assistant returns tool_calls, execute the permitted functions in the client. Append that complete assistant message and one tool result per tool_call_id to the next request. Never drop calls when switching models or omit a parallel tool result.

response = client.chat.completions.create(
    model="openai/gpt-5.6-sol",
    messages=messages,
    tools=tools,
    extra_body={"fallback_model": "anthropic/claude-sonnet-5"},
    timeout=210.0,
)
print(response.model)

fallback_model permits one retry on transient provider failure. It is opt-in and must identify the caller's main model. Marcel does not restart external workers, execute tools, or replay their side effects. Insufficient credit, malformed input and client disconnects do not trigger fallback.

Streaming

Set stream: true for OpenAI-compatible SSE. Chat output is buffered: headers and keepalive comments arrive while the upstream works, then content and indexed tool-call chunks are delivered. The stream ends with [DONE] on success; failures are sent as error objects. Clients must process errors, not treat every HTTP 200 stream as successful. Each provider attempt has a 90-second deadline.

Errors and idempotency

Operational endpoints can return 400 for invalid input, 401 for missing or invalid keys, 402 for insufficient prepaid credit, 404 for unknown resources, 429 for rate limits, and 5xx for upstream or server failure. Error responses contain an error object. Chat responses include X-Marcel-Request-Id for diagnostics.

An Idempotency-Key protects supported operations against duplicate work. On chat, reusing a key is rejected; it does not replay a stored response. Do not blindly retry actions or tool side effects. Consult each operation's schema and restrictions below.

Images, video and realtime voice

Image generation returns generated image data; it is not image editing. Video generation is asynchronous: submit a job, poll its status, and retrieve content when ready. Some model/resolution/duration combinations are restricted; consult the catalog and operation schema.

Realtime voice requires explicit recording and audio-processing consent when creating a session. The connection uses a short-lived, one-use session token rather than exposing upstream provider credentials.

Other router operations

The complete endpoint reference covers embeddings, moderation, reranking, text translation, multipart document translation with status and file download, tool discovery, and integrated tool execution. Request fields, response envelopes, content types and authentication requirements are included below.

Keep private data private

These documents do not grant access to accounts or paid API calls. Never paste real credentials into documentation examples. Private portal pages and operational endpoints still enforce their normal authentication and authorization.

Complete endpoint index

Complete routing contract

openapi: 3.1.0
info:
  # Do not change the title, if the title changes, the import paths will be broken
  title: Marcel Routing Public API
  version: 0.2.0
  description: Marcel Routing OpenAI-compatible gateway
servers:
  - url: https://marcel-agent.com/api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: marcel
    description: Marcel model routing operations
  - name: tools
    description: Search and data tool operations
paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      description: Returns server health status
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"
  /catalog:
    get:
      operationId: getPublicCatalog
      tags: [marcel]
      summary: Get the public Marcel provider, model, and tool catalog
      responses:
        "200":
          description: Public routing catalog
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicCatalog"
  /v1/models:
    get:
      operationId: listMarcelModels
      tags: [marcel]
      summary: List available Marcel models
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Available models
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModelList"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/chat/completions:
    post:
      operationId: createMarcelChatCompletion
      tags: [marcel]
      summary: Create an OpenAI-compatible chat completion
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
      responses:
        "200":
          description: Chat completion or server-sent event stream
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChatCompletion"
            text/event-stream:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/realtime/sessions:
    post:
      operationId: createRealtimeSession
      tags: [marcel]
      summary: Issue a one-use token for a Marcel realtime WebSocket
      description: Requires explicit consent. The token expires after 60 seconds, can be used once, and never contains provider credentials. The session has a hard customer spending ceiling of $5 for OpenAI or $1 for Gemini; the connection closes at the ceiling and unused reserved credit is reconciled.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RealtimeSessionRequest"
      responses:
        "201":
          description: Short-lived WebSocket token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RealtimeSession"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
  /v1/realtime/connect:
    get:
      operationId: connectRealtime
      tags: [marcel]
      summary: WebSocket upgrade endpoint for OpenAI Realtime or Gemini Live
      description: WebSocket endpoint. Use `new WebSocket(url, ["marcel-realtime", session.token])`; never place the token in a URL. Client messages and provider events are relayed bidirectionally, including interruption events. Maximum 30 minutes, 10,000 messages, and 25 MB audio per session.
      externalDocs:
        description: JavaScript client examples (consent, OpenAI session.update, Gemini setup, audio append, and interruption handling)
        url: https://platform.openai.com/docs/guides/realtime
      x-codeSamples:
        - lang: javascript
          label: OpenAI Realtime
          source: |
            const consent = { model: "openai/gpt-realtime-2.1",
              recording_consent: true, audio_processing_consent: true };
            const session = await fetch("/api/v1/realtime/sessions", {
              method: "POST", headers: { Authorization: `Bearer ${marcelKey}`,
              "Content-Type": "application/json" }, body: JSON.stringify(consent)
            }).then(r => r.json());
            const ws = new WebSocket("/api/v1/realtime/connect",
              ["marcel-realtime", session.token]);
            ws.onopen = () => {
              ws.send(JSON.stringify({ type: "session.update",
                session: { type: "realtime", instructions: "Be concise." }}));
              // Send 16-bit PCM audio as base64.
              ws.send(JSON.stringify({
                type: "input_audio_buffer.append", audio: pcmBase64
              }));
            };
            ws.onmessage = ({data}) => { const event = JSON.parse(data);
              if (event.type === "input_audio_buffer.speech_started")
                ws.send(JSON.stringify({ type: "response.cancel" })); };
        - lang: javascript
          label: Gemini Live
          source: |
            const session = await fetch("/api/v1/realtime/sessions", {
              method: "POST",
              headers: { Authorization: `Bearer ${marcelKey}`,
                "Content-Type": "application/json" },
              body: JSON.stringify({
                model: "google/gemini-3.1-flash-live-preview",
                recording_consent: true,
                audio_processing_consent: true
              })
            }).then(r => r.json());
            const ws = new WebSocket("/api/v1/realtime/connect",
              ["marcel-realtime", session.token]);
            ws.onopen = () => {
              // Marcel locks setup.model to the model reserved above.
              ws.send(JSON.stringify({ setup: {
                model: "models/gemini-3.1-flash-live-preview",
                generationConfig: { responseModalities: ["AUDIO"] }
              }}));
              ws.send(JSON.stringify({ realtimeInput: { audio: {
                data: pcmBase64, mimeType: "audio/pcm;rate=16000"
              }}}));
            };
            ws.onmessage = ({data}) => {
              const event = JSON.parse(data);
              if (event.serverContent?.interrupted)
                console.log("Model output interrupted by the caller");
            };
      responses:
        "101": { description: WebSocket protocol switch }
  /v1/images/generations:
    post:
      operationId: createMarcelImage
      tags: [marcel]
      summary: Generate images from a text prompt
      description: OpenAI-compatible text-to-image generation. This operation does not accept image inputs and is separate from chat vision support.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImageGenerationInput"
      responses:
        "200":
          description: Generated images
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ImageGenerationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/videos:
    post:
      operationId: createMarcelVideo
      tags: [marcel]
      summary: Start an asynchronous Sora or Veo video generation job
      description: Prompts are safety-checked before submission. The maximum model-specific cost is reserved immediately and reconciled when the job completes or fails. Job metadata and generated media expire after 24 hours.
      security:
        - bearerAuth: []
      parameters:
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VideoGenerationInput"
      responses:
        "202":
          description: Video job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VideoJob"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/videos/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
    get:
      operationId: getMarcelVideo
      tags: [marcel]
      summary: Inspect and refresh a video job
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Current video job state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VideoJob"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/ProviderError"
    delete:
      operationId: deleteMarcelVideo
      tags: [marcel]
      summary: Delete a video job and its retained media
      security:
        - bearerAuth: []
      responses:
        "204":
          description: Video job deleted
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/videos/{id}/content:
    get:
      operationId: downloadMarcelVideo
      tags: [marcel]
      summary: Download completed video content
      description: Streams completed media from Marcel's private object storage as an attachment. Media is copied from the provider promptly on completion and retained until the job expires.
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Generated video file
          content:
            video/mp4:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/BadRequest"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/embeddings:
    post:
      operationId: createMarcelEmbeddings
      tags: [embeddings]
      summary: Create OpenAI-compatible embeddings
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingRequest"
      responses:
        "200":
          description: Embeddings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/moderations:
    post:
      operationId: createMarcelModeration
      tags: [safety]
      summary: Moderate text with OpenAI omni-moderation
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ModerationRequest"
      responses:
        "200":
          description: Moderation result
          content:
            application/json:
              schema:
                type: object
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/rerank:
    post:
      operationId: createMarcelRerank
      tags: [rerank]
      summary: Rerank documents with Jina
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RerankRequest"
      responses:
        "200":
          description: Ranked documents
          content:
            application/json:
              schema:
                type: object
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/translate:
    post:
      operationId: createMarcelTranslation
      tags: [translation]
      summary: Translate text with DeepL while preserving formatting
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TranslationRequest"
      responses:
        "200":
          description: DeepL translations
          content:
            application/json:
              schema:
                type: object
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/documents/translations:
    post:
      operationId: createMarcelDocumentTranslation
      tags: [translation]
      summary: Upload a PDF, DOCX, or PPTX for layout-preserving translation
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/DocumentTranslationUpload"
      responses:
        "202":
          description: Translation accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentTranslation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "409":
          description: Original idempotent request is still being created
        "413":
          description: Document exceeds the size limit
        "415":
          description: Unsupported document format
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/documents/translations/{id}:
    get:
      operationId: getMarcelDocumentTranslation
      tags: [translation]
      summary: Get document translation status
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Current translation status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentTranslation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Translation not found
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/documents/translations/{id}/content:
    get:
      operationId: downloadMarcelDocumentTranslation
      tags: [translation]
      summary: Download a completed translated document
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Translated document binary
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.wordprocessingml.document:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.presentationml.presentation:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Translation not found
        "409":
          description: Translation is not ready
        "502":
          $ref: "#/components/responses/ProviderError"
  /v1/tools:
    get:
      operationId: listMarcelTools
      tags: [tools]
      summary: List available search and data tools
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Available tools
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ToolList"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /v1/tools/search:
    post:
      operationId: runMarcelSearch
      tags: [tools]
      summary: Run a normalized search or financial data tool
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ToolRequest"
      responses:
        "200":
          description: Normalized tool response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ToolResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BudgetExceeded"
        "404":
          $ref: "#/components/responses/NotFound"
        "502":
          $ref: "#/components/responses/ProviderError"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: MarcelClientApiKey
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing or invalid client API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    BudgetExceeded:
      description: Client budget exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Model or tool not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ProviderError:
      description: Upstream provider failure
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    HealthStatus:
      type: object
      properties:
        status:
          type: string
      required:
        - status
    ModelMetadata:
      type: object
      properties:
        capabilities:
          type: array
          items:
            type: string
        recommended_for:
          type: array
          items:
            type: string
        cost_tier:
          type: string
        speed_tier:
          type: string
        quality_tier:
          type: string
        context_window:
          type: integer
        supports_streaming:
          type: boolean
        supports_tool_calls:
          type: boolean
        supports_json_schema:
          type: boolean
      required:
        - capabilities
        - recommended_for
        - cost_tier
        - speed_tier
        - quality_tier
        - context_window
        - supports_streaming
        - supports_tool_calls
        - supports_json_schema
    MarcelModel:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          enum: [model]
        owned_by:
          type: string
        metadata:
          $ref: "#/components/schemas/ModelMetadata"
      required: [id, object, owned_by, metadata]
    ModelList:
      type: object
      properties:
        object:
          type: string
          enum: [list]
        data:
          type: array
          items:
            $ref: "#/components/schemas/MarcelModel"
      required: [object, data]
    ChatMessage:
      type: object
      properties:
        role:
          type: string
          enum: [system, developer, user, assistant, tool]
        content:
          nullable: true
        name:
          type: string
        tool_call_id:
          type: string
        tool_calls:
          type: array
          items:
            type: object
      required: [role]
    ChatTool:
      type: object
      properties:
        type:
          type: string
          enum: [function]
        function:
          type: object
          properties:
            name:
              type: string
            description:
              type: string
            parameters:
              type: object
          required: [name, parameters]
      required: [type, function]
    ChatCompletionRequest:
      type: object
      properties:
        model:
          type: string
        messages:
          type: array
          items:
            $ref: "#/components/schemas/ChatMessage"
        tools:
          type: array
          items:
            $ref: "#/components/schemas/ChatTool"
        tool_choice:
          nullable: true
        stream:
          type: boolean
        temperature:
          type: number
        max_tokens:
          type: integer
          minimum: 1
          maximum: 32768
        max_completion_tokens:
          type: integer
          minimum: 1
          maximum: 32768
          description: Alias of max_tokens; conflicting values are rejected.
        fallback_model:
          type: string
          description: Optional main model to attempt once on upstream rate limits, unavailability, transport errors or timeout. No fallback on invalid requests, insufficient credit or disconnect. The response model and billing identify the model that actually answered.
        response_format:
          nullable: true
      required: [model, messages]
    RealtimeSessionRequest:
      type: object
      properties:
        model:
          type: string
          enum: [openai/gpt-realtime-2.1, google/gemini-3.1-flash-live-preview]
        recording_consent:
          type: boolean
          description: Explicit consent to record/process audio for this session
        audio_processing_consent:
          type: boolean
          description: Explicit consent to send audio to the selected provider
      required: [model, recording_consent, audio_processing_consent]
    RealtimeSession:
      type: object
      properties:
        token: { type: string }
        expires_at: { type: string, format: date-time }
        model: { type: string }
        websocket_url: { type: string }
      required: [token, expires_at, model, websocket_url]
    ChatUsage:
      type: object
      properties:
        prompt_tokens:
          type: integer
        completion_tokens:
          type: integer
        total_tokens:
          type: integer
        cost_usd:
          type: number
      required: [prompt_tokens, completion_tokens, total_tokens, cost_usd]
    ChatCompletion:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          enum: [chat.completion]
        created:
          type: integer
        model:
          type: string
        choices:
          type: array
          items:
            type: object
        usage:
          $ref: "#/components/schemas/ChatUsage"
      required: [id, object, created, model, choices, usage]
    ImageGenerationInput:
      type: object
      properties:
        model:
          type: string
        prompt:
          type: string
          minLength: 1
        n:
          type: integer
          minimum: 1
          maximum: 10
          default: 1
        size:
          type: string
          enum: [1024x1024, 1024x1536, 1536x1024]
        quality:
          type: string
          enum: [low, medium, high]
        response_format:
          type: string
          enum: [url, b64_json]
      required: [model, prompt]
    GeneratedImage:
      type: object
      properties:
        url:
          type: string
          format: uri
        b64_json:
          type: string
        revised_prompt:
          type: string
    ImageGenerationResponse:
      type: object
      properties:
        created:
          type: integer
        data:
          type: array
          items:
            $ref: "#/components/schemas/GeneratedImage"
      required: [created, data]
    VideoGenerationInput:
      type: object
      properties:
        model:
          type: string
          enum:
            - openai/sora-2
            - openai/sora-2-pro
            - google/veo-3.1-fast
            - google/veo-3.1
        prompt:
          type: string
          minLength: 1
          maxLength: 4000
        duration_seconds:
          type: integer
          enum: [4, 6, 8, 12]
          default: 4
          description: Sora supports 4, 8, or 12 seconds. Veo supports 4, 6, or 8 seconds.
        resolution:
          type: string
          enum: [720p, 1024p, 1080p]
          default: 720p
          description: sora-2 supports 720p; sora-2-pro supports 720p and 1024p; Veo supports 720p and supports 1080p only for 8-second jobs.
        aspect_ratio:
          type: string
          enum: ["16:9", "9:16"]
          default: "16:9"
      required: [model, prompt]
    VideoJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum: [video.job]
        model:
          type: string
        status:
          type: string
          enum:
            [
              queued,
              submitting,
              in_progress,
              completed,
              failed,
              deleting,
              deleted,
            ]
        duration_seconds:
          type: integer
        resolution:
          type: string
          enum: [720p, 1024p, 1080p]
        aspect_ratio:
          type: string
          enum: ["16:9", "9:16"]
        created_at:
          type: string
          format: date-time
        completed_at:
          anyOf:
            - type: string
              format: date-time
            - type: "null"
        expires_at:
          type: string
          format: date-time
        error:
          anyOf:
            - type: object
              properties:
                code:
                  type: [string, "null"]
                message:
                  type: [string, "null"]
            - type: "null"
      required:
        - id
        - object
        - model
        - status
        - duration_seconds
        - resolution
        - aspect_ratio
        - created_at
        - completed_at
        - expires_at
        - error
    EmbeddingRequest:
      type: object
      properties:
        model:
          type: string
        input:
          oneOf:
            - type: string
            - type: array
              minItems: 1
              maxItems: 256
              items:
                type: string
        encoding_format:
          type: string
          enum: [float, base64]
        dimensions:
          type: integer
          minimum: 1
      required: [model, input]
    EmbeddingResponse:
      type: object
      properties:
        object:
          type: string
        model:
          type: string
        data:
          type: array
          items:
            type: object
        usage:
          type: object
      required: [object, model, data, usage]
    ModerationRequest:
      type: object
      properties:
        model:
          type: string
          enum: [openai/omni-moderation-latest]
        input:
          oneOf:
            - type: string
            - type: array
              minItems: 1
              items:
                type: string
      required: [input]
    RerankRequest:
      type: object
      properties:
        model:
          type: string
          enum: [jina/jina-reranker-v3.5]
        query:
          type: string
        documents:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: string
        top_n:
          type: integer
          minimum: 1
      required: [model, query, documents]
    TranslationRequest:
      type: object
      properties:
        text:
          oneOf:
            - type: string
            - type: array
              minItems: 1
              maxItems: 50
              items:
                type: string
        target_lang:
          type: string
        source_lang:
          type: string
        preserve_formatting:
          type: boolean
          default: true
      required: [text, target_lang]
    DocumentTranslation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum: [document.translation]
        status:
          type: string
          enum: [queued, translating, done, error, downloaded, expired]
        file_name:
          type: string
        content_type:
          type: string
        file_size_bytes:
          type: integer
        source_lang:
          type: [string, "null"]
        target_lang:
          type: string
        billed_characters:
          type: [integer, "null"]
        cost_usd:
          type: [number, "null"]
        error:
          type: [string, "null"]
        expires_at:
          type: string
          format: date-time
        downloaded_at:
          type: [string, "null"]
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - object
        - status
        - file_name
        - content_type
        - file_size_bytes
        - target_lang
        - expires_at
        - created_at
        - updated_at
    DocumentTranslationUpload:
      type: object
      properties:
        file:
          type: string
          format: binary
        target_lang:
          type: string
        source_lang:
          type: string
      required: [file, target_lang]
    ToolList:
      type: object
      properties:
        object:
          type: string
          enum: [list]
        data:
          type: array
          items:
            type: object
      required: [object, data]
    ToolRequest:
      type: object
      properties:
        provider:
          type: string
          enum: [brave, tavily, exa, perplexity, financial_datasets]
        query:
          type: string
        max_results:
          type: integer
        ticker:
          type: string
        resource:
          type: string
      required: [provider, query]
    ToolResponse:
      type: object
      properties:
        provider:
          type: string
        query:
          type: string
        data:
          nullable: true
        usage:
          type: object
      required: [provider, query, data, usage]
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            type:
              type: string
            code:
              type: string
          required: [message, type, code]
      required: [error]
    PublicCatalog:
      type: object
      properties:
        providers:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              models:
                type: array
                items:
                  $ref: "#/components/schemas/MarcelModel"
            required: [id, name, models]
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
        video_models:
          type: array
          items:
            type: object
            additionalProperties: true
        realtime_models:
          type: array
          items:
            type: object
            additionalProperties: true
      required: [providers, tools, video_models, realtime_models]