Platform Quickstart

Find out how to use Cal "atoms" to integrate scheduling into your product.

1. Unlocking access to atoms

  1. It is required to finish payment using Stripe link we send you.
  2. We will setup your Cal platform account and send you e-mail and password to login and proceed to the next steps of setting up atoms.

2. Setting up an OAuth client

The first step is to create an OAuth client, which will allow to connect your users to Cal, so that atoms can handle their scheduling.

  1. After logging in using provided credentials, open OAuth clients settings page https://app.cal.com/settings/organizations/platform/oauth-clients
  2. Add an OAuth client.
    1. Name: anything is fine, you can call it, for example, same as your company or your website.
    2. Redirect URIs: Used to validate internal requests origin and allow redirections on your platform when needed. You can example any URI of your company’s domain e.g. in our case cal.com.
    3. Booking, reschedule and cancel URLs: URLs of pages where users land after a successful booking or if they want to reschedule or cancel a booking. We will pass information in URL when redirecting to your pages and you will use our hooks and components to implement the pages. See this guide.
    4. Permissions - most likely you need all enabled:
      1. Event type: event type is a user event that others can book. For example, Alice is a language teacher and she has an event type “30 minutes Italian lesson” that others can then book.
      2. Booking: When Bob books the Italian lesson, a booking is created showing up on the calendar that Alice connected to using atoms.
      3. Schedule: used to represent when a user is available, From what to what time and when can an event type be booked. For example, Alice only can be booked for the Italian lessons on Mondays and Tuesdays from 9AM to 16PM.
      4. Profile: When you proceed to the chapter 2, you will create your users on our end, so that we can manage their scheduling. Profile permissions allow to either read or update user you create on our end.
      5. Apps: used to connect Google Calendar, Zoom, Microsoft Teams, etc. to atoms.

3. Creating managed users connected to the OAuth client

In order for atoms to handle scheduling on behalf of your users, you have to create what we call a “managed user” for each of your users.

What is a “Managed user”?

A representation of your user within our database containing basic information like e-mail and is used to manage the setup of Google Calendar. After creating a “managed user” you will receive user id, an access and a refresh tokens that are used by atoms to handle scheduling and can be used to modify “managed user” information, so make sure to store the tokens and the cal.com userId in your database. Also, you will have to connect each user on your end with the access and refresh tokens, for example, by adding new properties on your User model in database that store the tokens.

What is it not?

It's important to clarify that a "managed user" is completely independent of a regular user account on cal.com, meaning you don't need your users to register on cal.com web application.

Create managed users via our API

We now need OAuth client’s “client ID” and “client secret” that you can find in https://app.cal.com/settings/organizations/platform/oauth-clients.

You have to make a POST request to https://api.cal.com/v2/oauth-clients/YOUR_CLIENT_ID/users:

  1. Replace “YOUR_CLIENT_ID” in the URL with your “client ID”.
  2. Add "x-cal-secret-key” header with the value of your “client secret”.
  3. Add request body containing an object to create the “managed user” with the following properties:
    1. email: required, your user e-mail.
    2. name: optional, full name of your user.
    3. timeFormat: optional, value can be either 12 or 24 with 12 representing 12 hour time using AM and PM and 24 representing 24 hour time without AM and PM.
    4. weekStart: optional, value can be “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”, “Sunday”. If none passed, "Sunday” is used as the default. Countries in North and South America start their week on Sunday while countries in Europe and Oceania on Monday.
    5. timeZone: optional, in the format of “IANA timezone” e.g. “Europe/Rome”. If none passed, "Europe/London” is used as the default.

Example request

URL: https://api.cal.com/v2/oauth-clients/7Dcxu2fclb10001kha9x1dreyl4/users

Headers: { "x-cal-secret-key”: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX}

Body:

{
    "email": "[email protected]",
    "timeZone": "America/New_York"
}

Example response

{
  "status": "success",
  "data": {
    "user": {
      "id": 179,
      "email": "[email protected]",
      "username": "bob"
    },
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiY2xpZW50SWQiOiJjbHUxZmNtYjEwMDAxa2hyN3g3ZHJleWw0Iiwib3duZXJJZCI6MTc5LCJpYXQiOjE3MTE0NDI3OTR9.EsC3JRPHQnigcp_HSijKCIp8EgcWs2kj4AFxYXYc9sM",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaF90b2tlbiIsImNsaWVudElkIjoiY2x1MWZjbWIxMDAwMWtocjd4N2RyZXlsNCIsIm93bmVySWQiOjE3OSwiaWF0IjoxNzExNDQyNzk0fQ.GjklEucgey8yWMoGz7ABntbxYdiqqQFPooQjqGd3B5I"
  }
}

Make sure to store the user id, access and refresh tokens in your database and connect them to each user. For example, you can add “calAtomsAccessToken” and “calAtomsRefreshToken” properties to your user database model.

You will be able to provide an access token to our atoms so that they can handle scheduling on behalf of the user associated with the token. access tokens expires, so you will have to refresh them using the refresh token.

4. Backend: setting up refresh token endpoint

You have to setup an endpoint on your server to which atoms can send an expired access token and receive new one in return. This exchange will be automatically handled by atoms when you provide this endpoint URL when setting up frontend in the next step.

Q: Why do we need a separate endpoint just for this? A: Your OAuth client secret and users refresh tokens should never be exposed on the frontend. the oAuth client secret will reside as an environment variable in your backend and the users refresh tokens are stored in your database, those are used by the SDK to refresh the access token.

You can check an example refresh token endpoint in our atoms examples app: https://github.com/calcom/atoms-examples/blob/main/cal-sync/src/pages/api/refresh.ts

Shortly:

  1. Setup environment variables or pass values directly. Here are ones from examples app above:
    1. NEXT_PUBLIC_CALCOM_API_URL: https://api.cal.com/v2
    2. NEXT_PUBLIC_X_CAL_ID: your OAuth client ID
    3. X_CAL_SECRET_KEY - your OAuth client secret
  2. Your endpoint will receive a request from atoms.
  3. Make it a GET endpoint to which “Authorization: Bearer accessToken” header can be sent to.
  4. Assuming you have stored access and refresh tokens in your database and connected them to a specific user, fetch the user based on the received access token.
  5. Provide client ID, client secret and refresh token make a request to our “oauth/:clientId/refresh” endpoint.
  6. Store access and refresh tokens returned by the SDK in your database.
  7. Return access token to the request. it should be in the format of:
{ accessToken: "fresh access token" }

The atoms will now use the new access token to handle scheduling on behalf of your user.

5. Frontend: setting up atoms

Atoms are customizable UI components handling scheduling on behalf of your users.

5.1 Install the atoms package

npm:

npm install @calcom/atoms

yarn:

yarn add @calcom/atoms

pnpm:

pnpm add @calcom/atoms

5.2 Setup environment variables

CAL_OAUTH_CLIENT_ID: OAuth client ID

CAL_API_URL: https://api.cal.com/v2

REFRESH_URL: URL of the endpoint you implemented in step 3, for example, your.api.com/api/refresh

5.3 Setup root of your app

Next.js: open _app.js or or _app.tsx.

React: open App.js or App.ts.

First, import global css styles used by atoms.

import "@calcom/atoms/globals.min.css";

function MyApp({ Component, pageProps }) {
  return (
    <Component {...pageProps} />
  );
}

export default MyApp;

Second, import CalProvider that provides necessary information to atoms and wrap your components with it.

import "@calcom/atoms/globals.min.css";
import { CalProvider } from '@calcom/atoms';

function MyApp({ Component, pageProps }) {
  return (
    <CalProvider
      clientId={process.env.CAL_OAUTH_CLIENT_ID ?? ""}
      options={{
        apiUrl: process.env.CAL_API_URL ?? "",
        refreshUrl: process.env.REFRESH_URL
      }}
    >
      <Component {...pageProps} />
    </CalProvider>
  );
}

export default MyApp;

Third, CalProvider needs to get access token of the user for which atoms will handle scheduling, so you need to fetch user and provide its access token to the CalProvider.

import "@calcom/atoms/globals.min.css";
import { CalProvider } from '@calcom/atoms';

function MyApp({ Component, pageProps }) {
  const [accessToken, setAccessToken] = useState("");

  useEffect(() => {
    fetch(`/api/users/${pageProps.userId}`, {
    }).then(async (res) => {
      const data = await res.json();
      setAccessToken(data.accessToken);
    });
  }, []);
  
  return (
    <CalProvider
      accessToken={accessToken}
      clientId={process.env.CAL_OAUTH_CLIENT_ID ?? ""}
      options={{
        apiUrl: process.env.CAL_API_URL ?? "",
        refreshUrl: "/api/refresh"
      }}
    >
      <Component {...pageProps} />
    </CalProvider>
  );
}

export default MyApp;

6. Frontend: using atoms

It’s very easy, just import the atom and drop it in code! For example, for users to connect their Google Calendar drop in GcalConnect - it will handle everything.

import { GcalConnect } from "@calcom/atoms";

...
export default function Connect() {
  return (
    <main>
	    <GcalConnect/>
    </main>
  );
}

If you need to customize the appearance of any atom, you can pass in custom css styles via a className prop that every atom has:

<GcalConnect className="text-white hover:bg-orange-700" />

other more complex atoms will expose multiple classNames and props to react to events happening in the atoms

<AvailabilitySettings
  customClassNames={{
    subtitlesClassName: "text-red-500",
    ctaClassName: "border p-4 rounded-md",
    editableHeadingClassName: "underline font-semibold",
  }}
  onUpdateSuccess={() => {
    console.log("Updated successfully");
  }}
  onUpdateError={() => {
    console.log("update error");
  }}
  onDeleteError={() => {
    console.log("delete error");
  }}
  onDeleteSuccess={() => {
    console.log("Deleted successfully");
  }}
/>;

Was this page helpful?