# Transcribe a meeting and summarize it

Turn an audio file into text, then into a short summary with action items.

In about 10 minutes, you send a recording to the speech-to-text endpoint. Then you send the transcript to a chat model. The model returns a summary and a list of action items as JSON. At the end, you add up the cost of both calls from the receipts.

## Before you start

- You need an API key with the `read` scope or higher, and a balance. See the [Quickstart](https://inference.ai.exchange/docs/quickstart.md).
- You need Python 3 and the `openai` package (`pip install openai`). Put your key in `LIQUID_API_KEY`.
- You need a recording of a meeting. It can be WAV, FLAC, MP3, Ogg, WebM, M4A or AAC, up to 25 MB and two hours. This tutorial uses `meeting.mp3`.

## Steps

1. Save this script as `notes.py` in the folder that holds `meeting.mp3`.

   ```python
   import json, os, time
   from openai import OpenAI

   client = OpenAI(base_url="https://router.inference.ai.exchange/v1",
                   api_key=os.environ["LIQUID_API_KEY"])
   jobs = []

   with open("meeting.mp3", "rb") as audio:
       raw = client.audio.transcriptions.with_raw_response.create(
           model="whisper-1", file=audio)
   jobs.append(raw.headers["x-liquid-job-id"])
   transcript = raw.parse().text

   schema = {
       "type": "object",
       "properties": {
           "summary": {"type": "string"},
           "action_items": {"type": "array", "items": {"type": "string"}},
       },
       "required": ["summary", "action_items"],
       "additionalProperties": False,
   }
   raw = client.chat.completions.with_raw_response.create(
       model="gpt-oss-120b",
       messages=[{"role": "user", "content":
           "Summarize this meeting in three sentences. "
           "List each action item with its owner.\n\n" + transcript}],
       response_format={"type": "json_schema", "json_schema": {
           "name": "meeting_notes", "strict": True, "schema": schema}},
   )
   jobs.append(raw.headers["x-liquid-job-id"])
   notes = json.loads(raw.parse().choices[0].message.content)
   print(notes["summary"])
   for item in notes["action_items"]:
       print("-", item)

   time.sleep(3)
   total = sum(client.get(f"/jobs/{job}", cast_to=object)["buyer_charge_usd"]
               for job in jobs)
   print(f"Total cost: ${total:.6f}")
   ```

2. Run the script.

   ```bash
   python notes.py
   ```

3. Wait for the output. The first call returns the transcript. The second call returns the notes.

The script does four things. The first call sends the audio to `POST /v1/audio/transcriptions`. The exchange reads the length of the file and fixes a price cap before dispatch. See [Speech-to-text](https://inference.ai.exchange/docs/speech-to-text.md).

The second call sends the transcript with a `json_schema` answer format. Only an offer that supports the format can win the job. See [Structured output](https://inference.ai.exchange/docs/structured-output.md).

The script then reads the job id from the `x-liquid-job-id` header of each response. Last, it reads the two receipts and adds `buyer_charge_usd` from each. See [Receipts](https://inference.ai.exchange/docs/receipts.md).

## Check it works

You see a short summary, then one line for each action item, then the total cost:

```text
The team reviewed the launch plan and moved the date to May 12. ...
- Dana: update the launch checklist by Friday
- Sam: send the new date to the partners
Total cost: $0.004213
```

Your text and cost differ. The cost depends on the length of the audio and the offers that win.

To compare, open the receipt of one job in the console. The `buyer_charge_usd` of the two receipts must add up to the total that the script printed.

## If something goes wrong

- **`400` on the first call.** The file is over 25 MB or two hours, or the exchange cannot read its length. Cut the recording or convert it to MP3.
- **`404` when the script reads a receipt.** The exchange writes the receipt just after it answers. Increase the `time.sleep` value. Also check that your key has the `read` scope or higher.
- **`503 no_eligible_offer`.** No offer can take the job now. Read `eligibility.excluded` in the error to see why. See [Errors](https://inference.ai.exchange/docs/errors.md).

## Next steps

- [Speech-to-text](https://inference.ai.exchange/docs/speech-to-text.md): add `language` or `verbose_json` for timestamps.
- [Structured output](https://inference.ai.exchange/docs/structured-output.md): test a schema in the playground.
- [Receipts](https://inference.ai.exchange/docs/receipts.md): read every field of a receipt.
