Speech Services Basics
Six examples to get you started with Amazon Transcribe and Amazon Polly - four basic and two intermediate.
Search across all documentation pages
Six examples to get you started with Amazon Transcribe and Amazon Polly - four basic and two intermediate.
Each example shows the same operation in both Python (boto3) and TypeScript (AWS SDK for JavaScript v3). Transcribe's job-based API is asynchronous; Polly's synthesis call is synchronous. Both patterns are shown here.
pip install boto3 (boto3 1.43.x, Python 3.10+). TypeScript: npm install @aws-sdk/client-transcribe @aws-sdk/client-polly (Node.js 18+).aws configure, environment variables, or an IAM role - the SDK resolves them automatically.StartTranscriptionJob kicks off asynchronous transcription of an audio file already sitting in S3.
# --- Python (boto3) ---
import boto3
transcribe = boto3.client("transcribe", region_name="us-east-1")
transcribe.start_transcription_job(
TranscriptionJobName="voicemail-001",
Media={"MediaFileUri": "s3://my-bucket/audio/voicemail-001.wav"},
MediaFormat="wav",
LanguageCode="en-US",
OutputBucketName="my-bucket",
)
print("Job submitted")// --- TypeScript (AWS SDK v3) ---
import { TranscribeClient, StartTranscriptionJobCommand } from "@aws-sdk/client-transcribe";
const transcribe = new TranscribeClient({ region: "us-east-1" });
await transcribe.send(new StartTranscriptionJobCommand({
TranscriptionJobName: "voicemail-001",
Media: { MediaFileUri: "s3://my-bucket/audio/voicemail-001.wav" },
MediaFormat: "wav",
LanguageCode: "en-US",
OutputBucketName: "my-bucket",
}));
console.log("Job submitted");TranscriptionJobName must be unique per account/region; reusing a name for an existing job fails the call.Media.MediaFileUri points at an object already in S3 - Transcribe reads it directly, you never upload bytes through the call itself.OutputBucketName is where the finished JSON transcript is written; omit it and AWS manages a temporary location instead.Related: Transcribe & Polly: Speech In, Speech Out - how this batch API differs from streaming.
GetTranscriptionJob checks a job's status; poll it until TranscriptionJobStatus reaches a terminal state.
# --- Python (boto3) ---
import time
import boto3
transcribe = boto3.client("transcribe")
while True:
response = transcribe.get_transcription_job(TranscriptionJobName="voicemail-001")
status = response["TranscriptionJob"]["TranscriptionJobStatus"]
if status in ("COMPLETED", "FAILED"):
break
time.sleep(5)
print(status)
if status == "COMPLETED":
print(response["TranscriptionJob"]["Transcript"]["TranscriptFileUri"])// --- TypeScript (AWS SDK v3) ---
import { TranscribeClient, GetTranscriptionJobCommand } from "@aws-sdk/client-transcribe";
const transcribe = new TranscribeClient({});
let status: string | undefined;
let transcriptUri: string | undefined;
do {
const response = await transcribe.send(new GetTranscriptionJobCommand({
TranscriptionJobName: "voicemail-001",
}));
status = response.TranscriptionJob?.TranscriptionJobStatus;
transcriptUri = response.TranscriptionJob?.Transcript?.TranscriptFileUri;
if (status === "COMPLETED" || status === "FAILED") break;
await new Promise((r) => setTimeout(r, 5000));
} while (true);
console.log(status, transcriptUri);TranscriptionJobStatus moves through IN_PROGRESS before landing on COMPLETED or FAILED - both are terminal states you must check for.Transcript.TranscriptFileUri only appears once the job is COMPLETED; it points at the JSON transcript file.FAILED explicitly - a loop that only exits on success can hang forever on a failed job.SynthesizeSpeech converts text to audio and returns the audio stream in the same call - no job involved.
# --- Python (boto3) ---
import boto3
polly = boto3.client("polly", region_name="us-east-1")
response = polly.synthesize_speech(
Text="Your order has shipped.",
OutputFormat="mp3",
VoiceId="Joanna",
Engine="neural",
)
with open("notice.mp3", "wb") as f:
f.write(response["AudioStream"].read())// --- TypeScript (AWS SDK v3) ---
import { PollyClient, SynthesizeSpeechCommand } from "@aws-sdk/client-polly";
import { writeFileSync } from "node:fs";
const polly = new PollyClient({ region: "us-east-1" });
const response = await polly.send(new SynthesizeSpeechCommand({
Text: "Your order has shipped.",
OutputFormat: "mp3",
VoiceId: "Joanna",
Engine: "neural",
}));
const bytes = await response.AudioStream?.transformToByteArray();
writeFileSync("notice.mp3", Buffer.from(bytes ?? []));OutputFormat accepts mp3, ogg_vorbis, or pcm - pick based on what your playback pipeline expects.Engine: "neural" produces more natural speech than "standard" but costs more per character and is not available for every voice or region.response["AudioStream"] in boto3 is a readable stream; in the SDK v3 it exposes transformToByteArray()/transformToString() helpers.DescribeVoices returns every voice Polly offers, optionally filtered by language, before you hardcode a VoiceId.
# --- Python (boto3) ---
import boto3
polly = boto3.client("polly")
response = polly.describe_voices(LanguageCode="en-US")
for voice in response["Voices"]:
print(voice["Id"], voice["Gender"], voice["SupportedEngines"])// --- TypeScript (AWS SDK v3) ---
import { PollyClient, DescribeVoicesCommand } from "@aws-sdk/client-polly";
const polly = new PollyClient({});
const response = await polly.send(new DescribeVoicesCommand({ LanguageCode: "en-US" }));
for (const voice of response.Voices ?? []) {
console.log(voice.Id, voice.Gender, voice.SupportedEngines);
}LanguageCode narrows the list to voices for one language; omit it to list every voice Polly supports.SupportedEngines tells you which of standard, neural, or long-form a given voice can use - not every voice supports every engine.VoiceId.Passing TextType="ssml" lets you control pacing, pauses, and pronunciation with markup instead of plain text.
# --- Python (boto3) ---
import boto3
polly = boto3.client("polly")
ssml = '<speak>Your total is <break time="300ms"/> forty two dollars.</speak>'
response = polly.synthesize_speech(
Text=ssml,
TextType="ssml",
OutputFormat="mp3",
VoiceId="Matthew",
Engine="neural",
)
with open("total.mp3", "wb") as f:
f.write(response["AudioStream"].read())// --- TypeScript (AWS SDK v3) ---
import { PollyClient, SynthesizeSpeechCommand } from "@aws-sdk/client-polly";
import { writeFileSync } from "node:fs";
const polly = new PollyClient({});
const ssml = '<speak>Your total is <break time="300ms"/> forty two dollars.</speak>';
const response = await polly.send(new SynthesizeSpeechCommand({
Text: ssml,
TextType: "ssml",
OutputFormat: "mp3",
VoiceId: "Matthew",
Engine: "neural",
}));
const bytes = await response.AudioStream?.transformToByteArray();
writeFileSync("total.mp3", Buffer.from(bytes ?? []));TextType defaults to "text" - you must set it to "ssml" for markup to be interpreted rather than read aloud literally.<speak> root tag is required for any SSML input; unwrapped tags are rejected.<break time="300ms"/> inserts a controlled pause, useful for pacing around numbers or list items.Related: Polly Voices, SSML & Speech Marks - the full SSML tag set and speech marks.
Once a job is COMPLETED, fetch the JSON transcript file and pull out the plain text.
# --- Python (boto3) ---
import json
import urllib.request
# transcript_uri comes from GetTranscriptionJob's Transcript.TranscriptFileUri
transcript_uri = "https://s3.us-east-1.amazonaws.com/my-bucket/voicemail-001.json"
with urllib.request.urlopen(transcript_uri) as response:
data = json.load(response)
text = data["results"]["transcripts"][0]["transcript"]
print(text)// --- TypeScript (AWS SDK v3) ---
// transcriptUri comes from GetTranscriptionJobCommand's Transcript.TranscriptFileUri
const transcriptUri = "https://s3.us-east-1.amazonaws.com/my-bucket/voicemail-001.json";
const response = await fetch(transcriptUri);
const data = await response.json();
const text = data.results.transcripts[0].transcript;
console.log(text);TranscriptFileUri is a plain HTTPS URL - fetch it like any other file, no special SDK call needed.results.transcripts[0].transcript holds the full plain-text transcript; results.items alongside it breaks the same text into per-word timing and confidence.OutputBucketName was set, you can also fetch the object directly via an S3 GetObject call instead of the public URI.SynthesizeSpeech to read a transcript back aloud.Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 24, 2026