Audio in.
Minutes out.
One POST with an audio file returns a speaker-labelled transcript and a formatted minutes document — summary, decisions and action items — as PDF, Word or Markdown, carrying your branding rather than ours.
The same output
the app produces
Speaker-labelled transcript
Who said what, with timestamps, including meetings that switch between Hindi and English mid-sentence.
Minutes of meeting
Summary, key takeaways, decisions, and action items with owners — the structure people actually circulate.
Your branding
Set a logo and site on your partner account and the PDF goes out under your product's name.
Four calls, start to finish
1. Authenticate
Send your API key as a bearer token on every request.
Authorization: Bearer mk_live_...
2. Upload the audio
Audio only — video files are rejected, so extract the audio track first. Any common audio format is accepted, low volume is handled automatically, and the maximum upload is 300 MB.
curl -X POST https://getmeetnotes.com/v1/transcripts \
-H "Authorization: Bearer $KEY" \
-F "audio=@meeting.mp3" \
-F "client_reference=your-id-123"
→ 202 {"job_id":"...","status":"processing"}
client_reference is your own identifier and it makes retries safe: the same reference always returns the same job, so a dropped response can be retried without creating a duplicate.
Optional: who is speaking
Calls arrive with no names in them. Add -F "speaker_gender=true" and the completed job carries a speakers array reading each voice as male, female or unclear, keyed by the same speaker string the transcript uses.
"speakers": [
{ "speaker": "Speaker 1", "voice_gender": "female" },
{ "speaker": "Speaker 2", "voice_gender": "unclear" }
]
unclear is a real answer and your integration has to handle it. We read three separate clips of each speaker and return a gender only when they agree — on telephone audio, which strips the frequencies a voice is judged on, about one speaker in three does not. Guessing there would mean telling you a woman is a man, so we do not guess. This describes how a voice sounds, not who someone is.
Do you need it? Turn it on when the recording carries no names and you still have to sort it — an inbound helpline or sales line, where tagging and routing are all you have. Leave it off when your own system already knows who was on the call, because that record is correct where this is an inference; and leave it off if the answer would drive anything consequential for the person, because that is acting on a guess about someone from the sound of their voice.
3. Poll for the result
curl -H "Authorization: Bearer $KEY" https://getmeetnotes.com/v1/transcripts/JOB_ID
Status is one of processing, completed, failed or insufficient_minutes.
4. Download the document — in whichever format you ask for
You choose the format on each request, and the same job can be fetched in as many formats as you like without being charged again.
curl -OJ -H "Authorization: Bearer $KEY" \
"https://getmeetnotes.com/v1/transcripts/JOB_ID/file?format=pdf"
| format= | What comes back |
|---|---|
pdf | The branded minutes document — what most partners forward to their client. |
docx | The same minutes as an editable Word file. |
md | Markdown — minutes and transcript as plain text with headings. |
txt | Unformatted plain text. |
json | The full structured result — summary, takeaways, decisions, action items and every transcript segment with speaker and timestamp. Use this to load results into your own system. |
Add §ions=summary,action_items to trim the document to the parts you want — handy when you need the minutes without the full transcript. The PDF also accepts a participants list and a font size, so the minutes match the document conventions you already use.
Webhooks instead of polling
Set a webhook URL on your partner API page. When a job completes we POST the job JSON to that URL. Verify the signature before trusting it — it arrives in the X-MeetNotes-Signature header, prefixed with sha256=.
const crypto = require('crypto');
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(rawBody)
.digest('hex');
if (expected !== req.headers['x-meetnotes-signature']) {
throw new Error('bad signature');
}
The download_urls in a webhook payload are only usable when status is "completed" — ignore them for any other status.
Send recordings from
your systems — /v1/ingest
The other direction. Instead of uploading a file, you tell us where a recording already lives and we fetch it, transcribe it and file it as a meeting in a workspace — which is how a phone system, a dialler or a call recorder feeds MeetNotes without anyone touching an app.
Point us at a recording
Post the reference, not the bytes. source_id names the connected source the meeting belongs to; a source is created by the workspace owner on their Flows page.
curl -X POST https://getmeetnotes.com/v1/ingest \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{
"source_id": "SOURCE_ID",
"external_id": "call-8841",
"recording_url": "https://your-storage/calls/8841.mp3",
"agent": "Priya",
"customer": "Shah Pumps",
"occurred_at": "2026-08-28T10:15:00Z",
"duration_sec": 412,
"title": "Inbound support call",
"tags": ["support","escalation"]
}'
→ 202 {"event_id":"...","status":"received"}
recording_url is the only required field beyond source_id. It must be an https URL we can reach; presigned links are fine, and a recording behind a login works too — the owner stores those credentials on the source rather than putting them in the payload. Everything else is optional context that ends up on the meeting.
The 202 comes back as soon as the event is written down, before the recording is fetched. That is deliberate: a download plus a transcode is longer than most webhook timeouts, and a timeout is answered with a retry that races the first attempt.
Sent twice is stored once
Deduplication is on external_id within a source. Send the same one again and you get 200 with {"duplicate": true} and the event you already have — never a second meeting. Omit external_id and we derive one from the recording URL with its query string removed, so a presigned link whose signature is refreshed on every delivery still counts as one recording.
Ask what happened to it
curl -H "Authorization: Bearer $KEY" https://getmeetnotes.com/v1/ingest/EVENT_ID
→ 200 {"event_id":"...","status":"done","meeting_id":"...",
"external_id":"call-8841","error":null,"created_at":"..."}
Status walks received → fetching → queued → done, or stops at failed (with a reason in error) or skipped — skipped meaning we chose not to spend the workspace's minutes, most often because it has none left. Once the status is done, meeting_id is the meeting, and everything on /v1/transcripts above applies to it.
| Code | Meaning |
|---|---|
| 202 | Accepted — the event is queued |
| 200 | A duplicate of an event we already hold |
| 404 | No such source, or not one you may write to |
| 410 | The source has been switched off — stop retrying |
| 422 | The payload carried no usable recording |
| 429 | Too many events for that source |
No API key: give the provider a URL
Many phone systems and recorders can only post a webhook to a URL. For those, the workspace owner adds a source and hands over the URL it generates:
POST https://getmeetnotes.com/ingest/<source_id>/<secret>
The secret in the path is the whole authorisation, so treat that URL as a password: it is shown once, it can be rotated from the Flows page, and rotating it immediately stops anything still using the old one. The body is the same JSON as above (form-encoded is accepted too, because telephony providers post forms), and the responses are the same — including 410 when the source is disabled, which is what makes a provider's retry loop stop.
And back out again
A workspace can point finished meetings at a webhook of yours or a chat channel. Webhook deliveries carry the summary, decisions, action items and links as JSON, signed with the same X-MeetNotes-Signature header — verify it exactly as shown above.
What can come back
| Code | Meaning |
|---|---|
| 401 | Invalid API key |
| 402 | No minutes remaining on the account |
| 400 | Audio files only, or the audio could not be read |
| 409 | Not ready — the job is still processing |
| 429 | Too many requests |
| 500 | Processing failed |
Keys are issued by hand
We set partners up personally rather than through a signup form — it takes one conversation to understand what you are building and how much volume to expect.
Write to support@getmeetnotes.com, or sign in if you already have an account. Inside the portal you will find your key, a sandbox to test against, webhook configuration with a test button, and the full reference.
