# 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 - [Router OpenAPI YAML](/openapi.yaml) - [Live model, pricing, tool, video and voice catalog](/api/catalog) - [Routing introduction](/routing.md) - [Agent discovery index](/llms.txt) ## 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. ```http Authorization: Bearer YOUR_MARCEL_API_KEY Content-Type: application/json ``` ## Send a chat completion ```python 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. ```python 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 - GET /api/healthz — Health check - GET /api/catalog — Get the public Marcel provider, model, and tool catalog - GET /api/v1/models — List available Marcel models - POST /api/v1/chat/completions — Create an OpenAI-compatible chat completion - POST /api/v1/realtime/sessions — Issue a one-use token for a Marcel realtime WebSocket - GET /api/v1/realtime/connect — WebSocket upgrade endpoint for OpenAI Realtime or Gemini Live - POST /api/v1/images/generations — Generate images from a text prompt - POST /api/v1/videos — Start an asynchronous Sora or Veo video generation job - GET /api/v1/videos/{id} — Inspect and refresh a video job - DELETE /api/v1/videos/{id} — Delete a video job and its retained media - GET /api/v1/videos/{id}/content — Download completed video content - POST /api/v1/embeddings — Create OpenAI-compatible embeddings - POST /api/v1/moderations — Moderate text with OpenAI omni-moderation - POST /api/v1/rerank — Rerank documents with Jina - POST /api/v1/translate — Translate text with DeepL while preserving formatting - POST /api/v1/documents/translations — Upload a PDF, DOCX, or PPTX for layout-preserving translation - GET /api/v1/documents/translations/{id} — Get document translation status - GET /api/v1/documents/translations/{id}/content — Download a completed translated document - GET /api/v1/tools — List available search and data tools - POST /api/v1/tools/search — Run a normalized search or financial data tool ## Complete routing contract ```yaml 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] ```