{
  "openapi": "3.1.0",
  "info": {
    "title": "Ekravo",
    "version": "1.0.0",
    "description": "Ekravo is an agentic assistant for research, persistent work, and code. Its public API exposes OpenAI-compatible Chat Completions and Anthropic-compatible Messages, with a quota-aware Ekravo model route available by default.",
    "x-default-model": "ekravo/auto",
    "x-product-capabilities": [
      "server-side agent execution",
      "live web research",
      "streaming responses",
      "client-executed tool calls",
      "OpenAI compatibility",
      "Anthropic compatibility"
    ],
    "x-links": {
      "documentation": "https://ekravo.com/docs",
      "aiManifest": "https://ekravo.com/ai",
      "aiMarkdown": "https://ekravo.com/ai.md",
      "health": "https://ekravo.com/api/health"
    },
    "x-agent-loop": {
      "appliesWhen": "Ekravo-mode API keys on POST /v1/chat/completions without a client-supplied tools field. Raw-mode keys and any request with client tools bypass the server agent loop.",
      "controlledBy": "reasoning_effort",
      "note": "Separate from provider-native reasoning tokens. This is Ekravo's server-side tool-use step budget, not an OpenAI reasoning token knob on every model.",
      "maxAgentIterations": {
        "low": 12,
        "medium": 24,
        "high": 52,
        "auto": 24,
        "none": 12
      }
    }
  },
  "externalDocs": {
    "description": "Human API reference",
    "url": "https://ekravo.com/docs"
  },
  "servers": [
    {
      "url": "https://ekravo.com/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/models": {
      "get": {
        "summary": "List available models",
        "description": "Returns the OpenAI-compatible model id list (id, object, created, owned_by, context_length, catalog — no pricing). Send an API key to list only the models that key may call; without a key the full enabled list is returned. For pricing and account gating, see GET /api/chat/models. For BYOK models, see GET /v1/models/byok.",
        "responses": {
          "200": {
            "description": "Model list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/models/byok": {
      "get": {
        "summary": "List BYOK models for authenticated user",
        "description": "Returns OpenAI-shaped model descriptors for enabled models from providers where the user has configured a BYOK API key, plus enabled custom providers in the operator's BYOK catalog. Each model id is prefixed with `byok/`. Authenticate with a dashboard session cookie or an Ekravo API key.",
        "security": [
          {
            "cookieAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "BYOK model list with byok/ prefixed ids and configured flag",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ByokModelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "Get authenticated account usage",
        "description": "Returns the API-key owner's credit balance, plan, and soft Pro usage windows. Requires a Bearer API key; missing, invalid, revoked, or expired keys return 401.",
        "responses": {
          "200": {
            "description": "Account usage status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/web": {
      "post": {
        "summary": "Run advanced web research",
        "description": "Authenticated gateway for server-side web search and research. Returns structured sources (title, URL, content, metadata) suitable for agent reasoning. Requires a Bearer API key with web search enabled. Upstream provider credentials are never accepted from clients.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebResearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Structured web research results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebResearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          },
          "504": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/models/{id}": {
      "get": {
        "summary": "Fetch a single model",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Model id. External model ids may contain slashes; URL-encode the id when calling this endpoint."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Model descriptor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Model"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/chat/completions": {
      "post": {
        "summary": "Create a chat completion",
        "description": "Without tools, runs Ekravo's server-side agent. When tools is supplied, returns client tool calls without executing them.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completion, or a streamed SSE response when `stream` is true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletion"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE `chat.completion.chunk` frames ending in `data: [DONE]`."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/Error"
          },
          "504": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/messages": {
      "post": {
        "summary": "Create an Anthropic Messages response",
        "description": "Anthropic-compatible Messages API. Tool definitions are returned as tool_use blocks and are never executed by Ekravo. Streaming uses Anthropic-style SSE events.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnthropicMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anthropic message, or Anthropic SSE events when `stream` is true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicMessage"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Anthropic message_start, content_block_*, message_delta, and message_stop events."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/Error"
          },
          "504": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Service health",
        "description": "Liveness check. Unauthenticated.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/api/provider-keys": {
      "get": {
        "summary": "List masked account provider keys",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Masked key metadata; plaintext is never returned"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "summary": "Add an encrypted account provider key",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider",
                  "key"
                ],
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "openrouter",
                      "opencode_zen",
                      "skillz",
                      "deepseek",
                      "gemini",
                      "mistral",
                      "groq",
                      "vercel",
                      "together",
                      "fireworks",
                      "cerebras",
                      "sambanova",
                      "nvidia",
                      "huggingface",
                      "deepinfra",
                      "baseten",
                      "cohere",
                      "xai",
                      "perplexity",
                      "openai",
                      "anthropic",
                      "venice",
                      "amazonbedrock",
                      "azureopenai",
                      "ai21",
                      "hyperbolic",
                      "moonshot",
                      "novita",
                      "zhipu",
                      "siliconflow",
                      "aihubmix",
                      "edenai",
                      "portkey",
                      "sarvam",
                      "telnyx",
                      "featherless",
                      "watsonx",
                      "aliqwen",
                      "bytedoubao",
                      "poe",
                      "nebius",
                      "chutes",
                      "infermatic",
                      "inferenceNet",
                      "aimlapi",
                      "friendli",
                      "upstage",
                      "cometapi",
                      "crusoe",
                      "gmiCloud",
                      "openrouter-2",
                      "opencode_zen-2",
                      "deepseek-2"
                    ]
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "key": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "audience": {
                    "type": "string",
                    "enum": [
                      "personal",
                      "team",
                      "subscription"
                    ]
                  },
                  "teamId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Masked key metadata"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/provider-keys/team/{teamId}": {
      "get": {
        "summary": "List managed team provider keys",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "teamId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "organizationId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional organization scope; must match the team's server-side organization."
          },
          {
            "name": "provider",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Masked managed key metadata"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/provider-keys/{id}": {
      "patch": {
        "summary": "Rotate or update an account provider key",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Masked key metadata"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "summary": "Delete an account provider key",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/teams/policy": {
      "get": {
        "summary": "Get effective team policy",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Server-authoritative team policy"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "patch": {
        "summary": "Update team policy",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Updated policy"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/chat/models": {
      "get": {
        "summary": "Model metadata (pricing, context, gating)",
        "description": "Authoritative model metadata: per-model context length, USD pricing per 1M tokens (markup included), and whether the model requires a minimum balance or a special plan. Includes the caller's credit balance and plan when signed in. Supports optional search, page, and pageSize (1-1000) query parameters and returns total/hasMore. Unauthenticated. Models carry a `catalog` discriminator: 'aik' = Ekravo-funded (uses credits), 'byok' = user-key (uses personal API keys).",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Model metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatModelsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat/models/byok": {
      "get": {
        "summary": "BYOK model discovery with pagination",
        "description": "Lists all safely discoverable BYOK (Bring Your Own Key) models from providers for which the authenticated user has configured an API key. Discovery uses only the user's configured provider keys — never operator credentials. Supports pagination (page, pageSize), search, and provider filtering. Each model carries a `catalog: 'byok'` discriminator, a `configured` flag, and its provider's human-readable label. Requires authentication.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by provider ID"
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Search models by ID, name, or provider"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number"
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Models per page"
          }
        ],
        "responses": {
          "200": {
            "description": "BYOK model list with pagination metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ByokDiscoveryResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/responses": {
      "post": {
        "summary": "Create a Responses API response",
        "description": "OpenAI Responses API compatibility for SDKs such as OpenHands. The agent gateway is Chat Completions-native; this adapter translates the public wire format at the boundary. Translates instructions/input into messages, then runs the same Chat Completions gateway and converts the response back to the Responses shape. Streaming uses Responses-style SSE events.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResponsesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Response object, or a streamed SSE response when `stream` is true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResponsesResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE Responses events: response.created, response.in_progress, response.output_item.added, response.content_part.added, response.output_text.delta, response.output_text.done, response.content_part.done, response.output_item.done, response.completed."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/Error"
          },
          "504": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/catalog": {
      "get": {
        "summary": "Browse managed integrations",
        "description": "Search the live Composio toolkit catalog and merge it with the signed-in user's connection state.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 24
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Normalized toolkit catalog"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/status": {
      "get": {
        "summary": "Get managed-integration availability",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Provider availability and signed-in user's connections"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/connections": {
      "get": {
        "summary": "List managed connections",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sanitized connection metadata"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/connections/link": {
      "post": {
        "summary": "Connect an app",
        "description": "Creates a hosted Composio Managed Auth link. Ekravo never receives or stores the provider credential.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "toolkitSlug": {
                    "type": "string"
                  },
                  "returnPath": {
                    "type": "string",
                    "enum": [
                      "/dashboard/integrations"
                    ]
                  }
                },
                "required": [
                  "toolkitSlug"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Hosted connection URL"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/connections/{id}": {
      "patch": {
        "summary": "Enable or pause an app in chat",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated connection"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "summary": "Disconnect an app",
        "description": "Revokes the remote Composio account first, then removes Ekravo's sanitized connection cache.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "502": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/connections/{id}/reconnect": {
      "post": {
        "summary": "Reconnect an app",
        "description": "Creates a new hosted Managed Auth link for an expired or failed connection.",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Hosted reconnection URL"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/integrations/managed/connections/{id}/tools": {
      "get": {
        "summary": "Discover actions for a connected app",
        "servers": [
          {
            "url": "https://ekravo.com"
          }
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounded normalized tool schemas"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API keys are created at Dashboard → API keys. Prefix: ekravo_."
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "ekravo_session",
        "description": "Dashboard session cookie. Managed integration routes are for signed-in Ekravo product clients, not API-key callers."
      }
    },
    "schemas": {
      "ModelReasoning": {
        "type": "object",
        "description": "OpenRouter-synced reasoning effort metadata. Omitted on models without native reasoning controls.",
        "properties": {
          "supported_efforts": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "enum": [
                "none",
                "minimal",
                "low",
                "medium",
                "high",
                "xhigh",
                "max",
                "auto"
              ]
            },
            "description": "Allowed reasoning.effort values for this model (highest first). null means all gateway efforts are accepted."
          },
          "default_effort": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "none",
              "minimal",
              "low",
              "medium",
              "high",
              "xhigh",
              "max",
              "auto",
              null
            ],
            "description": "Pre-selected effort when the client omits reasoning_effort."
          },
          "mandatory": {
            "type": "boolean",
            "description": "When true, reasoning cannot be disabled (effort none is rejected)."
          },
          "default_enabled": {
            "type": "boolean"
          },
          "supports_max_tokens": {
            "type": "boolean"
          }
        },
        "required": [
          "mandatory"
        ]
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "enum": [
              "model"
            ]
          },
          "created": {
            "type": "integer"
          },
          "owned_by": {
            "type": "string"
          },
          "input_modalities": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "text",
                "image"
              ]
            },
            "description": "Supported input types. Image-capable models include image."
          },
          "context_length": {
            "type": "integer",
            "description": "The model's context window, in tokens."
          },
          "catalog": {
            "type": "string",
            "enum": [
              "aik",
              "byok"
            ],
            "description": "Catalog discriminator: 'aik' = Ekravo-funded (uses credits), 'byok' = user-key (uses personal API keys)."
          },
          "reasoning": {
            "$ref": "#/components/schemas/ModelReasoning"
          }
        },
        "required": [
          "id",
          "object",
          "created",
          "owned_by",
          "context_length",
          "catalog"
        ]
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Model"
            }
          }
        }
      },
      "ByokModelList": {
        "type": "object",
        "description": "OpenAI-shaped model list for BYOK models. Each model id is prefixed with `byok/` and carries a `configured` flag.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ByokModelDescriptor"
            }
          }
        },
        "required": [
          "object",
          "data"
        ]
      },
      "ByokModelDescriptor": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Model"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Registry id prefixed with `byok/` (e.g. `byok/openrouter/gpt-5`)."
              },
              "configured": {
                "type": "boolean",
                "description": "True when the authenticated user has configured a key for this provider."
              }
            },
            "required": [
              "configured"
            ]
          }
        ]
      },
      "ChatMessage": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "developer",
              "user",
              "assistant",
              "tool"
            ]
          },
          "content": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "tool_call_id": {
            "type": "string",
            "maxLength": 300
          },
          "tool_calls": {
            "type": "array"
          },
          "reasoning": {
            "type": "string",
            "description": "OpenRouter reasoning text when requested with include_reasoning."
          },
          "reasoning_details": {
            "type": "array",
            "description": "Opaque OpenRouter reasoning blocks. Return these unchanged on a subsequent assistant tool-call message."
          }
        },
        "required": [
          "role"
        ]
      },
      "ChatCompletionRequest": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string",
            "description": "Model id. Use \"GET /models\" to list valid ids; \"ekravo/auto\" is the default free-pool route (quotas apply — not the same as an unpriced model)."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "stream": {
            "type": "boolean",
            "description": "Stream the response as SSE."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Output token ceiling; capped server-side at 16,384."
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "reasoning_effort": {
            "type": "string",
            "enum": [
              "none",
              "minimal",
              "low",
              "medium",
              "high",
              "xhigh",
              "max",
              "auto"
            ],
            "description": "Reasoning depth for this request. Accepted values: none | minimal | low | medium | high | xhigh | max | auto. When omitted, the server uses the model's default_effort from GET /api/chat/models or GET /v1/models (see each model's reasoning object). Free accounts always run at default_effort; Pro may change effort within supported_efforts. On reasoning-capable OpenRouter models, the resolved value is forwarded as reasoning.effort. On models without reasoning metadata, it still controls Ekravo's server-side agent loop (see limits.agentLoop)."
          },
          "reasoning": {
            "type": "object",
            "description": "OpenRouter-native reasoning controls. Use either effort or max_tokens; max_tokens is accepted only when the selected model advertises reasoning.supports_max_tokens and requires a verified Pro account.",
            "properties": {
              "effort": {
                "type": "string",
                "enum": [
                  "none",
                  "minimal",
                  "low",
                  "medium",
                  "high",
                  "xhigh",
                  "max",
                  "auto"
                ]
              },
              "max_tokens": {
                "type": "integer",
                "minimum": 1024,
                "maximum": 128000
              },
              "enabled": {
                "type": "boolean"
              },
              "exclude": {
                "type": "boolean"
              }
            }
          },
          "include_reasoning": {
            "type": "boolean",
            "description": "Opt in to OpenRouter reasoning and reasoning_details fields when the selected model returns them. Other providers and models without a visible trace omit these fields."
          },
          "response_format": {
            "type": "object",
            "description": "OpenRouter structured output. json_schema requests are routed only to providers that support every requested parameter.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "json_object",
                  "json_schema"
                ]
              },
              "json_schema": {
                "type": "object"
              }
            }
          },
          "mode": {
            "type": "string",
            "enum": [
              "read",
              "plan",
              "edit",
              "auto"
            ],
            "description": "Client-tool safety mode. Used only when tools is supplied."
          },
          "tools": {
            "type": "array",
            "description": "OpenAI function tools. Supplying this selects client-tool gateway mode; Ekravo returns calls but never executes them."
          },
          "tool_choice": {
            "description": "OpenAI tool selection policy forwarded to the model in client-tool gateway mode."
          },
          "user": {
            "type": "string",
            "maxLength": 200
          }
        },
        "required": [
          "model",
          "messages"
        ]
      },
      "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",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "message": {
                  "$ref": "#/components/schemas/ChatMessage"
                },
                "finish_reason": {
                  "type": "string"
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": {
                "type": "integer"
              },
              "completion_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ResponsesRequest": {
        "type": "object",
        "required": [
          "model",
          "input"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Model id. Use \"GET /models\" to list valid ids; \"ekravo/auto\" is the default free-pool route (quotas apply — not the same as an unpriced model)."
          },
          "instructions": {
            "type": "string",
            "description": "System instructions for the response."
          },
          "input": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            ],
            "description": "Input text or structured input items."
          },
          "max_output_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Output token ceiling."
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "reasoning": {
            "type": "object",
            "properties": {
              "effort": {
                "type": "string",
                "enum": [
                  "none",
                  "low",
                  "medium",
                  "high",
                  "auto"
                ]
              }
            }
          },
          "tools": {
            "type": "array",
            "maxItems": 100,
            "description": "OpenAI function tools."
          },
          "tool_choice": {
            "description": "OpenAI tool selection policy."
          },
          "stream": {
            "type": "boolean",
            "description": "Stream Responses-style SSE events."
          },
          "user": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "ResponsesResponse": {
        "type": "object",
        "required": [
          "id",
          "object",
          "created_at",
          "status",
          "model",
          "output"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "enum": [
              "response"
            ]
          },
          "created_at": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "in_progress",
              "incomplete",
              "failed"
            ]
          },
          "model": {
            "type": "string"
          },
          "output": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "message",
                    "function_call"
                  ]
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "completed",
                    "in_progress"
                  ]
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "assistant"
                  ]
                },
                "content": {
                  "type": "array"
                },
                "call_id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "arguments": {
                  "type": "string"
                }
              }
            }
          },
          "output_text": {
            "type": "string"
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              },
              "total_tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "AnthropicMessageRequest": {
        "type": "object",
        "required": [
          "model",
          "max_tokens",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Model id. Use GET /models to list valid ids."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Output token ceiling; capped server-side at 16,384."
          },
          "system": {
            "description": "Optional Anthropic system prompt string or content blocks."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "type": "object",
              "required": [
                "role",
                "content"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "user",
                    "assistant"
                  ]
                },
                "content": {
                  "description": "A text string or Anthropic content blocks, including tool_use and tool_result blocks."
                }
              }
            }
          },
          "tools": {
            "type": "array",
            "maxItems": 100,
            "description": "Anthropic tool definitions with name, optional description, and input_schema."
          },
          "tool_choice": {
            "description": "Anthropic tool selection policy forwarded to the model."
          },
          "stream": {
            "type": "boolean",
            "description": "Stream Anthropic-style SSE events."
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "metadata": {
            "type": "object",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "read",
                  "plan",
                  "edit",
                  "auto"
                ]
              }
            }
          }
        }
      },
      "AnthropicMessage": {
        "type": "object",
        "required": [
          "id",
          "type",
          "role",
          "model",
          "content",
          "stop_reason",
          "stop_sequence",
          "usage"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "message"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "assistant"
            ]
          },
          "model": {
            "type": "string"
          },
          "content": {
            "type": "array",
            "description": "Text and/or tool_use content blocks."
          },
          "stop_reason": {
            "type": "string",
            "enum": [
              "end_turn",
              "tool_use"
            ]
          },
          "stop_sequence": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "type": "object",
            "required": [
              "input_tokens",
              "output_tokens"
            ],
            "properties": {
              "input_tokens": {
                "type": "integer"
              },
              "output_tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "time": {
            "type": "integer"
          }
        },
        "required": [
          "ok",
          "name",
          "time"
        ]
      },
      "AgentLoopLimits": {
        "type": "object",
        "description": "Separate from provider-native reasoning tokens. This is Ekravo's server-side tool-use step budget, not an OpenAI reasoning token knob on every model.",
        "properties": {
          "appliesWhen": {
            "type": "string"
          },
          "controlledBy": {
            "type": "string",
            "enum": [
              "reasoning_effort"
            ]
          },
          "maxAgentIterations": {
            "type": "object",
            "properties": {
              "low": {
                "type": "integer"
              },
              "medium": {
                "type": "integer"
              },
              "high": {
                "type": "integer"
              },
              "auto": {
                "type": "integer"
              },
              "none": {
                "type": "integer"
              }
            },
            "required": [
              "low",
              "medium",
              "high",
              "auto",
              "none"
            ]
          }
        },
        "required": [
          "appliesWhen",
          "controlledBy",
          "maxAgentIterations"
        ]
      },
      "ChatModelMetadata": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "external": {
            "type": "boolean"
          },
          "requiresMinCredits": {
            "type": "boolean"
          },
          "requiresPlan": {
            "type": "boolean"
          },
          "contextLength": {
            "type": "integer",
            "nullable": true
          },
          "promptPricePerMillion": {
            "type": "number",
            "nullable": true,
            "description": "USD per 1M tokens, markup included, when a fixed per-token list price exists in this snapshot. null means no fixed per-token price is represented here — use GET /api/chat/models (or GET /api/chat/models/byok) for authoritative pricing, quotas, and account gating. null does not mean the model is free."
          },
          "completionPricePerMillion": {
            "type": "number",
            "nullable": true,
            "description": "USD per 1M tokens, markup included, when a fixed per-token list price exists in this snapshot. null means no fixed per-token price is represented here — use GET /api/chat/models (or GET /api/chat/models/byok) for authoritative pricing, quotas, and account gating. null does not mean the model is free."
          },
          "workTier": {
            "type": "string",
            "enum": [
              "budget",
              "economy",
              "fast",
              "quality"
            ],
            "nullable": true,
            "description": "Work mode cost/speed tier, when eligible."
          },
          "workEligible": {
            "type": "boolean",
            "description": "Whether this model can be selected for Work mode."
          }
        },
        "required": [
          "id",
          "label",
          "provider",
          "external",
          "requiresMinCredits",
          "requiresPlan",
          "contextLength",
          "promptPricePerMillion",
          "completionPricePerMillion",
          "workTier",
          "workEligible"
        ]
      },
      "ChatModelsResponse": {
        "type": "object",
        "properties": {
          "minCredits": {
            "type": "number"
          },
          "balance": {
            "type": "number"
          },
          "plan": {
            "type": "string"
          },
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatModelMetadata"
            }
          }
        },
        "required": [
          "minCredits",
          "balance",
          "plan",
          "models"
        ]
      },
      "ByokModelMetadata": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "providerLabel": {
            "type": "string"
          },
          "configured": {
            "type": "boolean",
            "description": "True when the authenticated user has configured a key for this provider."
          },
          "contextLength": {
            "type": "integer",
            "nullable": true
          },
          "promptPricePerMillion": {
            "type": "number",
            "nullable": true,
            "description": "USD per 1M tokens, markup included, when a fixed per-token list price exists in this snapshot. null means no fixed per-token price is represented here — use GET /api/chat/models (or GET /api/chat/models/byok) for authoritative pricing, quotas, and account gating. null does not mean the model is free."
          },
          "completionPricePerMillion": {
            "type": "number",
            "nullable": true,
            "description": "USD per 1M tokens, markup included, when a fixed per-token list price exists in this snapshot. null means no fixed per-token price is represented here — use GET /api/chat/models (or GET /api/chat/models/byok) for authoritative pricing, quotas, and account gating. null does not mean the model is free."
          }
        },
        "required": [
          "id",
          "label",
          "provider",
          "providerLabel",
          "configured",
          "contextLength",
          "promptPricePerMillion",
          "completionPricePerMillion"
        ]
      },
      "ByokDiscoveryResponse": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ByokModelMetadata"
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ByokProviderFilter"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total number of discoverable models"
          },
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "pageSize": {
            "type": "integer",
            "description": "Models per page"
          },
          "hasMore": {
            "type": "boolean",
            "description": "Whether more pages are available"
          },
          "error": {
            "type": "string",
            "description": "Non-secret error information if discovery partially failed"
          }
        },
        "required": [
          "models",
          "providers",
          "total",
          "page",
          "pageSize",
          "hasMore"
        ]
      },
      "ByokModelsResponse": {
        "type": "object",
        "properties": {
          "models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ByokModelMetadata"
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ByokProviderFilter"
            }
          }
        },
        "required": [
          "models",
          "providers"
        ]
      },
      "ByokProviderFilter": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "configured": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "label",
          "configured"
        ]
      },
      "UsageStatus": {
        "type": "object",
        "properties": {
          "credits": {
            "type": "number",
            "description": "Current account credit balance."
          },
          "plan": {
            "type": "string",
            "description": "Account plan identifier."
          },
          "usageLimits": {
            "type": "object",
            "properties": {
              "monthly": {
                "$ref": "#/components/schemas/UsageWindow"
              },
              "weekly": {
                "$ref": "#/components/schemas/UsageWindow"
              },
              "rolling5h": {
                "$ref": "#/components/schemas/UsageWindow"
              }
            },
            "required": [
              "monthly",
              "weekly",
              "rolling5h"
            ]
          }
        },
        "required": [
          "credits",
          "plan",
          "usageLimits"
        ]
      },
      "UsageWindow": {
        "type": "object",
        "properties": {
          "used": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "until": {
            "type": "string",
            "format": "date-time"
          },
          "exceeded": {
            "type": "boolean"
          }
        },
        "required": [
          "used",
          "limit",
          "until",
          "exceeded"
        ]
      },
      "WebResearchRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 400,
            "description": "Search or research query."
          },
          "max_results": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Maximum number of sources to return."
          },
          "search_depth": {
            "type": "string",
            "enum": [
              "ultra-fast",
              "fast",
              "basic",
              "advanced"
            ],
            "description": "Latency vs. relevance tradeoff. Defaults to advanced."
          },
          "topic": {
            "type": "string",
            "enum": [
              "general",
              "news",
              "finance"
            ]
          },
          "time_range": {
            "type": "string",
            "enum": [
              "day",
              "week",
              "month",
              "year"
            ]
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "description": "Inclusive lower bound (YYYY-MM-DD)."
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "description": "Inclusive upper bound (YYYY-MM-DD)."
          },
          "include_domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 300
          },
          "exclude_domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 150
          },
          "include_answer": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": [
                  "basic",
                  "advanced"
                ]
              }
            ],
            "description": "Include a synthesized answer alongside sources."
          },
          "include_raw_content": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string",
                "enum": [
                  "markdown",
                  "text"
                ]
              }
            ],
            "description": "Include fuller page content when available."
          },
          "chunks_per_source": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          }
        },
        "required": [
          "query"
        ]
      },
      "WebResearchResponse": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "web.search"
            ]
          },
          "query": {
            "type": "string"
          },
          "answer": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebResearchSource"
            }
          },
          "response_time": {
            "type": "number",
            "description": "Upstream response time in seconds."
          },
          "usage": {
            "type": "object",
            "properties": {
              "searches": {
                "type": "integer"
              },
              "search_cost_credits": {
                "type": "number"
              }
            },
            "required": [
              "searches",
              "search_cost_credits"
            ]
          }
        },
        "required": [
          "object",
          "query",
          "results",
          "usage"
        ]
      },
      "WebResearchSource": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "content": {
            "type": "string"
          },
          "score": {
            "type": "number"
          },
          "published_date": {
            "type": "string"
          },
          "raw_content": {
            "type": "string"
          },
          "favicon": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "title",
          "url",
          "content"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "code": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "OpenAI-style error envelope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited; Retry-After and X-RateLimit-* headers are included.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}