Speech to text API in Go: transcribe audio with net/http

Transcribe audio in Go using only the standard library: submit to Sume STT, poll the job and print the text. A 30-line program at one cent per audio minute.

5 min readSume
All posts

To transcribe an audio file from Go, send one authenticated POST to https://api.sume.com/v1/stt-1.0/transcribe with a public HTTPS audio_url, then poll the job until it is completed and read text and words[] from the result. Sume STT 1.0 is $0.01 per audio minute, so a 2 minute clip is two cents. The program uses net/http, encoding/json and the standard library only, so go run is all you need.

The endpoint is a job API, not a streaming one: you get a job id back and ask for the result when it is ready. That is the right shape for recorded files, and it keeps the client small.

The request and the result

The body needs audio_url. Optional fields are language_code (a hint such as en or ko; omit it for auto-detect), duration_seconds (1 to 600, which lets Sume reserve the right amount; omit it and Sume reserves one minute), segmentation for sentence rows, and metadata, which is stored with your job and never sent to the provider. Send an Idempotency-Key header so a retry does not create a second paid job.

A submit returns 202 with request_id, which is the job id. Poll GET /v1/jobs/{id}/status with a pause between reads, and stop on completed, failed or canceled. Then GET /v1/jobs/{id}/result returns text, language_code, words[] with word, start and end in seconds from the audio start, and segments[] if you asked for them.

What the Go script does at each step, from Sume docs and schema read 2026-10-07
StepCallIn the script
SubmitPOST /v1/stt-1.0/transcribecall("POST", ...) with an Idempotency-Key header
WaitGET /v1/jobs/{id}/statusfor loop, 2 second pause
ReadGET /v1/jobs/{id}/resultfmt.Println of text

The script

Save it as main.go in a module (the code is gofmt-compatible apart from the one-line import list), set SUME_API_KEY, and run go run . https://media.sume.com/artifacts/artf_demo/clip.wav stt-001.

package main
import ("bytes"; "encoding/json"; "fmt"; "net/http"; "os"; "time")
func call(method, path string, body map[string]any) map[string]any {
	raw := []byte{}
	if body != nil {
		raw, _ = json.Marshal(body)
	}
	req, _ := http.NewRequest(method, "https://api.sume.com"+path, bytes.NewReader(raw))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SUME_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Idempotency-Key", os.Args[2])
	res, err := http.DefaultClient.Do(req)
	if err != nil || res.StatusCode >= 300 {
		panic(fmt.Sprint("request failed: ", err, res))
	}
	var out map[string]any
	json.NewDecoder(res.Body).Decode(&out)
	return out
}

func main() {
	job := call("POST", "/v1/stt-1.0/transcribe", map[string]any{"audio_url": os.Args[1], "duration_seconds": 120})
	id := job["request_id"].(string)
	for s := ""; s != "completed"; time.Sleep(2 * time.Second) {
		if s, _ = call("GET", "/v1/jobs/"+id+"/status", nil)["status"].(string); s == "failed" || s == "canceled" {
			panic("job ended " + s)
		}
	}
	fmt.Println(call("GET", "/v1/jobs/"+id+"/result", nil)["text"])
}

Go details worth knowing

Go's http.DefaultClient has no timeout. A hung connection will block your polling loop forever, so for production create your own http.Client{Timeout: 30 * time.Second} and use it in call. The example keeps the default client to stay short.

The map[string]any decode means numbers arrive as float64 and strings need a type assertion. The program asserts request_id and status as strings and prints text with fmt.Println. For a typed client, define a struct with Text string and Words []struct{Word string; Start, End float64} and decode into that.

The loop sleeps in its post statement, so it pauses two seconds after each status read, and it panics on failed or canceled rather than polling forever. The program takes the audio URL and an idempotency key as arguments, so you choose a stable key per file.

Limits and prices

One job takes at most 10 minutes of audio. For longer recordings, cut the audio into slices of up to 600 seconds and send one job per slice, adding each slice's start offset to its word times when you merge. The audio must be at a public HTTPS URL, and Sume media URLs are the preferred source. If the audio is inside a video, audio detach extracts a 16 kHz mono WAV for $0.01 per job.

If the request is refused for balance, the API returns 402; a changed body under a reused idempotency key returns 409; a rate limit returns 429. Do not resubmit a paid request just because your own process timed out, because the job may still be running. Read the status first, as the jobs docs advise.

Keep the returned job id next to your own record of the file. If a result looks wrong later, that id is what lets you fetch the same job again without paying for a second transcription, and the metadata object you sent at submit time is stored with it, so you can tag each job with your own file or ticket id and find it again.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume