A conference room, a company van, or a contractor who does shifts for you all need a calendar, and none of them has an account on Google or Microsoft to connect. If your organization already registers rooms in Google Workspace or Microsoft 365, book them as provider resources. For everything else, give the resource a Nylas Agent Account.
An Agent Account is a Nylas-hosted mailbox and calendar at an address you choose, such as [email protected]. You create it with one request, book it through the same Events API you use for people, and because it has a real address, anyone in your company can invite it straight from their own calendar app. This recipe sets one up for a meeting room and covers both booking paths.
How does an Agent Account work as a resource calendar?
Section titled “How does an Agent Account work as a resource calendar?”An Agent Account is a grant like any other, with a primary calendar that Nylas provisions when it creates the account. There’s no OAuth flow, no scopes to request, and the grant never expires. Every one of the Calendar and Events endpoints in the Agent Account endpoint list works on it.
The difference from a plain hosted calendar is the mailbox behind it. When someone adds [email protected] to a meeting in Google Calendar, Outlook, or Apple Calendar, the invitation arrives in the room’s mailbox and Nylas turns it into an event on the room’s calendar. Your code decides whether the room is free and answers through the Send RSVP endpoint, and the organizer sees the room accept or decline next to every other attendee. When your application books the room directly and adds the requester as a participant, the room sends them the invitation, and any later change or cancellation reaches their calendar too.
Create an Agent Account for the room
Section titled “Create an Agent Account for the room”Agent Accounts live on a domain registered with Nylas. A trial *.nylas.email subdomain takes no DNS work, and a custom subdomain such as rooms.yourcompany.com needs the MX and TXT records in Set up domains for Agent Accounts. Putting every room on its own subdomain also groups them into one workspace, so a single policy covers all of them.
With the domain verified, the Nylas CLI creates the account in one command, and the primary calendar is ready as soon as it returns. The top-level name becomes the display name on every invitation the room sends.
The same request through the API uses custom authentication with provider set to nylas. Save the id in the response, which is the grant ID for every later call.
curl --request POST \ --url "https://api.us.nylas.com/v3/connect/custom" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "provider": "nylas", "name": "Conference Room A", "settings": { "email": "[email protected]" } }'{ "request_id": "5967ca40-a2d8-4ee0-a0e0-6f18ace39a90", "data": { "id": "<ROOM_GRANT_ID>", "provider": "nylas", "grant_status": "valid", "name": "Conference Room A", "scope": [], "created_at": 1742932766 }}Book the room from your application
Section titled “Book the room from your application”To reserve the room from your own booking UI, create an event on the room’s grant with calendar_id=primary. Add the person who booked it as a participant and set notify_participants=true, and the room sends them an invitation they can accept in their own calendar. Later PUT and DELETE calls on the event with the same flag update or cancel it on their calendar as well.
The request below books the room for one hour and invites the requester. Add a metadata object with your own booking ID, up to 50 key-value pairs, if you want to correlate the event with a row in your database later.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<ROOM_GRANT_ID>/events?calendar_id=primary¬ify_participants=true" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "title": "Design review", "when": { "start_time": 1758016800, "end_time": 1758020400 }, "participants": [ { "email": "[email protected]", "name": "Leyah Miller" } ], "metadata": { "key1": "booking-4821" } }'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY, apiUri: "https://api.us.nylas.com",});
const event = await nylas.events.create({ identifier: process.env.ROOM_GRANT_ID, requestBody: { title: "Design review", when: { startTime: 1758016800, endTime: 1758020400 }, metadata: { key1: "booking-4821" }, }, queryParams: { calendarId: "primary", notifyParticipants: true, },});
console.log("Room booked:", event.data.id);import osfrom nylas import Client
nylas = Client(os.environ["NYLAS_API_KEY"], "https://api.us.nylas.com")
event = nylas.events.create( os.environ["ROOM_GRANT_ID"], request_body={ "title": "Design review", "when": {"start_time": 1758016800, "end_time": 1758020400}, "metadata": {"key1": "booking-4821"}, }, query_params={"calendar_id": "primary", "notify_participants": True},)
print("Room booked:", event.data.id)Let people book the room from their own calendar
Section titled “Let people book the room from their own calendar”The room’s address works as an attendee anywhere. When an organizer adds it to a meeting in Google Calendar, Outlook, or Apple Calendar, the invitation lands in the room’s mailbox, Nylas creates the matching event on the room’s primary calendar, and an event.created webhook fires. The organizer is the person who sent the invite, and the room appears in participants with status set to noreply.
Nothing is accepted automatically. Your webhook handler applies whatever rules the room has, such as business hours, a capacity limit, or a check against the Free/Busy endpoint for a conflicting booking, and then answers with one of three statuses: yes, no, or maybe. The reply goes back over standard iCalendar, so the organizer’s calendar shows the room as accepted or declined within seconds, without them ever opening your application.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<ROOM_GRANT_ID>/events/<EVENT_ID>/send-rsvp?calendar_id=primary" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "status": "yes" }'const response = await nylas.events.sendRsvp({ identifier: process.env.ROOM_GRANT_ID, eventId: "<EVENT_ID>", requestBody: { status: "yes" }, queryParams: { calendarId: "primary" },});response = nylas.events.send_rsvp( identifier=os.environ["ROOM_GRANT_ID"], event_id="<EVENT_ID>", request_body={"status": "yes"}, query_params={"calendar_id": "primary"},)Subscribe to event.created, event.updated, and event.deleted for the room’s grant so a reschedule or cancellation from the organizer’s side reaches your handler the same way. Sync calendar events with webhooks covers the subscription and payload shape.
Check when a room is free
Section titled “Check when a room is free”Before your application proposes a slot, ask the room’s calendar for its busy blocks. The Free/Busy endpoint on the room’s grant takes a time window and the room’s address, and returns every busy interval in it. The window below covers 2 days.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<ROOM_GRANT_ID>/calendars/free-busy" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "start_time": 1758009600, "end_time": 1758182400, "emails": ["[email protected]"] }'To find a slot that works for several rooms at once, or for a room plus the people attending, use the availability endpoint with each address as a participant. Find open meeting times across calendars walks through that request.
Offer a booking page with Scheduler
Section titled “Offer a booking page with Scheduler”For a self-serve booking link, an Agent Account can own a Scheduler Configuration. Create the Configuration against the room’s grant ID with availability.calendar_ids and booking.calendar_id both set to primary, and guests pick from the room’s real availability on a Scheduling Page. Each booking lands on the room’s calendar and the confirmation email comes from the room’s own address.
All 4 meeting types work, including round-robin pools that mix rooms with connected grants. Scheduler reads and writes the room’s primary calendar only, so keep one Agent Account per bookable resource rather than several calendars on one account.
Things to know about resource Agent Accounts
Section titled “Things to know about resource Agent Accounts”Every invitation, update, cancellation, and RSVP the room sends is an email, and each one counts against the account’s daily send quota, which is 200 messages per account per day on the free plan. A busy room that confirms 50 bookings a day is well inside that, but bulk backfills should pass notify_participants=false so they don’t send at all. If a room is over quota, the event still saves and the invitation is skipped.
The room is a full mailbox, so an inbound invitation also fires message.created for the invitation email itself. Drive your handler from event.created and ignore the message trigger, or attach a policy to the rooms’ workspace to filter the inbox down to invitations only.
What’s next
Section titled “What’s next”- How Agent Account calendars work for the full invitation, RSVP, and webhook behavior
- Provisioning Agent Accounts to create accounts in bulk, set display names, and assign workspaces
- Use Scheduler with Agent Accounts for booking pages, availability rules, and the
booking.*webhook triggers - How to book a room for a meeting when your rooms are already provider resources in Google Workspace or Microsoft 365