Skip to main content
A transcript is not notes. It is the meeting again, only longer to read than it was to sit through. What people actually want afterwards is short: what did we decide, who agreed to do what, and what is still open. This tutorial builds that, and links every item back to the second it was said so you can go and listen to the part you disagree with:
Along the way we will:
  1. Transcribe a recording with /audio/transcriptions
  2. Ask for timings, which not every model will give you
  3. Extract decisions and action items against a schema
  4. Deal with the fact that the transcript never says who is talking
  5. Split a long recording without losing the clock

Setup

You need Python 3.9 or newer, the requests package, and a Venice API key. See Generating an API Key if you do not have one. Bring any recording of a conversation, in wav, mp3, m4a, flac, aac, mp4, ogg, or webm.

1. Transcribe the recording

/audio/transcriptions is OpenAI-compatible and takes a multipart upload. The file has to be a real file part, since base64 is not accepted on this endpoint.
Transcription is billed by how long the audio is, not by how much was said in it, which makes the cost of a meeting easy to predict before you run it: Call GET /models?type=asr for the current list rather than pinning these, since the catalog changes.

2. Ask for timings

Timestamps are what make notes checkable, so this is the choice that matters most, and the default will not give them to you:
nvidia/parakeet-tdt-0.6b-v3 is the default model, and it accepts timestamps=true and then ignores it. There is no error and no warning, just a response with nothing but text in it. If you need timings, ask for a model that returns them and check that the key is there.
When a model does return timings, timestamps is an object rather than a list, and the key inside it depends on the model. Whisper groups by phrase, Scribe by word:
Phrase-level segments are the right size for this job. Word timings are useful for captions and are too fine to hang a decision off. We will flatten those segments into lines with a time in front of each one, which is all the model needs to cite them later:

3. Extract the notes

Describe the notes you want as a schema, so the result is a record instead of prose you have to parse:
disable_thinking is there for the same reason it belongs in any extraction step. The schema already decides the shape of the answer, so paying a reasoning model to deliberate about it buys nothing and makes the cost of each run different from the last. Extracting Structured Data from Documents measures that difference. Run it on a fifty-three second standup and the notes come back with the clock attached:
Every spoken_at is real. Skip to 19.6 seconds and you hear the sentence that created the task.
Give the model lines without times and every spoken_at comes back as 0. The field is required, the model has nothing to put in it, and a required field is an instruction to produce something rather than an invitation to say it does not know. This is worth remembering whenever a schema seems to be working: the shape being right is not the same as the values being right.

4. Nobody is labelled

Two things in that output are wrong, and both come from the same place. The owner of the rollout is May. Her name is Mei. Speech recognition is at its least reliable on proper nouns, and names are exactly what attribution needs, so this is the failure you should expect rather than the unlucky one. The last item is unassigned, even though somebody clearly took it. The line was “I’ll ask legal today and report back tomorrow”, and the transcript records the words without recording who said them. That second one is not a bug you can fix. No Venice transcription model performs diarization, so there is no speaker field to reach for on any of them. The transcript is one voice-less stream of text, and tasks can only be attributed when a name is spoken out loud, as in “Tomas, can you put the deprecation notice in the changelog”. The first one you can fix, by telling the model who was in the room:
May resolves to Mei Lin because the model now has a short list to match against, and the owners are full names your task tracker can look up. The third item stays unassigned, correctly. A roster fixes mishearing, and nothing recovers information the recording never carried.
If you need real speaker attribution, capture it upstream rather than inferring it downstream. Conferencing tools can record one track per participant, and transcribing each track separately gives you speakers for free, at the cost of one request per person.

5. Longer than one request

Uploads are capped at 25 MB, which arrives sooner than you would think for uncompressed audio, and a long meeting is worth splitting anyway so that one failure does not cost you the whole transcription. For WAV files the standard library is enough, no ffmpeg required:
The offset is the whole point. Each chunk is transcribed as if it started at zero, so its timings have to be shifted back into the timeline of the original recording before the model sees them. That is what the offset argument to timed_lines is for:
Split the same standup into twenty second pieces and the clock stays honest across the joins. The words do not:
One sentence was spoken there: “Tomas, can you put the deprecation notice in the changelog by Friday?” The cut landed in the middle of it, so the name went into one request and the request went into another. Whisper heard the orphaned name as a question, invented an awesome. to fill the gap at the end of the chunk, and turned one line into three. The timings are still right, and the notes step still finds the task. What it loses is the name, which is the thing attribution depends on.
Splitting on a fixed duration cuts somebody off at every boundary. Twenty seconds is short enough to hit a sentence almost every time; ten minutes makes it rare but not impossible, and it will eventually land on the one sentence that assigns the work. Splitting on silence avoids the problem properly and needs a tool that can find the gaps, such as ffmpeg or pydub. Split only when the file actually requires it.
Compressed formats cannot be sliced this way, since you cannot cut an MP3 on a frame boundary with the standard library. Use ffmpeg for those:

Putting it together

Next steps

  • Post the action items to your tracker, using the owner names the roster resolved.
  • Read the summary back with Text to Speech for people who missed the call.
  • Search across past meetings by storing transcripts with Embeddings.
  • Let an agent decide when to transcribe and when to answer from notes it already has, with Building a Tool-Using Agent with Function Calling.

Speech-to-Text

Reference for the transcriptions endpoint.

Extracting Structured Data from Documents

The same schema-first extraction, applied to files.

Structured Responses

How json_schema constrains a completion.

Voice Cloning

Give the summary a voice of its own.