OpenAI-compatible HTTP relay for Raycast AI.
The working runtime is the Node server in this repo. Cloudflare Workers are intentionally not included because Raycast currently rejects Worker outbound subrequests with 403 Forbidden when Cloudflare injects the CF-Worker header.
GET /v1/modelsworks from the Node relay.POST /v1/chat/completionsworks from the Node relay.- Streaming and non-streaming OpenAI-compatible responses are supported.
- Cloudflare Workers cannot call Raycast directly at the moment.
- Cloudflare DNS, CDN, and Tunnel can still sit in front of a Node origin.
- Node.js 22 or newer.
- A Raycast account with Raycast AI access.
- Proxyman, Charles, or another HTTPS debugging proxy on macOS.
Enable SSL proxying for backend.raycast.com, then send a normal Raycast AI chat request. In Proxyman or Charles, find a successful request to:
POST https://backend.raycast.com/api/v1/ai/chat_completions
Copy these request values:
Authorization: Bearer ...->RAYCAST_BEARER_TOKENX-Raycast-DeviceId->RAYCAST_DEVICE_IDX-Raycast-Signature-> decode this JWT and copy itsaid->RAYCAST_AID
Decode RAYCAST_AID:
node -e 'const jwt = process.argv[1]; console.log(JSON.parse(Buffer.from(jwt.split(".")[1], "base64url")).aid)' 'PASTE_X_RAYCAST_SIGNATURE_HERE'The relay generates fresh X-Raycast-Timestamp, X-Raycast-Signature-v2, and X-Raycast-Signature values for every Raycast request. Do not reuse captured signatures.
SIG_SECRET is also required. It is the Raycast app signing key used to calculate request signatures. It is not user-specific, but this repo does not bundle it in source code.
Create .dev.vars in the repo root. This file is gitignored.
RAYCAST_BEARER_TOKEN=your_captured_bearer_token
RAYCAST_DEVICE_ID=your_captured_device_id
RAYCAST_AID=your_decoded_aid
SIG_SECRET=your_current_signature_secret
API_KEY=local-test-key
RAYCAST_USER_AGENT=Raycast/1.104.20 (macOS Version 26.5.1 (Build 25F80))
RAYCAST_EXPERIMENTAL=chatBranching, mcpHTTPServer
# Optional model-list filters:
# INCLUDE_PREMIUM=false
# INCLUDE_DEPRECATED=false
Install and run:
npm run dev -- --host 127.0.0.1 --port 8788OpenAI-compatible base URL:
http://127.0.0.1:8788/v1
Use API_KEY as the API key if it is set. With the example above, use local-test-key.
Health:
curl -sS http://127.0.0.1:8788/health \
-H 'Authorization: Bearer local-test-key'Models:
curl -sS http://127.0.0.1:8788/v1/models \
-H 'Authorization: Bearer local-test-key'Non-streaming chat:
curl -sS http://127.0.0.1:8788/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer local-test-key' \
-d '{
"model": "openai-gpt-5-mini",
"messages": [{"role": "user", "content": "Reply with exactly: pong"}],
"stream": false
}'Streaming chat:
curl -sS -N http://127.0.0.1:8788/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer local-test-key' \
-d '{
"model": "baseten-zai-org/GLM-5.2",
"messages": [{"role": "user", "content": "Reply with exactly: pong"}],
"stream": true
}'Set the same environment variables from .dev.vars, then run:
npm startFor public hosting, bind to all interfaces:
HOST=0.0.0.0 PORT=8788 npm startThis works on a VPS, Fly.io, Railway, Render, Northflank, systemd, Docker, or similar Node-capable hosts.
Build and run directly:
docker build -t raycast-relay .
docker run --env-file .dev.vars -p 8788:8788 raycast-relayOr use Docker Compose:
docker compose up --buildDo not deploy the Raycast-calling relay as a Cloudflare Worker. Raycast rejects Worker subrequests because Cloudflare injects CF-Worker, and Workers cannot remove it.
These Cloudflare setups are fine:
- Cloudflare DNS or CDN in front of a Node origin.
- Cloudflare Tunnel to a local or remote Node relay.
- A Worker that forwards to your Node relay, as long as the Worker does not call
backend.raycast.comitself.
This repo is intentionally Node-only. It does not include a Worker entrypoint.
Returns:
{"status":"ok"}Fetches Raycast's current model list and returns OpenAI-compatible model objects.
Optional filters:
INCLUDE_PREMIUM=falsehides models where Raycast marksrequires_better_ai: true.INCLUDE_DEPRECATED=falsehides models where Raycast marksavailability: "deprecated".ADVANCED=falseis still accepted as a backward-compatible alias forINCLUDE_PREMIUM=false.
Accepts OpenAI-style chat requests:
{
"model": "openai-gpt-5-mini",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}Model IDs are mapped to Raycast provider/model pairs by prefix. Examples:
openai-gpt-5-mini-> provideropenai, modelgpt-5-minibaseten-zai-org/GLM-5.2-> providerbaseten, modelzai-org/GLM-5.2anthropic-claude-sonnet-4-6-> provideranthropic, modelclaude-sonnet-4-6
403 Forbidden from Raycast:
- If this happens from Cloudflare Workers, use the Node relay instead.
- If this happens from Node, refresh your Raycast login and recapture
RAYCAST_BEARER_TOKEN,RAYCAST_DEVICE_ID, andRAYCAST_AID. - If the request shape is correct but signatures fail, refresh
SIG_SECRET.
SSE unknown_api_error from Raycast:
- Usually means
X-Raycast-Signature-v2does not match the exact JSON payload sent to Raycast. - Make sure
SIG_SECRETis current if Raycast changed the signing secret.
Empty or failing /v1/models:
GET /api/v1/ai/modelsis signed as if the request body were the literal string{}even though the HTTP request has no body. The Node relay already does this.
Client says invalid API key:
- Use the value of
API_KEYas the OpenAI API key. - Unset
API_KEYif you want to disable relay-side authentication.
Raycast request credentials give access to your Raycast AI account. Keep .dev.vars private, use server-side environment variables in production, and rotate/re-login if a token was pasted into logs or chat.