{"openapi":"3.1.0","info":{"title":"WhenMeet.me API","version":"1.0.0","description":"Cross-provider group scheduling: rank meeting times when everyone is free (Google Calendar + Microsoft Outlook live data, hand-marked availability for everyone else), create calendar events with Meet/Teams links, and negotiate alternate times. An MCP server wrapping this API is served at POST /mcp (Streamable HTTP).","x-last-updated":"2026-06-12"},"servers":[{"url":"https://whenmeet.me"}],"components":{"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","description":"Personal access token (format mn_…). Create one with POST /api/tokens while signed in via OAuth in a browser. Tokens act with the full authority of the user."},"sessionCookie":{"type":"apiKey","in":"cookie","name":"__Host-session","description":"Browser session minted by the Google/Microsoft OAuth flow."}},"schemas":{"Interval":{"type":"object","properties":{"start":{"type":"integer"},"end":{"type":"integer"}},"required":["start","end"],"description":"Half-open interval in unix milliseconds."},"RankedSlot":{"type":"object","properties":{"start":{"type":"integer"},"end":{"type":"integer"},"freeCount":{"type":"integer"},"score":{"type":"number"}}}}},"paths":{"/api/availability":{"post":{"summary":"Rank meeting times when all participants are free","description":"Merges live free/busy per participant: their own connected calendar (source \"self\"), hand-marked slots (source \"manual\"), 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.","security":[{"bearerToken":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"type":"array","items":{"type":"string","format":"email"}},"from":{"type":"integer"},"to":{"type":"integer"},"durationMin":{"type":"integer","enum":[15,30,45,60]},"tz":{"type":"string","description":"IANA zone for business-hours filtering."}},"required":["participants","from","to","durationMin"]}}}},"responses":{"200":{"description":"availability[] (busy intervals per participant), slots[] (ranked everyone-free), participantSources[], source: \"real\", cached."}}}},"/api/meetings":{"post":{"summary":"Create a meeting (calendar event + Meet/Teams link + email invites)","security":[{"bearerToken":[]},{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"type":"array","items":{"type":"string","format":"email"}},"startsAt":{"type":"integer"},"endsAt":{"type":"integer"},"title":{"type":"string"},"conference":{"type":"string","enum":["meet","teams"],"nullable":true},"tz":{"type":"string"}},"required":["participants","startsAt","endsAt"]}}}},"responses":{"200":{"description":"id, shareUrl (/m/<id>), conferenceUrl, eventLink, provider, invites (per-recipient email delivery results)."},"400":{"description":"no_matching_connection — host lacks the provider needed for the requested conference type."}}}},"/api/meetings/{id}":{"get":{"summary":"Read a meeting by share-link id (no auth — the UUID is the capability)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Meeting details + suggestions[] (proposed alternate times with status)."},"404":{"description":"Unknown id."}}}},"/api/meetings/{id}/availability":{"post":{"summary":"Aggregated group availability for a meeting (symmetric heat-map)","description":"Returns per-30-min-slot counts of how many participants are free plus ranked everyone-free slots. Individual busy blocks are never exposed to link holders.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"integer"},"to":{"type":"integer"},"durationMin":{"type":"integer","enum":[15,30,45,60]},"tz":{"type":"string"}},"required":["from","to"]}}}},"responses":{"200":{"description":"total, cells[{start, free}], slots[], participantSources[]."},"409":{"description":"host_has_no_connection."}}}},"/api/meetings/{id}/suggest":{"post":{"summary":"Propose a different time (guest, no auth)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"suggestedBy":{"type":"string","format":"email"},"startsAt":{"type":"integer"},"endsAt":{"type":"integer"},"message":{"type":"string"}},"required":["suggestedBy","startsAt","endsAt"]}}}},"responses":{"200":{"description":"ok + suggestion id; the host is emailed."}}}},"/api/meetings/{id}/suggestions/{sid}":{"post":{"summary":"Host accepts or declines a proposed time","description":"Accept moves the meeting: D1 row, the provider calendar event, and a reschedule email with a SEQUENCE-bumped ICS to all participants.","security":[{"bearerToken":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"sid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["accept","decline"]}},"required":["action"]}}}},"responses":{"200":{"description":"status accepted/declined; on accept also startsAt/endsAt/eventUpdated."},"403":{"description":"Caller is not the host."},"409":{"description":"Suggestion already resolved."}}}},"/api/manual-availability":{"get":{"summary":"Read hand-marked free slots for an email (no auth)","parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","format":"email"}},{"name":"from","in":"query","required":true,"schema":{"type":"integer"}},{"name":"to","in":"query","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"email + freeSlots[]."}}},"post":{"summary":"Replace hand-marked free slots inside a window (no auth)","description":"The no-calendar fallback (Apple iCloud, privacy). Publishing free times only ever adds information the person chose to share; the heat-map labels it source \"manual\". Rate-limited per email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"windowFrom":{"type":"integer"},"windowTo":{"type":"integer"},"freeSlots":{"type":"array","items":{"$ref":"#/components/schemas/Interval"}}},"required":["email","windowFrom","windowTo","freeSlots"]}}}},"responses":{"200":{"description":"ok + merged slot count."},"429":{"description":"rate_limited."}}}},"/api/tokens":{"post":{"summary":"Create a personal access token (session-only)","description":"Requires a browser session — a token cannot mint more tokens. The plaintext token is returned exactly once.","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"{ id, name, token } — store the token now."}}},"get":{"summary":"List token metadata (never the secrets)","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"tokens[{id, name, createdAt, lastUsedAt}]."}}}},"/api/tokens/{id}":{"delete":{"summary":"Revoke a token","security":[{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok."},"404":{"description":"Unknown id."}}}},"/mcp":{"post":{"summary":"MCP server (Model Context Protocol, Streamable HTTP)","description":"JSON-RPC 2.0: initialize, tools/list, tools/call. Tools: find_common_slots, create_meeting, get_meeting, get_meeting_availability, suggest_time, respond_to_suggestion, set_manual_availability, get_manual_availability. Authenticated tools take the same Bearer mn_… token. Point any MCP client at this URL.","security":[{"bearerToken":[]},{}],"responses":{"200":{"description":"JSON-RPC response."}}}}}}