Skip to content

API and troubleshooting

Connection

Flask reads APIM_BASE_URL and APIM_API_KEY from the environment, with root .env as a fallback. The base URL is an HTTPS origin without an API path. All gateway REST requests use the api-key header, including Speech.

Browser calls such as fetch("/speak") target Flask on port 5050, not this guide. Port 5051 runs the solution; 8000 optionally previews documentation.

Requests

Feature Gateway request Response
Speech POST /speech/tts; SSML body; X-Microsoft-OutputFormat: riff-16khz-16bit-mono-pcm WAV bytes
Transcription POST /speech/transcribe?api-version=2025-10-15; multipart audio and JSON-encoded definition; enhancedMode selects MAI-Transcribe-2 combinedPhrases[].text
Images POST /mai/v1/images/generations; JSON with model: "mai-image-flash", English prompt, width: 1024, height: 1024 data[0].b64_json — base64 PNG
Mnemonics POST /mai/v1/chat/completions; JSON with model: "mai-thinking" and messages choices[0].message.content — text

mai-image-flash and mai-thinking are deployment names. The routes read MAI_IMAGE_DEPLOYMENT and MAI_THINKING_DEPLOYMENT from the environment and fall back to those names; the workshop gateway also offers mai-image (MAI-Image-2.6). Both image and thinking offerings are preview. Image responses include a usage object with token counts.

Provider contract: catalog, requests, routes. Microsoft APIs: Voice, Transcribe, Image, Thinking.

Provided helpers

You call these from your own files; you do not edit workshop.py or workshop.js.

starter/workshop.py

Helper What it does
create_app(__name__) Creates the Flask app: POST requests only from the app's own page, security headers (CSP), a 2 MiB request limit, template auto-reload, readable JSON errors, and gateway_configured for templates.
gateway() Returns (base, {"api-key": key}) from APIM_BASE_URL and APIM_API_KEY, or answers 503.
json_body() The request's JSON object, or 400/415.
text_field(data, name, limit=120) A required, trimmed text field, or 400.
optional_text(data, name, limit) An optional, trimmed text field; missing means "".
check_wav(audio, max_seconds=12, error_status=400) Stops unless the bytes are complete mono, 16-bit, 16 kHz PCM WAV.
upstream_json(response) Raises gateway errors, then returns the JSON object body, or 502.
check_png(image, width=1024, height=1024) Stops with 502 unless the bytes are a PNG of that size.
TIMEOUT 10 s to connect, 120 s to read.

Gateway errors become short messages: 401 (key), 403 (access or content), 404 (route or deployment), 429 (rate limit), timeouts as 504, and anything else as 502. For 429, the wait from the gateway's Retry-After or retry-after-ms header is passed on as Retry-After in seconds.

starter/static/workshop.js

Helper What it does
$(selector), showError(message) Find an element; show or clear the error banner.
runAction(message, action) Runs one async action at a time: shows the status, disables controls, and shows thrown errors.
isBusy() True while an action runs; selectWord then refuses to switch words.
jsonOptions(body), callApp(path, options), appJSON(response) POST JSON to Flask, turn failures (including Retry-After) into errors, and read JSON replies.
playAudio(player, blob), stopAudio(player), downloadBlob(blob, filename) Play, stop, or save audio.
setupAnswerRecorder({onNewAudio}) Wires consent, Record/Stop, the WAV picker, preview, and Discard. Returns {audio(), discard()}.
encodeWav(samples), recordingToWav(blob) Write 16-bit PCM WAV; convert a MediaRecorder recording to mono 16 kHz WAV.

Your app.js adds onWordChange(hook): hooks run after each selection, so lessons clear old results without editing selectWord.

Catch up with checkpoints

checkpoints/01-word-list to checkpoints/05-images hold your three files (app.py, templates/index.html, static/app.js) as they are after each lesson; solution/ is the finished app, including the mnemonic extension. All of them are generated from these pages, so they match the reference solutions exactly.

python checkpoints/restore.py 03

This copies a checkpoint over your starter files after saving yours to .checkpoint-backups/<time>/; python checkpoints/restore.py undo puts your latest backup back. Both give the files a fresh timestamp, so a running flask run --reload restarts on its own. Use it to catch up, or to compare: open your file next to the checkpoint's.

Languages and voices

The language menu uses the intersection of documented transcription languages and prebuilt MAI-Voice-2-Flash voices. TTS selects a voice; STT takes a language code. Changing the menu does not translate existing entries.

Locale Language STT code MAI-Voice-2-Flash voice
en-US English en en-US-Harper:MAI-Voice-2-Flash
zh-CN Chinese (Simplified Mandarin) zh zh-CN-Mei:MAI-Voice-2-Flash
nl-NL Dutch nl nl-NL-Sander:MAI-Voice-2-Flash
fr-FR French fr fr-FR-Soleil:MAI-Voice-2-Flash
de-DE German de de-DE-Mia:MAI-Voice-2-Flash
hi-IN Hindi hi hi-IN-Kavya:MAI-Voice-2-Flash
hu-HU Hungarian hu hu-HU-Lilla:MAI-Voice-2-Flash
it-IT Italian it it-IT-Rosa:MAI-Voice-2-Flash
ko-KR Korean ko ko-KR-Haena:MAI-Voice-2-Flash
pt-BR Portuguese (Brazil) pt pt-BR-Luana:MAI-Voice-2-Flash
pt-PT Portuguese (Portugal) pt pt-PT-Rui:MAI-Voice-2-Flash
ro-RO Romanian ro ro-RO-Elena:MAI-Voice-2-Flash
ru-RU Russian ru ru-RU-Masha:MAI-Voice-2-Flash
es-ES Spanish (Spain) es es-ES-Marta:MAI-Voice-2-Flash
es-MX Spanish (Mexico) es es-MX-Valeria:MAI-Voice-2-Flash
th-TH Thai th th-TH-Krit:MAI-Voice-2-Flash
tr-TR Turkish tr tr-TR-Elif:MAI-Voice-2-Flash

Formats and matching

  • Answer audio: mono, 16-bit, 16 kHz PCM WAV, up to 12 seconds; maximum request size 2 MiB. These are app limits. MediaRecorder output is decoded and resampled before the app writes WAV bytes; renaming a file is not conversion.
  • Image: 1024 × 1024 PNG, displayed as a smaller thumbnail. The API requires each dimension to be at least 768 and at most 1,048,576 pixels in total.
  • Match: whole transcript versus saved translation, ignoring case, surrounding punctuation, and extra whitespace. Accents and internal apostrophes/hyphens remain significant. This checks words, not pronunciation.
  • Caching: words survive reload in localStorage; speech and images last only for the page. Different app URLs have separate word lists.

Troubleshooting

Symptom Fix
Old code appears Save and reload the browser. Without --reload, restart Flask after Python edits. Check port 5050, not 5051.
Flask will not start or the page shows a traceback Read the terminal error; with --reload, fixing the file recovers on its own. Check that you replaced the whole marker line, and keep // Start the page. last in JavaScript.
501 Lesson N: finish the … route The skeleton's last line is still there: finish the TODOs, or compare with the reference solution.
Connection failure or HTML instead of JSON/audio Restart Flask or reopen the Codespaces port and sign in again.
503 or No gateway settings yet Check both settings and environment-over-.env precedence; restart Flask (the reloader does not watch .env).
401 Obtain a current participant key; keys expire 24 hours after issuance or earlier if revoked.
403 Use the Flask app tab; check the displayed message and gateway access with the instructor.
404 Check the gateway origin and deployment name.
429 or timeout Wait the seconds the app shows, then retry once. Image bursts from one key are limited first; check shared capacity with the instructor.
Microphone unavailable or prompt unanswered Open the HTTPS app in a normal tab. Press Stop recording to cancel; choose a downloaded synthetic WAV instead.
WAV rejected or 413 Use a short 16 kHz mono PCM WAV. Check duration, channels, and file size.
No words / 422 Preview the audio and check the selected language.
Image/text cannot be decoded Compare the response format with the table above.
Word list missing Return to the same app URL. For unreadable storage, use the app's reset if you want to clear it.