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.
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
Behaviour
Compatibility on
Off (CalMonkey)
All-day event written without transparency
transparent (free)
opaque (busy)
free_busy periods
event_uid only with include_ids=true
always 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_calendars
no account_id; linking_profile.profile_name null; the same token pair again while it is valid
account_id; the calendar’s name; a new pair on every call
Refreshing a token
the 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 calendar
403, plain text
200
GET /v1/userinfo
the type and data under the key names existing integrations read; an application calendar has application_calendar_id and no name or zoneinfo
calmonkey.type / calmonkey.data
Creating a channel
no signing_secret in the response
signing_secret, once
Webhook headers
a second HMAC header as well, under the name existing integrations verify
the first occurrence carries event_id and recurrence; later ones carry series_identifier and series_master, no event_id, and options.update false
every occurrence carries event_id, series_identifier and series_master; the first also recurrence
A timed recurring event written without tzid
accepted; it repeats at the same UTC time
422 on tzid: a series repeats in local time and needs its zone
recurrence with an empty rules list
accepted: the event stops repeating
422; send recurrence: null
attendees on a calendar that sends no invitations
errors.event.cannot_set_attendees
errors.attendees_unsupported
conferencing.profile_id integrated on a calendar without a link of its own
errors.integrated_conferencing_not_available
errors.conferencing_unavailable
An unknown conferencing.profile_id
errors.not_recognized
errors.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
Create an application in the dashboard. Copy the client id and the client secret (shown once).
Register your redirect URIs, exactly as your code sends them, and turn on compatibility mode.
Point your client at CalMonkey. If it reads the hosts from settings, that is four values (under the names your code already reads):
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.
Check webhook verification with the new client secret, and that your callback URL is https on port 443.
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.
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.