Skip to content

transcribe-proxy

Run a proxy server that forwards transcription requests to your configured ASR provider (Wyoming, OpenAI, or Gemini).

This is useful for integrating agent-cli's transcription capabilities into other applications, such as iOS Shortcuts.

Usage

agent-cli server transcribe-proxy [OPTIONS]

Examples

# Run on default port
agent-cli server transcribe-proxy

# Custom port
agent-cli server transcribe-proxy --port 8080

# Development mode with auto-reload
agent-cli server transcribe-proxy --reload

Options

Options

Option Default Description
--host 0.0.0.0 Network interface to bind. Use 0.0.0.0 for all interfaces
--port, -p 61337 Port for the HTTP API
--reload false Auto-reload on code changes (development only)

General Options

Option Default Description
--log-level info Set logging level.

API Endpoints

Endpoint Method Description
/transcribe POST Transcribe audio file, optionally with speaker labels
/diarize POST Identify speaker timestamps without transcription
/health GET Health check
/docs GET Interactive API documentation

POST /transcribe

Transcribe an audio file with optional LLM post-processing.

Request Parameters (multipart/form-data):

Parameter Type Default Description
audio file required Audio file (wav, mp3, m4a, ogg, flac, aac, webm)
cleanup boolean true Whether to apply LLM post-processing to clean up the transcript
diarize boolean false Return speaker-labeled segments; requires cleanup=false
min_speakers integer - Optional minimum number of speakers, at least 1
max_speakers integer - Optional maximum number of speakers, at least the minimum
align_words boolean false Use forced word alignment; requires diarize=true
align_language string en Forced alignment language: en, fr, de, es, it
extra_instructions string - Additional instructions for the LLM cleanup (appended to any config file instructions)

Disabling LLM Post-Processing:

If you only need raw transcription without LLM cleanup (e.g., for simple note-taking or environments without LLM infrastructure), pass cleanup=false:

curl -X POST http://localhost:61337/transcribe \
  -F "audio=@recording.wav" \
  -F "cleanup=false"

This skips the LLM step entirely, reducing latency and removing the LLM dependency for that request.

Response:

{
  "raw_transcript": "the original transcription",
  "cleaned_transcript": "The cleaned transcription.",
  "success": true,
  "error": null,
  "segments": null
}
Field Type Description
raw_transcript string The raw transcription from the ASR provider
cleaned_transcript string or null The LLM-cleaned transcript, or null if cleanup=false
success boolean Whether the transcription succeeded
error string or null Error message if something went wrong
segments array or null Speaker segments (speaker, start, end, text), or null without diarization

Speaker Diarization

Install the optional dependencies and set HF_TOKEN on the server:

uv sync --extra server --extra wyoming --extra llm --extra diarization
export HF_TOKEN=your-huggingface-token
agent-cli server transcribe-proxy

Accept the model access conditions for speaker-diarization-3.1, segmentation-3.0, and wespeaker-voxceleb-resnet34-LM. Use a token with read access to these models. The first request downloads the models; later requests reuse them.

For a speaker-labeled transcript, configure your usual ASR backend and send:

curl http://localhost:61337/transcribe \
  -F "audio=@interview.wav" \
  -F "cleanup=false" -F "diarize=true" \
  -F "min_speakers=2" -F "max_speakers=2" \
  -F "align_words=true" -F "align_language=en"

raw_transcript contains the original ASR text. The additional segments field contains:

[
  {"speaker": "SPEAKER_00", "start": 0.4, "end": 2.1, "text": "Hello there."},
  {"speaker": "SPEAKER_01", "start": 2.3, "end": 4.0, "text": "Welcome back."}
]

align_words=true uses the existing wav2vec2 alignment and downloads an additional language model. Without it, sentence timing is estimated from transcript length, so speaker assignment is approximate. Word alignment also falls back to this estimate if it cannot align any words. Speaker labels are local to each upload; this API does not enroll or identify persistent voice profiles.

POST /diarize

Return speaker labels and timestamps without using an ASR provider:

curl http://localhost:61337/diarize \
  -F "audio=@interview.wav" \
  -F "min_speakers=2" -F "max_speakers=2"

Accepts the same audio upload and optional min_speakers / max_speakers hints as /transcribe. Returns {"segments": [{"speaker": "SPEAKER_00", "start": 0.4, "end": 2.1, "text": ""}]}. Times are seconds from the start of the file. Overlapping speech can produce overlapping segments; silence returns an empty list.

Both endpoints delete temporary audio after processing. The model loads lazily and remains in memory until the process exits; concurrent diarization requests run sequentially while health checks remain responsive. Use one server worker to avoid loading extra model copies. /health checks the HTTP service; it does not verify model access or readiness. Missing token or dependencies return HTTP 503, invalid options return 422, empty uploads return 400, and inference failures return 500.

How It Works

The transcription proxy acts as a bridge between client applications and your configured ASR provider:

┌─────────────────┐     ┌─────────────────────┐     ┌─────────────────┐
│  iOS Shortcut   │────▶│  Transcription      │────▶│  ASR Provider   │
│  or other app   │     │  Proxy (:61337)     │     │  (configured)   │
└─────────────────┘     └─────────────────────┘     └─────────────────┘
                                                           │
                              ┌────────────────────────────┼────────────────────────────┐
                              ▼                            ▼                            ▼
                        ┌──────────┐                 ┌──────────┐                 ┌──────────┐
                        │ Wyoming  │                 │  OpenAI  │                 │  Gemini  │
                        │ (Local)  │                 │  (Cloud) │                 │  (Cloud) │
                        └──────────┘                 └──────────┘                 └──────────┘

The proxy reads your agent-cli configuration to determine which ASR provider to use, then forwards transcription requests accordingly.

Use Cases

iOS Shortcuts Integration

The proxy provides a simple HTTP endpoint that iOS Shortcuts can call to transcribe audio. See the iOS Shortcut Guide for setup instructions.

Custom Applications

Any application that can make HTTP requests can use the proxy to access transcription services without needing to implement provider-specific logic.

Installation

Requires the server extra:

pip install "agent-cli[server]"
# or
uv sync --extra server

Docker

Run using the published container image:

# Run standalone
docker run -p 61337:61337 ghcr.io/basnijholt/agent-cli-transcribe-proxy:latest

# Or use docker-compose (included in both cuda and cpu profiles)
docker compose -f docker/docker-compose.yml --profile cpu up transcribe-proxy

Docker with diarization

Run the published image, which includes pyannote, PyTorch, and FFmpeg:

docker run --rm -p 61337:61337 \
  -e HF_TOKEN \
  -v agent-cli-diarization-cache:/home/transcribe/.cache \
  ghcr.io/basnijholt/agent-cli-diarization:latest

This runs /diarize on CPU. For NVIDIA GPU inference, add --gpus all -e DIARIZATION_DEVICE=cuda to docker run. For /transcribe, also provide your ASR environment variables, for example -e ASR_PROVIDER=openai -e ASR_OPENAI_BASE_URL=http://your-whisper-host:10301/v1 -e OPENAI_API_KEY=dummy.

The optional Compose service can run beside the existing Whisper server:

# HF_TOKEN must be exported in the shell or set in docker/.env.
docker compose -f docker/docker-compose.yml --profile cpu up whisper-cpu diarization

Use diarization in place of transcribe-proxy, since both default to port 61337. Set DIARIZATION_PORT to use a different host port. The model cache persists in agent-cli-diarization-cache. GPU instructions are included beside the service in the Compose file.

To build from source instead, add --build to the Compose command or run:

docker build -f docker/diarization.Dockerfile -t agent-cli-diarization .

Environment Variables

Configure the proxy using environment variables (priority: env var > config file > default):

Variable Default Description
HF_TOKEN - Hugging Face read token for diarization models
DIARIZATION_DEVICE auto PyTorch device, e.g. cpu, cuda, cuda:0, mps
ASR_PROVIDER wyoming ASR provider: wyoming, openai, gemini
ASR_WYOMING_IP localhost Wyoming ASR server hostname/IP
ASR_WYOMING_PORT 10300 Wyoming ASR server port
ASR_OPENAI_MODEL whisper-1 OpenAI ASR model name
ASR_OPENAI_BASE_URL - Custom OpenAI-compatible ASR endpoint
ASR_OPENAI_PROMPT - Custom prompt to guide transcription
ASR_GEMINI_MODEL gemini-3-flash-preview Gemini ASR model name
LLM_PROVIDER ollama LLM provider: ollama, openai, gemini
LLM_OLLAMA_MODEL gemma3:4b Ollama model name
LLM_OLLAMA_HOST http://localhost:11434 Ollama server URL
LLM_OPENAI_MODEL gpt-5-mini OpenAI model name
LLM_GEMINI_MODEL gemini-3-flash-preview Gemini model name
TTS_PROVIDER wyoming TTS provider: wyoming, openai, kokoro, gemini
LOG_LEVEL info Logging level: debug, info, warning, error
OPENAI_API_KEY - OpenAI API key
OPENAI_BASE_URL - Custom OpenAI-compatible API base URL
GEMINI_API_KEY - Gemini API key

Example with Wyoming ASR in Docker Compose:

docker run -p 61337:61337 \
  -e ASR_WYOMING_IP=agent-cli-whisper \
  -e ASR_WYOMING_PORT=10300 \
  ghcr.io/basnijholt/agent-cli-transcribe-proxy:latest

Custom Config File

To use a custom config, mount it as a volume:

docker run -p 61337:61337 \
  -v ./config.toml:/home/transcribe/.config/agent-cli/config.toml:ro \
  ghcr.io/basnijholt/agent-cli-transcribe-proxy:latest