Troubleshooting

Find the symptom, check the cause, apply the fix. If none of these match, email hello@devsforfun.com with the request URL, the status code, the error message and roughly when it happened. Never include the full key.

Keys and requests

401 Missing API key, but I am sending one

The request went to https://clndr.pro, which redirects to https://www.clndr.pro. HTTP clients drop the Authorization header when a redirect changes host, so the key never arrives.

Use https://www.clndr.pro/api/v1 everywhere. If it's the SDK, upgrade: @clndr-pro/sdk before 0.1.5 and @clndr-pro/react before 0.2.1 default to the bare domain. If you can't upgrade yet, set the base URL yourself:

new Clndr({ apiKey: process.env.CLNDR_SECRET_KEY!, baseUrl: 'https://www.clndr.pro' });
<ClndrProvider publishableKey={key} baseUrl="https://www.clndr.pro">

The other cause is a hand-written fetch with an empty key: process.env.X was undefined (see the next entry), so the header said Bearer undefined, or wasn't set. The SDK doesn't get that far: new Clndr({ apiKey: undefined }) throws Clndr: `apiKey` is required, new Clndr(undefined) throws a TypeError, and ClndrProvider without a key throws ClndrProvider: either `publishableKey` or `client` is required.

401 Invalid API key

clndr.pro has no key with that value. Check, in order: the env var holds the whole key (they're 73 characters: clndr_sk_ or clndr_pk_ plus 64 hex characters); the deployment you're testing has the variable (Vercel keeps Production, Preview and Development separately); the key wasn't deleted in the dashboard.

In Next.js, a secret key read inside a 'use client' file is always undefined (with the SDK that throws when the client is built, before any request), and a NEXT_PUBLIC_ variable added after the last build isn't in the bundle until you rebuild.

401 API key has been revoked

Someone revoked it under API keys. Create a new one and deploy it. Revoked keys can't be switched back on.

403 API key is missing required scope(s): bookings:read

A publishable key called an endpoint only secret keys can use: listing, reading, confirming or cancelling bookings. Move that call to your server and use the secret key there. Book through a server action and Approve bookings in your admin show the shape.

403 This booking page does not belong to the API key owner

The bookingPageId you sent belongs to a different clndr.pro account than the key. It happens when you copy a page id from one account and a key from another, or mix up staging and production keys. Fetch the page with the same key (GET /booking-pages/{slug}) and use its bookingPage.id.

404 Booking page not found

Either the slug is wrong (it's the last part of the page's URL, lowercase, like intro-call) or the page is switched off. List booking pages shows every page on the account with its slug and is_active.

429 Rate limit exceeded

The key used up its window: 300 requests a minute for a secret key, 120 for a publishable one, shared by everyone using that key. Wait Retry-After seconds. If a page fires a slots request on every render, fix the effect's dependencies; if traffic is genuinely high, fetch slots on your server and cache them briefly. See Errors and rate limits.

CORS error in the browser console

The API allows every origin, so a CORS error almost always hides something else. The usual one: the request went to clndr.pro and the browser refused to follow the redirect on a preflight. Use https://www.clndr.pro. If the URL is right, open the Network tab: a failed request that isn't from clndr.pro (an ad blocker, a proxy) shows up there.

Slots and dates

The slot list is empty

Go through these in order:

  1. The host has no availability that weekday. Check Availability, and check which Hours the booking page uses.
  2. The day is past the page's Bookable up to limit.
  3. The day is in the past, or it's today and every slot has started.
  4. Bookings and calendar events fill the day. Synced Google Calendar events block slots like bookings do.
  5. You asked for the wrong day. date is a day in the host's timezone, as YYYY-MM-DD.
Slots are a day off, or show at odd hours

Two separate things cause this.

clndr.pro returns UTC times. Showing them without converting (slicing "2026-10-15T08:00:00.000Z" to 08:00) puts every slot at its UTC time. Format them with toLocaleTimeString() or Intl.DateTimeFormat, which use the guest's timezone.

Separately, date means the host's day. A guest far from the host can see some of those slots fall on their previous or next day. That's correct; group slots by the guest's local date if you show a calendar. How booking works has a worked example.

Older SDKs add a third cause: before @clndr-pro/sdk 0.1.5, getSlots(slug, date) sent a Date as a full UTC timestamp, which can land on the neighbouring day. Upgrade, or pass the day as a YYYY-MM-DD string.

A booking failed with 409

This time slot is no longer available means someone booked an overlapping time between your slots request and the guest's submit, or the guest double-submitted. This time slot is not available (publishable keys only, and what a publishable key usually gets for a taken slot too) means the startTime/endTime you sent isn't an open slot right now: it's in the past, beyond max_days_ahead, outside the host's hours, a different length than the page's duration, or no longer free. Send a slot's start and end exactly as the slots endpoint returned them.

Either way, fetch the slots again and let the guest pick. If the error came after a timed-out first attempt, the first attempt may have worked: see Retrying.

React and Next.js

createContext only works in Client Components

A clndr.pro component or hook was imported into a Server Component. Import @clndr-pro/react only in files that start with 'use client', and keep ClndrProvider in its own client file rendered from your layout (see the quickstart). Versions before 0.2.1 also left the 'use client' marker out of the published bundle, so upgrade.

require('@clndr-pro/sdk') fails with Cannot find module

Versions before @clndr-pro/sdk 0.1.5 and @clndr-pro/react 0.2.1 pointed their CommonJS entry at a file that wasn't published. ES module imports (import { Clndr } from '@clndr-pro/sdk') always worked. Upgrade, or switch the file to import.

useClndr must be used inside ClndrProvider

The component isn't under the provider. Render <Providers> around {children} in app/layout.tsx, or wrap just the booking section in ClndrProvider.

The date picker and slot buttons are unstyled

That's the default: the components ship plain HTML so they don't fight your CSS. Pass your own components and class names, as in Styling and slots.

Embeds

Clndr is not defined

Your code ran before embed.js loaded. Call Clndr from the script's onload (or onReady with next/script), as in Embed script.

The embed never resizes or reports bookings, or stays blank

Load the script and the iframe from https://www.clndr.pro. From the bare clndr.pro the frame ends up on another origin, so its messages are ignored: no resizing, no callbacks. A blank frame on a team page means the guest isn't signed in to clndr.pro inside the frame, which browsers block for third-party iframes; team pages can't be embedded.

The iframe doesn't resize

Only the embed script, or your own message listener, resizes the frame. Check the listener compares event.origin with https://www.clndr.pro exactly, with the www. See Sizing.

A private page says Booking page not found

Private pages need their access code: config.accessCode in the script, ?c= on the iframe URL. The code must belong to that page and not have expired.

Emails and calendar

The guest didn't get a confirmation email

On an approval page that's expected: the guest gets an email when the host confirms in the dashboard, not when they book. Confirming through the API sends no clndr.pro email (Google sends an invite if the page has Meet links). On a direct page, check the address on the booking under Meetings, then the guest's spam folder.

The booking has no Meet link

Meet links need Add a Google Meet link on the booking page and the host's Google Calendar connected. For approval pages the link appears once the booking is confirmed. If it's on and still missing, read the booking's sync_status and sync_failure_reason: Google Calendar is not connected means the host needs to reconnect Google in the dashboard. Those two fields aren't updated when you confirm through the API, so for a booking you approved with PATCH, an empty google_calendar_event_id is the sign that no event was created.