{"openapi":"3.1.0","info":{"title":"Tempi API","version":"1.0.0","description":"Scheduling API. Public operations let anyone, including AI assistants, look up open times and book a meeting. Account operations need an API key (`Authorization: Bearer bk_...`) from Settings → API keys in the web app. Times are ISO 8601 and returned in UTC. A `timezone` input sets how dates are read and how slots are grouped by day."},"servers":[{"url":"https://meettempi.com"}],"paths":{"/api/v1/public/{handle}":{"get":{"operationId":"get_public_profile","summary":"List a person's or team's event types","description":"Takes a handle, the first path segment of a booking link (https://host/acme/demo → \"acme\"), or the link itself. Returns the person or team and their bookable event types, with duration, price, and hosts with their time zones (`hostTimezones`, so you can say what time a slot is for them). A handle from before a rename also works. Next: call get_available_slots with one of the event type slugs.","tags":["public"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","description":"a handle, the first part of a booking link (\"acme\" in meettempi.com/acme/demo), or the whole link"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/{handle}/{slug}/slots":{"get":{"operationId":"get_available_slots","summary":"Find open times","description":"Returns the start times that can be booked, with the hosts' calendars, buffers and limits already applied. Pass the invitee's `timezone` and either `date` (YYYY-MM-DD in `timezone`) or `from` and `to` (ISO, at most 62 days apart). With neither, it covers the next 7 days. `days` groups the slots by date in `timezone`, and `times` gives each slot as a local time to show people. People can book up to `bookableUntil`. When the range has no open times, `next` is the next one. When `timezone` was an abbreviation that names more than one zone (CST, IST, BST), `timezoneNote` says which one was used: check it with the person. For new times for an existing booking, also pass its `bookingId` and `token`. Next: call create_booking (or reschedule_booking_as_invitee) with one of the `slots` values as `start`.","tags":["public"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","description":"a handle, the first part of a booking link (\"acme\" in meettempi.com/acme/demo), or the whole link"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"an event type slug from get_public_profile (\"demo\" in meettempi.com/acme/demo), or the whole link"}},{"name":"timezone","in":"query","required":true,"schema":{"type":"string","description":"the invitee's time zone, an IANA id like America/New_York or an abbreviation like EST: days, dates and times are in it"}},{"name":"date","in":"query","required":false,"schema":{"type":"string","format":"date","description":"one day to look at, as YYYY-MM-DD in `timezone`"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"start of the range, ISO with an offset"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"end of the range, ISO with an offset"}},{"name":"bookingId","in":"query","required":false,"schema":{"type":"string","description":"the booking id from create_booking, or its manageUrl"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","description":"the token from create_booking, or the manageUrl"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/{handle}/{slug}/series":{"get":{"operationId":"check_series","summary":"Check a repeating series before booking it","description":"For event types with `repeatMax` (from get_public_profile): lists each meeting of a series that starts at `start` (one of the slots) and repeats `every` 1, 2 or 4 weeks at the host's clock time, and whether each is free. Pass the invitee's `timezone` to get each meeting's `when` in it: where clocks change on other dates than the host's, some meetings move for the invitee, and `message` says which. Book it with create_booking and `repeat` when `bookable` is true. A series is booked whole or not at all, and round robin gives every meeting to the same host.","tags":["public"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","description":"a handle, the first part of a booking link (\"acme\" in meettempi.com/acme/demo), or the whole link"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"an event type slug from get_public_profile (\"demo\" in meettempi.com/acme/demo), or the whole link"}},{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"one of the `slots` values from get_available_slots, as given (ISO in UTC)"}},{"name":"every","in":"query","required":true,"schema":{"anyOf":[{"type":"number","enum":[1,2,4],"description":"weeks between meetings: 1, 2 or 4"},{"type":"string","description":"a string to be decoded into a number"}],"description":"weeks between meetings: 1, 2 or 4"}},{"name":"count","in":"query","required":true,"schema":{"anyOf":[{"type":"integer","description":"how many meetings, first included (2 up to the event type's repeatMax)","title":"between(2, 52)","minimum":2,"maximum":52},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many meetings, first included (2 up to the event type's repeatMax)"}},{"name":"timezone","in":"query","required":false,"schema":{"type":"string","description":"the invitee's time zone, an IANA id like America/New_York or an abbreviation like EST: each meeting's `when` is in it"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/{handle}/{slug}/check":{"get":{"operationId":"check_time","summary":"Check a time is still open before booking it","description":"Whether `start` (one of the slots) is still open. The booking page calls it while the invitee fills in the form: it reads the hosts' calendars again, so the booking itself needn't wait on them. Agents can call create_booking directly.","tags":["public"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","description":"a handle, the first part of a booking link (\"acme\" in meettempi.com/acme/demo), or the whole link"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"an event type slug from get_public_profile (\"demo\" in meettempi.com/acme/demo), or the whole link"}},{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"one of the `slots` values from get_available_slots, as given (ISO in UTC)"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/{handle}/{slug}/bookings":{"post":{"operationId":"create_booking","summary":"Book a meeting","description":"Books `start`, which must be one of the `slots` from get_available_slots. Free events are confirmed at once, unless the event type has `requiresApproval`: then it's a request (status=pending_approval) that holds the time until a host approves or declines it, and the invitee hears either way by email. Nobody answering by its start (`holdExpiresAt`) lets it lapse. Tell the human it isn't confirmed yet. Paid events return status=pending_payment and a `paymentUrl`. Give it to the human to pay before `holdExpiresAt`, or the slot is released. Once confirmed, the event is added to the host's calendar if they connected one, and the invitee gets a confirmation email with a calendar file (.ics). Keep `manageUrl` and `token`: they are the only way to view, reschedule or cancel later. 409 slot_unavailable says why the time can't be booked and lists the `nearest` open times. The result's `when` is the start in the invitee's time zone, to tell them.","tags":["public"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string","description":"a handle, the first part of a booking link (\"acme\" in meettempi.com/acme/demo), or the whole link"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"an event type slug from get_public_profile (\"demo\" in meettempi.com/acme/demo), or the whole link"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string","format":"date-time","description":"one of the `slots` values from get_available_slots, as given (ISO in UTC)"},"name":{"type":"string","description":"the invitee's full name"},"email":{"type":"string","format":"email","description":"the invitee's email address, which gets the confirmation and its calendar file"},"timezone":{"type":"string","description":"the invitee's time zone, an IANA id like America/New_York or an abbreviation like EST: their emails and reminders show times in it"},"notes":{"type":"string","description":"anything the invitee wants the host to know","title":"maxLength(2000)","minLength":0,"maxLength":2000},"phone":{"type":"string","description":"the invitee's phone number with its country code, like +1 415 555 0134: required when the event type is a phone call (whereKind phone on get_public_profile), where the host calls them","title":"maxLength(40)","minLength":0,"maxLength":40},"repeat":{"type":"object","required":["every","count"],"properties":{"every":{"type":"number","enum":[1,2,4],"description":"weeks between meetings: 1, 2 or 4"},"count":{"type":"integer","description":"how many meetings, first included (2 up to the event type's repeatMax)","title":"between(2, 52)","minimum":2,"maximum":52}},"additionalProperties":false,"description":"book a series instead: `count` meetings, `every` 1, 2 or 4 weeks at the host's clock time, when the event type's repeatMax allows. All or nothing: check_series first. The result's `series.meetings` give each start as `when` in the invitee's time zone, and `series.clockNote` says when that moves"},"cover":{"type":"string","description":"leave it out: the id of the painting the booking page showed, when the event type shows a different one each booking (cover \"hand\", \"light\" or \"still\"), so the booking keeps it. Anything else is ignored, and Tempi picks one","title":"maxLength(40)","maxLength":40}},"required":["start","name","email","timezone"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bookings/{id}":{"get":{"operationId":"get_booking","summary":"View a booking (invitee)","description":"Returns a booking by its id and token (from create_booking), or its manageUrl passed as both, with `changeableUntil`, `refundableUntil` and `canCancel`. For a series, `series` lists every meeting with its own manageUrl.","tags":["public"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"the booking id from create_booking, or its manageUrl"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","description":"the token from create_booking, or the manageUrl"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bookings/{id}/reschedule":{"post":{"operationId":"reschedule_booking_as_invitee","summary":"Reschedule a booking (invitee)","description":"Moves a confirmed booking to `start` with the same hosts. Get valid times from get_available_slots with this `bookingId` and `token`. The calendar event is updated and its provider notifies attendees. Payment carries over. Not possible after `changeableUntil` (409 change_too_late). A request (status=pending_approval) can’t be moved: cancel it and request another time.","tags":["public"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"the booking id from create_booking, or its manageUrl"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"the token from create_booking, or the manageUrl"},"start":{"type":"string","format":"date-time","description":"one of the `slots` values from get_available_slots, as given (ISO in UTC)"}},"required":["token","start"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/bookings/{id}/cancel":{"post":{"operationId":"cancel_booking_as_invitee","summary":"Cancel a booking (invitee)","description":"Cancels a booking by its id and token. The event is removed from the host's calendar. Paid bookings cancelled before `refundableUntil` are refunded in full. Later cancellations get no refund. The event type sets the rules, which get_booking shows: no changes after `changeableUntil` (409 change_too_late), no cancelling when `canCancel` is false (409 cancel_not_allowed, reschedule instead).","tags":["public"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"the booking id from create_booking, or its manageUrl"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"the token from create_booking, or the manageUrl"},"reason":{"type":"string","description":"why the invitee cancels, sent to the host","title":"maxLength(500)","minLength":0,"maxLength":500},"following":{"type":"boolean","description":"for a series: also cancel every later meeting"}},"required":["token"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/public/review/{id}":{"get":{"operationId":"get_review","summary":"View a request from its email link (host)","description":"Returns a request for approval as its host sees it, from the signed link in their email.","tags":["public"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a string at least 1 character(s) long","title":"minLength(1)","minLength":1}},{"name":"host","in":"query","required":true,"schema":{"type":"string","description":"a string at least 1 character(s) long","title":"minLength(1)","minLength":1}},{"name":"token","in":"query","required":true,"schema":{"type":"string","description":"a string at least 10 character(s) long","title":"minLength(10)","minLength":10}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"answer_review","summary":"Approve or decline a request from its email link (host)","description":"Approves or declines a request with the signed link from the host’s email. The invitee is told either way.","tags":["public"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a string at least 1 character(s) long","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"host":{"type":"string","description":"a string at least 1 character(s) long","title":"minLength(1)","minLength":1},"token":{"type":"string","description":"a string at least 10 character(s) long","title":"minLength(10)","minLength":10},"decision":{"type":"string","enum":["approve","decline"]},"reason":{"type":"string","description":"a string at most 500 character(s) long","title":"maxLength(500)","minLength":0,"maxLength":500}},"required":["host","token","decision"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/waitlist":{"post":{"operationId":"join_waitlist","summary":"Join the private beta waitlist","description":"Requests access to host on Tempi (create event types, share a booking page). Not needed to book meetings, since booking pages are public. Returns the status: pending, approved (sign in to the Tempi web app), rejected, or open (no waitlist, sign in directly). Joining again with the same email returns the current status.","tags":["public"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"an email address"},"name":{"type":"string","description":"the person’s name (at most 100 characters)","title":"maxLength(100)","minLength":0,"maxLength":100},"note":{"type":"string","description":"anything the admins should know, e.g. what you want to use Tempi for","title":"maxLength(1000)","minLength":0,"maxLength":1000},"ref":{"type":"string","description":"a short source tag of lowercase letters, digits, - or _, like \"embed\"","pattern":"^[a-z0-9_-]{1,32}$"}},"required":["email"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me":{"get":{"operationId":"get_me","summary":"Get the current account","description":"Returns your account: email, name, username (your personal team's slug: get_team with it returns your booking page `url`), time zone, booking calendar, and the teams you belong to with your role in each.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update_me","summary":"Update your profile","description":"Changes your display name, username or time zone. The username is your personal booking handle, and old links keep working and redirect. A new name also renames your personal team.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"your name, as invitees see it on your booking page and in their emails (at most 80 characters, no web addresses)"},"username":{"type":"string","description":"1-40 lowercase letters, digits or dashes, starting and ending with a letter or digit","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"},"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"}},"required":[]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/video":{"post":{"operationId":"set_my_video_call","summary":"Set your video call","description":"Chooses the video call your bookings get, in every event type whose place is a video call without one link for everyone. meet: a Google Meet link from your Google Calendar (it must be your booking calendar). teams: a Microsoft Teams link from Outlook (a work or school account; on a personal Outlook account, add your own Teams link as `link`). link: your own link, the same for every booking. null: Tempi picks from your booking calendar. Returns your account, whose `video` says how bookings get their link and why not.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tool":{"anyOf":[{"type":"string","enum":["meet","teams","link"]},{"type":"null"}],"description":"meet | teams | link, or null to let your booking calendar decide"},"link":{"anyOf":[{"type":"string","format":"uri","description":"a Google Meet, Zoom, Microsoft Teams or Webex link (https)"},{"type":"null"}],"description":"a Google Meet, Zoom, Microsoft Teams or Webex link (https)"}},"required":["tool"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/onboarding":{"post":{"operationId":"complete_onboarding","summary":"Finish first-run setup","description":"Marks first-run setup as done or skipped, so the app stops showing it.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"skipped":{"type":"boolean","description":"true when the person skipped setup instead of finishing it"},"ref":{"type":"string","description":"a short source tag of lowercase letters, digits, - or _, like \"embed\"","pattern":"^[a-z0-9_-]{1,32}$"}},"required":[]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability":{"get":{"operationId":"get_availability","summary":"Get working hours","description":"Returns your weekly hours, upcoming date overrides and meeting caps, in your time zone. They apply to every event type you host, in every team. Busy times in your connected calendars are subtracted automatically.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"set_availability","summary":"Replace weekly hours","description":"Replaces all your weekly hours. Each rule is one window on one weekday, in your time zone. Use several rules for a split day, such as 09:00-12:00 and 13:00-17:00. An empty list means you are never available, except on date overrides. Pass `timezone` to change your time zone too.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","required":["weekday","start","end"],"properties":{"weekday":{"type":"number","enum":[0,1,2,3,4,5,6],"description":"a weekday number, 0 = Sunday … 6 = Saturday"},"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"an array of at most 50 item(s)","title":"maxItems(50)","maxItems":50},"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"}},"required":["rules"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability/preview":{"get":{"operationId":"preview_availability","summary":"See your next days as people booking you see them","description":"Shows your next days (14 by default) as people booking you see them, in your time zone. Each day has a `status` (open: some time can be booked; busy: calendar events or bookings take your hours; full: your daily limit is reached; day_off: you took the day off; not_working: your weekly hours have nothing that day; past: today's hours are over) and a `summary` to show or say. It also gives your working hours, what takes time inside them (`busy`: Tempi bookings with what they are and a link, and busy time in each calendar with the calendar's account and, for Google, a link to that day. Calendar event titles aren't read) and the `open` time left, each with a local `time`. `eventTypes` lists the event types you host and whether each uses these hours or its own (set in that event type: update_event_type `schedule`). Use it to answer \"when can people book me this week?\" Next: to take a date off or work other hours on it, call set_date_override (windows [] takes the day off). To change your usual week, call set_availability. For why one event type can't be booked at a time, call explain_availability.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many days to show, today first (1-28)","title":"between(1, 28)","minimum":1,"maximum":28}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/open-times":{"get":{"operationId":"get_open_times","summary":"Check that your booking pages have open times","description":"For each active event type you host, whether invitees can book any time in the next 14 days. One with none shows \"Nothing open\" on its booking page: check working hours, days off, calendars and limits. explain_availability says why a given time is not offered.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability/limits":{"patch":{"operationId":"set_meeting_limits","summary":"Cap meetings per day or week","description":"Caps how many meetings you host per day and per calendar week (Monday first, in your time zone), across all event types and teams. Once a cap is reached, that day or week offers no more slots. null removes a cap, and an omitted field stays as it is. Event types have their own limits too (maxPerDay, maxPerWeek, maxPerMonth).","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"perDay":{"anyOf":[{"type":"integer","description":"a number between 1 and 100","title":"between(1, 100)","minimum":1,"maximum":100},{"type":"null"}],"description":"the most meetings per day (1-100), or null for no cap"},"perWeek":{"anyOf":[{"type":"integer","description":"a number between 1 and 100","title":"between(1, 100)","minimum":1,"maximum":100},{"type":"null"}],"description":"the most meetings per calendar week (1-100), or null for no cap"}},"required":[]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability/overrides":{"post":{"operationId":"set_date_override","summary":"Set hours for a specific date","description":"Sets your hours on one date, in your time zone, in place of the weekly hours and any earlier override for that date. Use windows: [] for a day off, or add hours on a day you usually don't work.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"},"windows":{"type":"array","items":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"an array of at most 10 item(s)","title":"maxItems(10)","maxItems":10}},"required":["date","windows"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability/overrides/{date}":{"delete":{"operationId":"delete_date_override","summary":"Remove a date override","description":"Removes the override for a date, so your weekly hours apply again.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams":{"get":{"operationId":"list_teams","summary":"List your teams","description":"Lists the teams you belong to, with your role in each. Every user has a personal team, which is their own booking page. Event types always belong to a team.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create_team","summary":"Create a team","description":"Creates a team with you as owner. Returns the team with its booking page `url`. `slug` defaults to one made from the name.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"the team’s name, as invitees see it on its booking pages (at most 80 characters, no web addresses)"},"slug":{"type":"string","description":"1-40 lowercase letters, digits or dashes, starting and ending with a letter or digit","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"}},"required":["name"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}":{"get":{"operationId":"get_team","summary":"Get a team","description":"Returns a team with its members, pending invites, payment status and invitee blocklist. Each member's `setup` says whether they have a calendar connected and working hours set, which the team's open times need. `setup.meet` says whether bookings they organize get a Google Meet link (their booking calendar is a working Google Calendar), `setup.teams` whether they get a Microsoft Teams link (a working Outlook calendar), and `setup.hostsMeet` whether they host one of the team's Google Meet event types. Any member can call it.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update_team","summary":"Update a team","description":"Changes the team name or slug (old links keep working and redirect), its time format, or replaces its invitee blocklist. Blocked emails and domains can no longer book any of its event types. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"the team’s name, as invitees see it on its booking pages (at most 80 characters, no web addresses)"},"slug":{"type":"string","description":"1-40 lowercase letters, digits or dashes, starting and ending with a letter or digit","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"},"blockedInvitees":{"type":"array","items":{"type":"string","description":"a string at most 254 character(s) long","title":"maxLength(254)","maxLength":254},"description":"the full blocklist of email addresses and domains (\"spam.com\" or \"@spam.com\" also covers subdomains), or [] to clear it","title":"maxItems(500)","maxItems":500},"timeFormat":{"anyOf":[{"type":"string","enum":["12h","24h"]},{"type":"null"}],"description":"how times read on the team's booking pages and in its emails: 12h (1:30 PM), 24h (13:30), or null for each visitor's own"}},"required":[]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete_team","summary":"Delete a team","description":"Deletes a team with all its event types and bookings. Calendar events stay, invitees are not told and payments are not refunded, so cancel bookings first with cancel_booking if needed. Owner only. Personal teams cannot be deleted.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/members":{"post":{"operationId":"add_team_member","summary":"Add a team member","description":"Adds someone by email and emails them. If they have an account, they join now. Otherwise the invite is saved and they join when they first sign in with that email, even while sign-ups need the waitlist. Requires admin, and owner to add an owner. Needs the Team plan (402 upgrade_required).","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"an email address"},"role":{"type":"string","enum":["owner","admin","member"],"description":"a team role: owner | admin | member"}},"required":["email"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/invites/resend":{"post":{"operationId":"resend_team_invite","summary":"Email an invite again","description":"Emails a pending invite again, with the link to sign in and join. Requires admin. 404 when there's no pending invite for that email (they may have joined already).","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"an email address"}},"required":["email"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/members/{userId}":{"patch":{"operationId":"update_team_member","summary":"Change a member's role","description":"Sets a member's role. Owner only. A team must keep at least one owner.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}},{"name":"userId","in":"path","required":true,"schema":{"type":"string","description":"a member’s user id (\"usr_…\") from get_team","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["owner","admin","member"],"description":"a team role: owner | admin | member"}},"required":["role"]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/members/{member}":{"delete":{"operationId":"remove_team_member","summary":"Remove a member or invite","description":"Removes a member (by user id) or a pending invite (by email). Admins can remove anyone, and members can remove themselves. The member stops hosting the team's event types. Their existing bookings are kept.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}},{"name":"member","in":"path","required":true,"schema":{"type":"string","description":"a member's user id or an invited email address"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/shared-hours":{"get":{"operationId":"get_shared_hours","summary":"Check when hosts all work","description":"For a collective event type: when all of these hosts are in their working hours over the next `days` days, counting days off and each host's time zone, not calendars. Returns the shared time, the hours per week, and how many meetings of `durationMinutes` could start per week. Collective event types only offer times inside it. Any member can call it.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}},{"name":"hostIds","in":"query","required":true,"schema":{"type":"string","description":"the hosts' user ids from get_team, separated by commas (2 to 20)"}},{"name":"durationMinutes","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"a meeting length in minutes between 5 and 480 (default 30)","title":"between(5, 480)","minimum":5,"maximum":480}},{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many days ahead to look, between 7 and 28 (default 14)","title":"between(7, 28)","minimum":7,"maximum":28}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types":{"get":{"operationId":"list_event_types","summary":"List event types","description":"Lists event types with their public booking `url`, including archived ones (`archivedAt` set: closed to new bookings, restorable). Pass `team` for one team, or omit it for every team you belong to.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"query","required":false,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create_event_type","summary":"Create an event type","description":"Creates an event type in a team (default: your personal team). Requires admin. Only `title` and `durationMinutes` are required. For team scheduling, set `kind` to round_robin or collective and list `hostIds`. Round robin, collective and paid event types need the Team plan (402 upgrade_required). Returns the event type with its public `url`.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1},"title":{"type":"string","description":"its name, as invitees see it (at most 120 characters, no web addresses)"},"durationMinutes":{"type":"integer","description":"a duration in minutes between 5 and 480","title":"between(5, 480)","minimum":5,"maximum":480},"slug":{"type":"string","description":"the last part of the booking link (<team link>/<slug>): 1-40 lowercase letters, digits or dashes","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"},"description":{"type":"string","description":"what invitees read about it on its booking page (at most 2000 characters)","title":"maxLength(2000)","minLength":0,"maxLength":2000},"kind":{"type":"string","enum":["one_on_one","round_robin","collective"],"description":"one_on_one (one host) | round_robin (one free host per booking, load-balanced) | collective (all hosts attend, only when everyone is free)"},"hostIds":{"type":"array","items":{"type":"string"},"description":"the user ids of team members who host it (on create, defaults to you)","title":"maxItems(50)","minItems":1,"maxItems":50},"where":{"type":"array","items":{"anyOf":[{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["video"],"description":"video: a video call"},"link":{"type":"string","format":"uri","description":"a Google Meet, Zoom, Microsoft Teams or Webex link (https)"}},"additionalProperties":false},{"type":"object","required":["kind","address"],"properties":{"kind":{"type":"string","enum":["place"],"description":"place: in person"},"address":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"the address, as invitees read it once they book (at most 300 characters)"}},"additionalProperties":false},{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["phone"],"description":"phone: the host calls the invitee at the number they give when booking"}},"additionalProperties":false},{"type":"object","required":["kind","text"],"properties":{"kind":{"type":"string","enum":["other"],"description":"other: somewhere else, in your words"},"text":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"where it happens, in your words: a room, a link, or how to reach you (at most 500 characters)"}},"additionalProperties":false}],"description":"a place: { kind: video, link? } | { kind: place, address } | { kind: phone } | { kind: other, text }"},"description":"where it happens: [] for nowhere, or one place. A video call without `link` uses each host's own (their Google Meet, Teams or own link, set with set_my_video_call). Prefer this to locationType and location, which still work.","title":"maxItems(1)","maxItems":1},"locationType":{"type":"string","enum":["none","google_meet","teams","zoom","custom"],"description":"none | google_meet (adds a Meet link when the organizer's booking calendar is Google Calendar) | teams (adds a Microsoft Teams link when the organizer's booking calendar is Outlook with Teams, a work or school account) | zoom (every booking gets the Zoom link in `location`) | custom (uses `location`)"},"location":{"type":"string","description":"where it happens, when you write the place yourself: an address, a room or a phone number (at most 500 characters)","title":"maxLength(500)","minLength":0,"maxLength":500},"minNoticeMinutes":{"type":"integer","description":"the minimum notice in minutes before a booking can start (0-43200)","title":"between(0, 43200)","minimum":0,"maximum":43200},"maxDaysAhead":{"type":"integer","description":"how many days ahead invitees can book (1-365)","title":"between(1, 365)","minimum":1,"maximum":365},"bufferBeforeMinutes":{"type":"integer","description":"free minutes to keep before each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"bufferAfterMinutes":{"type":"integer","description":"free minutes to keep after each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"maxPerDay":{"anyOf":[{"type":"integer","description":"a number between 1 and 100","title":"between(1, 100)","minimum":1,"maximum":100},{"type":"null"}],"description":"the most bookings of this event per host per day (1-100), or null for no limit"},"maxPerWeek":{"anyOf":[{"type":"integer","description":"a number between 1 and 500","title":"between(1, 500)","minimum":1,"maximum":500},{"type":"null"}],"description":"the most bookings of this event per host per calendar week, Monday first, in the host's time zone (1-500), or null for no limit"},"maxPerMonth":{"anyOf":[{"type":"integer","description":"a number between 1 and 2000","title":"between(1, 2000)","minimum":1,"maximum":2000},{"type":"null"}],"description":"the most bookings of this event per host per calendar month, in the host's time zone (1-2000), or null for no limit"},"inviteeLimit":{"anyOf":[{"type":"integer","description":"a number between 1 and 50","title":"between(1, 50)","minimum":1,"maximum":50},{"type":"null"}],"description":"the most bookings of this event one invitee (matched by email, ignoring +tags and Gmail dots) may have per inviteeLimitPeriod (1-50), or null for no limit"},"inviteeLimitPeriod":{"type":"string","enum":["upcoming","week","month"],"description":"upcoming (future bookings at one time) | week (per calendar week) | month (per calendar month), in the first host's time zone"},"inviteeChangeCutoffHours":{"type":"integer","description":"how many hours before the start invitees can still cancel or reschedule (0-720, 0 = until it starts, hosts always can)","title":"between(0, 720)","minimum":0,"maximum":720},"inviteeCanCancel":{"type":"boolean","description":"false to let invitees reschedule but not cancel (hosts still can)"},"priceCents":{"anyOf":[{"type":"integer","description":"a number between 50 and 10000000","title":"between(50, 10000000)","minimum":50,"maximum":10000000},{"type":"null"}],"description":"a price in the smallest currency unit, such as 5000 for $50.00, or null for free (needs the Team plan and a connected Stripe account)"},"currency":{"type":"string","description":"a lowercase ISO 4217 currency code, such as usd","pattern":"^[a-z]{3}$"},"refundWindowHours":{"type":"integer","description":"how many hours before the start an invitee can cancel with a full refund (0-720, host cancellations are always refunded)","title":"between(0, 720)","minimum":0,"maximum":720},"color":{"type":"string","enum":["orange","yellow","green","teal","blue","purple","red","gray"],"description":"its color in the Tempi app: orange | yellow | green | teal | blue | purple | red | gray (default: one picked for it)"},"schedule":{"anyOf":[{"type":"object","required":["timezone","rules"],"properties":{"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"},"rules":{"type":"array","items":{"type":"object","required":["weekday","start","end"],"properties":{"weekday":{"type":"number","enum":[0,1,2,3,4,5,6],"description":"a weekday number, 0 = Sunday … 6 = Saturday"},"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"weekly hours: one window per rule, several rules for a split day","title":"maxItems(50)","maxItems":50},"overrides":{"type":"array","items":{"type":"object","required":["date","windows"],"properties":{"date":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"},"windows":{"type":"array","items":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"the hours that date, or [] for none","title":"maxItems(10)","maxItems":10}},"additionalProperties":false},"description":"dates with other hours than the weekly ones, or none","title":"maxItems(200)","maxItems":200}},"additionalProperties":false},{"type":"null"}],"description":"its own hours, used instead of every host's working hours: { timezone, rules: [{ weekday, start, end }], overrides: [{ date, windows }] }, such as Fridays 14:00-17:00 only. Hosts' days off, calendars, bookings and caps still count. null (the default): each host's own working hours"},"repeatMax":{"anyOf":[{"type":"integer","description":"a number between 2 and 52","title":"between(2, 52)","minimum":2,"maximum":52},{"type":"null"}],"description":"let invitees book a repeating series (every 1, 2 or 4 weeks at the same time) of up to this many meetings (2-52), or null for one at a time. Free event types only"},"requiresApproval":{"type":"boolean","description":"true: each booking is a request that holds the time until a host approves it (approve_booking) or declines it (decline_booking), and lapses if nobody does before it starts. Not with a price or repeatMax"},"remindInvitees":{"type":"boolean","description":"true (the default) to email invitees a day and an hour before each booking, false for no reminders"},"cover":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"a painted cover for the booking page, the confirmation, link previews and the invitee's emails: a cover id from list_covers, \"sky\" (the sky at the meeting's time of day), \"hand\", \"light\" or \"still\" (a different painting of that family for each booking), or null for none (the default)"}},"required":["title","durationMinutes"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types/{id}":{"patch":{"operationId":"update_event_type","summary":"Update an event type","description":"Changes any of an event type's fields, including hosts. Omitted fields stay as they are. Existing bookings keep their time, hosts and price. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"an event type id (\"evt_…\") from list_event_types, not its slug","title":"minLength(1)","minLength":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"its name, as invitees see it (at most 120 characters, no web addresses)"},"slug":{"type":"string","description":"the last part of the booking link (<team link>/<slug>): 1-40 lowercase letters, digits or dashes","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"},"description":{"type":"string","description":"what invitees read about it on its booking page (at most 2000 characters)","title":"maxLength(2000)","minLength":0,"maxLength":2000},"kind":{"type":"string","enum":["one_on_one","round_robin","collective"],"description":"one_on_one (one host) | round_robin (one free host per booking, load-balanced) | collective (all hosts attend, only when everyone is free)"},"hostIds":{"type":"array","items":{"type":"string"},"description":"the user ids of team members who host it (on create, defaults to you)","title":"maxItems(50)","minItems":1,"maxItems":50},"durationMinutes":{"type":"integer","description":"a duration in minutes between 5 and 480","title":"between(5, 480)","minimum":5,"maximum":480},"where":{"type":"array","items":{"anyOf":[{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["video"],"description":"video: a video call"},"link":{"type":"string","format":"uri","description":"a Google Meet, Zoom, Microsoft Teams or Webex link (https)"}},"additionalProperties":false},{"type":"object","required":["kind","address"],"properties":{"kind":{"type":"string","enum":["place"],"description":"place: in person"},"address":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"the address, as invitees read it once they book (at most 300 characters)"}},"additionalProperties":false},{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["phone"],"description":"phone: the host calls the invitee at the number they give when booking"}},"additionalProperties":false},{"type":"object","required":["kind","text"],"properties":{"kind":{"type":"string","enum":["other"],"description":"other: somewhere else, in your words"},"text":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"where it happens, in your words: a room, a link, or how to reach you (at most 500 characters)"}},"additionalProperties":false}],"description":"a place: { kind: video, link? } | { kind: place, address } | { kind: phone } | { kind: other, text }"},"description":"where it happens: [] for nowhere, or one place. A video call without `link` uses each host's own (their Google Meet, Teams or own link, set with set_my_video_call). Prefer this to locationType and location, which still work.","title":"maxItems(1)","maxItems":1},"locationType":{"type":"string","enum":["none","google_meet","teams","zoom","custom"],"description":"none | google_meet (adds a Meet link when the organizer's booking calendar is Google Calendar) | teams (adds a Microsoft Teams link when the organizer's booking calendar is Outlook with Teams, a work or school account) | zoom (every booking gets the Zoom link in `location`) | custom (uses `location`)"},"location":{"type":"string","description":"where it happens, when you write the place yourself: an address, a room or a phone number (at most 500 characters)","title":"maxLength(500)","minLength":0,"maxLength":500},"minNoticeMinutes":{"type":"integer","description":"the minimum notice in minutes before a booking can start (0-43200)","title":"between(0, 43200)","minimum":0,"maximum":43200},"maxDaysAhead":{"type":"integer","description":"how many days ahead invitees can book (1-365)","title":"between(1, 365)","minimum":1,"maximum":365},"bufferBeforeMinutes":{"type":"integer","description":"free minutes to keep before each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"bufferAfterMinutes":{"type":"integer","description":"free minutes to keep after each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"maxPerDay":{"anyOf":[{"type":"integer","description":"a number between 1 and 100","title":"between(1, 100)","minimum":1,"maximum":100},{"type":"null"}],"description":"the most bookings of this event per host per day (1-100), or null for no limit"},"maxPerWeek":{"anyOf":[{"type":"integer","description":"a number between 1 and 500","title":"between(1, 500)","minimum":1,"maximum":500},{"type":"null"}],"description":"the most bookings of this event per host per calendar week, Monday first, in the host's time zone (1-500), or null for no limit"},"maxPerMonth":{"anyOf":[{"type":"integer","description":"a number between 1 and 2000","title":"between(1, 2000)","minimum":1,"maximum":2000},{"type":"null"}],"description":"the most bookings of this event per host per calendar month, in the host's time zone (1-2000), or null for no limit"},"inviteeLimit":{"anyOf":[{"type":"integer","description":"a number between 1 and 50","title":"between(1, 50)","minimum":1,"maximum":50},{"type":"null"}],"description":"the most bookings of this event one invitee (matched by email, ignoring +tags and Gmail dots) may have per inviteeLimitPeriod (1-50), or null for no limit"},"inviteeLimitPeriod":{"type":"string","enum":["upcoming","week","month"],"description":"upcoming (future bookings at one time) | week (per calendar week) | month (per calendar month), in the first host's time zone"},"inviteeChangeCutoffHours":{"type":"integer","description":"how many hours before the start invitees can still cancel or reschedule (0-720, 0 = until it starts, hosts always can)","title":"between(0, 720)","minimum":0,"maximum":720},"inviteeCanCancel":{"type":"boolean","description":"false to let invitees reschedule but not cancel (hosts still can)"},"priceCents":{"anyOf":[{"type":"integer","description":"a number between 50 and 10000000","title":"between(50, 10000000)","minimum":50,"maximum":10000000},{"type":"null"}],"description":"a price in the smallest currency unit, such as 5000 for $50.00, or null for free (needs the Team plan and a connected Stripe account)"},"currency":{"type":"string","description":"a lowercase ISO 4217 currency code, such as usd","pattern":"^[a-z]{3}$"},"refundWindowHours":{"type":"integer","description":"how many hours before the start an invitee can cancel with a full refund (0-720, host cancellations are always refunded)","title":"between(0, 720)","minimum":0,"maximum":720},"color":{"type":"string","enum":["orange","yellow","green","teal","blue","purple","red","gray"],"description":"its color in the Tempi app: orange | yellow | green | teal | blue | purple | red | gray (default: one picked for it)"},"schedule":{"anyOf":[{"type":"object","required":["timezone","rules"],"properties":{"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"},"rules":{"type":"array","items":{"type":"object","required":["weekday","start","end"],"properties":{"weekday":{"type":"number","enum":[0,1,2,3,4,5,6],"description":"a weekday number, 0 = Sunday … 6 = Saturday"},"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"weekly hours: one window per rule, several rules for a split day","title":"maxItems(50)","maxItems":50},"overrides":{"type":"array","items":{"type":"object","required":["date","windows"],"properties":{"date":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"},"windows":{"type":"array","items":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"the hours that date, or [] for none","title":"maxItems(10)","maxItems":10}},"additionalProperties":false},"description":"dates with other hours than the weekly ones, or none","title":"maxItems(200)","maxItems":200}},"additionalProperties":false},{"type":"null"}],"description":"its own hours, used instead of every host's working hours: { timezone, rules: [{ weekday, start, end }], overrides: [{ date, windows }] }, such as Fridays 14:00-17:00 only. Hosts' days off, calendars, bookings and caps still count. null (the default): each host's own working hours"},"repeatMax":{"anyOf":[{"type":"integer","description":"a number between 2 and 52","title":"between(2, 52)","minimum":2,"maximum":52},{"type":"null"}],"description":"let invitees book a repeating series (every 1, 2 or 4 weeks at the same time) of up to this many meetings (2-52), or null for one at a time. Free event types only"},"requiresApproval":{"type":"boolean","description":"true: each booking is a request that holds the time until a host approves it (approve_booking) or declines it (decline_booking), and lapses if nobody does before it starts. Not with a price or repeatMax"},"remindInvitees":{"type":"boolean","description":"true (the default) to email invitees a day and an hour before each booking, false for no reminders"},"cover":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"a painted cover for the booking page, the confirmation, link previews and the invitee's emails: a cover id from list_covers, \"sky\" (the sky at the meeting's time of day), \"hand\", \"light\" or \"still\" (a different painting of that family for each booking), or null for none (the default)"}},"required":[]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types/{id}/archive":{"post":{"operationId":"archive_event_type","summary":"Archive an event type","description":"Takes an event type off the dashboard and closes it to new bookings: its link says it isn’t taking bookings. Bookings already made still work: invitees and hosts can view, reschedule and cancel them. It keeps its slug, and restore_event_type brings it back. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"an event type id (\"evt_…\") from list_event_types, not its slug","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types/{id}/restore":{"post":{"operationId":"restore_event_type","summary":"Restore an archived event type","description":"Brings an archived event type back to the dashboard, bookable again at its old link. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"an event type id (\"evt_…\") from list_event_types, not its slug","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types/{id}/availability":{"get":{"operationId":"explain_availability","summary":"Explain why times can or can't be booked","description":"For one date (YYYY-MM-DD in `timezone`), lists every start time the hosts' working hours (or the event type's own hours) offer and whether each can be booked. For each blocked time it gives the reason per host: outside working hours or a day off, too soon or too far ahead, a calendar event or another booking, a buffer, a daily, weekly or monthly limit, or a start that is off the slot grid. Pass `time` (HH:MM) to ask about one time. `notices` lists what affects every time, such as an inactive event type or a calendar that couldn't be read. Use it to answer \"why can't anyone book Tuesday at 3pm?\" and to suggest the setting to change. Any team member can call it.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"an event type id (\"evt_…\") from list_event_types, not its slug","title":"minLength(1)","minLength":1}},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"}},{"name":"timezone","in":"query","required":false,"schema":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"}},{"name":"time","in":"query","required":false,"schema":{"type":"string","description":"a time as HH:MM on `date` in `timezone`","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/event-types/preview-times":{"post":{"operationId":"preview_event_type_times","summary":"Try event type settings before saving","description":"Shows the times an event type's booking page would offer over the next `days` days (7 by default) with these settings, before anything is saved. Hosts' working hours (or the event type's own `schedule`), days off, calendars, bookings, notice, buffers and limits all count, as on the real page. Pass `id` to start from a saved event type and change only the fields you pass, or `team` and the fields for a new one (the rest take create_event_type's defaults). Each day lists its `times` in `timezone`, `count` is the total, `next` is the first open time after the range when it has none, and `summary` says it in a sentence. Use it to answer \"what would people see if I needed a day's notice?\" Nothing changes: save with update_event_type or create_event_type. For why one time can't be booked, call explain_availability.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"a saved event type to start from","title":"minLength(1)","minLength":1},"team":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1},"kind":{"type":"string","enum":["one_on_one","round_robin","collective"],"description":"one_on_one (one host) | round_robin (one free host per booking, load-balanced) | collective (all hosts attend, only when everyone is free)"},"hostIds":{"type":"array","items":{"type":"string"},"description":"the user ids of team members who host it (on create, defaults to you)","title":"maxItems(50)","minItems":1,"maxItems":50},"durationMinutes":{"type":"integer","description":"a duration in minutes between 5 and 480","title":"between(5, 480)","minimum":5,"maximum":480},"minNoticeMinutes":{"type":"integer","description":"the minimum notice in minutes before a booking can start (0-43200)","title":"between(0, 43200)","minimum":0,"maximum":43200},"maxDaysAhead":{"type":"integer","description":"how many days ahead invitees can book (1-365)","title":"between(1, 365)","minimum":1,"maximum":365},"bufferBeforeMinutes":{"type":"integer","description":"free minutes to keep before each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"bufferAfterMinutes":{"type":"integer","description":"free minutes to keep after each meeting (0-240)","title":"between(0, 240)","minimum":0,"maximum":240},"maxPerDay":{"anyOf":[{"type":"integer","description":"a number between 1 and 100","title":"between(1, 100)","minimum":1,"maximum":100},{"type":"null"}],"description":"the most bookings of this event per host per day (1-100), or null for no limit"},"maxPerWeek":{"anyOf":[{"type":"integer","description":"a number between 1 and 500","title":"between(1, 500)","minimum":1,"maximum":500},{"type":"null"}],"description":"the most bookings of this event per host per calendar week, Monday first, in the host's time zone (1-500), or null for no limit"},"maxPerMonth":{"anyOf":[{"type":"integer","description":"a number between 1 and 2000","title":"between(1, 2000)","minimum":1,"maximum":2000},{"type":"null"}],"description":"the most bookings of this event per host per calendar month, in the host's time zone (1-2000), or null for no limit"},"schedule":{"anyOf":[{"type":"object","required":["timezone","rules"],"properties":{"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"},"rules":{"type":"array","items":{"type":"object","required":["weekday","start","end"],"properties":{"weekday":{"type":"number","enum":[0,1,2,3,4,5,6],"description":"a weekday number, 0 = Sunday … 6 = Saturday"},"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"weekly hours: one window per rule, several rules for a split day","title":"maxItems(50)","maxItems":50},"overrides":{"type":"array","items":{"type":"object","required":["date","windows"],"properties":{"date":{"type":"string","format":"date","description":"a date as YYYY-MM-DD"},"windows":{"type":"array","items":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"},"end":{"type":"string","description":"a 24-hour time as HH:MM, or \"24:00\" for the end of the day","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$|^24:00$"}},"additionalProperties":false},"description":"the hours that date, or [] for none","title":"maxItems(10)","maxItems":10}},"additionalProperties":false},"description":"dates with other hours than the weekly ones, or none","title":"maxItems(200)","maxItems":200}},"additionalProperties":false},{"type":"null"}],"description":"its own hours, used instead of every host's working hours: { timezone, rules: [{ weekday, start, end }], overrides: [{ date, windows }] }, such as Fridays 14:00-17:00 only. Hosts' days off, calendars, bookings and caps still count. null (the default): each host's own working hours"},"timezone":{"type":"string","description":"an IANA time zone such as \"America/New_York\" (abbreviations like EST and offsets like UTC+8 work too)"},"days":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many days to show, today first (1-14)","title":"between(1, 14)","minimum":1,"maximum":14}},"required":[]}}}},"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings":{"get":{"operationId":"list_bookings","summary":"List bookings","description":"Lists bookings. Without `team`: the ones you host, in every team. With `team`: all of that team's bookings for admins, only yours for members. `when=upcoming` (default) lists soonest first and includes bookings awaiting payment. `past` lists newest first. `cancelled` lists cancellations. Or pass `from` and `to` (ISO, at most 62 days apart) for every active booking in that window, soonest first, such as a week view. `only=calendar_problems` keeps the bookings whose calendar event is missing, out of date or without its Meet or Teams link (see `calendarProblem`). Each booking has a `manageUrl` with the token that get_available_slots needs to reschedule it.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"when","in":"query","required":false,"schema":{"type":"string","enum":["upcoming","past","cancelled"],"description":"upcoming (the default), past or cancelled"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"an ISO 8601 date and time with an offset, such as 2026-10-05T14:00:00Z"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"an ISO 8601 date and time with an offset, such as 2026-10-05T14:00:00Z"}},{"name":"team","in":"query","required":false,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}},{"name":"only","in":"query","required":false,"schema":{"type":"string","enum":["calendar_problems"],"description":"calendar_problems: only bookings with a `calendarProblem`"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many bookings to return, 1 to 500 (default 50)","title":"between(1, 500)","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}":{"get":{"operationId":"get_host_booking","summary":"View a booking (host)","description":"Returns one booking you host, or any booking in a team you administer, by its id (from list_bookings or a booking email). For a meeting of a series, `series.meetings` lists every meeting of it in time order, cancelled ones too.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/reschedule":{"post":{"operationId":"reschedule_booking","summary":"Reschedule a booking (host)","description":"Moves a booking you host, or any booking in a team you administer, to `start`. Hosts and duration stay the same. Only confirmed bookings can move (409 not_reschedulable). The calendar event is updated and its provider notifies the invitee. To find valid times, call get_available_slots with this booking's id as `bookingId` and the token from its `manageUrl`.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string","format":"date-time","description":"an ISO 8601 date and time with an offset, such as 2026-10-05T14:00:00Z"}},"required":["start"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/cancel":{"post":{"operationId":"cancel_booking","summary":"Cancel a booking (host)","description":"Cancels a booking you host, or any booking in a team you administer. Paid bookings are refunded in full. If the refund fails, the booking stays active (502 refund_failed). The event is deleted from the organizer's calendar, and Tempi emails the invitee. For a meeting of a series, `following` cancels every later one too.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"why, shown to the invitee in their email (at most 500 characters, no web addresses)","title":"maxLength(500)","minLength":0,"maxLength":500},"following":{"type":"boolean","description":"for a series: also cancel every later meeting"}},"required":[]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/approve":{"post":{"operationId":"approve_booking","summary":"Approve a booking request","description":"Confirms a booking request (status pending_approval) you host, or any in a team you administer. It goes on the organizer's calendar and Tempi emails the invitee. 409 not_a_request if it isn't waiting for approval, 409 request_expired once its time has passed.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/decline":{"post":{"operationId":"decline_booking","summary":"Decline a booking request","description":"Declines a booking request (status pending_approval) you host, or any in a team you administer. The time is freed, and Tempi emails the invitee with your reason and a link to request another time. 409 not_a_request if it isn't waiting for approval.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"shown to the invitee, without web addresses","title":"maxLength(500)","minLength":0,"maxLength":500}},"required":[]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/calendar":{"post":{"operationId":"retry_booking_calendar","summary":"Retry a booking’s calendar event","description":"Tries again to put a booking you host (or any in a team you administer) in its organizer's booking calendar: creates the event if it isn't there, else updates it to the booking's time and asks the calendar again for a missing Google Meet or Teams link. For a cancelled booking, deletes the event. Use it on a booking with a `calendarProblem` (from list_bookings or get_host_booking). Returns the booking: `calendarProblem` is gone when it worked. When it says the calendar can't be reached, the organizer must reconnect it in the Tempi web app under Calendars first. 409 not_confirmed for requests and unpaid bookings, which go on the calendar later.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/bookings/{id}/link":{"post":{"operationId":"set_booking_link","summary":"Give a booking its meeting link","description":"Sets the meeting link of a confirmed booking you host (or any in a team you administer), and Tempi emails it to the invitee. It must be a Google Meet, Zoom, Microsoft Teams or Webex link. Use it when a Google Meet booking has no link because the organizer's booking calendar couldn't make one (`calendarProblem` no_meet), or to change the link. The manage page and the reminders then show it. 409 not_confirmed for requests, unpaid and cancelled bookings, 409 already_ended once the meeting is over.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a booking id (\"bkg_…\") from list_bookings","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"a Google Meet, Zoom, Microsoft Teams or Webex link (https)"}},"required":["url"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calendars":{"get":{"operationId":"list_calendar_connections","summary":"List connected calendars","description":"Lists your connected calendar accounts. All of them are checked for conflicts. The one with isBookingCalendar=true gets new bookings. status=\"error\" means the account needs reconnecting (see statusDetail). Google Calendar and Outlook (when this server offers it) are connected in the Tempi web app under Calendars. iCloud connects with connect_apple_calendar.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calendars/apple":{"post":{"operationId":"connect_apple_calendar","summary":"Connect iCloud Calendar","description":"Connects an iCloud calendar over CalDAV. Apple has no OAuth for calendars, so this needs the Apple ID email and an app-specific password from https://account.apple.com → Sign-In and Security → App-Specific Passwords. The credentials are checked, then stored encrypted. Your first connected calendar becomes the booking calendar.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"appleId":{"type":"string","format":"email","description":"an email address"},"appPassword":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"an app-specific password, e.g. abcd-efgh-ijkl-mnop"},"serverUrl":{"type":"string","description":"an https CalDAV server URL (advanced, defaults to iCloud)","pattern":"^https:\\/\\/"}},"required":["appleId","appPassword"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calendars/{id}/booking":{"post":{"operationId":"set_booking_calendar","summary":"Choose the booking calendar","description":"Makes this connection your booking calendar: new bookings are written to it. Only a Google booking calendar gives bookings a Google Meet link, and only an Outlook one (a work or school account) a Microsoft Teams link. Existing bookings stay where they are. A connection with status \"error\" can’t take bookings until it is connected again. Returns all connections.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a calendar connection id (\"cal_…\") from list_calendar_connections","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calendars/{id}":{"delete":{"operationId":"delete_calendar_connection","summary":"Disconnect a calendar","description":"Removes a calendar connection and its stored credentials. Events already in that calendar stay there, and Tempi no longer updates or deletes them. If it was your booking calendar, another connected calendar takes over: a working one, and Google when you host a Google Meet event type.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a calendar connection id (\"cal_…\") from list_calendar_connections","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/api-keys":{"get":{"operationId":"list_api_keys","summary":"List API keys","description":"Lists your API keys by prefix. A secret is shown only once, when the key is created.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create_api_key","summary":"Create an API key","description":"Creates an API key for scripts and AI assistants. The returned `secret` is shown only once.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"a name to tell the key apart later, like the app that uses it (at most 60 characters)"}},"required":["name"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/api-keys/{id}":{"delete":{"operationId":"delete_api_key","summary":"Revoke an API key","description":"Revokes an API key. It stops working at once.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"an API key id (\"key_…\") from list_api_keys","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/webhooks":{"get":{"operationId":"list_webhooks","summary":"List webhooks","description":"Lists the addresses a team's booking events are sent to, and how each one's last delivery went. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create_webhook","summary":"Add a webhook","description":"Sends the team’s booking events (booking.created, booking.requested, booking.rescheduled, booking.cancelled) to an https address as signed JSON. The returned `secret` checks the Tempi-Signature header and is shown only once. Use it to connect Zapier, Make, n8n or your own code. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"the https address that receives the events, like a Zapier or Make webhook URL (at most 500 characters)"}},"required":["url"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}/test":{"post":{"operationId":"test_webhook","summary":"Test a webhook","description":"Sends a `ping` event to the webhook now and says how it answered. A webhook that was turned off after failing is turned back on when the test works. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a webhook id (\"whk_…\") from list_webhooks","title":"minLength(1)","minLength":1}}],"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}":{"delete":{"operationId":"delete_webhook","summary":"Remove a webhook","description":"Stops sending booking events to this address at once. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a webhook id (\"whk_…\") from list_webhooks","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/webhooks/sample":{"get":{"operationId":"sample_webhook_events","summary":"Sample webhook events","description":"Up to three events of one type as a webhook would get them, from the team's latest bookings, or a made-up one when it has none. For apps like Zapier to show the fields before a real booking comes. Requires admin.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["booking.created","booking.requested","booking.rescheduled","booking.cancelled"],"description":"the event to show: booking.created, booking.requested, booking.rescheduled or booking.cancelled"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/connected-apps":{"get":{"operationId":"list_connected_apps","summary":"List connected AI assistants","description":"Lists the AI assistants connected to your account over OAuth, and what each may do.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/connected-apps/{id}":{"delete":{"operationId":"disconnect_app","summary":"Disconnect an AI assistant","description":"Disconnects an AI assistant. Its tokens stop working at once.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a connected app’s id from list_connected_apps","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/billing":{"get":{"operationId":"get_billing","summary":"Get a team's plan","description":"Returns the team's plan (free or team), subscription status, seats (one per member) and prices. Any member can call it. The Team plan adds round robin and collective meetings and adding members, and removes the \"Scheduling by Tempi\" line. `payments` and `customDomains` say whether charging for meetings and custom domains are offered too.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/billing/seen":{"post":{"operationId":"note_plan_card","summary":"Count a look at the Team plan card","description":"Counts that you saw the Team plan card, for Insights. The app sends it once per visit.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source":{"type":"string","enum":["team_page","event_type","add_member","team_kind"],"description":"where it was asked: team_page | event_type (the card and line) | add_member | team_kind (before adding a teammate or turning on round robin or collective)"}},"required":["source"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams/{team}/billing/interest":{"post":{"operationId":"want_team_plan","summary":"Say whether you'd pay for the Team plan","description":"While the Team plan isn't sold, notes whether you'd pay for it for this team: yes at its price, yes but cheaper (with what you'd pay), or no (with why, if you like). Nothing is charged, and nothing changes: every feature stays free. Any member can call it. One answer per person and team, the latest kept.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","description":"the id or slug of a team you belong to","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source":{"type":"string","enum":["team_page","event_type","add_member","team_kind"],"description":"where it was asked: team_page | event_type (the card and line) | add_member | team_kind (before adding a teammate or turning on round robin or collective)"},"answer":{"type":"string","enum":["yes","cheaper","no"],"description":"yes (at the price shown) | cheaper (at priceCents) | no. Default: yes"},"priceCents":{"type":"integer","description":"for cheaper: what you would pay per member a month, in cents","title":"between(0, 100000)","minimum":0,"maximum":100000},"reason":{"type":"string","description":"why not, or what it would need (up to 1000 characters)","title":"maxLength(1000)","maxLength":1000}},"required":["source"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/waitlist":{"get":{"operationId":"list_waitlist","summary":"List the waitlist (admin)","description":"Lists waitlist requests: pending ones oldest first, others newest first. Tempi admins only (ADMIN_EMAILS).","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","approved","rejected"],"description":"which requests to list: pending (the default), approved or rejected"}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/waitlist/decide":{"post":{"operationId":"decide_waitlist","summary":"Approve or reject waitlist requests (admin)","description":"Approves or rejects waitlist requests by email. Emails not on the list are added with that decision. Approved people can sign in to the Tempi web app, and Tempi emails them to say so (once, when they are first approved). Rejected people get no email. Tempi admins only.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"string","format":"email","description":"an email address"},"description":"an array of at most 100 item(s)","title":"maxItems(100)","minItems":1,"maxItems":100},"decision":{"type":"string","enum":["approve","reject"],"description":"approve to let them sign in, reject to turn them down"}},"required":["emails","decision"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/insights":{"get":{"operationId":"get_insights","summary":"Get product analytics (admin)","description":"For Tempi admins (ADMIN_EMAILS): daily, weekly and monthly active users, active users per day, counts of key events (booking page views, slots viewed, bookings, sign-ups, calendar connections…) by channel (web app, REST API, MCP, or AI assistants reading Markdown), and how new hosts get to their first bookings.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","description":"how many days back to count events (1-90)","title":"between(1, 90)","minimum":1,"maximum":90}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/insights/features":{"get":{"operationId":"get_feature_use","summary":"Get who uses which feature (admin)","description":"For Tempi admins (ADMIN_EMAILS): a row per host with how much they used each feature in the last `days`, and what they have set up now.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","description":"an integer","title":"int"},{"type":"string","description":"a string to be decoded into a number"}],"description":"how many days back to count use (1-90)","title":"between(1, 90)","minimum":1,"maximum":90}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/covers":{"get":{"operationId":"list_covers","summary":"List painted covers","description":"Lists the painted covers an event type can open with (set one with `cover` on create_event_type or update_event_type): a detail of a public-domain painting, with nothing written on it. Each has an id, its family (hand, light, still, sky), what it suits (`themes`), what it shows (`alt`), the painting, the artist and the museum. Pass `for` (an event type’s name, and its description if you like) to also get `suggested`: the covers whose themes it mentions, best first, with the themes that matched. Besides these ids, `cover: \"sky\"` picks the sky at the meeting’s time of day where the invitee is.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"for","in":"query","required":false,"schema":{"type":"string","description":"an event type’s name and description, like \"Coffee chat. A quick catch up.\", to get the covers that suit it in `suggested`","title":"maxLength(500)","minLength":0,"maxLength":500}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/covers":{"get":{"operationId":"list_made_covers","summary":"List every cover, for the cover maker (admin)","description":"For Tempi admins: every cover, the ones Tempi started with first, hidden ones too, with how many event types use each and the crop to edit it again. `canDraft` says whether the cover maker can draft alt text here.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/covers/sources":{"get":{"operationId":"search_cover_sources","summary":"Search the museums for paintings to make a cover from (admin)","description":"For Tempi admins: paintings in the public domain (CC0) at The Met or the Cleveland Museum of Art that match a search, with their full pictures to crop.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"museum","in":"query","required":true,"schema":{"type":"string","enum":["The Met","Cleveland Museum of Art"],"description":"which museum to search: The Met | Cleveland Museum of Art"}},{"name":"q","in":"query","required":true,"schema":{"allOf":[{"type":"string","description":"a string that will be trimmed"}],"description":"what to search for, like \"hands\" or \"window\""}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/covers/gaps":{"get":{"operationId":"list_cover_gaps","summary":"What hosts need covers for (admin)","description":"For Tempi admins: the words in the names of event types that no cover suits yet, most used first, to look for paintings of. Only words and counts, never whose event types they are.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/admin/covers/{id}/hidden":{"post":{"operationId":"set_cover_hidden","summary":"Hide or show a cover (admin)","description":"For Tempi admins: takes a cover off the picker (hidden: true) or puts it back. Event types that use it keep it. A hidden sky is no longer anyone’s sky by the hour, and the last sky can’t be hidden.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"a cover’s id, from list_made_covers","title":"minLength(1)","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hidden":{"type":"boolean","description":"true to take it off the picker, false to offer it again"}},"required":["hidden"]}}}},"responses":{"201":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/addresses":{"get":{"operationId":"search_addresses","summary":"Suggest addresses for an in-person event type","description":"Addresses that match what you typed, nearest your own time zone first, from OpenStreetMap (Photon). Pass one as `address` in a `where` place of kind place on create_event_type or update_event_type. No suggestions means none matched: the address can still be written in full.","tags":["account"],"security":[{"bearerAuth":[]},{"sessionCookie":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"what you typed of the address, at least 3 characters, like \"1 ferry building\" or \"Unter den Linden 10\"","title":"maxLength(200)","minLength":3,"maxLength":200}}],"responses":{"200":{"description":"Success","content":{"application/json":{}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key from Settings → API keys"},"sessionCookie":{"type":"apiKey","in":"cookie","name":"tempi_session"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}