Quick Start Guide

Read as Markdown

Scheduling sounds simple until you build it. Calendars are fragmented across providers, availability shifts in real time, and adding conferencing means wiring up yet another integration.

This guide walks through a complete integration, end to end. Starting from a fresh set of API credentials, you’ll connect a user’s calendar, find when they’re free, book a meeting, sync events back to your application, and handle real-time notifications when anything changes.

The examples work with any language or framework. If you’d rather use an SDK, the same steps apply.

What we’ll build #

By the end of this guide you’ll have a working scheduling integration. A user will connect their calendar to your application, you’ll query when they’re free, book a meeting with a conferencing link provisioned automatically, sync their events back into your system, and receive real-time notifications whenever their calendar changes - all without writing separate code for Google, Outlook, Exchange, or iCloud.

The six steps cover each phase of that workflow in order:

  1. Authorize - connect a user’s calendar to your application via OAuth 2.0.
  2. List calendars - see which calendars are available on their account.
  3. Query availability - find the open slots in their schedule.
  4. Create an event - book the slot and attach a conferencing link automatically.
  5. Read events - sync events back into your application within a time-bounded window.
  6. Push notifications - receive real-time webhooks when a user’s calendar changes.

Before you start #

Create a Cronofy developer account and app #

If you haven’t already, create a free Cronofy Developer Account. From your developer dashboard, create a new application. This gives you:

  • A Client ID - the public identifier for your app.
  • A Client Secret - keep this private; never expose it in client-side code.
  • An SDK Identifier - used to route requests to the correct data center.

Configure a redirect URI #

During the OAuth flow, Cronofy redirects the user back to a URI you control after they grant access. This should be an endpoint in your own application that can receive the code query parameter and exchange it for tokens.

While your app is in developer mode you can use any redirect URI without registering it - useful for testing locally or trying things out quickly. When you’re ready to move to production, you’ll need to provide your redirect URIs to our support team as part of the application verification process.

Keep your Client ID, Client Secret, and redirect URI handy for the next step.


Step 1. Authorize a user #

Cronofy uses the OAuth 2.0 Authorization Code flow (RFC 6749 §4.1). Your application sits between Cronofy and the user’s calendar provider - Cronofy handles all the provider-specific OAuth complexity.

Step 1a - Redirect the user to the authorization URL #

Construct the authorization URL with the parameters below and redirect the user to it. The parameters are URL-encoded in the querystring as described in RFC 6749 appendix B.

https://app.cronofy.com/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=read_write&state={STATE}

Parameters

ParameterDescription
response_typeAlways code.
client_idYour application’s Client ID from the developer dashboard.
redirect_uriThe endpoint in your application that will receive the code. In developer mode any value is accepted; in production this must be pre-registered with Cronofy support.
scopeThe permissions your application needs. See the scopes table below.
stateA random value you generate. Cronofy returns it unchanged so you can verify it and prevent CSRF attacks.

Scopes

ScopeWhat it allows
read_writeRead event details and free/busy status; create, update, and delete events. Use this for most integrations.
free_busy_writeRead free/busy status only (no event details); create, update, and delete events. Use this when you need availability and booking but not the content of existing events.

This guide uses read_write throughout. For more granular control, see the Permissions reference.

To test this, either wire the authorization URL up in your application or make a call to it in Postman.

Step 1b - Handle the redirect and exchange the code #

Cronofy redirects the user to your redirect_uri with a code on the query string. Exchange it immediately - it is single-use and short-lived:

POST /oauth/token HTTP/1.1
Host: api.cronofy.com
Content-Type: application/json; charset=utf-8

{
  "client_id": "{CLIENT_ID}",
  "client_secret": "{CLIENT_SECRET}",
  "grant_type": "authorization_code",
  "code": "{CODE}",
  "redirect_uri": "{REDIRECT_URI}"
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "bearer",
  "access_token": "P531x88i05Ld2yXHIQ7WjiEyqlmOHsgI",
  "expires_in": 3600,
  "refresh_token": "3gBYG1XamYDUEXUyybbummQWEe5YqPmf",
  "scope": "read_write",
  "account_id": "acc_5ba21743f408617d1269ea1e",
  "sub": "acc_5ba21743f408617d1269ea1e",
  "linking_profile": {
    "provider_name": "google",
    "profile_id": "pro_n23kjnwrw2",
    "profile_name": "example@cronofy.com"
  }
}

Store access_token, refresh_token, and sub against the user record:

  • access_token - passed as Authorization: Bearer {ACCESS_TOKEN} on all subsequent API requests.
  • refresh_token - use this to obtain a new access_token when the current one expires; do not discard it.
  • sub - the stable Cronofy account identifier for this user; use it to look up their tokens on future requests.

Step 2. List calendars #

Now that you have an access_token from Step 1, you can start making authenticated API calls on behalf of the user. The first call retrieves all calendars on their account - you’ll need a calendar_id from the response to query availability and create events in the steps ahead.

GET /v1/calendars HTTP/1.1
Host: api.cronofy.com
Authorization: Bearer {ACCESS_TOKEN}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "calendars": [
    {
      "provider_name": "google",
      "profile_name": "example@gmail.com",
      "calendar_id": "cal_n23kjnwrw2_jsdfjksn234",
      "calendar_name": "example@gmail.com",
      "calendar_readonly": false,
      "calendar_deleted": false,
      "calendar_primary": true,
      "calendar_integrated_conferencing_available": true
    }
  ]
}

Note the calendar_id - you’ll use it in the next two steps. The calendar_integrated_conferencing_available field indicates whether that calendar can automatically generate a conferencing link (for example, Google Meet on Google Calendar, or Microsoft Teams on a Microsoft 365 calendar connected via the Graph API).


Step 3. Query availability #

With the access_token from Step 1 and the calendar_id and sub from Step 2, you can now query when the user is free. The Availability API inspects their calendar and returns open slots within the time windows you specify.

The request body defines three things:

  • participants - the users to check, identified by sub.
  • required_duration - the minimum slot length in minutes.
  • query_periods - the time windows to search within.
POST /v1/availability HTTP/1.1
Host: api.cronofy.com
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json; charset=utf-8

{
  "participants": [
    {
      "required": "all",
      "members": [
        {
          "sub": "{SUB}",
          "calendar_ids": ["{CALENDAR_ID}"]
        }
      ]
    }
  ],
  "required_duration": { "minutes": 60 },
  "response_format": "slots",
  "query_periods": [
    {
      "start": "2026-06-27T09:00:00Z",
      "end": "2026-06-27T17:00:00Z"
    }
  ]
}

Using response_format: "slots" returns non-overlapping slots of exactly required_duration minutes.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "available_slots": [
    {
      "start": "2026-06-27T09:00:00Z",
      "end": "2026-06-27T10:00:00Z",
      "participants": [
        { "sub": "acc_5700a00eb0ccd07000000000" }
      ]
    },
    {
      "start": "2026-06-27T10:00:00Z",
      "end": "2026-06-27T11:00:00Z",
      "participants": [
        { "sub": "acc_5700a00eb0ccd07000000000" }
      ]
    },
    {
      "start": "2026-06-27T14:00:00Z",
      "end": "2026-06-27T15:00:00Z",
      "participants": [
        { "sub": "acc_5700a00eb0ccd07000000000" }
      ]
    }
  ]
}

Each entry in available_slots is a time where all participants are free for the required duration. Pick a slot’s start and end to use in the next step.


Step 4. Create an event #

Now that you have an available time slot from Step 3, you can schedule the event. Using the access_token from Step 1, the calendar_id from Step 2, and the start and end from Step 3, you can create the event in the user’s calendar. Including "conferencing": { "profile_id": "default" } instructs Cronofy to automatically provision a conferencing link.

The default profile resolves in this order:

  1. The user’s default authorized conferencing service (such as Zoom), if they have connected one.
  2. The calendar’s integrated conferencing provider - Google Meet for Google Calendar, Microsoft Teams for eligible Microsoft 365 calendars.
  3. No conferencing, if neither is available.
POST /v1/calendars/{CALENDAR_ID}/events HTTP/1.1
Host: api.cronofy.com
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json; charset=utf-8

{
  "event_id": "scheduling_demo_01",
  "summary": "Demo meeting",
  "description": "Scheduled via the Cronofy API.",
  "start": "2026-06-27T09:00:00",
  "end": "2026-06-27T10:00:00",
  "tzid": "America/New_York",
  "conferencing": {
    "profile_id": "default"
  }
}

tzid is an IANA timezone identifier. It tells Cronofy which timezone to interpret the start and end values in - important for events near DST transitions and for displaying the correct local time to attendees. When tzid is set, omit the trailing Z from the time strings (a trailing Z forces UTC interpretation and overrides tzid). For more detail, see the Time Zones guide and the Daylight Saving Time guide.

A successful response is HTTP 202 Accepted. The event is created immediately; the conferencing link is provisioned asynchronously. If you read the event before the link is ready, the conferencing field will be { "pending": true }. Once generated, it resolves to:

{
  "conferencing": {
    "provider_name": "google_meet",
    "join_url": "https://meet.google.com/abc-defg-hij"
  }
}

Step 5. Read events #

With an event created, you can now read events back from the user’s calendar. You’ll use this both for the initial load and as the response to push notifications (covered in the next step).

tzid is required for read events - it tells Cronofy which timezone to express event times in.

GET /v1/events?tzid={TZID}&from={FROM}&to={TO}&include_deleted=true&include_managed=true HTTP/1.1
Host: api.cronofy.com
Authorization: Bearer {ACCESS_TOKEN}

Key parameters

ParameterDescription
tzidRequired. IANA timezone identifier for the returned event times - for example America/New_York, Europe/London, or Etc/UTC.
fromStart of the sync window. Defaults to 42 days in the past.
toEnd of the sync window. Defaults to 201 days in the future.
last_modifiedISO 8601 timestamp. Returns only events modified after this time. Use for incremental sync - pass the changes_since value from a push notification (see Step 6).
include_deletedSet to true to include deleted events, so you can remove them from your database. Defaults to false.
include_managedSet to true to include events created by your application via the Cronofy API. Defaults to false.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "pages": {
    "current": 1,
    "total": 1,
    "next_page": "https://api.cronofy.com/v1/events/pages/08a07b034306679e"
  },
  "events": [
    {
      "calendar_id": "cal_n23kjnwrw2_jsdfjksn234",
      "event_uid": "evt_external_539e00003f5e",
      "summary": "Demo meeting",
      "description": "Scheduled via the Cronofy API.",
      "start": "2026-06-27T09:00:00",
      "end": "2026-06-27T10:00:00",
      "deleted": false,
      "participation_status": "accepted",
      "transparency": "opaque",
      "event_status": "confirmed",
      "categories": [],
      "attendees": [],
      "conferencing": {
        "provider_name": "google_meet",
        "join_url": "https://meet.google.com/abc-defg-hij"
      },
      "created": "2026-06-27T08:00:00Z",
      "updated": "2026-06-27T08:00:00Z"
    }
  ]
}

Handling pagination

When pages.total is greater than pages.current, follow the pages.next_page URL to retrieve the next page. Keep fetching until you’ve processed the last page.

Incremental sync with last_modified

For ongoing sync, pass last_modified set to the changes_since timestamp from the most recent push notification. This returns only the events that have changed since that point, keeping the response small. See Incremental Synchronization for full details.


Step 6. Push notifications #

Register a notification channel to receive a webhook the moment a user’s calendar changes. Your server then calls Read Events with the changes_since timestamp from the notification to fetch only what changed.

Testing locally

Your callback_url must be publicly reachable. For local development, you can use ngrok to expose your local server - see Push Notifications for details.

Register a notification channel #

POST /v1/channels HTTP/1.1
Host: api.cronofy.com
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json; charset=utf-8

{
  "callback_url": "https://your-app.com/webhooks/cronofy",
  "filters": {}
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "channel": {
    "channel_id": "chn_54cf7c7cb4ad4c1027000001",
    "callback_url": "https://your-app.com/webhooks/cronofy",
    "filters": {}
  }
}

Handle the verification notification #

Immediately after creating a channel, Cronofy sends a verification notification to your callback_url to confirm it is reachable. Your endpoint must respond HTTP 200 within 5 seconds, or the channel will be closed.

{
  "notification": {
    "type": "verification"
  },
  "channel": {
    "channel_id": "chn_54cf7c7cb4ad4c1027000001",
    "callback_url": "https://your-app.com/webhooks/cronofy",
    "filters": {}
  }
}

Handle change notifications #

When a user’s calendar changes, Cronofy sends a change notification. The changes_since value is the timestamp to pass as last_modified in your Read Events call.

{
  "notification": {
    "type": "change",
    "changes_since": "2024-06-23T09:24:16Z"
  },
  "channel": {
    "channel_id": "chn_54cf7c7cb4ad4c1027000001",
    "callback_url": "https://your-app.com/webhooks/cronofy",
    "filters": {}
  }
}

On receiving a change notification:

  1. Read the changes_since timestamp from the notification body.
  2. Call GET /v1/events with last_modified={CHANGES_SINCE}, include_deleted=true, and include_managed=true.

For more information about staying up to date with a user’s calendar, see Recommended Event Sync.

Testing change notifications

You can trigger test notifications directly from your Cronofy developer dashboard without needing to make real calendar changes. Go to your application dashboard, select Channels from the left navigation, and choose an active channel. Under the Trigger Push Notifications heading you’ll find buttons to send a dummy version of each notification type to your channel’s callback URL.


You’ve now built a complete calendar integration: authorized a user, queried availability, created a meeting, synced events, and wired up real-time notifications. From here you can explore more advanced capabilities like Availability Rules for setting working hours, UI Elements to add pre-built scheduling components to your interface, or Meeting Agents to automatically record, transcribe, and summarise the meetings you’ve just scheduled.

Next steps