Virtual calendars work like any other calendar in Nylas. They also make it easy to include customized scheduling in your project by allowing users to schedule events without needing to connect to a third-party provider, like Google or Microsoft.
You might prefer virtual calendars for your project if…
- Your users have sensitive information on their personal calendars that they don’t want to expose to complete their task.
- Your users don’t have accounts on the providers your project supports (for example, external contractors).
- You want to let users book resources that don’t have accounts on the providers your project supports (for example, meeting rooms).
How virtual calendars work
Section titled “How virtual calendars work”Nylas’ virtual calendars are linked to virtual accounts within your Nylas application. Unlike normal grants, virtual accounts don’t expire, they don’t generate grant.expired notifications, and you don’t need to define or manage their scopes. Each Nylas application can have as many virtual accounts as necessary, billed at your usual per-grant price. Each virtual account can have up to 10 virtual calendars.
You can only use the Manage Grants, Calendar, and Events endpoints with virtual accounts. They don’t have access to the Email and Contacts APIs.
Virtual accounts are displayed in the Nylas Dashboard as grants. Their provider is always “Virtual calendar”.

The first virtual calendar that you create for a virtual account becomes the account’s primary calendar. Like third-party calendars, you can reference the virtual calendar in API requests using the primary keyword instead of the calendar_id. You can’t change which virtual calendar is the primary, and you can’t delete the primary calendar.
You can use virtual calendars with Scheduler to book events.
Authenticate a virtual account
Section titled “Authenticate a virtual account”There are two steps to authenticating a virtual account: first, you have to create a virtual calendar connector, then create a virtual account.
Create a virtual calendar connector
Section titled “Create a virtual calendar connector”To create a virtual calendar connector in the Nylas Dashboard, select Connectors in the left navigation, scroll to the Virtual calendar option, and click the plus symbol beside it.

Programmatically, you can create a virtual calendar connector by making a Create Connector request.
curl --request POST \ --url "https://api.us.nylas.com/v3/connectors" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data '{ "provider": "virtual-calendar" }'import 'dotenv/config'import Nylas from 'nylas'
const NylasConfig = { apiKey: process.env.NYLAS_API_KEY, apiUri: process.env.NYLAS_API_URI,}
const nylas = new Nylas(NylasConfig)
async function createConnector() { try { const connector = await nylas.connectors.create({ requestBody: { name: 'nylas', provider: 'virtual-calendar', } })
console.log('Connector created:', connector) } catch (error) { console.error('Error creating provider:', error) }}
createConnector()from dotenv import load_dotenvload_dotenv()import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
connector = nylas.connectors.create( request_body={ "name": 'nylas', "provider": "virtual-calendar" })
print(connector)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')
request_body = { name: 'Nylas', provider: 'virtual-calendar'}
begin nylas.connectors.create(request_body: request_body)rescue Exception => exception puts exceptionendimport com.nylas.NylasClientimport com.nylas.models.CreateConnectorRequest
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>") val request = CreateConnectorRequest.VirtualCalendar() val connector = nylas.connectors().create(request)
print(connector.data)}import com.nylas.NylasClient;import com.nylas.models.*;
public class connector { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build(); CreateConnectorRequest request = new CreateConnectorRequest.VirtualCalendar(); Response<Connector> connectorResponse = nylas.connectors().create(request);
System.out.println(connectorResponse); }}Create a virtual account
Section titled “Create a virtual account”Make a BYO Authentication request and set the provider to virtual-calendar.
curl --request POST \ --url 'https://api.us.nylas.com/v3/connect/custom' \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'\ --header 'Content-Type: application/json' \ --data '{ "provider": "virtual-calendar", "settings": { "email": "Nyla-virtual-account" } }'{ "request_id": "5967ca40-a2d8-4ee0-a0e0-6f18ace39a90", "data": { "id": "<NYLAS_GRANT_ID>", "provider": "virtual-calendar", "grant_status": "valid", "email": "Nyla-virtual-account", "scope": [], "user_agent": "string", "ip": "string", "state": "<STATE>", "created_at": 1617817109, "updated_at": 1617817109 }}import 'dotenv/config'import Nylas from 'nylas'
const NylasConfig = { apiKey: process.env.NYLAS_API_KEY, apiUri: process.env.NYLAS_API_URI,}
const nylas = new Nylas(NylasConfig)
async function createVirtualCalendarGrant() { try { const response = await nylas.auth.grants.create({ requestBody: { provider: 'virtual-calendar', settings: { email: 'devrel-virtual-calendar', }, scope: ['calendar'] } })
console.log('Grant created:', response) } catch (error) { console.error('Error creating grant:', error) }}
createVirtualCalendarGrant()from dotenv import load_dotenvload_dotenv()import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
grant_id = nylas.auth.custom_authentication( request_body={ "provider": "virtual-calendar", "settings": { "email": 'devrel-virtual-calendar', }, "scope": ['calendar'] })
print(grant_id)# frozen_string_literal: true
require 'nylas'require 'sinatra'
set :show_exceptions, :after_handler
error 404 do 'No authorization code returned from Nylas'end
error 500 do 'Failed to exchange authorization code for token'end
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')
get '/nylas/auth' do request_body = { provider: 'virtual-calendar', settings: { email: "nylas-virtual-calendar" } }
response = nylas.auth.custom_authentication(request_body) "#{response}"endimport com.nylas.NylasClientimport com.nylas.models.*import spark.kotlin.Httpimport spark.kotlin.ignite
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>") val http: Http = ignite()
http.get("/nylas/auth") { val settings = mapOf("email" to "nylas-virtual-calendar") val scopes = listOf("openapi")
val requestBody = CreateGrantRequest. Builder(AuthProvider.VIRTUAL_CALENDAR, settings). scopes(scopes). state("xyz"). build()
var authResponse : Response<Grant>
nylas.auth().customAuthentication(requestBody).also { authResponse = it }
authResponse }}import java.util.*;import static spark.Spark.*;import com.nylas.NylasClient;import com.nylas.models.*;
public class AuthRequest { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
get("/nylas/auth", (request, response) -> { Map<String, String> settings = new HashMap<>(); settings.put("email", "nylas-virtual-calendar");
List<String> scopes = new ArrayList<>(); scopes.add("openid");
CreateGrantRequest requestBody = new CreateGrantRequest. Builder(AuthProvider.VIRTUAL_CALENDAR, settings). scopes(scopes).state("xyz"). build();
Response<Grant> authResponse = nylas.auth().customAuthentication(requestBody);
return "%s".formatted(authResponse); }); }}The settings.email field can be any arbitrary string — it doesn’t need to be formatted as an email address. Nylas uses this value as an ID to manage the virtual account. Because of this, you can’t use the identifier listed in settings.email to authenticate a third-party account with Nylas. We strongly recommend against using an existing email address as the identifier.
Create a virtual calendar
Section titled “Create a virtual calendar”To create a virtual calendar, make a Create Calendar request that specifies the grant ID of an existing virtual account.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/calendars" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data '{ "name": "DevRel calendar", "description": "Nylas Developer Relations calendar." }'import 'dotenv/config'import Nylas from 'nylas'
const NylasConfig = { apiKey: process.env.NYLAS_API_KEY, apiUri: process.env.NYLAS_API_URI,}
const nylas = new Nylas(NylasConfig)
async function createVirualCalendar() { try { const calendar = await nylas.calendars.create({ identifier: process.env.VIRTUAL_CALENDAR_GRANT_ID, requestBody: { name: 'Nylas DevRel', description: 'Nylas Developer Relations', } })
console.log('Virtual Calendar:', calendar) } catch (error) { console.error('Error to create virtual calendar:', error) }}
createVirualCalendar()from dotenv import load_dotenvload_dotenv()
import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
grant_id = os.environ.get("VIRTUAL_CALENDAR_GRANT_ID")
calendar = nylas.calendars.create( grant_id, request_body={ "name": 'Nylas DevRel', "description": 'Nylas Developer Relations' })
print(calendar)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')
request_body = { "name": "Nylas DevRel", "description": "Nylas Developer Relations", "timezone": "America/Toronto"}
begin calendars, _request_ids = nylas.calendars.create( identifier: '<VIRTUAL_CALENDAR_ID>', request_body: request_body)
puts calendarsrescue Exception => exception puts exceptionendimport com.nylas.NylasClientimport com.nylas.models.*
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
val requestBody = CreateCalendarRequest( "Nylas DevRel", "Nylas Developer Relations", "Nylas Headquarters", "America/Toronto", mapOf<String, String>() )
val calendar: Response<Calendar> = nylas.calendars().create( "<VIRTUAL_CALENDAR_GRANT_ID>", requestBody)
print(calendar.data)}import com.nylas.NylasClient;import com.nylas.models.*;import java.util.HashMap;
public class CreateVirtualCalendar { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
CreateCalendarRequest requestBody = new CreateCalendarRequest( "Nylas DevRel", "Nylas Developer Relations", "Nylas Headquarters", "America/Toronto", new HashMap<String, String>());
try { Response<Calendar> calendar = nylas.calendars().create( "<VIRTUAL_CALENDAR_GRANT_ID>", requestBody);
System.out.println(calendar); } catch(Exception e) { System.out.printf(" %s%n", e); } }}Create a virtual calendar event
Section titled “Create a virtual calendar event”Nylas doesn’t send email invitations or event reminders to participants on virtual calendar events.
Now that you have both a virtual account and a virtual calendar, you can start creating events. Make a Create Event request that specifies the virtual account’s grant ID and the virtual calendar ID.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data '{ "when": { "start_time": 1704302073, "end_time": 1704305673 }, "title": "Build With Nylas" }'import 'dotenv/config'import Nylas from 'nylas'
const NylasConfig = { apiKey: process.env.NYLAS_API_KEY, apiUri: process.env.NYLAS_API_URI}
const nylas = new Nylas(NylasConfig)const now = Math.floor(Date.now() / 1000) // Time in Unix timestamp format (in seconds)
async function createAnEvent() { try { const event = await nylas.events.create({ identifier: process.env.VIRTUAL_CALENDAR_GRANT_ID, requestBody: { title: 'Build With Nylas', when: { startTime: now, endTime: now + 3600, } }, queryParams: { calendarId: process.env.VIRTUAL_CALENDAR_ID, }, })
console.log('Event:', event) } catch (error) { console.error('Error creating event:', error) }}
createAnEvent()from dotenv import load_dotenvload_dotenv()
import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
grant_id = os.environ.get("VIRTUAL_CALENDAR_GRANT_ID")calendar_id = os.environ.get("VIRTUAL_CALENDAR_ID")
events = nylas.events.create( grant_id, request_body={ "title": 'Build With Nylas', "when": { "start_time": 1609372800, "end_time": 1609376400 }, }, query_params={ calendar_id })
print(events)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')
query_params = { calendar_id: '<VIRTUAL_CALENDAR_ID>'}
start_time = Time.now.to_iend_time = start_time + 3600
request_body = { when: { start_time: start_time, end_time: end_time }, title: "Build With Nylas",}
events, _request_ids = nylas.events.create( identifier: '<VIRTUAL_CALENDAR_GRANT_ID>', query_params: query_params, request_body: request_body)
if _request_ids != "" puts events[:id] puts events[:title] puts "Event created successfully"else puts "There was an error creating the event"endimport com.nylas.NylasClientimport com.nylas.models.*
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
val eventWhenObj: CreateEventRequest.When = CreateEventRequest.When.Timespan(<START_TIME>, <END_TIME>)
val eventRequest: CreateEventRequest = CreateEventRequest.Builder(eventWhenObj). title("Nylas DevRel"). description("Nylas Developer Relations"). location("Nylas Headquarters"). build()
val eventQueryParams: CreateEventQueryParams = CreateEventQueryParams("<VIRTUAL_CALENDAR_ID>")
val event: Response<Event> = nylas.events().create( "<VIRTUAL_CALENDAR_GRANT_ID>", eventRequest, eventQueryParams)
print(event.data)}import com.nylas.NylasClient;import com.nylas.models.*;
public class CreateVirtualEvent { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
CreateEventRequest.When.Timespan timespan = new CreateEventRequest.When.Timespan. Builder(Math.toIntExact("<START_TIME>"), Math.toIntExact("<END_TIME>")). build();
CreateEventRequest request = new CreateEventRequest.Builder(timespan). title("Build With Nylas"). build();
CreateEventQueryParams queryParams = new CreateEventQueryParams.Builder("<VIRTUAL_CALENDAR_ID>").build();
Response<Event> event = nylas.events().create( "<VIRTUAL_CALENDAR_GRANT_ID", request, queryParams);
System.out.println(event); }}Get virtual calendar availability
Section titled “Get virtual calendar availability”You can use the Get Availability endpoint to get availability information for a virtual calendar.
curl --request POST \ --url "https://api.us.nylas.com/v3/calendars/availability" \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "participants": [{ "email": "Nyla-virtual-account", "calendar_ids": ["primary"], "open_hours": [{ "days": [0,1,2], "timezone": "America/Toronto", "start": "9:00", "end": "17:00", "exdates": [] }] }], "start_time": 1600890600, "end_time": 1600999200, "interval_minutes": 30, "duration_minutes": 30, "round_to": 15, "availability_rules": { "availability_method": "max-availability", "buffer": { "before": 15, "after": 15 }, "default_open_hours": [ { "days": [0,1,2], "timezone": "America/Toronto", "start": "9:00", "end": "17:00", "exdates": [] }, { "days": [3,4,5], "timezone": "America/Toronto", "start": "10:00", "end": "18:00", "exdates": [] } ] } }'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "time_slots": [ { "emails": ["Nyla-virtual-account"], "start_time": 1659367800, "end_time": 1659369600 }, { "emails": ["Nyla-virtual-account"], "start_time": 1659376800, "end_time": 1659378600 } ] }}Virtual calendar notifications
Section titled “Virtual calendar notifications”Virtual calendars support calendar.created, event.created, and event.updated notifications.