Apex — API Reference
Anthropic Messages API endpoints, authentication, request/response shapes for the Apex LLM gateway.
Apex is the CMMD LLM gateway. It exposes a stable, brand-controlled API surface at apex.cmmd.ai and routes per-model traffic to upstream providers (DeepSeek, Moonshot, or local Ollama on Mac Studio) without exposing those upstreams to consumers.
Base URL
Section titled “Base URL”| Use | URL |
|---|---|
| Canonical | https://apex.cmmd.ai |
| Legacy alias (same backend; do not use for new code) | https://apex-api.cmmd.ai |
Endpoints
Section titled “Endpoints”POST /v1/messages — Anthropic Messages API (canonical)
Section titled “POST /v1/messages — Anthropic Messages API (canonical)”- Native Anthropic Messages spec, no translation.
- Streaming:
stream: true→ Anthropic SSE event types (message_start,content_block_start,content_block_delta,content_block_stop,message_delta,message_stop). - Tool use: native Anthropic
tool_use/tool_resultcontent blocks. - Auth required (Bearer or x-api-key).
POST /v1/chat/completions — OpenAI-compatible (local Ollama only)
Section titled “POST /v1/chat/completions — OpenAI-compatible (local Ollama only)”- Routes only to local
apex-coreon Mac Studio. - Cloud models (
apex-flash/apex-general/apex-reason) won’t tool-call cleanly through this path — shape mismatch with Anthropic-native upstreams. - Discouraged for new code; use Anthropic instead.
GET /v1/models
Section titled “GET /v1/models”- Open (no auth required; used by health checks).
- Returns OpenAI-style
{"object":"list","data":[...]}with the 4 apex slugs.
Send one of:
Authorization: Bearer <APEX_API_KEY>OR
x-api-key: <APEX_API_KEY>The shim accepts both. Anthropic SDK uses x-api-key when constructed with apiKey:. Do NOT pass both apiKey AND authToken to the Anthropic SDK constructor — it sends both headers and is messy.
Cloudflare WAF — custom User-Agent required
Section titled “Cloudflare WAF — custom User-Agent required”Cloudflare WAF blocks the default OpenAI/JS AND Anthropic/JS SDK user-agents at the edge (returns 403 — Your request was blocked before the request reaches the shim).
Override the UA in the SDK config:
const apex = new Anthropic({ apiKey: process.env.APEX_API_KEY, baseURL: "https://apex.cmmd.ai", defaultHeaders: { "User-Agent": "<your-product>/<version>", },});UAs known to bypass:
cmmd-apex-provider/1.0(CMMD platform)watchtower/cmmd-blackbox(Watchtower probes)forge/0.x(Forge)curl/8.x(manual testing)claude-code-cli/*(Claude Code viaANTHROPIC_BASE_URL)
Models
Section titled “Models”| Slug | Upstream | p95 latency | Best for |
|---|---|---|---|
apex-core | Local Ollama on Mac Studio (qwen3.5-122b-a10b) | 5–8s | Private/sensitive (no data leaves CMMD), long context |
apex-flash | DeepSeek /anthropic (deepseek-v4-flash) | ~0.7s | Chat, low-latency UX |
apex-general | Moonshot /anthropic (kimi-k2.6) | ~0.8s | RAG, summarization, long context |
apex-reason | DeepSeek /anthropic (deepseek-v4-pro thinking) | ~1.3s | Tool-heavy agents, deep reasoning |
All four support streaming and tool use natively in Anthropic format.
Token Registry
Section titled “Token Registry”Three tokens are active in the shim allowlist:
| Token id | Consumer | Storage location |
|---|---|---|
cmmd-prod | CMMD platform on Railway | Railway env APEX_API_KEY |
forge-prod | Forge cloud / desktop | Forge env APEX_API_KEY |
github | GitHub Actions across CMMD-Center | GitHub org secret APEX_API_KEY |
Token format: sk-apex-<id>-<32-char-secret>
Source of truth: ~/.apex/think-shim/tokens.db on Mac Studio. Only sha256 hashes are stored; plaintext exists only on the machine that issued the token. Treat tokens as one-write: paste into the secret store on issue, retrieve from there forever after.
Token operations (run on Mac Studio)
Section titled “Token operations (run on Mac Studio)”# List all tokens (IDs + status + last_used, never plaintext)sqlite3 ~/.apex/think-shim/tokens.db \ 'SELECT id, status, last_used FROM tokens;'
# Issue a new token (prints plaintext ONCE)~/.apex/think-shim/apex-token issue --id <consumer-name>
# Revoke~/.apex/think-shim/apex-token revoke <consumer-name>
# Rotate (revoke + reissue same id, prints new plaintext ONCE)~/.apex/think-shim/apex-token rotate <consumer-name>
# Reload tokens without restarting the shimkill -HUP $(pgrep -f think-shim/shim.py)After issuing or rotating, paste the new value into the consumer’s env immediately. If the consumer is on Railway, redeploy the service so the new env value takes effect.
Required headers (full set)
Section titled “Required headers (full set)”POST /v1/messages HTTP/1.1Host: apex.cmmd.aiAuthorization: Bearer <APEX_API_KEY>Content-Type: application/jsonanthropic-version: 2023-06-01User-Agent: <product>/<version>Smoke tests
Section titled “Smoke tests”curl -sS https://apex.cmmd.ai/v1/messages \ -H "Authorization: Bearer $APEX_API_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -H "User-Agent: cmmd-smoke/1.0" \ --data '{"model":"apex-flash","max_tokens":20,"messages":[{"role":"user","content":"ping"}]}'Expect 200 + JSON with content[0].text.
curl -N https://apex.cmmd.ai/v1/messages \ -H "Authorization: Bearer $APEX_API_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -H "User-Agent: cmmd-smoke/1.0" \ --data '{"model":"apex-flash","max_tokens":50,"stream":true,"messages":[{"role":"user","content":"count to 5"}]}'Expect a stream of event: … / data: {…} lines for ~1s.
curl -sS https://apex.cmmd.ai/v1/modelsExpect {"object":"list","data":[{"id":"apex-core",...}, ...]} with four entries.
SDK reference setup
Section titled “SDK reference setup”TypeScript (Anthropic SDK)
Section titled “TypeScript (Anthropic SDK)”import Anthropic from "@anthropic-ai/sdk";
const apex = new Anthropic({ apiKey: process.env.APEX_API_KEY, baseURL: "https://apex.cmmd.ai", // NO /v1 suffix defaultHeaders: { "User-Agent": "your-app/1.0", // bypass WAF },});
// non-streamingconst res = await apex.messages.create({ model: "apex-flash", max_tokens: 1024, messages: [{ role: "user", content: "hello" }],});
// streamingconst stream = await apex.messages.stream({ model: "apex-flash", max_tokens: 1024, messages: [{ role: "user", content: "hello" }],});for await (const event of stream) { // event.type: message_start | content_block_start | content_block_delta | ...}const final = await stream.finalMessage();Tool use
Section titled “Tool use”const res = await apex.messages.create({ model: "apex-reason", // tool-heavy workflows prefer thinking max_tokens: 4096, tools: [{ name: "get_user_by_id", description: "Look up a CMMD user record", input_schema: { type: "object", properties: { id: { type: "string" } }, required: ["id"], }, }], messages: [{ role: "user", content: "Find user 42" }],});
// res.content may contain { type: "tool_use", id, name, input }// reply with: { role: "user", content: [{ type: "tool_result", tool_use_id, content: "..." }] }What NOT to do
Section titled “What NOT to do”- ❌ Don’t use OpenAI SDK or
/v1/chat/completionsfor cloud models (apex-flash/apex-general/apex-reason) — tool-call shape mismatch. - ❌ Don’t reference upstream model IDs (
deepseek-v4-pro,kimi-k2.6, etc.) in code. Always useapex-*slugs. - ❌ Don’t include
/v1in thebaseURL— the SDK appends it. - ❌ Don’t pass both
apiKeyANDauthTokento the Anthropic SDK constructor — sends duplicate headers. - ❌ Don’t send the default
OpenAI/JSorAnthropic/JSUA — CF WAF blocks it. - ❌ Don’t try
apex-*-labslugs — LAB tier is currently paused. - ❌ Don’t hard-code token values in repos — pull from env.
Architecture (one-paragraph)
Section titled “Architecture (one-paragraph)”Consumer → Cloudflare (apex.cmmd.ai) → cloudflared tunnel → Mac Studio shim (apex-think-shim on :11500) → upstream. The shim:
- Validates Bearer / x-api-key against SQLite allowlist.
- Looks up
body.modelin the in-process route table. - Rewrites
body.modelto the upstream’s canonical ID (e.g.apex-reason→deepseek-v4-pro). - Forwards the request to the upstream’s
/anthropic/v1/messages(cloud) or local Ollama at/v1/messages(apex-core). - Returns the response unchanged.
DeepSeek and Moonshot both expose /v1/messages natively in Anthropic format — there is no protocol translation in the path. That is why the shim is fast and simple.
See mac-studio/think-shim/shim.py for the implementation.
Related
Section titled “Related”- Apex models — per-model capabilities and pricing
- Apex architecture — full stack diagram
- Apex operations — runbooks, monitoring, recovery
- CMMD integration — how the CMMD platform talks to Apex
- Forge integration — how Forge talks to Apex