From audio to minutes
The long half of this API is asynchronous. You upload, we work, you poll. Here is exactly what happens in between.
There are no webhooks yet, so a client follows a meeting by reading its status. The status route asks you not to poll more often than every thirty seconds, and says so in its Cache-Control header.
1. Ask for an upload target
The audio never travels through this API. You ask for a one-time URL and PUT the file to it yourself. The URL is signed for exactly the content_type you named.
curl -X POST 'https://api.notulai.nl/v1/meetings/mtg_b39d31c7/upload-url' \
-H 'Authorization: Bearer nla_live_...' \
-H 'Content-Type: application/json' \
-d '{
"content_type": "audio/mpeg"
}'Then PUT the file to the URL you got back. No Authorization header: the signed URL carries its own. Once the file lands, the meeting moves on by itself.
# UPLOAD_URL is the upload_url from the previous response.
curl -X PUT "$UPLOAD_URL" \
-H 'Content-Type: audio/mpeg' \
--data-binary '@meeting.mp3'Send the same Content-Type you asked for
2. Follow the status
These are the values a meeting moves through when all goes well.
createdThe meeting exists. No audio yet, and participants can still be changed.
audio_uploadedThe recording arrived and is being prepared.
transcribingSpeech to text is running.
transcribedThe transcript is available.
generating_minutesThe minutes are being written from the transcript.
minutes_readyDone. Both the transcript and the minutes can be fetched.
And these end it
transcription_failedTranscription did not complete. The recording stays; create a new meeting to try again.minutes_failedThe transcript exists but the minutes could not be written.transcribe_blockedThere was not enough transcription quota when the audio arrived. Topping up does not restart it: create the meeting again.audio_rejectedThe file was refused, most often because it is over one gigabyte.Asking for a transcript or minutes before they exist is not an error in your code: it answers 409 TRANSCRIPT_NOT_READY or 409 MINUTES_NOT_READY, which means come back later. A 404 on the same route means the meeting is gone or was never yours to see.
3. Collect the result
curl -X GET 'https://api.notulai.nl/v1/meetings/mtg_b39d31c7/minutes' \
-H 'Authorization: Bearer nla_live_...'Names are in the minutes, not in the transcript
spk_0: they say that speakers differ, not who they are. Attribution to named people happens while the minutes are written, which is why participants are worth setting before you upload. Nothing in this API maps a spk_ label to a member id.Participants are set before the audio
Their names improve how names are spelled in the transcript and make speaker attribution possible in the minutes. Pass them when you create the meeting, or replace them while the meeting still has no audio. Once a recording is in, changing them would change nothing, so it is refused with 409 PARTICIPANTS_LOCKED. Delete the meeting and create it again to start over with a different set.
Everything else stays editable
Participants are the exception, not the rule. The title, the date, the start time and the project can be changed at any point in a meeting's life, including long after the minutes are ready, because none of them steers the processing. A meeting created under a placeholder title does not have to be thrown away.
curl -X PATCH 'https://api.notulai.nl/v1/meetings/mtg_b39d31c7' \
-H 'Authorization: Bearer nla_live_...' \
-H 'Content-Type: application/json' \
-d '{
"date": "2026-09-01",
"project_id": "proj_5b28e4",
"time": "09:30",
"title": "Weekly stand-up"
}'Only the fields you send are touched. Sending time or project_id as an empty string clears it; leaving a field out keeps it as it is. Changing the title, the date or the project re-indexes the meeting for search, which costs a credit once its minutes have been indexed. See limits and quota.
See the reference for the exact shape of every call above.