Connecting accounts

Before you can read or write a user's calendar, the user connects their Google, Microsoft Outlook or Zoom account to your app. You can set this up in two ways. You can add PyasConnect, a ready-made sign-in dialog, to your page, or you can build your own screens on the API.

PyasConnectYour own screens
What you buildOne endpoint on your server, and the component on your pageThe provider buttons, the redirects, and two requests from your server
Requests from your serverOne, from your endpointTwo, one to get a sign-in URL and one to send the code
Works withPyas Auth and Native AuthPyas Auth and Native Auth

With either option, your API key stays on your server, and only your server uses it. To connect real accounts, you need a live key. A test key works only in the sandbox, which already has its own accounts.

Pyas Auth or Native Auth

For each provider, you choose how people sign in. You set this in the dashboard, under your app's Provider setup.

  • Pyas Auth is the default. People sign in through Pyas's own OAuth app for Google, Microsoft or Zoom, so you don't need to set anything up. The provider's consent screen shows the Pyas name.
  • Native Auth uses your own OAuth app for Google, Microsoft or Zoom. The provider's consent screen shows your app's name. To use it, enter your OAuth app's client ID, its client secret and your callback URL under Provider setup.

Additional scopes

By default, a connection asks for the access most apps need: reading and writing events, reading calendars, free/busy and settings. Some features need more access, so the user is asked for it only when your app requests the feature. There are two ways to request them:

  • In the dashboard, under your app's Provider setup, in Additional scopes. Choose them once for each provider, and every connection from your app asks for them, in PyasConnect and on your own screens.
  • In a request, with features when you create a connect session or a sign-in URL. A request that includes features uses those instead of the dashboard's choice.
FeatureWhat it adds
manageCalendarsCreate, rename, recolor and delete calendars, and add calendars to the person's list or remove them.
sharingShare a calendar with other people and change their access.
meetingSettingsChange Google Meet and Microsoft Teams settings, such as who can join without waiting.
attachmentUploadsUpload files to events.
roomsList meeting rooms and book them for events.

On Google, manageCalendars, sharing, meetingSettings, rooms and attachmentUploads need Native Auth for now. Google must approve this access for Pyas's own Google app first. If your app uses Pyas Auth for Google and requests one of them in features, the request fails with CONNECTION_PROVIDER_NOT_CONFIGURED. In the dashboard, they can't be chosen until then. With Native Auth, add each feature's scopes to your own Google app; the dashboard lists them, and any Google API to turn on.

On Microsoft, every feature works with either option. For rooms, the user's Microsoft 365 administrator must approve the access, so people whose organization hasn't approved it can't connect while you request it.

Option 1: PyasConnect

PyasConnect is a sign-in dialog that you add to your page. It shows the provider buttons, handles the sign-in, blocked pop-ups and errors, and saves the connected account. Before you start, add your site to your app's allowed origins in the dashboard. PyasConnect works only on those origins.

To try it first, open Lab in the dashboard. Pick your app, change how PyasConnect looks and behaves, and connect a real account in the preview, with no server code. The Lab then gives you the code for Vue, React, Angular or plain HTML, already filled in for your app.

1. Add an endpoint to your server

PyasConnect calls this endpoint each time a user opens the dialog. With the SDK, the endpoint is one line of code:

import { Pyas } from '@pyas/sdk'

const pyas = new Pyas({ apiKey: process.env.PYAS_API_KEY })

app.post('/api/pyas-session', pyas.connect.sessionHandler())

Optional: Save your own user id on the account. If your app has its own id for each user, pass a function that returns the id of the user who is connecting. Pyas saves the id on the account as subjectId, so you can find that user's accounts later.

app.post('/api/pyas-session', pyas.connect.sessionHandler((req) => req.user.id))

Replace req.user.id with wherever your app stores the id of the signed-in user.

2. Add the component to your page

Give the component a function that calls your endpoint and returns the session token. When an account is connected, save its accountId:

import { PyasConnect } from '@pyas/connect-react'

const getSessionToken = () =>
  fetch('/api/pyas-session', { method: 'POST' }).then((r) => r.json()).then((s) => s.token)

<PyasConnect getSessionToken={getSessionToken} onConnected={(account) => save(account.accountId)} />

PyasConnect shows a "Connect an account" button, with your app's name and icon from the dashboard. When a user connects an account, your handler receives the account:

{
  "accountId": "6b0e3f1a-8c2d-4e5f-9a7b-1c3d5e7f9a2b",
  "provider": "google",
  "email": "avery@yourcustomer.com",
  "name": "Avery Brooks",
  "subjectId": "user_42"
}

With Native Auth, the provider redirects people to your callback URL instead of to Pyas. Add this code to your callback page so that it passes the result to the dialog:

import { postCallbackResult } from '@pyas/connect'
postCallbackResult()

The dialog is available in English, Norwegian, Swedish, Danish, German, French, Spanish, Dutch and Portuguese (Brazil). It uses your page's language (<html lang>), then the browser's. To choose the language yourself, set locale, for example locale="de". The pages Pyas shows in redirect mode work the same way: they use the browser's language, or add &locale=de to the session's url.

For themes, custom text, a custom button and every other option, see the package documentation on npm: @pyas/connect-react (opens in a new tab), @pyas/connect-vue (opens in a new tab), @pyas/connect-angular (opens in a new tab), and @pyas/connect (opens in a new tab) for any other page.

Option 2: Your own screens

With this option, you show the provider buttons yourself. When a user selects one, your server requests a sign-in URL from the API and redirects the user to it. After the user signs in, they return to your app with a code, and your server sends that code to the API to save the account.

First, set the page that users return to after they sign in. You do this under Provider setup in the dashboard:

  • Pyas Auth: Set the provider's Redirect URL to the page in your app that receives the code. Pyas redirects people to that page after they sign in.
  • Native Auth: Your callback URL is already set in Provider setup, and the provider redirects people to it.

1. Get a sign-in URL

When the user selects a provider button, request a sign-in URL from your server, then redirect the user to the url in the response:

import { Pyas } from '@pyas/sdk'

const pyas = new Pyas({ apiKey: process.env.PYAS_API_KEY })

const { url } = await pyas.connect.oauth({ provider: 'google', state: 'user_42' })
res.redirect(url)
{
  "success": true,
  "data": {
    "provider": "google",
    "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&response_type=code&scope=...&state=..."
  },
  "provider": null,
  "traceId": "trc_5f2a9c1e7b3d4a6f8e0c2b1d",
  "meta": {}
}
ParameterDescription
providergoogle, microsoft or zoom. Required.
stateAny value you choose, such as your id for the user. It's returned to you with the code.
loginHintAn email address to prefill on the provider's sign-in page.
featuresAdditional access to request, as a comma-separated list of manageCalendars, sharing, meetingSettings, attachmentUploads and rooms. See Additional scopes.

2. The user returns with a code

After signing in, the user is redirected to your Redirect URL (Pyas Auth) or your callback URL (Native Auth). The URL includes code and your state as query parameters:

https://yourapp.com/connected/google?code=4/0AeanS0Z...&state=user_42

If the user cancels or the provider denies access, there's no valid code, and the request in step 3 returns an error. Show your own message and let the user try again.

3. Exchange the code for an account

From your server, send the code to the API. The API saves the account and returns it:

const account = await pyas.connect.accounts.create({
  provider: 'google',
  code: req.query.code,
  subjectId: req.query.state,
})
await saveAccount(user, account.accountId)
{
  "success": true,
  "data": {
    "accountId": "6b0e3f1a-8c2d-4e5f-9a7b-1c3d5e7f9a2b",
    "provider": "google",
    "email": "avery@yourcustomer.com",
    "name": "Avery Brooks",
    "subjectId": "user_42",
    "existing": false,
    "grantedScopes": ["https://www.googleapis.com/auth/calendar"]
  },
  "provider": null,
  "traceId": "trc_5f2a9c1e7b3d4a6f8e0c2b1d",
  "meta": {}
}

subjectId is optional. It's your own id for the user, and the API saves it on the account. Each code can be used only once. If the request fails, the error explains why, for example with CONNECTION_CONSENT_FAILED or BILLING_CONNECTION_LIMIT. See Errors.

If you're coming from v1, steps 1 and 3 replace these two v1 endpoints:

GET /{provider}/oauth
POST /{provider}/connect

In v2, the provider is a parameter instead of part of the path. The v1 endpoints still work.

Save the account id

With either option, store the accountId with your user. Every request that reads or writes their calendar must include it. To find a user's accounts later, list them by the id you saved as subjectId:

for await (const account of pyas.accounts.list({ subjectId: user.id })) { /* ... */ }

Pyas also sends a connection.created webhook event for every new account.

When a connection breaks

A connection can stop working when a user revokes access, changes their password or leaves their company. To find the accounts that need attention, call:

GET /v2/accounts/status?overall=attention,broken

Each account in the response has an overall status:

overall.statusMeaning
healthyThe connection works.
attentionThe connection works, but some features are limited, for example because of a missing permission. reason explains the limitation.
brokenThe account no longer syncs.

The response is paginated, so keep requesting pages until meta.nextCursor is null. To check a single account, call:

GET /v2/accounts/{accountId}/status

When overall.fix is reconnect, the user needs to sign in again.

With PyasConnect, send the account's accountId from your page to your endpoint, and return it from the function you pass to sessionHandler. The dialog then opens directly to that account:

app.use(express.json())

app.post('/api/pyas-reconnect', pyas.connect.sessionHandler((req) => ({ accountId: req.body.accountId })))

With your own screens, send the same two requests again, first to get a sign-in URL and then to send the code. When the same person signs in, the API reconnects the account. The account keeps its accountId, and existing is true in the response.

Each account's status also includes userMessage, a sentence you can show the user. The connection.broken and connection.reauthorized webhook events notify you when an account stops syncing and when it's reconnected.