# Quickstart (https://www.cal-sdk.com/docs/quickstart)

Start with a calendar you can't break: `@cal-sdk/mock`, which keeps events in memory. You'll create a weekday standup, move Wednesday's to later in the morning, and find a free slot around it.

<Steps>
<Step>

### Install [#install]

```npm
npm install @cal-sdk/core @cal-sdk/mock
```

</Step>
<Step>

### Create a recurring event [#create-a-recurring-event]

Save this as `quickstart.ts`:

```ts title="quickstart.ts"
import { createEvent, findFreeTime, listEvents, updateEvent } from '@cal-sdk/core';
import { mockCalendar } from '@cal-sdk/mock';

const calendar = mockCalendar();

const { event: standup } = await createEvent({
  calendar,
  event: {
    title: 'Standup',
    start: { dateTime: '2026-11-09T09:30:00', timeZone: 'Europe/Berlin' },
    end: { dateTime: '2026-11-09T09:45:00', timeZone: 'Europe/Berlin' },
    recurrence: ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR'],
  },
});

console.log(standup.kind, standup.start.dateTime);
// series 2026-11-09T09:30:00+01:00
```

You didn't pass `calendarId`, so the event went to `'primary'`, the user's default calendar. And because the `dateTime` had no offset, calsdk read it as 9:30 on the Berlin wall clock and returned it with the offset filled in.

</Step>
<Step>

### List a week [#list-a-week]

```ts title="quickstart.ts"
const { events } = await listEvents({
  calendar,
  timeMin: '2026-11-09T00:00:00+01:00',
  timeMax: '2026-11-14T00:00:00+01:00',
});

for (const event of events) console.log(event.id, event.kind, event.start.dateTime);
// evt_1_20261109T083000Z occurrence 2026-11-09T09:30:00+01:00
// evt_1_20261110T083000Z occurrence 2026-11-10T09:30:00+01:00
// evt_1_20261111T083000Z occurrence 2026-11-11T09:30:00+01:00
// evt_1_20261112T083000Z occurrence 2026-11-12T09:30:00+01:00
// evt_1_20261113T083000Z occurrence 2026-11-13T09:30:00+01:00
```

`listEvents` expands recurring events by default, so the series comes back as one `occurrence` per weekday. Each has its own id, which is what you pass to change a single day, and `seriesId` points back at the series.

</Step>
<Step>

### Move one occurrence [#move-one-occurrence]

```ts title="quickstart.ts"
const wednesday = events[2];

const { event: moved } = await updateEvent({
  calendar,
  eventId: wednesday.id,
  patch: {
    start: { dateTime: '2026-11-11T11:00:00', timeZone: 'Europe/Berlin' },
    end: { dateTime: '2026-11-11T11:15:00', timeZone: 'Europe/Berlin' },
  },
});

console.log(moved.kind, moved.start.dateTime);
// exception 2026-11-11T11:00:00+01:00
```

Updating an occurrence changes only that one by default, and it becomes an `exception`: an occurrence that no longer follows the series rule. Pass `scope: 'all'` to change the whole series instead. [Recurring events](/docs/guides/recurring-events) covers the other scopes.

</Step>
<Step>

### Find free time [#find-free-time]

```ts title="quickstart.ts"
const { slots } = await findFreeTime({
  calendars: [{ calendar }],
  timeMin: '2026-11-09T00:00:00+01:00',
  timeMax: '2026-11-10T00:00:00+01:00',
  duration: 30,
  slotInterval: 30,
  workingHours: { start: '09:00', end: '11:00', timeZone: 'Europe/Berlin' },
});

console.log(slots.map((slot) => slot.start));
// [ '2026-11-09T08:00:00.000Z', '2026-11-09T09:00:00.000Z', '2026-11-09T09:30:00.000Z' ]
```

Monday's standup blocks the 9:30 slot, so the free half hours are 9:00, 10:00 and 10:30 in Berlin. Slots come back as UTC instants, which is why 9:00 in Berlin prints as `08:00:00.000Z`.

</Step>
<Step>

### Run it [#run-it]

```bash title="Terminal"
bun quickstart.ts
# or: npx tsx quickstart.ts
```

Along with your own output, you'll see this line:

<div className="wrap-code">

```txt title="Output"
calsdk warning (mock, getFreeBusy): freeBusy uses a fallback: Provider "mock" has no free/busy endpoint, so busy times were computed from events.
```

</div>

That's a [warning](/docs/concepts/warnings), not an error. The mock has no free/busy endpoint, so calsdk worked out busy times from its events and tells you it did. calsdk does the same for Outlook, so expect a similar line there.

</Step>
</Steps>

## Use a real calendar [#use-a-real-calendar]

When the logic does what you want, install a provider and swap it in for the mock. Only the line that creates `calendar` changes; every call after it stays the same.

```ts tab="Google"
// npm install @cal-sdk/google
import { google } from '@cal-sdk/google';

const calendar = google({ accessToken: () => getFreshAccessToken() });
```

```ts tab="Outlook"
// npm install @cal-sdk/microsoft
import { microsoft } from '@cal-sdk/microsoft';

const calendar = microsoft({ accessToken: () => getFreshAccessToken() });
```

```ts tab="iCloud"
// npm install @cal-sdk/caldav
import { icloud } from '@cal-sdk/caldav';

const calendar = icloud({
  appleId: 'me@icloud.com',
  appPassword: process.env.ICLOUD_APP_PASSWORD!,
});
```

```ts tab="Zoom"
// npm install @cal-sdk/zoom
import { zoom } from '@cal-sdk/zoom';

const calendar = zoom({ accessToken: () => getFreshAccessToken() });
```

Each provider page, linked from [Choose a provider](/docs/choose-a-provider), says which token or password it needs and where it behaves differently.

<Callout type="warn" title="Use a test calendar">
  Writes go to the real account. Point `calendarId` at a calendar made for testing until your code does what you expect.
</Callout>
