---
title: "Rank meeting times for the participants (per-participant timezones)"
method: POST
path: "/api/availability"
---

# Rank meeting times for the participants (per-participant timezones)

`POST /api/availability`

## Request

- Base URL: `https://whenmeet.me`
- URL: `https://whenmeet.me/api/availability`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `__Host-session`

Reading another user’s connected calendar is logged for that user (GET /api/me/availability-lookups) and limited per viewer (429 lookup_limit); an owner can block a viewer (POST /api/me/availability-blocks). Merges live free/busy per participant: their own connected calendar (source "self"), hand-marked slots (source "manual": the owner-proven ones, plus those marked through the caller’s own ask-links), or the host’s view (source "host"). Calls without a connected host calendar (anonymous, or signed in but not connected) fall back to hand-marked slots only — participants with none are returned as source "host" (no data, may look free). Never fabricated. Ranking is per-participant timezone-aware (each filtered/scored in their own zone), scores buffer time around existing meetings and prefers sooner slots, and — when there are too few everyone-free slots — appends compromise slots (allFree:false). At most the 16 most-common participant timezones are evaluated per request (rarer zones fall back to the requester tz) to bound worst-case CPU.

## Request body

- object
  - `participants` string[], required: Up to 50 distinct emails (trimmed, lower-cased, deduped).
  - `from` integer, required: Window start, unix ms.
  - `to` integer, required: Window end, unix ms: after from, at most 366 days after it and at most 60 days from now.
  - `durationMin` 15 | 30 | 45 | 60: Optional; anything else falls back to 30.
  - `tz` string: Requester IANA zone: fallback for participants whose own zone is unknown, plus the round-hour scoring bonus.
  - `turnstileToken` string: Cloudflare Turnstile token. Only needed after a 403 turnstile_required (browser sessions past 15 new foreign addresses in the rolling hour); a valid token exempts the session for one hour. PATs and MCP never need it.

## Response `200`

availability[] (busy intervals per participant), slots[] (ranked; allFree:true everyone-free first, then allFree:false compromise slots; capped to the first 90 days of the window), participantSources[] (per-participant tz only for authenticated callers; calendarStatus "unavailable" when a connected calendar could not be read — a token that needs re-auth, a provider error, or an owner who blocked you — and that participant is then treated as busy for the whole window, never as free), source: "real", cached.

## Other responses

- `400`: bad_request | invalid_participants (none, more than 50, or an invalid email) | bad_window (to ≤ from, longer than 366 days, or ending more than 60 days from now).
- `401`: invalid_token (a Bearer token that is malformed, unknown or revoked).
- `403`: turnstile_required — browser sessions only (never PATs or MCP): the request adds new foreign addresses past 15 in the rolling hour; resend with turnstileToken. Repeat-only requests are never challenged; nothing is written on refusal.
- `429`: rate_limited — anonymous callers: more than 60 requests per IP in 10 minutes. lookup_limit — you read more than 30 other people’s connected calendars in the last hour or 50 in the last 24 h (only new addresses count; repeats are free; nothing is counted on refusal).

---

[API](https://skmtc.dev/whenmeet/apis/whenmeet-me-api.md) · [All operations](https://skmtc.dev/whenmeet/apis/whenmeet-me-api/llms.txt) · [OpenAPI document](https://skmtc.dev/whenmeet/apis/whenmeet-me-api/revisions/202b80022215?raw)
