{
  "openapi": "3.1.0",
  "info": {
    "title": "Merki inference compatibility contract",
    "version": "1.0.0",
    "description": "Documentation-derived common text baseline for the OpenAI and Anthropic compatible inference families. This static website does not implement the API and this document is not evidence of deployed backend conformance. It is not an exhaustive field catalogue: tools, multimodal inputs, provider-specific fields and metering extensions are outside this baseline. Hosted model names and BYOK provider IDs are strings, not a closed enum. Unsupported fields may return 400 invalid_request; capabilities and limits depend on the model and route. See the linked documentation before generating or configuring a client.",
    "contact": { "name": "Merki Inference", "email": "hello@merki.dev", "url": "https://merki.dev/docs" }
  },
  "externalDocs": { "description": "Compatibility scope and SDK configuration", "url": "https://merki.dev/docs/product/openapi-sdks" },
  "servers": [{ "url": "https://api.merki.dev", "description": "Documented API server (separate from the website hosting this specification)" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "OpenAI compatible", "description": "Common text chat completions and model-dependent embeddings." },
    { "name": "Anthropic compatible", "description": "Common text Messages requests with Merki bearer authentication." }
  ],
  "paths": {
    "/v1/chat/completions": {
      "post": {
        "operationId": "createChatCompletion",
        "summary": "Create a text chat completion",
        "description": "Common text baseline. Set stream to true for OpenAI-style SSE data chunks ending with data: [DONE]. A disconnect without the terminal marker is incomplete; re-issue rather than resume. Model and BYOK route capabilities apply.",
        "tags": ["OpenAI compatible"],
        "externalDocs": { "url": "https://merki.dev/docs/product/streaming" },
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/ChatRequest" },
            "examples": {
              "text": { "value": { "model": "Qwen3-32B", "messages": [{ "role": "user", "content": "Hi" }] } },
              "stream": { "value": { "model": "Qwen3-32B", "messages": [{ "role": "user", "content": "Hi" }], "stream": true } }
            }
          } }
        },
        "responses": {
          "200": {
            "description": "A JSON completion, or an SSE stream when stream is true. Examples illustrate family shapes, not live responses.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatResponse" },
                "example": { "id": "chatcmpl-example", "object": "chat.completion", "created": 1755648000, "model": "Qwen3-32B", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Hello." }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 8, "completion_tokens": 2, "total_tokens": 10 } }
              },
              "text/event-stream": {
                "schema": { "type": "string", "description": "SSE wire text, not a JSON document. Each data record contains a chat.completion.chunk with choices[].delta; the terminal data record is [DONE]. Delta fields can be absent on individual chunks. Extensions are outside this baseline." },
                "example": "data: {\"id\":\"chatcmpl-example\",\"object\":\"chat.completion.chunk\",\"created\":1755648000,\"model\":\"Qwen3-32B\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello.\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"chatcmpl-example\",\"object\":\"chat.completion.chunk\",\"created\":1755648000,\"model\":\"Qwen3-32B\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/InvalidKey" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Refused" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "5XX": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/embeddings": {
      "post": {
        "operationId": "createEmbeddings",
        "summary": "Create text embeddings where supported",
        "description": "Model-dependent endpoint. The text baseline accepts a string or a batch of strings. An illustrative provider model ID is used below; it does not assert that any particular hosted model offers embeddings. Check route and model capabilities first.",
        "tags": ["OpenAI compatible"],
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/EmbeddingRequest" },
            "example": { "model": "provider-embedding-model-id", "input": ["A short sentence."] }
          } }
        },
        "responses": {
          "200": {
            "description": "Embedding vectors and token usage. Vector dimension is model-dependent; this short example is illustrative.",
            "content": { "application/json": {
              "schema": { "$ref": "#/components/schemas/EmbeddingResponse" },
              "example": { "object": "list", "model": "provider-embedding-model-id", "data": [{ "object": "embedding", "index": 0, "embedding": [0.12, -0.34, 0.56] }], "usage": { "prompt_tokens": 4, "total_tokens": 4 } }
            } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/InvalidKey" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Refused" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "5XX": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "summary": "Create a text message",
        "description": "Anthropic-compatible Messages shape with Merki Authorization: Bearer authentication, not X-Api-Key. For the official TypeScript SDK use baseURL https://api.merki.dev and authToken; the SDK appends /v1/messages. Streaming uses named events and ends with message_stop, not [DONE]. The SDK also sends anthropic-version; header/version support must be confirmed with the backend operator, not inferred from this static contract.",
        "tags": ["Anthropic compatible"],
        "externalDocs": { "url": "https://merki.dev/docs/product/openapi-sdks" },
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "$ref": "#/components/schemas/MessageRequest" },
            "examples": {
              "text": { "value": { "model": "Qwen3-32B", "max_tokens": 128, "messages": [{ "role": "user", "content": "Hi" }] } },
              "stream": { "value": { "model": "Qwen3-32B", "max_tokens": 128, "messages": [{ "role": "user", "content": "Hi" }], "stream": true } }
            }
          } }
        },
        "responses": {
          "200": {
            "description": "A JSON message, or named SSE events when stream is true. Incomplete streams cannot be resumed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MessageResponse" },
                "example": { "id": "msg-example", "type": "message", "role": "assistant", "model": "Qwen3-32B", "content": [{ "type": "text", "text": "Hello." }], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 8, "output_tokens": 2 } }
              },
              "text/event-stream": {
                "schema": { "type": "string", "description": "SSE wire text with named message_start, content_block_start, content_block_delta (text_delta), content_block_stop, message_delta and terminal message_stop events. Non-content events may also occur; this is a text baseline, not an exhaustive event catalogue." },
                "example": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg-example\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"Qwen3-32B\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":8,\"output_tokens\":0}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"output_tokens\":2}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/InvalidKey" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Refused" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "5XX": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Merki API key in Authorization: Bearer <key>. No query-string keys. Keys are not documented as JWTs." }
    },
    "schemas": {
      "Model": { "type": "string", "minLength": 1, "description": "Hosted catalogue name or BYOK provider model ID; capabilities vary. Not constrained to the hosted catalogue.", "examples": ["Qwen3-32B", "provider-model-id"] },
      "TextBlock": { "type": "object", "required": ["type", "text"], "properties": { "type": { "type": "string", "const": "text" }, "text": { "type": "string" } }, "description": "Common text content block; non-text content is outside this baseline." },
      "ChatInputMessage": { "type": "object", "required": ["role", "content"], "properties": { "role": { "type": "string", "enum": ["system", "user", "assistant"] }, "content": { "type": "string" } }, "description": "Text-only chat input; tools and multimodal messages are not specified here." },
      "ChatRequest": {
        "type": "object", "required": ["model", "messages"],
        "properties": {
          "model": { "$ref": "#/components/schemas/Model" },
          "messages": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/ChatInputMessage" } },
          "stream": { "type": "boolean", "default": false },
          "max_tokens": { "type": "integer", "minimum": 1, "description": "Output token cap; allowed maximum depends on model." },
          "temperature": { "type": "number", "description": "Sampling temperature; accepted range depends on model/route." },
          "top_p": { "type": "number", "description": "Nucleus sampling setting; support depends on model/route." },
          "stop": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }], "description": "Stop text; model-specific support and limits apply." }
        },
        "description": "Representative common text request. Omitted fields are not a promise of support; this is not an exhaustive validation schema."
      },
      "ChatUsage": { "type": "object", "required": ["prompt_tokens", "completion_tokens", "total_tokens"], "properties": { "prompt_tokens": { "type": "integer", "minimum": 0 }, "completion_tokens": { "type": "integer", "minimum": 0 }, "total_tokens": { "type": "integer", "minimum": 0 } }, "description": "Common family token counters; Merki metering extensions are outside the baseline." },
      "ChatResponse": {
        "type": "object", "required": ["id", "object", "model", "choices"],
        "properties": {
          "id": { "type": "string" }, "object": { "type": "string", "const": "chat.completion" },
          "created": { "type": "integer", "description": "Unix timestamp in seconds." },
          "model": { "$ref": "#/components/schemas/Model" },
          "choices": { "type": "array", "items": {
            "type": "object", "required": ["index", "message", "finish_reason"],
            "properties": {
              "index": { "type": "integer", "minimum": 0 },
              "message": { "type": "object", "required": ["role", "content"], "properties": { "role": { "type": "string", "const": "assistant" }, "content": { "type": ["string", "null"] } } },
              "finish_reason": { "type": ["string", "null"], "description": "For example stop or length. Not a closed enum across provider routes." }
            }
          } },
          "usage": { "$ref": "#/components/schemas/ChatUsage" }
        }
      },
      "EmbeddingRequest": {
        "type": "object", "required": ["model", "input"],
        "properties": {
          "model": { "$ref": "#/components/schemas/Model" },
          "input": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string" } }] }
        },
        "description": "Common text input only. Token arrays, dimension overrides and alternative encodings are not specified by this baseline."
      },
      "EmbeddingResponse": {
        "type": "object", "required": ["object", "model", "data", "usage"],
        "properties": {
          "object": { "type": "string", "const": "list" }, "model": { "$ref": "#/components/schemas/Model" },
          "data": { "type": "array", "items": { "type": "object", "required": ["object", "index", "embedding"], "properties": { "object": { "type": "string", "const": "embedding" }, "index": { "type": "integer", "minimum": 0 }, "embedding": { "type": "array", "items": { "type": "number" }, "description": "Float vector; dimension depends on model." } } } },
          "usage": { "type": "object", "required": ["prompt_tokens", "total_tokens"], "properties": { "prompt_tokens": { "type": "integer", "minimum": 0 }, "total_tokens": { "type": "integer", "minimum": 0 } } }
        }
      },
      "MessageInput": {
        "type": "object", "required": ["role", "content"],
        "properties": { "role": { "type": "string", "enum": ["user", "assistant"] }, "content": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/TextBlock" } }] } }
      },
      "MessageRequest": {
        "type": "object", "required": ["model", "max_tokens", "messages"],
        "properties": {
          "model": { "$ref": "#/components/schemas/Model" },
          "max_tokens": { "type": "integer", "minimum": 1, "description": "Output token cap; model-specific maximum applies." },
          "messages": { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/MessageInput" } },
          "system": { "type": "string", "description": "Text system prompt, separate from messages." },
          "stream": { "type": "boolean", "default": false },
          "temperature": { "type": "number", "description": "Sampling temperature; support/range depends on route." },
          "top_p": { "type": "number", "description": "Nucleus sampling setting; support depends on route." },
          "stop_sequences": { "type": "array", "items": { "type": "string" } }
        },
        "description": "Representative text Messages baseline. Tools, thinking and multimodal content are outside this document."
      },
      "MessageResponse": {
        "type": "object", "required": ["id", "type", "role", "model", "content", "stop_reason", "usage"],
        "properties": {
          "id": { "type": "string" }, "type": { "type": "string", "const": "message" }, "role": { "type": "string", "const": "assistant" },
          "model": { "$ref": "#/components/schemas/Model" },
          "content": { "type": "array", "items": { "$ref": "#/components/schemas/TextBlock" } },
          "stop_reason": { "type": ["string", "null"], "description": "For example end_turn, max_tokens or stop_sequence; not a closed enum across routes." },
          "stop_sequence": { "type": ["string", "null"] },
          "usage": { "type": "object", "required": ["input_tokens", "output_tokens"], "properties": { "input_tokens": { "type": "integer", "minimum": 0 }, "output_tokens": { "type": "integer", "minimum": 0 } } }
        }
      },
      "Error": {
        "type": "object", "required": ["error"],
        "properties": { "error": {
          "type": "object", "required": ["code", "message"],
          "properties": {
            "code": { "type": "string", "enum": ["invalid_request", "invalid_key", "insufficient_credits", "forbidden", "not_found", "refused", "rate_limited", "upstream_error", "internal_error"] },
            "message": { "type": "string", "description": "Human explanation; branch on code, not message text." },
            "retry_after": { "type": "integer", "minimum": 0, "description": "Optional retry delay in seconds, illustrated for rate limits." }
          }
        } },
        "examples": [{ "error": { "code": "rate_limited", "message": "Slow down.", "retry_after": 12 } }]
      }
    },
    "responses": {
      "InvalidRequest": { "description": "invalid_request: unknown model, unsupported field or exceeded context. Fix the request, do not retry unchanged.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "invalid_request", "message": "Unsupported field." } } } } },
      "InvalidKey": { "description": "invalid_key: missing, revoked or unknown key. Rotate rather than retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "invalid_key", "message": "Invalid API key." } } } } },
      "InsufficientCredits": { "description": "insufficient_credits: top up before retrying.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "insufficient_credits", "message": "Balance too low." } } } } },
      "Forbidden": { "description": "forbidden: access profile or verification does not cover the request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "forbidden", "message": "Access profile does not cover this request." } } } } },
      "NotFound": { "description": "not_found: unknown endpoint or model name.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "not_found", "message": "Model not found." } } } } },
      "Refused": { "description": "refused: content policy refusal. Surface to the operator; do not silently retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "refused", "message": "Request refused under content policy." } } } } },
      "RateLimited": {
        "description": "rate_limited: honor Retry-After and back off with jitter.",
        "headers": { "Retry-After": { "description": "Retry delay; seconds or an HTTP date per HTTP semantics. Documentation illustrates seconds.", "schema": { "type": "string" }, "example": "12" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": { "code": "rate_limited", "message": "Slow down.", "retry_after": 12 } } } }
      },
      "ServerError": { "description": "upstream_error or internal_error: Merki or a BYOK provider failed. Retry with exponential backoff and jitter. After streaming starts, failure instead cuts the stream; only delivered tokens are billed and there is no resume.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "upstream": { "value": { "error": { "code": "upstream_error", "message": "Upstream provider failed." } } }, "internal": { "value": { "error": { "code": "internal_error", "message": "Internal error." } } } } } } }
    }
  }
}
