Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Azure Speech to Text REST API for short audio (Python). Use for simple speech recognition of audio files up to 60 seconds without the Speech SDK.
.claude/skills/azure-speech-to-text-rest-py/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-20 | ✗→✓ | ▲ Improved | — | — |
| case-23 | ✓→✓ | = Same ✓ | — | — |
| case-21 | ✓→✓ | = Same ✓ | — | — |
Simple REST API for speech-to-text transcription of short audio files (up to 60 seconds). No SDK required - just HTTP requests.
bash# Required AZURE_SPEECH_KEY=<your-speech-resource-key> AZURE_SPEECH_REGION=<region> # e.g., eastus, westus2, westeurope # Alternative: Use endpoint directly AZURE_SPEECH_ENDPOINT=https://<region>.stt.speech.microsoft.com
bashpip install requests
pythonimport os import requests def transcribe_audio(audio_file_path: str, language: str = "en-US") -> dict: """Transcribe short audio file (max 60 seconds) using REST API.""" region = os.environ["AZURE_SPEECH_REGION"] api_key = os.environ["AZURE_SPEECH_KEY"] url = f"https://{region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1" headers = { "Ocp-Apim-Subscription-Key": api_key, "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000", "Accept": "application/json" } params = { "language": language, "format": "detailed" # or "simple" } with open(audio_file_path, "rb") as audio_file: response = requests.post(url, headers=headers, params=params, data=audio_file) response.raise_for_status() return response.json() # Usage result = transcribe_audio("audio.wav", "en-US") print(result["DisplayText"])
| Format | Codec | Sample Rate | Notes | |--------|-------|-------------|-------| | WAV | PCM | 16 kHz, mono | Recommended | | OGG | OPUS | 16 kHz, mono | Smaller file size |
Limitations:
python# WAV PCM 16kHz "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000" # OGG OPUS "Content-Type": "audio/ogg; codecs=opus"
pythonparams = {"language": "en-US", "format": "simple"}
json{ "RecognitionStatus": "Success", "DisplayText": "Remind me to buy 5 pencils.", "Offset": "1236645672289", "Duration": "1236645672289" }
pythonparams = {"language": "en-US", "format": "detailed"}
json{ "RecognitionStatus": "Success", "Offset": "1236645672289", "Duration": "1236645672289", "NBest": [ { "Confidence": 0.9052885, "Display": "What's the weather like?", "ITN": "what's the weather like", "Lexical": "what's the weather like", "MaskedITN": "what's the weather like" } ] }
For lower latency, stream audio in chunks:
pythonimport os import requests def transcribe_chunked(audio_file_path: str, language: str = "en-US") -> dict: """Stream audio in chunks for lower latency.""" region = os.environ["AZURE_SPEECH_REGION"] api_key = os.environ["AZURE_SPEECH_KEY"] url = f"https://{region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1" headers = { "Ocp-Apim-Subscription-Key": api_key, "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000", "Accept": "application/json", "Transfer-Encoding": "chunked", "Expect": "100-continue" } params = {"language": language, "format": "detailed"} def generate_chunks(file_path: str, chunk_size: int = 1024): with open(file_path, "rb") as f: while chunk := f.read(chunk_size): yield chunk response = requests.post( url, headers=headers, params=params, data=generate_chunks(audio_file_path) ) response.raise_for_status() return response.json()
pythonheaders = { "Ocp-Apim-Subscription-Key": os.environ["AZURE_SPEECH_KEY"] }
pythonimport requests import os def get_access_token() -> str: """Get access token from the token endpoint.""" region = os.environ["AZURE_SPEECH_REGION"] api_key = os.environ["AZURE_SPEECH_KEY"] token_url = f"https://{region}.api.cognitive.microsoft.com/sts/v1.0/issueToken" response = requests.post( token_url, headers={ "Ocp-Apim-Subscription-Key": api_key, "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "0" } ) response.raise_for_status() return response.text # Use token in requests (valid for 10 minutes) token = get_access_token() headers = { "Authorization": f"Bearer {token}", "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000", "Accept": "application/json" }
| Parameter | Required | Values | Description | |-----------|----------|--------|-------------| | language | Yes | en-US, de-DE, etc. | Language of speech | | format | No | simple, detailed | Result format (default: simple) | | profanity | No | masked, removed, raw | Profanity handling (default: masked) |
| Status | Description | |--------|-------------| | Success | Recognition succeeded | | NoMatch | Speech detected but no words matched | | InitialSilenceTimeout | Only silence detected | | BabbleTimeout | Only noise detected | | Error | Internal service error |
python# Mask profanity with asterisks (default) params = {"language": "en-US", "profanity": "masked"} # Remove profanity entirely params = {"language": "en-US", "profanity": "removed"} # Include profanity as-is params = {"language": "en-US", "profanity": "raw"}
pythonimport requests def transcribe_with_error_handling(audio_path: str, language: str = "en-US") -> dict | None: """Transcribe with proper error handling.""" region = os.environ["AZURE_SPEECH_REGION"] api_key = os.environ["AZURE_SPEECH_KEY"] url = f"https://{region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1" try: with open(audio_path, "rb") as audio_file: response = requests.post( url, headers={ "Ocp-Apim-Subscription-Key": api_key, "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000", "Accept": "application/json" }, params={"language": language, "format": "detailed"}, data=audio_file ) if response.status_code == 200: result = response.json() if result.get("RecognitionStatus") == "Success": return result else: print(f"Recognition failed: {result.get('RecognitionStatus')}") return None elif response.status_code == 400: print(f"Bad request: Check language code or audio format") elif response.status_code == 401: print(f"Unauthorized: Check API key or token") elif response.status_code == 403: print(f"Forbidden: Missing authorization header") else: print(f"Error {response.status_code}: {response.text}") return None except requests.exceptions.RequestException as e: print(f"Request failed: {e}") return None
pythonimport os import aiohttp import asyncio async def transcribe_async(audio_file_path: str, language: str = "en-US") -> dict: """Async version using aiohttp.""" region = os.environ["AZURE_SPEECH_REGION"] api_key = os.environ["AZURE_SPEECH_KEY"] url = f"https://{region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1" headers = { "Ocp-Apim-Subscription-Key": api_key, "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000", "Accept": "application/json" } params = {"language": language, "format": "detailed"} async with aiohttp.ClientSession() as session: with open(audio_file_path, "rb") as f: audio_data = f.read() async with session.post(url, headers=headers, params=params, data=audio_data) as response: response.raise_for_status() return await response.json() # Usage result = asyncio.run(transcribe_async("audio.wav", "en-US")) print(result["DisplayText"])
Common language codes (see full list):
| Code | Language | |------|----------| | en-US | English (US) | | en-GB | English (UK) | | de-DE | German | | fr-FR | French | | es-ES | Spanish (Spain) | | es-MX | Spanish (Mexico) | | zh-CN | Chinese (Mandarin) | | ja-JP | Japanese | | ko-KR | Korean | | pt-BR | Portuguese (Brazil) |
Use the Speech SDK or Batch Transcription API instead when you need:
| File | Contents | |------|----------| | references/pronunciation-assessment.md | Pronunciation assessment parameters and scoring |
This skill is applicable to execute the workflow or actions described in the overview.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.