Errors and limits

What a failed request looks like, how fast you may call, how to trace a request, and what happens when your webhook endpoint is down.

Error shapes

The status code says what went wrong. Bodies follow Cronofy’s conventions, so existing Cronofy clients read them unchanged.

Error responses
StatusBodyWhen
400{"error": "…"}OAuth endpoints only (/oauth/token, /oauth/token/revoke), including wrong client credentials.
401empty, with WWW-AuthenticateThe access token is missing, expired or revoked; or wrong client credentials on /v1/application_calendars.
403emptyWriting to a read-only calendar.
404emptyA calendar or channel the account does not have. Another customer’s id is “not found” too. (DELETE /v1/channels/… sends a small JSON body, as Cronofy does.)
422{"errors": {…}}The request is invalid; the body says which fields and why.
429empty, with Retry-AfterRate limited (below).
5xxemptyOur fault. Retry with backoff; if it persists, write to support@calmonkey.com with the request id.

401: refresh and retry once

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="calmonkey", error="invalid_token", error_description="The access token is missing, invalid, expired or revoked."
Calmonkey-Request-Id: req_4b0d7c2e9f1a46d38b5e0c7a2f9d1e63

A request without a token says error="invalid_request". On a 401, get a new access token with the refresh token and repeat the request once. If the refresh answers invalid_grant, the user revoked access or was disconnected: ask them to connect again.

422: field errors

GET /v1/free_busy without tzid, and with a bad from
{
  "errors": {
    "tzid": [
      { "key": "errors.required", "description": "required" }
    ],
    "from": [
      {
        "key": "errors.invalid",
        "description": "must be a date (YYYY-MM-DD) or an ISO 8601 time with an offset, e.g. 2026-10-10T01:00:00Z"
      }
    ]
  }
}

Each field maps to a list of { key, description }. Match on key; description is for people and may be reworded. Nested fields are written location.description, array items calendar_ids[0], problems with the whole body base.

Error keys
NameTypeDescription
errors.requiredkeyThe field is missing.
errors.invalidkeyThe value is not acceptable (a time, a time zone, end before start…).
errors.invalid_typekeyWrong type, such as a number where a string belongs.
errors.invalid_formatkeyWrong format.
errors.invalid_valuekeyNot one of the allowed values (e.g. transparency).
errors.too_small / errors.too_bigkeyToo short or too long, or too many items.
errors.invalid_jsonkeyThe body is not a JSON object (base).
errors.invalid_callback_urlkeyThe callback URL is not an https URL.
errors.invalid_callback_url_blockedkeyCalMonkey will not call that host: local, private or example addresses, or a port other than 443.
errors.too_many_channelskeyThe account already has 100 open channels.

Event writes have keys of their own for guests, repeating events and meeting links, such as errors.invalid_email, errors.attendees_unsupported, errors.not_an_occurrence and errors.conferencing_unavailable. They are listed in the API reference.

Applications in Cronofy compatibility mode get Cronofy’s wording instead: "tzid must be specified"-style descriptions, errors.tzid_unrecognized for a missing or unknown tzid, and errors.invalid_time_pair when an event ends before it starts.

OAuth errors

400 Bad Request
{ "error": "invalid_grant" }
OAuth error codes
NameTypeDescription
invalid_clienterrorUnknown client id or wrong client secret (the two are not told apart).
invalid_granterrorThe code is used, expired or was issued for another redirect URI; or the refresh token is revoked or belongs to another application.
invalid_requesterrorA required field is missing or the body cannot be read; error_description says which.
unsupported_grant_typeerrorOnly authorization_code and refresh_token are supported.

On /oauth/authorize errors go back to your redirect URI as error= (access_denied, unsupported_response_type, invalid_scope), except an unknown client id or redirect URI, which shows an error page.

Rate limits

Two token buckets apply to every authenticated request. A request needs room in both.

Rate limits
BucketBurstSustained
Per application600 requests100 a second
Per authorised account (access token)150 requests25 a second

Responses carry the state of the tighter bucket:

RateLimit-Limit: 150
RateLimit-Remaining: 149
RateLimit-Reset: 1

RateLimit-Reset is the number of seconds until the bucket is full again. Over the limit the answer is 429 with an empty body and Retry-After (seconds): wait that long, then retry.

Request ids and the request log

Every response has a Calmonkey-Request-Id header. Send your own in the request’s Calmonkey-Request-Id header (8 to 64 letters, digits, dots, dashes or underscores) and it comes back unchanged, which ties our logs to yours.

Authenticated API requests are kept in your request log in the dashboard for 30 days (7 days on the Developer plan): method, path, status, duration and the bodies, with tokens, credentials and event text replaced by [redacted].

Webhook delivery and retries

A notification counts as delivered when your callback URL answers with a 2xx status within 5 seconds. Redirects are not followed and count as failures. Anything else is retried:

Webhook retry schedule
AttemptWhen
1Straight away (a change waits about a second, so changes close together share one notification)
215 seconds after the first failure
31 minute later
45 minutes later
515 minutes later
61 hour later
72 hours later
84 hours later
96 hours later
1010 hours later, the last attempt (about 23 hours after the first)
  • Each wait has up to 10% added at random, so a recovering endpoint is not hit by every waiting notification in the same second.
  • After the tenth failed attempt, or 48 hours after the notification was created, the delivery is marked failed.
  • 410 Gone closes the channel: no more notifications are sent to it.
  • Every attempt of one notification has the same Calmonkey-Delivery-Id, and Calmonkey-Delivery-Attempt counts up. Delivery is at least once: ignore an id you have already handled.
  • Deliveries and their attempts are listed in the dashboard for 30 days.