Version:
Only show these results:

Using virtual calendars

Virtual calendars work like any other calendar in Nylas, and they make it easy to include customized scheduling in your Nylas application. They let you provide scheduling features to your users without requiring them to connect a third-party service provider like Google or Microsoft.

ℹ️ Virtual calendars is available for Free, Core, and Plus plans. Available for demo purposes in the Free-tier Sandbox.

There are two major use cases for virtual calendars:

  • Enabling scheduling for users who have sensitive information on their personal calendars that they don't want to expose to complete their task.
  • Enabling scheduling for users or resources that don't have accounts on the usual providers, such as external contractors or meeting rooms.

How virtual calendars work

Virtual calendars are linked to virtual accounts ("grants"), and can represent a person or resource that doesn't otherwise have a calendar. You can use virtual accounts as part of any Nylas application. Unlike normal grants, virtual accounts can't become invalid (they don't expire), they don't generate grant.expired notifications, and you don't need to select scopes for them. You can delete virtual accounts by making a Delete Grant request.

You can create virtual accounts using a Virtual Calendar connector and Custom Authentication. Each Nylas application can have as many virtual accounts as needed, billed at your usual per-account price, and 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 calendars appear in the Nylas Dashboard as grants, and their provider is always listed as "Virtual Calendar".

The Nylas Dashboard showing a list of grants. The virtual calendar's Provider is listed as Virtual Calendar.

When you create a virtual account, the email field you specified becomes its unique ID, which you use in requests to manage the calendar's account. After you create the account, you can create virtual calendars for it.

The first virtual calendar that you create for a virtual account becomes the account's primary calendar. Like normal calendars, you can reference the virtual calendar in API calls using the primary keyword instead of the calendar_id. You can't change which virtual calendar is the primary calendar, and you can't delete the primary calendar.

You can use virtual calendars to book meetings using Nylas Scheduler.

Set up virtual calendars

Before you create a virtual calendar, create a Nylas application, generate an API key for the application, and set up your authentication flow.

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

Before you can create a virtual calendar, you need a Virtual Calendar connector. You can create a connector from the Nylas Dashboard, by making a request to the POST /v3/connectors endpoint, or using the Nylas SDKs. You only need to create one Virtual Calendar connector for each application.

curl --request POST \
--url 'https://api.us.nylas.com/v3/connectors' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>' \
--data '{
"provider": "virtual-calendar",
"name": "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)

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_dotenv
load_dotenv()
import os
import sys
from 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 exception
end
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);
}
}
import com.nylas.NylasClient
import 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)
}

Create a virtual account

Now, you can create a grant for the virtual account. This grant represents the "owner" of the account.

Virtual accounts use Custom Authentication only. You can use the same auth process each time you need to create a virtual account.

The following cURL request is an example of a Custom Authentication request for a new virtual account.

curl --request POST 
--url https://api.us.nylas.com/v3/connect/custom \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>'\
--header 'Content-Type: application/json' \
--data '{
"provider": "virtual-calendar",
"settings": {
"email": "<VIRTUAL_ACCOUNT_IDENTIFIER>"
},
"state": "<STATE>"
}'

The email field can contain any arbitrary string and doesn't need to be email-formatted. Nylas uses the email value as an ID to manage the virtual account. Because of this, you can't use the identifier listed in the email field as a regular email account with Nylas. Nylas strongly recommends against using an existing email address for this field.

Nylas returns information about the new virtual account, including its grant ID.

{
"request_id": "1",
"data": {
"id": "<NYLAS_GRANT_ID>",
"provider": "virtual-calendar",
"grant_status": "valid",
"email": "<VIRTUAL_ACCOUNT_IDENTIFIER>",
"scope": [],
"user_agent": "string",
"ip": "string",
"state": "<STATE>",
"created_at": 1617817109,
"updated_at": 1617817109
}
}

You can also use the Nylas SDKs to authenticate a virtual account, as in the following examples.

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_dotenv
load_dotenv()
import os
import sys
from 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}"
end
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);
});
}
}
import com.nylas.NylasClient
import com.nylas.models.*
import spark.kotlin.Http
import 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
}
}

Create a virtual calendar

Now that you've authenticated a virtual account, you can make a Create Calendar request or use the Nylas SDKs to create a virtual calendar.

curl --request POST \
--url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/calendars' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>' \
--data '{
"name": "Nylas DevRel",
"description": "Nylas Developer Relations"
}'
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_dotenv
load_dotenv()

import os
import sys
from 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 calendars
rescue Exception => exception
puts exception
end
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);
}
}
}
import com.nylas.NylasClient
import 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)
}

Create an event on a virtual calendar

When you have both a virtual account and a virtual calendar, you can start creating events.

⚠️ Virtual calendars don't send email invitations to event participants.

To create an event on a virtual calendar, you can either make a Create Event request or use the Nylas SDKs.

curl --request POST \
--url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>&notify_participants=true' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--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)

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_dotenv
load_dotenv()

import os
import sys
from 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_i
end_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"
end
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);
}
}
import com.nylas.NylasClient
import 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)
}

Get availability for virtual calendars

You can use the Get Availability endpoint to get availability information for virtual calendars. The following cURL request uses the virtual account's id in the email field, and specifies the primary calendar.

curl --request POST \
--url https://api.us.nylas.com/v3/calendars/availability \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"participants": [
{
"email": "<VIRTUAL_ACCOUNT_ID>",
"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": "1",
"data": {
"time_slots": [
{
"emails": [
"<VIRTUAL_ACCOUNT_ID>"
],
"start_time": 1659367800,
"end_time": 1659369600
},
{
"emails": [
"<VIRTUAL_ACCOUNT_ID>"
],
"start_time": 1659376800,
"end_time": 1659378600
}
]
}
}

Virtual calendar notifications

Virtual calendars support the following Calendar and Events notifications:

  • calendar.created
  • event.created
  • event.updated

You can subscribe to these notifications by setting up webhook subscriptions or a Pub/Sub notification channel.