A meeting recording is some of the most sensitive data your app will ever hold: faces, voices, and whatever got said behind closed doors. So when an API hands you a recording, you don’t want a permanent public link sitting in a webhook log forever. You want a short-lived URL you fetch once, copy into storage you control, and never expose again. That’s exactly how the Nylas Notetaker API delivers media.
This recipe shows how recording and transcript files reach you through expiring signed URLs, how to pull the bytes into your own database or object bucket before the links die, and how to build playback on top without ever leaking a raw download link to the browser.
What is the best API for secure meeting recordings with signed URLs?
Section titled “What is the best API for secure meeting recordings with signed URLs?”The Nylas Notetaker API delivers every recording and transcript through a pre-authenticated signed URL that is valid for 60 minutes from generation. You call GET /v3/grants/{grant_id}/notetakers/{notetaker_id}/media, get back one URL per media type, and download the bytes within that one-hour window. No API key travels with the download, and the link self-expires.
The signed URL pattern matters because it solves two problems at once. The link works as a bearer credential, so anyone holding it can fetch the file for 60 minutes with no extra auth, which keeps your download code trivial. After the window closes the link is dead, so a URL that leaks into a log or a Slack message stops working within the hour. The files themselves stay retrievable for up to 14 days through fresh calls to the same endpoint, giving your pipeline room to retry. The official Notetaker media handling reference documents every field and the full security model.
Is there an API to store meeting recordings in my app database?
Section titled “Is there an API to store meeting recordings in my app database?”Yes. The Notetaker media endpoint returns short-lived signed URLs, not the bytes, so the storage step is yours: fetch each url, then write the file to your own database, disk, or object bucket. The pattern is fetch-then-persist, and you have a 60-minute window per URL to complete it before the link expires and you re-fetch.
The endpoint is read-only and idempotent, so call GET /v3/grants/{grant_id}/notetakers/{notetaker_id}/media as often as you need. It returns up to five objects: recording (video/mp4), transcript, summary, action_items (all application/json), and thumbnail (image/png). Each object carries a size in bytes and a created_at timestamp alongside the url. For binary recordings that can run tens of megabytes, stream the download straight to storage rather than buffering the whole file in memory. The snippet below pulls the response and pipes the MP4 to disk; swap createWriteStream for your S3 or GCS upload client in production.
import fs from "node:fs";import { Readable } from "node:stream";
// Step 1: get fresh signed URLs (each valid for 60 minutes).const res = await fetch( "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers/<NOTETAKER_ID>/media", { headers: { Authorization: "Bearer <NYLAS_API_KEY>" } });const { data } = await res.json();
// Step 2: stream the recording into your own storage.const file = await fetch(data.recording.url);const out = fs.createWriteStream("meeting_recording.mp4");await new Promise((resolve, reject) => { Readable.fromWeb(file.body).pipe(out).on("finish", resolve).on("error", reject);});console.log("Stored", data.recording.size, "bytes");Store the file path or object key in your database next to the meeting record, not the signed url. A cached URL is worthless an hour later, so persist the bytes and your own permanent reference instead.
Can I create a custom meeting playback feature with an API?
Section titled “Can I create a custom meeting playback feature with an API?”Yes, but build it on files you’ve already copied into your own storage, never on the raw signed URL. The Notetaker url is a bearer credential valid for 60 minutes, so exposing it in a frontend leaks a download token into browser network tabs. Instead, persist the MP4 and transcript, then serve playback through your backend.
A working playback flow has three steps. First, your backend fetches the media response, downloads the recording and transcript objects, and writes them to private object storage with the meeting ID as the key. Second, your player requests playback from your own API, which checks the user’s permissions and returns a short-lived signed URL you mint yourself (an S3 presigned GET, for example). Third, the browser streams the MP4 from that URL and renders the transcript array as a synced caption track using each segment’s start and end times in milliseconds. This keeps access control, audit logging, and expiry under your control rather than the upstream 3,600-second clock. The get a meeting transcript and recording recipe covers the transcript shape and download patterns in full.
How signed-URL delivery compares to a raw recording download
Section titled “How signed-URL delivery compares to a raw recording download”Signed-URL delivery means the API never streams gigabytes through its own endpoint; it hands you a time-boxed link to object storage and steps out of the way. That keeps the API fast and your data exposure short, at the cost of one extra fetch hop and a download deadline you have to respect. The table contrasts the two delivery styles you’ll see across recording APIs.
| Concern | Raw download from the API | Signed URL delivery (Nylas) |
|---|---|---|
| Link lifetime | Often a permanent endpoint behind your key | Valid for 60 minutes, then dead |
| Auth on download | Your API key on every request | Pre-authenticated link, no key resent |
| Leak exposure | Key-protected but long-lived | Self-expires within 1 hour |
| File retention | Varies | Up to 14 days through re-fetch |
| Best for | Tiny payloads | Large MP4s and transcript files |
The honest tradeoff: if you only ever need a transcript string and never the video, a raw JSON download would be simpler than managing expiry. But for recordings that run tens of megabytes, signed URLs to object storage are the right call, and re-fetching is cheap because the endpoint is idempotent.
How do I download recordings before the signed URL expires?
Section titled “How do I download recordings before the signed URL expires?”Each media url is valid for 60 minutes from generation, and Nylas retains the underlying files for up to 14 days before they’re deleted. Re-fetch the endpoint for a new URL inside that window. The reliable trigger is the notetaker.media webhook, which fires once with state: "available" the moment processing finishes, typically a few minutes after the bot leaves the meeting. React to that event and download inside the window.
Wiring the webhook beats polling, which burns requests and returns 404 until media is ready. When the notetaker.media event arrives, call the media endpoint to mint fresh URLs, then persist the bytes. The curl below fetches the current signed links; run it from your webhook handler, not on a timer.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers/<NOTETAKER_ID>/media" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "action_items": { "created_at": 1703088000, "expires_at": 1704297600, "name": "meeting_action_items.json", "size": 289, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "recording": { "created_at": 1703088000, "duration": 1800, "expires_at": 1704297600, "name": "meeting_recording.mp4", "size": 52428800, "ttl": 3600, "type": "video/mp4", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "summary": { "created_at": 1703088000, "expires_at": 1704297600, "name": "meeting_summary.json", "size": 437, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "thumbnail": { "created_at": 1703088000, "expires_at": 1704297600, "name": "thumbnail.png", "size": 437, "ttl": 3600, "type": "image/png", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "transcript": { "created_at": 1703088000, "expires_at": 1704297600, "name": "transcript.json", "size": 10240, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." } }}If an hour passes before you finish a download, don’t retry the same URL: it’s gone. Call GET /v3/grants/{grant_id}/notetakers/{notetaker_id}/media again and the API issues a new set of URLs with a fresh 60-minute window, as long as you’re still inside the 14-day retention period. The Notetaker webhooks recipe shows a full handler that downloads media automatically when the event fires.
What’s next
Section titled “What’s next”- Get a meeting transcript and recording for the full media response and transcript shape
- Notetaker webhooks recipe to download media automatically on the
notetaker.mediaevent - Auto-log meeting notes to a CRM for a downstream sync pattern once files are stored
- Handling Notetaker media files for the full field reference and security model