Compatibility and migrating

CalMonkey’s v1 API uses the paths, parameter names, response shapes, status codes and error shapes that many existing calendar integrations already call, for connecting calendars, free/busy, events with attendees, recurring events and conferencing, and push notifications. Such an integration usually switches with a change of hostnames and credentials, and its users reconnecting.

What is compatible

These endpoints have the paths, parameters, response fields, status codes and error shapes that many existing calendar integrations already call. A client that uses only them switches by changing hostnames and credentials.

  • GET /oauth/authorize
  • POST /oauth/token
  • POST /oauth/token/revoke
  • POST /v1/application_calendars
  • GET /v1/account
  • GET /v1/userinfo
  • GET /v1/profiles
  • GET /v1/calendars
  • GET /v1/free_busy
  • GET /v1/events
  • POST /v1/calendars/{id}/events
  • DELETE /v1/calendars/{id}/events
  • POST /v1/calendars/{id}/events/occurrences
  • POST /v1/channels
  • GET /v1/channels
  • DELETE /v1/channels/{id}
  • POST /v1/link_tokens

Provider names are google, office365, live_connect and apple. Every response also carries a Calmonkey-Request-Id header, and authenticated responses the RateLimit-* headers.

Attendees, recurring events and conferencing

Event writes and reads take these fields and return these shapes:

  • attendees: { invite: [{ email, display_name }], remove: [{ email }] } on writes; attendees[] with email, display_name and status, organizer and participation_status on reads. A wrong address is errors.invalid_email on attendees.invite[0].email.
  • recurrence: { rules: [{ frequency, interval, count | until, by_day: [{ day, nth_of_period }] }] }, with one rule, a date-only until and these keys for what a rule cannot say: errors.not_supported, errors.not_recognized, errors.must_be_date, errors.must_be_integer_greater_than_zero, errors.not_supported_count_until_combination, errors.max_recurrence_rules_exceeded, errors.invalid_format. Leaving recurrence out keeps the series and null removes it. exceptions.add skips dates.
  • conferencing: { profile_id } with default, integrated and none; reads show { pending: true }, then the join_url.

Outside the compatible set

These have no counterpart on this API: availability-search and scheduling products, conferencing.profile_id: "explicit" with a join URL of your own, changing a participation status through the API, on-premises Exchange, and per-profile revoke (POST /v1/profiles/{id}/revoke). Revoke the whole grant with /oauth/token/revoke instead.

Compatibility mode

Where CalMonkey’s own behaviour differs from what existing integrations expect, an application can ask for the behaviour they expect. Compatibility mode is a setting of each application, off by default. Turn it on for the application an existing client will use; leave it off for new integrations. You switch it in the application’s page in the dashboard.

Compatibility mode on and off
BehaviourCompatibility onOff (CalMonkey)
All-day event written without transparencytransparent (free)opaque (busy)
free_busy periodsevent_uid only with include_ids=truealways event_uid
422 wording“<field> must be specified”; errors.tzid_unrecognized “TZID not recognized”; errors.invalid_time_pair “end must be after start”errors.required “required”; errors.invalid
POST /v1/application_calendarsno account_id; linking_profile.profile_name null; the same token pair again while it is validaccount_id; the calendar’s name; a new pair on every call
Refreshing a tokenthe same access token while it has at least 10 minutes left (expires_in = what is left)a new access token
GET /v1/account for an application calendar403, plain text200
GET /v1/userinfothe type and data under the key names existing integrations read; an application calendar has application_calendar_id and no name or zoneinfocalmonkey.type / calmonkey.data
Creating a channelno signing_secret in the responsesigning_secret, once
Webhook headersa second HMAC header as well, under the name existing integrations verifyCalmonkey-* headers only
Application calendars’ provider_name / provider_servicethe name existing integrations expectcalmonkey
Reading a recurring event your application wrotethe first occurrence carries event_id and recurrence; later ones carry series_identifier and series_master, no event_id, and options.update falseevery occurrence carries event_id, series_identifier and series_master; the first also recurrence
A timed recurring event written without tzidaccepted; it repeats at the same UTC time422 on tzid: a series repeats in local time and needs its zone
recurrence with an empty rules listaccepted: the event stops repeating422; send recurrence: null
attendees on a calendar that sends no invitationserrors.event.cannot_set_attendeeserrors.attendees_unsupported
conferencing.profile_id integrated on a calendar without a link of its ownerrors.integrated_conferencing_not_availableerrors.conferencing_unavailable
An unknown conferencing.profile_iderrors.not_recognizederrors.invalid_value

Webhook signatures: in compatibility mode a second header carries the same value as Calmonkey-HMAC-SHA256 (base64 HMAC-SHA256 of the body, keyed with your client secret), under the header name existing integrations verify, so your existing verification keeps working once it uses the new client secret.

Differences in every mode

  • Callback URLs must be https on port 443 and on a public host. A test-mode application may also register http://localhost… for webhooks on your own machine.
  • Channels have no scheduling_conversations key.
  • Profiles carry two added keys, which existing clients ignore: profile_calendars (each profile’s calendars, so one call is enough) and profile_relink_url (always null: a profile is reconnected through /oauth/authorize).
  • Events always carry event_private (false), extended_transparency (the same as transparency), organizer, and url when one was written. Calendars carry calendar_attachments_available (false). Profiles carry provider_service.
  • Attendees. notify_attendees (on writes and deletes) is CalMonkey’s own field. With notify_attendees: false an application calendar stores guests; taking guests off with remove alone is always accepted. A body with a mistake in it answers with the mistake first, and with the calendar’s refusal once the body is valid.
  • Recurring events. The event’s start must itself be an occurrence of the rule (a rule for Tuesdays needs a start on a Tuesday, in the event’s time zone). A monthly rule without by_day starts on the 1st to the 28th, a yearly one on any date but 29 February. count goes up to 730 and interval to 99. start and end are also accepted as { time, tzid } objects.
  • Single occurrences. recurrence.exceptions.change, DELETE with occurrence_date and POST /v1/calendars/{id}/events/occurrences are CalMonkey’s own, and every occurrence carries an added occurrence_date. Other keys under exceptions (exclude among them) are errors.not_supported.
  • Conferencing. A made link reads { provider_name, join_url }: provider_name is added. profile_id: "default" on a calendar without a link of its own (iCloud, application calendars) writes the event without one and reads show no conferencing key. profile_id: "explicit" is refused with 422 on conferencing.profile_id.
  • Wrong client credentials are 400 {"error":"invalid_client"} on /oauth/* and 401 with an empty body on POST /v1/application_calendars.
  • Webhooks are retried for about 23 hours (schedule); 410 Gone closes the channel.

Switching, step by step

  1. Create an application in the dashboard. Copy the client id and the client secret (shown once).
  2. Register your redirect URIs, exactly as your code sends them, and turn on compatibility mode.
  3. Point your client at CalMonkey. If it reads the hosts from settings, that is four values (under the names your code already reads):
    CALENDAR_CLIENT_ID=<your CalMonkey client id>
    CALENDAR_CLIENT_SECRET=<your CalMonkey client secret>
    CALENDAR_API_HOST=https://api.calmonkey.com
    CALENDAR_SITE_HOST=https://app.calmonkey.com
    
    The API host replaces your current API host; the app host replaces the host your code sends people to for /oauth/authorize. If your code checks that callback or API URLs end in your current provider’s domain, change that check to compare with the CalMonkey hosts exactly.
  4. Check webhook verification with the new client secret, and that your callback URL is https on port 443.
  5. Move your users. Tokens cannot be carried over from another service: each user connects their calendar again through CalMonkey. Before switching, for each connection: delete the events you wrote through the old service, close its channels and revoke its token there; then mark the connection as needing to reconnect, and show the user a short message (“Calendar sync was upgraded. Connect your calendar again.”). When they reconnect, write their upcoming events again: writes are idempotent by event_id.
  6. Test first with an application calendar, which needs no browser, then with one real account per provider you offer.

CalMonkey is hosted in Australia. There is no choice of data centre, so there is no data centre in the hostnames.