A stateless FastAPI service, packaged as a Docker container, that bridges Apple Siri Shortcuts to an Open WebUI instance. Say "Hey Siri, Siri Plus" to start a voice-driven LLM conversation β your words are transcribed by the Shortcuts app, sent to the service, and the LLM response is spoken back to you.
- webui-siri-shortcut
- You invoke the Siri Shortcut ("Hey Siri, Siri Plus").
- The shortcut says "Yes?" and records your spoken question via dictation.
- The shortcut calls
POST /api/chatβ the service creates a new Open WebUI chat and sends the first message. - The LLM response is spoken back to you.
- The shortcut asks if you want to continue β your next dictation becomes the follow-up message.
- Say "no", "nope", "none", "stop", or any phrase containing "no" to end the session.
All conversation history is stored in Open WebUI and visible in its browser interface.
- Docker and Docker Compose on your home server
- A running Open WebUI instance reachable from the server
- An Open WebUI API token (Settings β Account β API Keys)
- The service must be reachable over HTTPS from your iPhone or Mac (iOS/macOS Shortcuts blocks plain HTTP)
- iOS 16+ or macOS Ventura+ for the Siri Shortcut
cp docker-compose.yml.example docker-compose.ymlEdit docker-compose.yml and fill in at minimum:
| Field | What to set |
|---|---|
OPEN_WEBUI_URL |
Base URL of your Open WebUI instance |
OPEN_WEBUI_TOKEN |
API token from Open WebUI β Settings β Account β API Keys |
OPEN_WEBUI_MODEL |
Model ID to use (e.g. llama3.2, gpt-4o) |
API_KEY |
A random secret β see step 2 |
openssl rand -hex 32Paste the output into API_KEY in docker-compose.yml.
The Docker image is published on Docker Hub as acaranta/webui-siri-shortcuts:latest and pulled automatically.
docker compose up -dcurl http://localhost:8080/api/health
# {"status":"ok"}The simplest method is to install the Siri Plus shortcut template directly from iCloud: π¬π§ English Β· π«π· French.
Then rename the template to whatever you wish to invoke with Siri (e.g. "Siri Plus"). Edit the shortcut and set the Base URL and API Key fields to match your configuration.
All configuration is through environment variables. Set them in docker-compose.yml.
| Variable | Required | Default | Description |
|---|---|---|---|
OPEN_WEBUI_URL |
yes | β | Base URL of your Open WebUI instance (e.g. http://open-webui:3000) |
OPEN_WEBUI_TOKEN |
yes | β | Open WebUI API token |
OPEN_WEBUI_MODEL |
yes | β | Default model ID (e.g. llama3.2, gpt-4o) |
API_KEY |
yes | β | Shared secret sent by the Siri Shortcut in the X-API-Key header |
API_PORT |
no | 8080 |
Port the service listens on inside the container |
OPEN_WEBUI_FOLDER |
no | β | Name of the Open WebUI folder to file Siri chats under β see below |
When OPEN_WEBUI_FOLDER is set, every chat created by this service is automatically moved into a named folder in Open WebUI (e.g. "Siri"). This keeps your Siri conversations separated from chats you start manually in the browser.
Behaviour:
- At the time the first chat is created after startup, the service looks up the folder by name via the Open WebUI API.
- If the folder does not exist it is created automatically.
- The resolved folder ID is cached for the lifetime of the process. No repeated lookups are made.
- Restarting the container clears the cache; the lookup runs again on the next chat creation.
Example docker-compose.yml snippet:
environment:
OPEN_WEBUI_FOLDER: "Siri"Leave the variable unset (or remove the line) to disable folder filing β new chats will land in the default location.
To get more specific answers, it is advised to use a more refined model, using OpenwebUI model creation (Workspace β Models β Create Model) with a system prompt like:
You are a helpful voice assistant answering the user through Siri.
Your answers will be heard, not read, so optimise for spoken conversation:
- Do not use links, URLs, markdown, bullet points, emojis, code formatting, or any other visual-only elements.
- Do not mention attachments, images, buttons, menus, or anything the user cannot see.
- Keep answers concise and easy to follow.
- Default to 1 or 2 short sentences unless the user explicitly asks for more detail.
- Prioritise the most useful information first.
- Use natural spoken phrasing.
Language rules:
- Always reply in the same language as the userβs request.
- Supported languages are English and French.
- If the user switches language, switch with them.
Behaviour rules:
- Answer directly and clearly.
- If the request is ambiguous, ask one short clarifying question.
- If the answer is uncertain, say so briefly and give the most likely helpful answer.
- Avoid unnecessary disclaimers, filler, and repetition.
Your goal is to sound natural, helpful, and efficient in a voice-only interaction.
Then use the id for this created model as the model field in the request body when calling the API, or set it as OPEN_WEBUI_MODEL to make it the default.
All chat endpoints require the header X-API-Key: <your key>. The health endpoint has no authentication.
Interactive API docs are available at http://localhost:8080/api/docs while the service is running.
Start a new conversation. Returns a chat_id that must be passed to subsequent follow-up requests.
Request body:
{
"message": "What is the capital of France?",
"model": "llama3.2"
}model is optional and defaults to OPEN_WEBUI_MODEL.
Response:
{
"chat_id": "abc123-...",
"response": "The capital of France is Paris.",
"title": "Capital of France"
}Example:
curl -s -X POST https://YOUR_SERVER/api/chat \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "What is the capital of France?"}' | python -m json.toolSend a follow-up message to an existing chat. Conversation history stored in Open WebUI is included automatically as context.
Request body:
{
"message": "And what language do they speak there?"
}Response:
{
"chat_id": "abc123-...",
"response": "The official language of France is French."
}Health check. No authentication required.
Response:
{"status": "ok"}Quickest method: Install the Siri Plus shortcut template directly from iCloud β π¬π§ English or π«π· French β on your iPhone or Mac, then edit the Base URL and API Key fields inside the shortcut to match your configuration.
Alternatively, generate a pre-configured shortcut with the provided script on macOS (stdlib only, no extra dependencies):
python shortcut/generate_shortcut.py \
--url https://YOUR_SERVER \
--api-key YOUR_API_KEY \
--serveThe --serve flag starts a local HTTP server and prints a shortcuts://import-shortcut?url=...
link. Open it in Safari on your iPhone or Mac to import. This works on all versions including
macOS Sequoia and iOS 18+, where direct file import is blocked for unsigned shortcuts.
Without --serve (macOS Ventura/Sonoma, iOS 16β17 only), double-click siri-plus.shortcut to import.
Follow the step-by-step guide in shortcut/SETUP.md. The guide covers each Shortcuts action, variable naming, the follow-up loop, and the stop-phrase detection logic.
The API key is stored in plain text inside the shortcut file. Do not share the exported .shortcut file with others. If the key is compromised, generate a new one (openssl rand -hex 32), update API_KEY in docker-compose.yml, and rebuild the shortcut.
iOS and macOS Shortcuts enforce HTTPS for all outbound URL requests. The container itself serves plain HTTP β you must place it behind a reverse proxy that handles TLS termination.
nginx example:
server {
listen 443 ssl;
server_name siri.example.com;
ssl_certificate /etc/letsencrypt/live/siri.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/siri.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}
}Set proxy_read_timeout to at least 90s. LLM inference can take 5β30 seconds and the Shortcuts app has a hard URL request timeout of approximately 60 seconds; large models on slow hardware can approach this limit.
Self-signed certificates will fail on iOS unless you install a trust profile on the device.
# Install dependencies (requires uv)
uv sync
# Set required environment variables
export OPEN_WEBUI_URL=http://localhost:3000
export OPEN_WEBUI_TOKEN=sk-your-token
export OPEN_WEBUI_MODEL=llama3.2
export API_KEY=dev-key-123
# Run locally
uv run python -m webui_siri.mainInteractive API docs are then available at http://localhost:8080/api/docs.
Stack: Python 3.11+, FastAPI, uvicorn, httpx, pydantic-settings. Dependency management via uv.
- Stateless β no database, no volumes. All conversation history lives in Open WebUI.
- The Siri Shortcut holds
chat_idin a local variable across loop iterations; the service itself is session-unaware. - Open WebUI's linked-list message history is fully maintained by the service: each message is written back so the conversation is readable in the browser interface.
- The folder ID resolved via
OPEN_WEBUI_FOLDERis process-scoped. It is looked up once and cached in memory; a container restart resets the cache.
