The Nylas Notetaker API sends a bot to Zoom, Microsoft Teams, or Google Meet calls to record audio and video and generate transcripts. You can send it to a specific meeting on demand, or configure it to automatically join every meeting on a user’s calendar.
This quickstart covers both: sending a one-off Notetaker and setting up automatic calendar-based recording for your users.
Before you begin
Section titled “Before you begin”You need two things from Nylas to make API calls:
- An API key: authenticates your application. You’ll pass it as a Bearer token.
- A grant ID: identifies which user’s account to act on. You get one when you connect an account to Nylas.
If you don’t have these yet, follow one of the setup guides first:
- Get started with the CLI: run
nylas initto create an account, generate an API key, and connect a test account in one command. - Get started with the Dashboard: do the same steps through the web UI.
Then install the Nylas SDK for your language:
npm install nylaspip install nylasgem install nylasFor Java and Kotlin, see the Kotlin/Java SDK setup guide.
Send a Notetaker to a meeting
Section titled “Send a Notetaker to a meeting”The simplest use case: give Notetaker a meeting link and it joins, records, and transcribes. Works with any Zoom, Microsoft Teams, or Google Meet URL.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "meeting_link": "https://zoom.us/j/123456789", "notetaker_settings": { "name": "Meeting Notes" } }'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY });
const notetaker = await nylas.notetakers.create({ identifier: process.env.NYLAS_GRANT_ID, requestBody: { meetingLink: "https://zoom.us/j/123456789", name: "Meeting Notes", },});
console.log("Notetaker created:", notetaker.data.id);import osfrom nylas import Client
nylas = Client(os.environ["NYLAS_API_KEY"])
notetaker = nylas.notetakers.invite( request_body={ "meeting_link": "https://zoom.us/j/123456789", "name": "Meeting Notes", }, identifier=os.environ["NYLAS_GRANT_ID"],)
print("Notetaker created:", notetaker.data.id)require 'nylas'
nylas = Nylas::Client.new(api_key: ENV['NYLAS_API_KEY'])
notetaker, _ = nylas.notetakers.create( identifier: ENV['NYLAS_GRANT_ID'], request_body: { meeting_link: 'https://zoom.us/j/123456789', name: 'Meeting Notes' })
puts "Notetaker created: #{notetaker[:id]}"import com.nylas.NylasClient;import com.nylas.models.CreateNotetakerRequest;import com.nylas.models.Notetaker;import com.nylas.models.NylasApiError;import com.nylas.models.NylasSdkTimeoutError;import com.nylas.models.Response;
public class SendNotetaker { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
CreateNotetakerRequest requestBody = new CreateNotetakerRequest.Builder( "https://zoom.us/j/123456789") .name("Meeting Notes") .build();
Response<Notetaker> notetaker = nylas.notetakers().create(requestBody, "<NYLAS_GRANT_ID>");
System.out.println("Notetaker created: " + notetaker.getData().getId()); }}import com.nylas.NylasClientimport com.nylas.models.CreateNotetakerRequest
fun main() { val nylas = NylasClient.Builder("<NYLAS_API_KEY>").build()
val requestBody = CreateNotetakerRequest.Builder("https://zoom.us/j/123456789") .name("Meeting Notes") .build()
val notetaker = nylas.notetakers().create(requestBody, "<NYLAS_GRANT_ID>")
println("Notetaker created: ${notetaker.data.id}")}The bot joins the meeting within seconds. If the meeting is restricted to organization members, someone has to admit it from the lobby, and it gives up after 10 minutes; see Troubleshoot Notetaker. Replace the Zoom URL with any Microsoft Teams or Google Meet link. The API is the same.
Auto-join all meetings on a user’s calendar
Section titled “Auto-join all meetings on a user’s calendar”For most apps, you’ll want Notetaker to automatically join every meeting on a user’s calendar rather than sending it to individual links. Update the user’s calendar settings to enable this.
curl --request PUT \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/calendars/primary" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "notetaker": { "notetaker_settings": { "name": "Meeting Notes", "recording_type": "audio_video", "transcription": true }, "rules": { "event_selection": ["all"] } } }'const calendar = await nylas.calendars.update({ identifier: process.env.NYLAS_GRANT_ID, calendarId: "primary", requestBody: { notetaker: { name: "Meeting Notes", meetingSettings: { videoRecording: true, audioRecording: true, transcription: true, }, rules: { eventSelection: ["all"], }, }, },});
console.log("Calendar sync enabled");calendar = nylas.calendars.update( os.environ["NYLAS_GRANT_ID"], calendar_id="primary", request_body={ "notetaker": { "name": "Meeting Notes", "meeting_settings": { "video_recording": True, "audio_recording": True, "transcription": True, }, "rules": { "event_selection": ["all"], }, }, },)
print("Calendar sync enabled")calendar, _ = nylas.calendars.update( identifier: ENV['NYLAS_GRANT_ID'], calendar_id: 'primary', request_body: { notetaker: { name: 'Meeting Notes', meeting_settings: { video_recording: true, audio_recording: true, transcription: true }, rules: { event_selection: ['all'] } } })
puts "Calendar sync enabled"import com.nylas.NylasClient;import com.nylas.models.*;import java.util.Collections;
public class AutoJoinMeetings { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
CalendarNotetaker.MeetingSettings meetingSettings = new CalendarNotetaker.MeetingSettings.Builder() .videoRecording(true) .audioRecording(true) .transcription(true) .build();
CalendarNotetaker.Rules rules = new CalendarNotetaker.Rules.Builder() .eventSelection(Collections.singletonList("all")) .build();
CalendarNotetaker notetaker = new CalendarNotetaker.Builder() .name("Meeting Notes") .meetingSettings(meetingSettings) .rules(rules) .build();
UpdateCalendarRequest request = new UpdateCalendarRequest.Builder() .notetaker(notetaker) .build();
nylas.calendars().update("<NYLAS_GRANT_ID>", "primary", request);
System.out.println("Calendar sync enabled"); }}import com.nylas.NylasClientimport com.nylas.models.*
fun main() { val nylas = NylasClient(apiKey = "<NYLAS_API_KEY>")
val request = UpdateCalendarRequest( notetaker = CalendarNotetaker( name = "Meeting Notes", meetingSettings = CalendarNotetaker.MeetingSettings( videoRecording = true, audioRecording = true, transcription = true ), rules = CalendarNotetaker.Rules( eventSelection = listOf("all") ) ) )
nylas.calendars().update("<NYLAS_GRANT_ID>", "primary", request)
println("Calendar sync enabled")}Once enabled, Notetaker automatically joins all meetings that have a video conferencing link. Replace primary with a specific calendar ID to target a different calendar. You can also add a participant_filter to limit which meetings Notetaker joins. See the calendar sync docs for details.
Get the recording and transcript
Section titled “Get the recording and transcript”After a meeting ends, retrieve the recording and transcript. You’ll need the Notetaker ID from when you created it, or from listing your Notetakers.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers/<NOTETAKER_ID>/media" \ --header "Authorization: Bearer <NYLAS_API_KEY>"const media = await nylas.notetakers.media({ identifier: process.env.NYLAS_GRANT_ID, notetakerId: "<NOTETAKER_ID>",});
console.log("Recording:", media.data);media = nylas.notetakers.get_media( notetaker_id="<NOTETAKER_ID>", identifier=os.environ["NYLAS_GRANT_ID"],)
print("Recording:", media.data)media, _ = nylas.notetakers.media( identifier: ENV['NYLAS_GRANT_ID'], notetaker_id: '<NOTETAKER_ID>')
media.each do |file| puts "#{file[:type]}: #{file[:url]}"endResponse<List<NotetakerMedia>> media = nylas.notetakers().media("<NYLAS_GRANT_ID>", "<NOTETAKER_ID>");
for (NotetakerMedia file : media.getData()) { System.out.println(file.getType() + ": " + file.getUrl());}val media = nylas.notetakers().media("<NYLAS_GRANT_ID>", "<NOTETAKER_ID>")
media.data.forEach { file -> println("${file.type}: ${file.url}")}{ "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/..." } }}The response includes download URLs for the recording, thumbnail, and transcript, plus the summary and action items if you enabled them. Each URL is valid for 60 minutes; call the endpoint again for fresh ones. Media files are available a few minutes after the meeting ends. See Handling Notetaker media files.
Check Notetaker status
Section titled “Check Notetaker status”List all Notetakers for a user to see which are waiting to join, currently recording, or finished.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers" \ --header "Authorization: Bearer <NYLAS_API_KEY>"const notetakers = await nylas.notetakers.list({ identifier: process.env.NYLAS_GRANT_ID,});
notetakers.data.forEach((nt) => { console.log(`${nt.name} -- status: ${nt.status}`);});notetakers = nylas.notetakers.list(os.environ["NYLAS_GRANT_ID"])
for nt in notetakers.data: print(f"{nt.name} -- status: {nt.status}")notetakers, _ = nylas.notetakers.list(identifier: ENV['NYLAS_GRANT_ID'])
notetakers.each do |nt| puts "#{nt[:name]} -- status: #{nt[:status]}"endListResponse<Notetaker> notetakers = nylas.notetakers().list("<NYLAS_GRANT_ID>");
for (Notetaker nt : notetakers.getData()) { System.out.println(nt.getName() + " -- status: " + nt.getStatus());}val notetakers = nylas.notetakers().list("<NYLAS_GRANT_ID>").data
for (nt in notetakers) { println("${nt.name} -- status: ${nt.status}")}What’s next
Section titled “What’s next”- Authentication: set up OAuth so your users can connect their accounts
- Calendar sync deep dive: event selection rules, participant filters, and per-calendar configuration
- Handling media files: downloading, storing, and processing recordings and transcripts
- Webhooks: get notified when a Notetaker finishes recording
- Notetaker API reference: full endpoint documentation