Skip to main content
When a request fails, the Cal ID API returns a JSON body with a machine-readable error.code and a human-readable message. Branch your error handling on error.code rather than on the HTTP status alone — the same status can carry different codes, and the codes tell you whether the fix is in your request or in your data.

The codes

Six distinct error.code values were observed in testing.
Do not build a retry-with-backoff policy around the 5xx class. On this API, 500 is usually a deterministic rejection of your request. The only status worth retrying automatically is 429 — see Rate limits.

The three validation codes carry different details

FST_ERR_VALIDATION, BAD_REQUEST, and VALIDATION_ERROR all mean “your request is wrong”, but they are not interchangeable in code. Each attaches a different payload, so a single parser written against one of them will break on the other two.
Write your error formatter to check for error.details before reading it, and to fall back to error.message and then the top-level message. That covers all three codes without special-casing any of them.
VALIDATION_ERROR was observed on a 422. Whether it can also accompany a 400 on other endpoints has not been established — match on error.code, not on the status code, so your handling is correct either way.

no_available_users_found_error

This is not an error.code. It is a domain message that arrives in the top-level message field alongside error.code: "INTERNAL_ERROR" and a 500 status.
It means the booking you asked for is not permitted by the scheduling rules — practically always because start was not a time that GET /slots/ returned. Availability is the product of schedules, buffers, minimum notice, booking limits, connected-calendar busy times, and timezone rules; a time that looks free on a calendar is frequently not bookable.
This is the most common 500 on the API, and it is entirely deterministic. Retrying with the same start will fail every time. Fetch fresh slots from GET /slots/, pick a time it actually offered, and send that. See the Quickstart for the full sequence.
POST /slots/reserve does not protect you from this. Reserving a time that GET /slots/ never offered returns success; the failure surfaces later, at POST /booking/.

Two error shapes that are not the envelope

Not every failure comes back in the { success, message, error } envelope. Two do not, and both will break a client that assumes it.

Unknown route or wrong HTTP method

Mistype a path, or call a real path with the wrong verb, and the response comes from the router rather than the API. There is no success field, and error is a plain string instead of an object.
Code written as if (!body.success) treats this as a success, and code written as body.error.code throws a type error on a string. Either way you lose the real diagnosis, which is simply that the URL is wrong. Check the HTTP status code before you read the body.

Rate limiting

A 429 returns its own shape, with a retryAfter in seconds and no error.code.
Unlike the codes above, this one is transient. Wait retryAfter seconds, then back off exponentially. See Rate limits.

Handling errors in practice

1

Branch on the HTTP status first

It is the only field guaranteed to be present. It tells you immediately whether you are in envelope territory (400, 401, 422, 500 from the API) or not (404 from the router, 429 from the limiter).
2

Then read error.code

Use it to decide whose problem this is: a 4xx code means fix the request; INTERNAL_ERROR means read message before assuming anything.
3

Surface message, not code, to humans

The message field names the offending field or the domain rule. FST_ERR_VALIDATION on its own tells a user nothing.
4

Retry only 429

Everything else in this list is deterministic. A retry loop on 500 will burn your rate-limit allowance producing the identical failure.
These six codes are the ones observed in live testing. They are not guaranteed to be the complete set — always keep a default branch that logs the raw body for any error.code you do not recognise.

Debugging resources

Quickstart

From a fresh API key to a confirmed booking in five requests.

API integration

Envelopes, pagination, and the behaviours that cause most first-build bugs.

Rate limits

The headers, the 429 body, and how to back off cleanly.

API Reference

Every endpoint, with schemas and a live “Try it” playground.