{
  "openapi": "3.0.3",
  "info": {
    "title": "Nexforce Router",
    "version": "1.0.0",
    "description": "OpenAI-compatible inference API. One endpoint routes to Anthropic, OpenAI, Google, DeepSeek, Moonshot, Zhipu and Cloudflare Workers AI, with per-model fallback and per-key cost control. If your tool speaks the OpenAI Chat Completions API, it speaks Nexforce Router: swap the base URL and the API key. Human docs: https://router.nexforce.ai/docs",
    "contact": { "name": "Nexforce Router docs", "url": "https://router.nexforce.ai/docs" },
    "license": { "name": "Proprietary", "url": "https://router.nexforce.ai/docs" }
  },
  "servers": [
    { "url": "https://router.nexforce.ai/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Chat", "description": "Chat completions and image generation" },
    { "name": "Embeddings", "description": "Vector embeddings for RAG and semantic search" },
    { "name": "Models", "description": "Model discovery" }
  ],
  "paths": {
    "/chat/completions": {
      "post": {
        "tags": ["Chat"],
        "operationId": "createChatCompletion",
        "summary": "Create a chat completion",
        "description": "OpenAI Chat Completions schema. Routing to the right provider is automatic from the `model` field. Pass `nexforce/smart-route` as the model to let Nexforce pick the model for you. This endpoint also generates images: send `modalities: [\"image\", \"text\"]` on an image-capable model and read `choices[].message.images`.",
        "parameters": [
          {
            "name": "X-Nexforce-Agent",
            "in": "header",
            "required": false,
            "description": "Agent name that originated the request, for per-agent cost/volume attribution. Max 120 chars.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Nexforce-Metadata",
            "in": "header",
            "required": false,
            "description": "JSON metadata, e.g. {\"agent\":\"support-bot\"}. Also accepts `agent_label` and `_agent`. `X-Nexforce-Agent` takes precedence.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Nexforce-Smart-Routing",
            "in": "header",
            "required": false,
            "description": "Set to `off` (or `false`/`0`) to disable Smart Routing for this request. Only has effect when Smart Routing is enabled on the config.",
            "schema": { "type": "string", "enum": ["off", "false", "0"] }
          },
          {
            "name": "X-Nexforce-Pin-Model",
            "in": "header",
            "required": false,
            "description": "Set to `true` to force exactly the requested model with no substitution.",
            "schema": { "type": "string", "enum": ["true"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatCompletionRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion (or an SSE stream of `chat.completion.chunk` events when `stream` is true).",
            "headers": {
              "X-Nexforce-Requested-Model": { "description": "The model you requested.", "schema": { "type": "string" } },
              "X-Nexforce-Served-Model": { "description": "The model that actually answered.", "schema": { "type": "string" } },
              "X-Nexforce-Substituted": { "description": "`true` when Smart Routing swapped the model.", "schema": { "type": "string" } },
              "X-Nexforce-Smart-Route": { "description": "`true` when the request used `nexforce/smart-route`.", "schema": { "type": "string" } },
              "X-Config-Synced-At": { "description": "Timestamp of the config snapshot used.", "schema": { "type": "string" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatCompletionResponse" }
              },
              "text/event-stream": {
                "schema": { "type": "string", "description": "Server-Sent Events stream, terminated by `data: [DONE]`." }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/embeddings": {
      "post": {
        "tags": ["Embeddings"],
        "operationId": "createEmbedding",
        "summary": "Create embeddings",
        "description": "OpenAI Embeddings schema, same `nfc_` key as chat. The embedding model is NEVER substituted: Smart Routing does not apply here, because vectors from two different models are not comparable and swapping the model would corrupt an index you already wrote. No streaming. Billing counts input tokens only (no output, no cache tier).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EmbeddingsRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding vectors, in the same order as `input`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EmbeddingsResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/models": {
      "get": {
        "tags": ["Models"],
        "operationId": "listModels",
        "summary": "List available models",
        "description": "Public (no key required). With a valid key, the list is narrowed to the models that key may call. Prices are never exposed. Includes the synthetic `nexforce/smart-route` model.",
        "security": [{ "bearerAuth": [] }, {}],
        "responses": {
          "200": {
            "description": "Model list.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ModelList" }
              }
            }
          },
          "4XX": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Nexforce API key, prefix `nfc_`. Header: `Authorization: Bearer nfc_...`."
      }
    },
    "responses": {
      "Error": {
        "description": "Error in the OpenAI error format.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    },
    "schemas": {
      "ChatCompletionRequest": {
        "type": "object",
        "required": ["model", "messages"],
        "properties": {
          "model": {
            "type": "string",
            "description": "Catalog model id (e.g. `claude-3-5-sonnet`), or `nexforce/smart-route` to let Nexforce pick.",
            "example": "claude-3-5-sonnet"
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/ChatMessage" }
          },
          "temperature": { "type": "number" },
          "top_p": { "type": "number" },
          "max_tokens": { "type": "integer" },
          "max_completion_tokens": { "type": "integer" },
          "stream": { "type": "boolean", "default": false },
          "stop": {
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ]
          },
          "frequency_penalty": { "type": "number" },
          "presence_penalty": { "type": "number" },
          "tools": { "type": "array", "items": { "$ref": "#/components/schemas/Tool" } },
          "tool_choice": {
            "oneOf": [
              { "type": "string", "enum": ["auto", "none", "required"] },
              { "$ref": "#/components/schemas/ToolChoiceFunction" }
            ]
          },
          "modalities": {
            "type": "array",
            "items": { "type": "string", "enum": ["text", "image"] },
            "description": "Modalities requested in the response. Include `image` to generate images on a model whose `architecture.output_modalities` contains `image`; the result comes back in `choices[].message.images`. Omit for text-only.",
            "example": ["image", "text"]
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "required": ["role"],
        "properties": {
          "role": { "type": "string", "enum": ["system", "user", "assistant", "tool"] },
          "content": {
            "oneOf": [
              { "type": "string", "nullable": true },
              { "type": "array", "items": { "$ref": "#/components/schemas/ContentBlock" } }
            ]
          },
          "tool_calls": { "type": "array", "items": { "$ref": "#/components/schemas/ToolCall" } },
          "tool_call_id": { "type": "string" }
        }
      },
      "ContentBlock": {
        "oneOf": [
          { "$ref": "#/components/schemas/TextContentBlock" },
          { "$ref": "#/components/schemas/ImageContentBlock" },
          { "$ref": "#/components/schemas/FileContentBlock" }
        ]
      },
      "TextContentBlock": {
        "type": "object",
        "required": ["type", "text"],
        "properties": {
          "type": { "type": "string", "enum": ["text"] },
          "text": { "type": "string" }
        }
      },
      "ImageContentBlock": {
        "type": "object",
        "required": ["type", "image_url"],
        "properties": {
          "type": { "type": "string", "enum": ["image_url"] },
          "image_url": {
            "type": "object",
            "required": ["url"],
            "properties": {
              "url": { "type": "string", "description": "https URL or data URI (base64)." },
              "detail": { "type": "string" }
            }
          }
        }
      },
      "FileContentBlock": {
        "type": "object",
        "required": ["type", "file"],
        "properties": {
          "type": { "type": "string", "enum": ["file"] },
          "file": {
            "type": "object",
            "properties": {
              "file_data": { "type": "string", "description": "Base64 file contents." },
              "filename": { "type": "string" },
              "media_type": { "type": "string", "description": "Defaults to application/pdf when omitted." },
              "url": { "type": "string" }
            }
          }
        }
      },
      "Tool": {
        "type": "object",
        "required": ["type", "function"],
        "properties": {
          "type": { "type": "string", "enum": ["function"] },
          "function": {
            "type": "object",
            "required": ["name"],
            "properties": {
              "name": { "type": "string" },
              "description": { "type": "string" },
              "parameters": { "type": "object", "additionalProperties": true }
            }
          }
        }
      },
      "ToolChoiceFunction": {
        "type": "object",
        "required": ["type", "function"],
        "properties": {
          "type": { "type": "string", "enum": ["function"] },
          "function": {
            "type": "object",
            "required": ["name"],
            "properties": { "name": { "type": "string" } }
          }
        }
      },
      "ToolCall": {
        "type": "object",
        "required": ["id", "type", "function"],
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "enum": ["function"] },
          "function": {
            "type": "object",
            "required": ["name", "arguments"],
            "properties": {
              "name": { "type": "string" },
              "arguments": { "type": "string", "description": "JSON-encoded argument object." }
            }
          }
        }
      },
      "OutputImage": {
        "type": "object",
        "description": "An image the model generated. The URL is always a data URI: the Router does not host files, so there is nothing to fetch and nothing expires. The format varies by model (gemini-2.5-flash-image returns PNG 1024x1024, gemini-3-pro-image returns JPEG 1408x768), so read the mime type from the data URI rather than assuming PNG.",
        "required": ["type", "image_url"],
        "properties": {
          "type": { "type": "string", "enum": ["image_url"] },
          "image_url": {
            "type": "object",
            "required": ["url"],
            "properties": {
              "url": { "type": "string", "example": "data:image/png;base64,iVBORw0KGgo..." }
            }
          }
        }
      },
      "ChatCompletionResponse": {
        "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": {
                  "type": "object",
                  "properties": {
                    "role": { "type": "string", "enum": ["assistant"] },
                    "content": { "type": "string", "nullable": true },
                    "tool_calls": { "type": "array", "items": { "$ref": "#/components/schemas/ToolCall" } },
                    "images": {
                      "type": "array",
                      "description": "Images the model generated. Present only when the request asked for image output and the model returned one; absent on every text response. When the model returned only an image, `content` is an empty string.",
                      "items": { "$ref": "#/components/schemas/OutputImage" }
                    }
                  }
                },
                "finish_reason": { "type": "string", "enum": ["stop", "length", "content_filter", "tool_calls"] }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": { "type": "integer" },
              "completion_tokens": { "type": "integer" },
              "total_tokens": { "type": "integer" }
            }
          }
        }
      },
      "EmbeddingsRequest": {
        "type": "object",
        "required": ["model", "input"],
        "properties": {
          "model": { "type": "string", "description": "Embedding model id. A chat model here returns 400 with code `wrong_endpoint`.", "example": "text-embedding-3-small" },
          "input": {
            "description": "Text to embed: a string, an array of up to 2048 strings, or token ids.",
            "example": ["first document", "second document"],
            "oneOf": [
              { "type": "string" },
              { "type": "array", "maxItems": 2048, "items": { "type": "string" } },
              { "type": "array", "items": { "type": "integer" } },
              { "type": "array", "items": { "type": "array", "items": { "type": "integer" } } }
            ]
          },
          "dimensions": { "type": "integer", "description": "Output vector size, on models that support it. Passed through to the provider unchanged." },
          "encoding_format": { "type": "string", "enum": ["float", "base64"], "description": "Passed through unchanged. The OpenAI Python SDK asks for base64 by default." },
          "user": { "type": "string", "description": "End-user identifier, recorded on the request Trace." }
        }
      },
      "EmbeddingsResponse": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "enum": ["list"] },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "object": { "type": "string", "enum": ["embedding"] },
                "index": { "type": "integer" },
                "embedding": {
                  "description": "Vector as an array of floats, or a base64 string when `encoding_format` is base64.",
                  "oneOf": [{ "type": "array", "items": { "type": "number" } }, { "type": "string" }]
                }
              }
            }
          },
          "model": { "type": "string" },
          "usage": {
            "type": "object",
            "description": "Input only: embeddings have no output tier and no cache tier.",
            "properties": {
              "prompt_tokens": { "type": "integer" },
              "total_tokens": { "type": "integer" }
            }
          }
        }
      },
      "ModelList": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "enum": ["list"] },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Model" } }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "object": { "type": "string", "enum": ["model"] },
          "created": { "type": "integer" },
          "owned_by": { "type": "string" },
          "name": { "type": "string", "description": "Display name. Omitted when unknown (clients fall back to id)." },
          "context_length": { "type": "integer", "description": "Maximum context window in tokens. Omitted when unknown." },
          "max_output_tokens": { "type": "integer", "description": "Maximum output tokens. Omitted when unknown." },
          "kind": { "type": "string", "enum": ["chat", "embedding"], "description": "Nexforce extension: which endpoint serves this model. `chat` -> POST /v1/chat/completions, `embedding` -> POST /v1/embeddings." },
          "capabilities": {
            "type": "object",
            "description": "Nexforce extension, present only for models with known capabilities.",
            "additionalProperties": true,
            "properties": {
              "tool_use": { "type": "boolean" },
              "vision": { "type": "boolean" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "message": { "type": "string" },
              "type": { "type": "string", "nullable": true },
              "code": { "type": "string", "nullable": true },
              "param": { "type": "string", "nullable": true }
            }
          }
        }
      }
    }
  }
}
