Calemander is an OAuth 2.1 authorization server. A token is bound to one workspace, chosen by the person who authorises it, and acts as that person with their permissions. Back to the portal.
The authorization server issuer is https://auth.calemander.com/api/auth. Its RFC 8414
metadata document is authoritative; the URLs below are what it currently contains.
Each workspace publishes its own RFC 9728 protected-resource metadata, which names this authorization server:
https://<workspace>.calemander.com/.well-known/oauth-protected-resource/mcp https://<workspace>.calemander.com/.well-known/oauth-protected-resource/api/v1
Send the matching resource identifier as the RFC 8707 resource parameter. Identifiers are compared as exact strings, and a token issued for one is refused on
the other.
A token issued for one identifier is refused on the other, and /mcp refuses a token that carries no resource at all. The REST API still accepts one.
There is no /.well-known/openid-configuration. Access tokens
are opaque and there is no ID token, so there is no OpenID Provider to describe.
https only, except loopback: http://127.0.0.1/… and http://localhost/… are accepted for native apps.localhost or 127.0.0.1: we store both spellings, and an incoming localhost redirect is normalised to 127.0.0.1 so the port rule applies to
it too.Scopes are resource:action strings with no implication ladder: bookings:write does not grant bookings:read. Ask for both if you need both. What a connection
actually grants is bounded by the connecting person's role, so a member grants less than an
admin; the consent screen shows them the difference.
bookings:read — List your bookings, including each attendee’s name, email and notes. bookings:write — Create bookings on your event types, change their status (confirm, cancel), and mark no-shows. event_types:read — List your event types and their settings. event_types:write — Create event types and edit their name, slug, duration, seats, location and check-in settings. availability:read — Read open slots for your event types by date. contacts:read — List your contacts: names, emails, phone numbers, companies and booking counts. scheduling_links:read — List your single-use scheduling links. scheduling_links:write — Create single-use scheduling links for your event types. webhooks:read — List your webhook subscriptions and their URLs. webhooks:manage — Create and delete webhook subscriptions for your own bookings. — Organization-wide subscriptions (every host’s bookings) can only be created by a workspace admin.check_in:read — Read today’s group sessions and who has checked in. check_in:write — Check attendees in, add walk-ins, and undo a check-in. check_in:manage — Mark no-shows and close or reopen a session. theme:read — Read your booking page’s colors, font and layout settings. theme:write — Change your booking page’s colors, font, background and button shape, and read a website you name to propose them. Cannot write custom CSS. requirements:read — Read what a booking is still waiting on, and the requirements set on an event type. requirements:write — Clear, waive, verify or send back a booking’s requirements, and set which requirements an event type asks for. A booking confirms itself when its last requirement clears. offline_access is not a permission: it asks for a refresh token.
Refresh tokens rotate, and replaying one outside a short retry window revokes the whole family.
Two limits apply. A per-credential limit of 60 requests a minute bounds one token; the
per-application limits below bound your whole application across every workspace that
connected it. Both answer with 429 and a Retry-After header; the per-application one names which budget it
was in the JSON body.
| Category | Requests / min | Writes / day | Consent | Approval |
|---|---|---|---|---|
| first_party | no limit | no limit | remembered | required |
| verified_partner | 600 | no limit | remembered | required |
| third_party | 60 | 200 | every time | required |
| cimd | 60 | 200 | every time | domain allowlist |
| dynamic | 60 | 200 | every time | required |
Everything registered through this portal starts as third_party. Only writes count against the daily budget; reads do not.
https://<workspace>.calemander.com/mcp is a hosted
Model Context Protocol server: Streamable HTTP, stateless, POST only (GET and DELETE answer
405).
It is read-only, and each workspace has to switch it on. A workspace admin turns it on under Settings → Connected apps; until they do, the endpoint answers 404 with a JSON body saying so. No tool here creates, changes or cancels a booking, and none of them moves money.
Twelve tools are served:
whoami list_event_types find_event_type list_bookings get_availability get_booking_responses list_booking_requirements list_one_off_links list_webhooks get_webhook get_theme list_unpaid_claims
list_contacts is not served, and contacts:read is not a scope the endpoint asks for. An assistant
does not get the customer list.
A result that can carry text somebody outside the workspace typed — an attendee name, an
answer to a booking question, a note on a requirement — arrives with an untrusted array of JSON paths naming exactly those values, and
the server's instructions tell the assistant they are data and
not instructions.
Claude.ai: Settings → Connectors → Add custom connector, paste the URL, sign in and approve the permissions. ChatGPT: developer mode, add the same URL. Claude Code:
claude mcp add --transport http calemander https://<workspace>.calemander.com/mcp
Cursor and VS Code need a pre-registered public client_id, because our Dynamic Client Registration requires a
platform-minted initial access token that those clients cannot obtain. There is no published
public client id yet; this page will carry it when there is.
60 requests per minute per token at /mcp, and a separate 60 per
minute for the /api/v1 calls the tools make — so sixty tool
calls a minute, not thirty. A 429 carries Retry-After.
Every tool call is recorded in the workspace: which tool, whether it worked, which app, when. Arguments and results are not recorded. A workspace admin reads the last fifty on the Connected apps page.
An MCP client may identify itself with a Client ID Metadata Document instead of registering:
its client_id is the HTTPS URL of that document. Two conditions,
both hard:
client_name and redirect_uris mandatory.CIMD clients are not reviewed by us, and the consent screen says so in those words. They see the full consent screen on every connection.
POST https://auth.calemander.com/api/auth/oauth2/register accepts an RFC 7591 registration when
it carries an initial access token issued by Calemander (Authorization: Bearer cma_iat_…). Tokens are single-use and expire after seven days. Ask us for one; registering still
leaves the client pending approval.
Ask for access to the partner-review workspace rather than
testing against a live one. It exists for integration review, and it is what we use to
verify a partner integration ourselves. See docs/REVIEW-TENANT.md in the repository for what it contains and
how to request it.
S256 is required. No other method is supported.authorization_code and refresh_token. There is no client_credentials: a token with no user has no workspace,
and every token here is refused without one. Use a workspace API key for server-to-server
work.Authorization header. Never in a URL.