You scheduled the meeting, invited everyone, and forgot the room. Now two teams are standing in the same doorway. Booking a room or piece of equipment is a two-step job: list the resources your organization has, then attach the one you want to the event you create. Both Google Workspace and Microsoft 365 model rooms as calendars with their own email addresses, and the Calendar API reserves them the same way for both.
This recipe shows how to list the room resources available to a grant, then book one by adding its email to an event’s resources array. The same flow reserves projectors, vehicles, or any bookable equipment your admin has registered.
Why use Nylas instead of a provider room API directly?
Section titled “Why use Nylas instead of a provider room API directly?”Google and Microsoft both expose room data, but through completely different shapes. Going direct means maintaining code for 2 providers. The API collapses both providers into a single resources endpoint and one event field:
- One schema for two providers. Google returns resources from the Admin SDK Directory API; Microsoft returns them from the Graph
placesendpoint. Nylas normalizes both into oneroom_resourceobject. - One booking field. You add the room’s email to the event’s
resourcesarray regardless of provider, instead of learning Graph attendeetypevalues or GoogleresourceEmailsemantics. - Admin scopes handled in auth. Listing rooms needs directory-read scopes that the API requests during the grant’s OAuth flow, so you don’t manage two separate consent screens.
Before you begin
Section titled “Before you begin”You need a connected Google Workspace or Microsoft 365 grant, an API key from the Nylas Dashboard, and a calendar you can write to. Listing rooms also requires directory-read access, which an admin grants once for the whole organization. Personal Gmail and Outlook.com accounts don’t expose room resources, since rooms live in the workspace directory.
Directory scopes for listing rooms
Section titled “Directory scopes for listing rooms”Room resources live in the organization’s directory, so the read scope is admin-controlled, not per-user. For Google, the grant needs admin.directory.resource.calendar.readonly. For Microsoft, it needs Place.Read.All. Both are admin-consent scopes: a Workspace or Microsoft 365 administrator approves them once, and every grant in that tenant can then list rooms. Booking a room only needs standard calendar write scopes, so a grant can reserve a room even when it can’t list every room in the directory.
List the room resources
Section titled “List the room resources”Send a GET request to /v3/grants/{grant_id}/resources to return every room and equipment resource the directory exposes to the grant. The response is a data array of room_resource objects, each with an email, name, capacity, building, and floor fields. The email is the value you’ll book against. The default page size is 50 resources, paged with the next_cursor token.
curl --compressed --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/resources' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'// The Node SDK has no dedicated resources method yet, so read room// resources directly from the REST /resources endpoint.const response = await fetch( `https://api.us.nylas.com/v3/grants/${process.env.NYLAS_GRANT_ID}/resources`, { headers: { Authorization: `Bearer ${process.env.NYLAS_API_KEY}` } },)const { data } = await response.json()console.log(data)import osimport requests
grant_id = os.environ["NYLAS_GRANT_ID"]api_key = os.environ["NYLAS_API_KEY"]
response = requests.get( f"https://api.us.nylas.com/v3/grants/{grant_id}/resources", headers={"Authorization": f"Bearer {api_key}"},)print(response.json()["data"])A successful response carries a request_id and the resource list. Read the email and capacity of each room so you can match a 4-person room to a 4-person meeting. Here’s a single-room response:
{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": [ { "building": "West Building", "capacity": "8", "floor_name": "7", "floor_section": "7", "floor_number": "7", "name": "Training Room 1A", "object": "room_resource" } ], "next_cursor": "OQ=="}Book a room when you create the event
Section titled “Book a room when you create the event”Create the event with a POST request to /v3/grants/{grant_id}/events?calendar_id={calendar_id} and include the room’s email in the resources array. The calendar_id query parameter is required; use primary for the grant’s main calendar. Each entry in resources takes 2 fields, a name and an email, where the email is the value from the resources list. The provider sends the booking request to the room’s calendar, which accepts automatically within seconds if the room is free.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "title": "Q3 Roadmap Review", "when": { "start_time": 1735660800, "end_time": 1735664400 }, "participants": [ { "name": "Leyah Miller", "email": "[email protected]" } ], "resources": [ { "name": "Training Room 1A", "email": "[email protected]" } ] }'const event = await nylas.events.create({ identifier: process.env.NYLAS_GRANT_ID, queryParams: { calendarId: "primary" }, requestBody: { title: "Q3 Roadmap Review", when: { startTime: 1735660800, endTime: 1735664400 }, resources: [ ], },})console.log(event)from nylas import Client
nylas = Client(api_key=os.environ["NYLAS_API_KEY"])
event = nylas.events.create( identifier=os.environ["NYLAS_GRANT_ID"], query_params={"calendar_id": "primary"}, request_body={ "title": "Q3 Roadmap Review", "when": {"start_time": 1735660800, "end_time": 1735664400}, "resources": [ ], },)print(event)The room shows up in the event’s resources and participants once the provider processes the booking. If the room is already taken, the provider declines the invite and the event keeps the slot without the room, so check the response after a few seconds.
Things to know about room resources
Section titled “Things to know about room resources”A room is a mailbox with a calendar, so booking one means sending it an invite, and a few quirks fall out of that. Knowing them up front saves a round of debugging when a room fails to confirm.
- Acceptance depends on the room’s policy. Google and Microsoft let admins configure rooms to accept automatically, decline conflicts, or require manual approval by a delegate. A room set to manual approval won’t confirm instantly, so don’t treat a
200create response as a guaranteed reservation. - Double-booking is provider-enforced. If 2 events request the same room for overlapping times, the room’s calendar declines the second one. The API surfaces the decline in the event’s resource status, not as a create error.
emailis the stable identifier. Resourcenamevalues like “Training Room 1A” can be renamed by admins, but theemailstays constant. Store the email, not the display name, when you cache rooms.- Equipment uses the same model. Projectors, cars, and other bookable equipment are registered as resource calendars too, so they appear in the same resources list and book through the same
resourcesarray. - Personal accounts return an empty list. Consumer Gmail and Outlook.com grants have no directory, so the resources endpoint returns an empty
dataarray. Room booking is a workspace feature. See Google Workspace resource calendars for how admins create rooms.
Paginate through large room directories
Section titled “Paginate through large room directories”Large organizations register hundreds of rooms across buildings, so the resources endpoint returns 50 rooms at a time by default. Each response includes a next_cursor token when more rooms exist. Pass it back as the page_token query parameter to fetch the next page, and stop when next_cursor is absent. This mirrors how the Events API paginates lists.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/resources?limit=50&page_token=<NEXT_CURSOR>' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'rooms, cursor = [], Nonewhile True: params = {"limit": 50} if cursor: params["page_token"] = cursor res = requests.get( f"https://api.us.nylas.com/v3/grants/{grant_id}/resources", headers={"Authorization": f"Bearer {api_key}"}, params=params, ).json() rooms.extend(res["data"]) cursor = res.get("next_cursor") if not cursor: breakWhat’s next
Section titled “What’s next”- Check Free/Busy status to confirm attendees are free before you reserve a room
- Create Microsoft calendar events for Teams conferencing and write scopes
- Create Google calendar events for automatic Google Meet links and invites
- Manage calendars to find the
calendar_idyou book the event on - Room resources API reference for every field the resources endpoint returns