API surface
Rapid-MLX exposes an OpenAI-shaped HTTP API at
http://localhost:8000/v1 by default. If a tool speaks
OpenAI, it speaks Rapid-MLX. Point the base URL at your local
server and pass any non-empty API key.
GET /openapi.json and an
interactive Swagger UI at GET /docs. The list below
is the human-readable summary; the JSON schema is the source of truth.
Endpoints
| method | path | description |
|---|---|---|
| GET | /v1/models | List available models — alias, parser bindings, capability flags. |
| POST | /v1/chat/completions | Chat completions — tools, vision, streaming, reasoning_content all supported. |
| POST | /v1/responses | OpenAI Responses API — streaming with item-by-item events, reasoning items, tool calls. |
| POST | /v1/messages | Anthropic Messages API — the surface Claude Code targets; tools, streaming, and reasoning content all supported. |
| POST | /v1/messages/count_tokens | Anthropic token-count endpoint for the Messages API. |
| POST | /v1/completions | Legacy text completion (no chat template). |
| POST | /v1/embeddings | Embeddings — requires the [embeddings] install extra and an embedding alias (e.g. embeddinggemma-300m-6bit). |
| POST | /v1/audio/speech | Text-to-speech (Kokoro, Qwen3-TTS, IndexTTS, Chatterbox, VibeVoice, VoxCPM, F5-TTS, Dia) — requires the [audio] extra. Cloning models take a ref_audio extension field; Qwen3-TTS Base and F5-TTS also need ref_text. |
| POST | /v1/audio/transcriptions | Speech-to-text (Whisper, Parakeet, SenseVoice) — requires the [audio] extra. Returns word-level timestamps. Passing a text field switches to forced alignment against that transcript instead of recognition. |
| POST | /v1/audio/music | Text-to-music / SFX — requires the [audio] extra. |
| POST | /v1/videos | Create a video-generation job (Wan, CogVideoX-Fun, LTX-2.3) — requires the [video] extra, ffmpeg, and Python 3.11+. Asynchronous: returns a job id. |
| GET | /v1/videos/{id} | Poll a video job's status. |
| GET | /v1/videos/capabilities | What the loaded video model accepts — modes, native fps, size rule and frame-count rule. Same contract the route validates against. |
| GET | /v1/videos/{id}/content | Download the finished MP4. |
| GET | /health | Health probe (also /healthz, /readyz, /livez). |
| GET | /metrics | Prometheus-format counters and histograms. |
| GET | /openapi.json | OpenAPI 3 schema for everything above. |
| GET | /docs | Swagger UI (interactive). |
What's the same as OpenAI
- Request and response shapes for
/chat/completions,/completions,/embeddings,/responses. - SSE streaming via
stream: truewithdata: [DONE]terminator. - Function / tool calling via
toolsandtool_choice. - Vision inputs as
image_urlcontent parts on multimodal models (Gemma 4, Qwen3-VL). - Authorization header: when the server is started without
--api-keyit is parsed but not enforced (pass any string); start with--api-keyand a missing or wrong key returns401.
What is on by default in 0.9
Two features flipped from opt-in to on-by-default in 0.9 because they help every multi-user workload and cost nothing on the single-user path.
-
Compressed KV cache (K8V4) on 9 hero MoE aliases.
Qwen3.5-9B / 27B and the Qwen3.6-35B-A3B family (4 / 6 / 8-bit + DWQ)
automatically use the K8V4 compressed KV cache. This roughly doubles
the concurrent-user count at the same RAM budget, and the output
is byte-identical to the uncompressed baseline. If you'd rather
run without it — for a bench or an A/B — start the server with
rapid-mlx serve <alias> --kv-cache-turboquant none. - Radix-tree prefix cache. When several requests share a common prefix (system prompt, tool schema, RAG boilerplate) the server reuses the already-computed KV. Turns on automatically — no flag to flip, no per-request opt-in. Measured 13× aggregate throughput at 10 concurrent clients on the K8V4 hero aliases.
-
Speculative decoding is opt-in. MTP and DFlash are both
verified lossless (byte-identical output) but you have to
enable them explicitly:
MTP via
--speculative-config '{"method":"mtp","num_speculative_tokens":3}', DFlash viapip install 'rapid-mlx[dflash]'+--speculative-config '{"method":"dflash"}'.
Where it diverges
-
reasoning_contentis surfaced as a sibling tocontentfor reasoning models — same field name as DeepSeek-R1's API, not OpenAI'sreasoningblocks. -
reasoning_max_tokenscaps the thinking portion of a request (tokens inside<think>…</think>); the model then answers. It does not bound the answer — pair it withmax_tokensfor total length. -
/v1/modelsincludes Rapid-MLX-specific fields:tool_call_parser,reasoning_parser,is_hybrid,is_moe,supports_spec_decode. -
Tool-call output respects the effective parser for the
served model rather than always emitting OpenAI-flavoured JSON —
most aliases use one of Hermes / Qwen3-XML / GLM4.7 / Harmony /
UI-TARS / Llama / DeepSeek / Minimax. The wire shape the client
sees is normalised back to OpenAI's
tool_callsarray. -
Streaming SSE for
/v1/responsesemitsresponse.output_item.added,response.output_item.done, reasoning items, and tool-call items in the order OpenAI specifies — but the per-token deltas are batched per model: an AR model emits one token per delta, a diffusion model emits a block.
Examples
List models
$ curl http://localhost:8000/v1/models | jq .data[0] { "id": "qwen3.5-4b-4bit", "object": "model", "tool_call_parser": "hermes", "reasoning_parser": "qwen3", "is_hybrid": false, "is_moe": false, "supports_spec_decode": false }
Streaming chat completion
from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="x") stream = client.chat.completions.create( model="qwen3.5-4b-4bit", messages=[{"role": "user", "content": "Hello"}], stream=True, ) for chunk in stream: print(chunk.choices[0].delta.content or "", end="")
Embeddings
# first install: pip install 'rapid-mlx[embeddings]' $ rapid-mlx serve embeddinggemma-300m-6bit --port 8001 & $ curl http://localhost:8001/v1/embeddings \ -H "Content-Type: application/json" \ -d '{"model": "embeddinggemma-300m-6bit", "input": "ping"}'
Long inputs. Before 0.11.8 the tokenizer was pinned at 512
tokens, so anything longer came back HTTP 200, correctly
shaped, and quietly missing its tail — which degrades a vector
index with no signal. The limit is now derived from the model
(config.max_position_embeddings, else
tokenizer.model_max_length), so a 32K-context
embedder like Qwen3-Embedding-4B uses its real ceiling. Overflow
is never silent: it either logs and increments
rapid_mlx_embedding_truncations_total on
/metrics, or — with
--embedding-overflow-policy error — returns a
structured 400:
$ rapid-mlx serve embeddinggemma-300m-6bit --port 8001 \ --embedding-max-length 2048 --embedding-overflow-policy error # a 3,000-token input then returns, instead of a silently short vector: {"error": {"code": "input_too_long", "message": "…observed vs allowed tokens…"}}
Audio (TTS)
# first install: pip install 'rapid-mlx[audio]' $ rapid-mlx serve kokoro-tts --port 8002 & $ curl http://localhost:8002/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model": "kokoro-tts", "voice": "af_heart", "input": "hello"}' \ -o speech.wav
Where to next
For the full request/response field reference, hit the live
Swagger UI at http://localhost:8000/docs on your own
server — it stays in sync with the running build, including any
flags wired up by recent releases. The
engine README
covers CLI flags, environment variables, and the
rapid-mlx launch <client> bootstrap targets.