{
  "openapi": "3.0.3",
  "info": {
    "title": "FreeRouter API",
    "version": "1.0.0",
    "description": "FreeRouter is a meta-router for AI inference: one API key that routes requests across multiple LLM gateways (OpenRouter, Vercel AI Gateway, Cloudflare AI Gateway, and more). Every endpoint mirrors the API shape your key speaks — OpenAI by default. See the full documentation at https://docs.freerouter.com/api-reference.html.",
    "contact": {
      "name": "FreeRouter",
      "url": "https://freerouter.com/"
    }
  },
  "servers": [
    {
      "url": "https://api.freerouter.com/v1",
      "description": "FreeRouter inference proxy (HTTPS only)"
    }
  ],
  "security": [
    {
      "FreeRouterKey": []
    }
  ],
  "tags": [
    {
      "name": "Inference",
      "description": "OpenAI-compatible chat completions plus per-shape endpoints"
    },
    {
      "name": "Discovery",
      "description": "Model listing and service health"
    },
    {
      "name": "MCP",
      "description": "Management-key Model Context Protocol endpoint (Streamable HTTP)"
    }
  ],
  "paths": {
    "/chat/completions": {
      "post": {
        "tags": ["Inference"],
        "summary": "Create a chat completion",
        "description": "The primary inference endpoint for keys with the `openai` (default) or `openrouter` shape. Accepts the standard OpenAI chat completion body and returns a standard chat completion object, or an SSE stream when `stream` is `true`. Request fields beyond the documented ones are forwarded to the selected gateway as-is. Routing metadata is returned in `X-FreeRouter-*` response headers.",
        "externalDocs": {
          "url": "https://docs.freerouter.com/api-reference.html#chat-completions-endpoint"
        },
        "security": [
          {
            "FreeRouterKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion object, or an SSE stream when `stream` is true",
            "headers": {
              "X-FreeRouter-Provider": {
                "$ref": "#/components/headers/X-FreeRouter-Provider"
              },
              "X-FreeRouter-Attempts": {
                "$ref": "#/components/headers/X-FreeRouter-Attempts"
              },
              "X-FreeRouter-Failover": {
                "$ref": "#/components/headers/X-FreeRouter-Failover"
              },
              "X-FreeRouter-Request-Id": {
                "$ref": "#/components/headers/X-FreeRouter-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Standard OpenAI chat completion object (relayed from the winning gateway)",
                  "additionalProperties": true
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "OpenAI-style server-sent events ending with `data: [DONE]`"
                }
              }
            }
          },
          "400": {
            "description": "Missing `model` or `messages`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "No way to spend: the key has no routing targets configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "404": {
            "description": "Key speaks another shape, or no target offers the model (upstream 404 relayed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "429": {
            "description": "Every eligible target returned 429",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "501": {
            "description": "None of this key's targets are routable yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "502": {
            "description": "All eligible gateways failed after failover retries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          }
        }
      }
    },
    "/models": {
      "get": {
        "tags": ["Discovery"],
        "summary": "List models",
        "description": "Lists the models available to your key across its attached providers, in the OpenAI `list` format. Use it to discover valid `model` strings.",
        "externalDocs": {
          "url": "https://docs.freerouter.com/api-reference.html#models-endpoint"
        },
        "security": [
          {
            "FreeRouterKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Model list relayed from the first routable target",
            "headers": {
              "X-FreeRouter-Provider": {
                "$ref": "#/components/headers/X-FreeRouter-Provider"
              },
              "X-FreeRouter-Request-Id": {
                "$ref": "#/components/headers/X-FreeRouter-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "501": {
            "description": "No routable targets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          },
          "502": {
            "description": "Upstream unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAIError"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "post": {
        "tags": ["Inference"],
        "summary": "Create a message (Anthropic shape)",
        "description": "Speaks the Anthropic Messages API for keys with the `anthropic` shape. FreeRouter translates the request to OpenAI chat upstream and translates the answer back, including SSE streams.",
        "externalDocs": {
          "url": "https://docs.freerouter.com/api-reference.html#shape-anthropic"
        },
        "security": [
          {
            "FreeRouterKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["model", "messages"],
                "properties": {
                  "model": {
                    "type": "string",
                    "example": "anthropic/claude-3.5-sonnet"
                  },
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  },
                  "max_tokens": {
                    "type": "integer"
                  },
                  "stream": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anthropic message object, or Anthropic SSE events when streaming"
          },
          "400": {
            "description": "Invalid Anthropic-shape fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AnthropicUnauthorized"
          },
          "404": {
            "description": "Key speaks another shape, or no target offers the model",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnthropicError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/AnthropicError"
          },
          "502": {
            "$ref": "#/components/responses/AnthropicError"
          }
        }
      }
    },
    "/models/{modelPath}:generateContent": {
      "post": {
        "tags": ["Inference"],
        "summary": "Generate content (Google Gemini shape)",
        "description": "Speaks the Google Gemini `generateContent` format for keys with the `google` shape, with the model in the path (it may itself contain slashes, e.g. `google/gemini-1.5-pro`). Streaming via `:streamGenerateContent` is not supported — call `:generateContent` without streaming, or use an `openai`-shaped key for the same model.",
        "externalDocs": {
          "url": "https://docs.freerouter.com/api-reference.html#shape-google"
        },
        "security": [
          {
            "FreeRouterKey": []
          }
        ],
        "parameters": [
          {
            "name": "modelPath",
            "in": "path",
            "required": true,
            "description": "Model identifier plus the `:generateContent` action suffix",
            "schema": {
              "type": "string",
              "pattern": "^.+:(generateContent|streamGenerateContent)$",
              "example": "google/gemini-1.5-pro:generateContent"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Gemini generateContent request (`contents`, `systemInstruction`, `generationConfig`, …)",
                "additionalProperties": true,
                "example": {
                  "contents": [
                    {
                      "parts": [
                        {
                          "text": "Ping"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gemini generateContent response translated from the upstream answer"
          },
          "400": {
            "description": "Invalid body, or `:streamGenerateContent` (streaming unsupported)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoogleError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GoogleUnauthorized"
          },
          "404": {
            "description": "Key speaks another shape, or no target offers the model",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoogleError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/GoogleError"
          },
          "502": {
            "$ref": "#/components/responses/GoogleError"
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "tags": ["Discovery"],
        "summary": "Service health",
        "description": "Unauthenticated liveness probe for the inference proxy.",
        "servers": [
          {
            "url": "https://api.freerouter.com",
            "description": "FreeRouter inference proxy root (this path lives outside /v1)"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ok"],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": ["MCP"],
        "summary": "Model Context Protocol (Streamable HTTP)",
        "description": "JSON-RPC 2.0 endpoint for management automation. Authenticate with a management key (`fr_mgmt_…`) — inference keys are never valid here. Supported methods: `initialize`, `ping`, `tools/list`, `tools/call` (any other method returns JSON-RPC `-32601`). The tool catalog (provider keys, API keys, routing, usage) is documented at https://docs.freerouter.com/mcp.html.",
        "externalDocs": {
          "url": "https://docs.freerouter.com/mcp.html#endpoint-auth"
        },
        "servers": [
          {
            "url": "https://api.freerouter.com",
            "description": "FreeRouter inference proxy root (this path lives outside /v1)"
          }
        ],
        "security": [
          {
            "ManagementKey": []
          }
        ],
        "parameters": [
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "description": "Client protocol version (the server answers with its own, currently `2025-03-26`)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (result or error); the `MCP-Protocol-Version` response header carries the server version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON-RPC request"
          },
          "401": {
            "description": "Missing or invalid management key (JSON-RPC error code -32001)"
          }
        }
      },
      "get": {
        "tags": ["MCP"],
        "summary": "MCP is POST-only",
        "description": "GET on this path always answers 405 — POST JSON-RPC to /mcp instead.",
        "servers": [
          {
            "url": "https://api.freerouter.com",
            "description": "FreeRouter inference proxy root (this path lives outside /v1)"
          }
        ],
        "security": [],
        "responses": {
          "405": {
            "description": "MCP is POST-only Streamable HTTP"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "FreeRouterKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "FreeRouter inference key (`fr_live_…`) as `Authorization: Bearer <key>`. Each key carries its own API shape and routing rule. Management keys are never valid on /v1 paths."
      },
      "ManagementKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "FreeRouter management key (`fr_mgmt_…`) as `Authorization: Bearer <key>`. Valid on /mcp only — never on /v1 inference paths."
      }
    },
    "headers": {
      "X-FreeRouter-Provider": {
        "description": "Gateway that served the request (e.g. `openrouter`). Omitted when no gateway served it.",
        "schema": {
          "type": "string"
        }
      },
      "X-FreeRouter-Attempts": {
        "description": "Number of upstream tries, including failover retries",
        "schema": {
          "type": "integer"
        }
      },
      "X-FreeRouter-Failover": {
        "description": "Comma-separated non-winning attempts as `gateway:reason` (e.g. `darkbloom:ttfb-timeout`). Set only when a failover happened.",
        "schema": {
          "type": "string"
        }
      },
      "X-FreeRouter-Request-Id": {
        "description": "Opaque per-request ID for support and log correlation",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "OpenAIError": {
        "description": "OpenAI-style error envelope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked FreeRouter key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenAIError"
            }
          }
        }
      },
      "AnthropicError": {
        "description": "Anthropic-style error envelope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "AnthropicUnauthorized": {
        "description": "Missing, invalid, or revoked FreeRouter key (Anthropic envelope)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AnthropicError"
            }
          }
        }
      },
      "GoogleError": {
        "description": "Google-style error envelope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GoogleError"
            }
          }
        }
      },
      "GoogleUnauthorized": {
        "description": "Missing, invalid, or revoked FreeRouter key (Google envelope)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GoogleError"
            }
          }
        }
      }
    },
    "schemas": {
      "ChatCompletionRequest": {
        "type": "object",
        "required": ["model", "messages"],
        "properties": {
          "model": {
            "type": "string",
            "description": "A `provider/model` identifier",
            "example": "openai/gpt-4o-mini"
          },
          "messages": {
            "type": "array",
            "description": "Chat messages with `role` and `content` (non-empty)",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "stream": {
            "type": "boolean",
            "description": "Stream partial deltas as server-sent events"
          },
          "temperature": {
            "type": "number",
            "description": "Sampling temperature, passed through to the gateway"
          },
          "max_tokens": {
            "type": "integer",
            "description": "Upper bound on generated tokens"
          },
          "tools": {
            "type": "array",
            "description": "Tool/function definitions, passed through unchanged",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "ModelList": {
        "type": "object",
        "required": ["object", "data"],
        "properties": {
          "object": {
            "type": "string",
            "example": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "object"],
              "properties": {
                "id": {
                  "type": "string",
                  "example": "openai/gpt-4o-mini"
                },
                "object": {
                  "type": "string",
                  "example": "model"
                }
              }
            }
          }
        }
      },
      "OpenAIError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["message"],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "example": "invalid_request_error"
              },
              "code": {
                "type": "string",
                "example": "missing_model"
              }
            }
          }
        }
      },
      "AnthropicError": {
        "type": "object",
        "required": ["type", "error"],
        "properties": {
          "type": {
            "type": "string",
            "example": "error"
          },
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "GoogleError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "status": {
                "type": "string"
              }
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": ["2.0"]
          },
          "id": {
            "description": "Request id (number or string); omit for notifications",
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ]
          },
          "method": {
            "type": "string",
            "enum": ["initialize", "ping", "tools/list", "tools/call"],
            "description": "Supported methods; anything else returns JSON-RPC error -32601"
          },
          "params": {
            "type": "object",
            "description": "Method params (`tools/call` takes `{ name, arguments }`; see the MCP docs for the tool catalog)",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "enum": ["2.0"]
          },
          "id": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "string",
                "nullable": true
              }
            ]
          },
          "result": {
            "type": "object",
            "description": "Method result (`tools/list` returns `{ tools: [...] }`)",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
