The QuantiMeet API
Your own code can work your team's calendar through the QuantiMeet API: make and change event types, find open times, book, move and cancel, list bookings and add webhooks, with your team's own key, on your team's own address.
LivePer-team API keys: each team's keys work on its own address only.
Each example says how it was checked. Most were run on October 17, 2026 on two test teams, one for each way QuantiMeet runs a team, by a script that checks every answer; each shows the call as it was sent and, in the dashed box below it, its answer, shortened, with the team's address, names, ids and secrets replaced. No key appears on this page: $QM_KEY stands for yours.
Start here
- Signed in, open
your-team.quantimeet.com/settings/developer/api-keysand add a key, named after what will use it, with an expiry. - Copy the key when it is shown: QuantiMeet keeps only a scrambled form of it (a hash), so it cannot show it again. Treat it like a password, and keep it on your server: never put one in a web page or an app.
- Put your team's address in a variable, then paste your key at the silent prompt
readgives you and press Enter, so it stays out of your shell's history:
export QM="https://your-team.quantimeet.com/api/v2"
read -rs QM_KEY && export QM_KEYcurl -H "Authorization: Bearer $QM_KEY" "$QM/me"{"status":"success","data":{"id":1,"username":"owner","email":"you@example.com","name":"Your Team",
"timeZone":"America/Vancouver","defaultScheduleId":1, ...}}Replace your-team with your team's address. /me answers with the profile of the person who made the key. Each command below runs as written in a Unix shell (macOS, Linux, or Git Bash or WSL on Windows); the dashed box under a command is its answer, shortened.
Your keys
- Make a key in your dashboard: Settings, then Developer, then API keys. Name it after what will use it, and choose when it expires: in 7 days, 30 days, 3 months or a year, or never.
- A key is shown once. It is
cal_followed by 32 characters (0 to 9 and a to f). QuantiMeet stores only its hash. - The same page shows when each key was last used, within a minute, and when it was made; a key with no use recorded yet says so. A key you do not recognise, or one that should be idle and is not, is one to delete.
- Rotate a key from its menu: a new key with the same name and the same length of life is made and shown once (a 30-day key rotated today lasts 30 days from today), and you choose when the old key stops working: at once, in 1 hour, in 24 hours or in 7 days. Move your code to the new key before the old one stops; rotating never makes the old key last longer than it would have.
- Delete a key from its menu, and its next call is refused (401). Keys are made and deleted in the dashboard only, never through the API.
- An expired key is refused (401), from its expiry's exact time.
- A key acts as the person who made it. Keys have no scopes:
/meanswers with that person, and the bookings a key lists are the ones that person may see, so a member's key does not list the owner's bookings. Make the key your code uses while signed in as the person whose calendar it works, usually the owner. - A key works on its own team's address only. The same key on quantimeet.com or on another team's address is refused (401) on every call that checks a key, booking included.
Your address, and the version each call needs
- Your team's own address. Every call goes to
https://your-team.quantimeet.com/api/v2, with the key in anAuthorization: Bearerheader. - Versions. Send the
cal-api-versionheader each part expects:2024-06-14for event types,2024-09-04for open times (slots),2024-08-13for bookings and2024-06-11for schedules. Your profile, webhooks and the time zone list take none. Without it, a call reaches an older version of that part, which reads other fields: for open times, a 404 that says "Cannot GET". - Calls that need no key. Open times, booking, and reading, moving or cancelling one booking by its uid also work without a key, as your booking pages and the links in your emails do. So a booking's uid is a secret: whoever has it can see, move or cancel that booking. From your server, send your key on these calls too.
Event types
List the key owner's own event types, hidden ones too:
curl -H "Authorization: Bearer $QM_KEY" -H "cal-api-version: 2024-06-14" "$QM/event-types"Without a key, name a person to get their public event types. Every new team's owner has the username owner:
curl -H "cal-api-version: 2024-06-14" "$QM/event-types?username=owner"{"status":"success","data":[{"id":1,"slug":"intro-call","title":"Intro Call","lengthInMinutes":30,
"locations":[{"type":"integration","integration":"jitsi"}],
"bookingUrl":"https://your-team.quantimeet.com/owner/intro-call", ...}]}Make one: a title, a slug for its link and its length. It takes your default video location, a QuantiMeet Video room, unless you send locations:
curl -X POST "$QM/event-types" -H "Authorization: Bearer $QM_KEY" \
-H "cal-api-version: 2024-06-14" -H "Content-Type: application/json" \
-d '{"title":"Planning session","slug":"planning-session","lengthInMinutes":30}'{"status":"success","data":{"id":2,"slug":"planning-session","title":"Planning session",
"lengthInMinutes":30,"hidden":false,"locations":[{"type":"integration","integration":"jitsi"}], ...}}Read it, change it, and delete it, by its id:
curl -H "Authorization: Bearer $QM_KEY" -H "cal-api-version: 2024-06-14" "$QM/event-types/2"curl -X PATCH "$QM/event-types/2" -H "Authorization: Bearer $QM_KEY" \
-H "cal-api-version: 2024-06-14" -H "Content-Type: application/json" \
-d '{"title":"Planning call","description":"A first look at your project."}'curl -X DELETE "$QM/event-types/2" -H "Authorization: Bearer $QM_KEY" -H "cal-api-version: 2024-06-14"A key changes and deletes only its own person's event types. Round-robin and collective event types are made in the dashboard; their open times and bookings work through the API by their id, as any other event type's do.
Open times
curl -H "cal-api-version: 2024-09-04" \
"$QM/slots?eventTypeId=1&start=2027-02-02T07:00:00Z&end=2027-02-03T06:59:59Z&timeZone=America/Vancouver"{"data":{"2027-02-02":[{"start":"2027-02-02T00:00:00.000-07:00"},
{"start":"2027-02-02T00:30:00.000-07:00"}, ...]},"status":"success"}- Times come grouped by date, in the zone you name (UTC if you name none), each with its offset. Add
&format=rangeto get each time's end as well. startandendare read in UTC: the call above asks for the whole of Tuesday, February 2, 2027 in Vancouver.- No key is needed here, as on your booking pages. Team event types work the same way, by their id.
Book
curl -X POST "$QM/bookings" -H "Authorization: Bearer $QM_KEY" \
-H "cal-api-version: 2024-08-13" -H "Content-Type: application/json" \
-d '{"start":"2027-02-02T16:00:00Z","eventTypeId":1,
"attendee":{"name":"Sam Lee","email":"sam@example.com","timeZone":"America/Vancouver"}}'{"status":"success","data":{"id":12,"uid":"BOOKING_UID","status":"accepted",
"start":"2027-02-02T16:00:00.000Z","end":"2027-02-02T16:30:00.000Z","duration":30,
"location":"https://meet.quantimeet.com/...","attendees":[ ... ],"hosts":[ ... ], ...}}- 201 means booked.
startis one of the open times, in UTC: 9am on February 2, 2027 in Vancouver is2027-02-02T16:00:00Z. - QuantiMeet emails the attendee and the host as it does for any booking, with the QuantiMeet Video room's link when that is the event type's location.
- Optional:
guests(more email addresses),metadata(your own keys and values) andbookingFieldsResponses(answers to your booking questions, by their slug).
Read, move and cancel
curl -H "Authorization: Bearer $QM_KEY" -H "cal-api-version: 2024-08-13" "$QM/bookings/BOOKING_UID"curl -X POST "$QM/bookings/BOOKING_UID/reschedule" -H "Authorization: Bearer $QM_KEY" \
-H "cal-api-version: 2024-08-13" -H "Content-Type: application/json" \
-d '{"start":"2027-02-02T16:30:00Z","reschedulingReason":"The client asked to move it"}'{"status":"success","data":{"uid":"NEW_BOOKING_UID","rescheduledFromUid":"BOOKING_UID",
"status":"accepted","start":"2027-02-02T16:30:00.000Z", ...}}curl -X POST "$QM/bookings/NEW_BOOKING_UID/cancel" -H "Authorization: Bearer $QM_KEY" \
-H "cal-api-version: 2024-08-13" -H "Content-Type: application/json" \
-d '{"cancellationReason":"The client cannot come"}'{"status":"success","data":{"uid":"NEW_BOOKING_UID","status":"cancelled", ...}}- Moving makes a new booking with a new uid. The new booking names the old one in
rescheduledFromUid; the old one then readscancelled, withrescheduledToUidnaming the new one. Keep the new uid. - Read it back. After a move or a cancellation, read the booking and check its
statusbefore you tell anyone it is done. - Both send QuantiMeet's usual emails to everyone on the booking. Handing a booking to another host (
/reassign) answers 501: QuantiMeet does not do it.
List bookings
curl -H "Authorization: Bearer $QM_KEY" -H "cal-api-version: 2024-08-13" \
"$QM/bookings?status=upcoming&sortStart=asc&take=50&skip=0"{"status":"success","data":[ ... ],"pagination":{"totalItems":1,"remainingItems":0,"returnedItems":1,
"itemsPerPage":50,"currentPage":1,"totalPages":1,"hasNextPage":false,"hasPreviousPage":false}}Filters: status (upcoming, recurring, past, cancelled, unconfirmed; several with commas), attendeeEmail, eventTypeId, and afterStart and beforeEnd (times with their offset). Pages: take and skip. Without a key, the list answers 403.
Webhooks
LiveA webhook receives a signed message for each booking and each cancellation of its event type.
Add one on an event type, switched off, with its secret; switch it on ("active":true) once your receiver is ready. Its address must be HTTPS on a public host: private and internal addresses are refused (400). You can also add one on the event type's Webhooks tab in your dashboard.
curl -X POST "$QM/event-types/1/webhooks" -H "Authorization: Bearer $QM_KEY" \
-H "Content-Type: application/json" \
-d '{"active":false,"subscriberUrl":"https://example.com/hooks/quantimeet",
"triggers":["BOOKING_CREATED","BOOKING_CANCELLED"],"secret":"YOUR_WEBHOOK_SECRET"}'{"status":"success","data":{"id":"WEBHOOK_ID","eventTypeId":1,"active":false,
"subscriberUrl":"https://example.com/hooks/quantimeet","triggers":[ ... ],"secret":"YOUR_WEBHOOK_SECRET", ...}}curl -X PATCH "$QM/event-types/1/webhooks/WEBHOOK_ID" -H "Authorization: Bearer $QM_KEY" \
-H "Content-Type: application/json" -d '{"triggers":["BOOKING_CREATED"]}'
curl -H "Authorization: Bearer $QM_KEY" "$QM/event-types/1/webhooks"
curl -X DELETE "$QM/event-types/1/webhooks/WEBHOOK_ID" -H "Authorization: Bearer $QM_KEY"- Always set a secret: without one, there is nothing to check a message against.
POST $QM/webhooksadds one for every event type of the key's owner, with the same body.- An answer about a webhook, and every list of them, carries its secret. Treat that answer like a password.
Each message is a POST with Content-Type: application/json, X-Cal-Webhook-Version: 2021-10-20 and X-Cal-Signature-256: the HMAC-SHA256 of the exact body, under your secret, in hex.
{"triggerEvent":"BOOKING_CREATED","createdAt":"2026-10-14T10:28:04.715Z",
"payload":{"uid":"BOOKING_UID","bookingId":1,"eventTypeId":1,"status":"ACCEPTED",
"title":"Intro Call between owner and Sam Lee",
"startTime":"2026-10-15T16:00:00Z","endTime":"2026-10-15T16:30:00Z",
"organizer":{ ... },"attendees":[ ... ],
"metadata":{"videoCallUrl":"https://meet.quantimeet.com/ROOM"},
"bookerUrl":"https://your-team.quantimeet.com", ...}}Check the signature
import hashlib, hmac
def from_quantimeet(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), (signature or "").encode())const crypto = require("node:crypto");
function fromQuantiMeet(rawBody, signature, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(String(signature || ""));
return a.length === b.length && crypto.timingSafeEqual(a, b);
}- Check the bytes you received, before parsing them: JSON written out again will not match. In Express, read the body with
express.raw({ type: "application/json" }); in Flask, withrequest.get_data(). - Answer with a 2xx quickly. Each message is sent once, while the booking is being made, so a slow answer slows the booking; QuantiMeet follows no redirect and does not retry.
- A cancellation's payload adds
cancellationReasonandcancelledBy, and has nometadata.
Limits
curl -si -H "Authorization: Bearer $QM_KEY" "$QM/me" | grep -i '^x-ratelimit'x-ratelimit-limit-default: 120
x-ratelimit-remaining-default: 119
x-ratelimit-reset-default: 60- Each key may make 120 calls a minute. Its answers carry the three headers above: the limit, the calls left in this minute, and the seconds until the count starts again. A 404 for a path the API does not have, or does not serve on your address, carries none.
- Over the limit, the key's calls answer 429 for a minute, then the count starts again. Wait for the reset; do not retry in a loop.
- Each key has its own budget: while one key waits, another key of the same team keeps working. Calls without a key share 120 a minute per address, on each team's address.
- Each team's limits are its own. There are no per-request fees and no overage charges.
{"status":"error","timestamp":"2026-10-18T01:48:23.170Z","path":"/v2/me",
"error":{"code":"ThrottlerException","message":"CustomThrottlerGuard - Too many requests. Please try again later.", ...}}Errors
An error answers with its HTTP status and a body like this one, for a call with no key:
curl "$QM/me"{"status":"error","timestamp":"2026-10-18T01:42:48.709Z","path":"/v2/me",
"error":{"code":"UnauthorizedException","message":"ApiAuthStrategy - No authentication method provided. ...", ...}}- 400 Something you sent is wrong; the error's message names each field.
- 401 A call that needs a key came with none, or with one that is wrong, expired, deleted or another team's.
- 403 The key's person may not do this, or the call needs a key (listing bookings).
- 404 No such item; "This endpoint is not available on a team host."; or, for open times, "Cannot GET" when the
cal-api-versionheader is missing or wrong. - 429 Over the limit: see Limits.
- 501 Handing a booking to another host, which QuantiMeet does not do.
- 500 and up Our side. Email info@quantimeet.com with the answer's
X-Request-Idheader.
Times and time zones
- Send every time with its offset:
2027-02-02T16:00:00Z, or2027-02-02T09:00:00-07:00for the same moment, never a bare2027-02-02T09:00:00. Searches for open times readstartandendin UTC. - Name zones the IANA way, like
America/Vancouver, never an abbreviation or an offset. Each attendee needs one: it sets the times in their emails.$QM/timezoneslists them, with no key. - British Columbia keeps UTC-7 all year from November 1, 2026, and QuantiMeet has the new rule. The first open time above, asked for with
timeZone=America/Los_Angeles, comes back as2027-02-01T23:00:00.000-08:00: the same moment, an hour earlier on the clock. Each winter, from November to March, BC is an hour ahead of the US West Coast.
Check the time zone data your own code uses. Each line must show -07:00; -08:00 means data older than BC's new rule. On Windows, type python for python3; Python there may need the tzdata package (pip install tzdata).
python3 -c "from datetime import datetime; from zoneinfo import ZoneInfo; print(datetime(2027,2,2,9,tzinfo=ZoneInfo('America/Vancouver')).isoformat())"
node -e "console.log(new Intl.DateTimeFormat('en-US',{timeZone:'America/Vancouver',timeZoneName:'longOffset'}).format(new Date('2027-02-02T16:00:00Z')))"More in Time zones.
What your address answers
These parts of the API: your profile (/me), event types, bookings, open times (/slots), schedules, your connected calendars and their busy times (/calendars), conferencing apps, destination and selected calendars, webhooks, and the time zone list. You connect a calendar and a video app in your dashboard, not through the API.
Anything else answers 404, "This endpoint is not available on a team host." That includes organizations, managed users, OAuth clients and platform tokens (a business on QuantiMeet is one team, with its own keys), making or refreshing a key (/api-keys), teams (/teams), and Stripe and verified-resource endpoints.
Still stuck? Email info@quantimeet.com. We answer support requests by email within one business day, and usually within hours.
Is something not working for everyone? The status page shows what our own checks see, every 5 minutes.