Docs.
Booqit is a booking backend you drive over the Model Context Protocol (MCP) or a plain REST API — group polls, one-to-one booking pages, and events, no lists-and-forms UI required. There are two first-class ways in: connect it to an assistant (OAuth, no keys to handle) or call it from your own code (API key). This page gets you to a first tool call in about a minute either way.
Quickstart
Using the Booqit app (marketplace)
Once Booqit lands in the ChatGPT app directory and the Claude connector directory, you add it the way you add any other app: open your assistant's app or connector gallery, search for Booqit, enable it, and authorize with your Booqit account (or create one in the same step). The app brings the scheduling and events tools with it: polls, booking pages, open slots, bookings, and events.
The directory listings aren't live yet. Until they are, add the connector yourself (next) and you get every tool today, including subscription, checkout, and account management.
Want all the tools now? Add the custom connector.
You don't have to wait for a directory listing. Add the full custom connector at:
https://mcp.booqit.online/api/mcpThis self-add endpoint carries the complete tool set — the scheduling tools plus subscription, checkout, and account management — and it works today. In ChatGPT, add it as a developer-mode connector; in Claude, add it as a custom connector; in any other MCP client, point the client at the URL. The step-by-step for each client is right below. For the difference between this endpoint and the narrower marketplace one, see Connect →.
Using an assistant? Connect it. (OAuth)
If you want Booqit inside ChatGPT, Claude, or any MCP client that speaks OAuth, you don't need an API key. You add the endpoint and authorize with your Booqit account.
- Add the endpoint. The full connector lives at:
https://mcp.booqit.online/api/mcp- ChatGPT— turn on developer mode (Settings → Apps & Connectors → Advanced settings), then create a connector with that URL.
- Claude — Settings → Connectors → Add custom connector, paste the URL.
- Any other MCP client — point it at the URL; Booqit publishes standard OAuth discovery metadata (
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server), registers clients dynamically, and requires PKCE (S256). No pre-registration, no client secret to request from us.
- Authorize. Your browser opens once: sign in (or create an account on the spot), review the consent screen, approve. Connectors typically request the coarse
booqitscope, which covers all operations; a client can narrow to granular scopes (polls:read,polls:write,pages:read,pages:write,bookings:write) instead. - That's it.The tools appear in your assistant and you talk in plain words — “find a time for the team next week” becomes a poll. Which tools you see depends on which endpoint you added; see Connect → for the three endpoints and why the self-add one is the full set.
One boundary worth knowing: anything involving payment hands you a hosted Stripe checkout link in the browser. Card details never pass through the chat.
Building your own thing? Use a key.
For a server, a backend job, or an agent you run yourself, an API key is the right credential — no browser hop, no per-user consent.
- Create an API key. Open the keys dashboard, create a key, and copy the secret. It's shown once — we store only a hash, so if you lose it you rotate the key rather than recover it. Keep it server-side; never ship it in client code or commit it.
- Point your MCP client at the endpoint. Authenticate with the key as a bearer token:
{ "mcpServers": { "booqit": { "type": "http", "url": "https://mcp.booqit.online/api/mcp", "headers": { "Authorization": "Bearer ak_your_secret_here" } } } } - Call a tool. The endpoint speaks JSON-RPC 2.0 over a single POST and exposes every operation below as an MCP tool. A raw
tools/callforcreate_pollwithcurl:
The response carries a text content block plus acurl -X POST https://mcp.booqit.online/api/mcp \ -H "Authorization: Bearer $BOOQIT_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "create_poll", "arguments": { "title": "Team sync", "options": [ { "start": "2026-07-10T09:00:00Z", "end": "2026-07-10T09:30:00Z" }, { "start": "2026-07-10T14:00:00Z", "end": "2026-07-10T14:30:00Z" } ] } } }'structuredContentobject with the new poll'sidand secretadminKey(returned once, to the creator only). Discover the tool set at any time with atools/listrequest.
Both paths hit the same operation registry — OAuth for assistants, keys for your own code. Neither is a second-class door.
Tools
These are the operations the endpoint exposes — the same set backs the REST routes, so the two can't drift. Each lists the scope a key must hold; ops marked subscription require an active subscription on the calling account.
| Tool | What it does | Scopes |
|---|---|---|
get_startedread | Start here. Returns the SINGLE next step in the Booqit funnel for the calling agent: 'connect' (anonymous — connect a Booqit account; creating the account happens inside the same OAuth connection step, and a signupUrl is included as a browser fallback), 'subscribe' or 'upgrade' (connected but not yet able to create — the response carries the exact next step: a billing tool to call where this endpoint exposes one, otherwise an `upgradeUrl` to open in a browser), or 'create' (able to create now — the response's `suggestedTools` lists the create_* tools you can use, e.g. create_poll / create_booking_page / create_event; pass a plain-language `brief` to compile, or explicit fields). Every tool it names is one this endpoint actually exposes. Public and read-only; it persists nothing and needs no authentication. You may pass a free-text `intent` describing what you want to build — it is ignored server-side (the next step does not depend on it) and is never stored. | |
draftread | Preview what Booqit would build from a plain-language brief WITHOUT creating anything (nothing is persisted — no row, no account). Pass `kind` ('poll', 'booking_page', or 'event') and a one-sentence `brief`; the server compiles a best-effort composed shape (title, time options / weekly availability / a fixed agenda, and answer-algebra questions). This is a preview that MAY NEED CORRECTION: always check `status` — "ok" means buildable as-is; "needs_clarification" means a required detail is missing or was dropped and you must supply it explicitly before creating (read `quality.issues` and `message` for exactly what to fix); "unavailable"/"failed" mean the compile could not run. Read-only and public — no auth, no state change, safe to retry. To build it, connect an account (see get_started) then call create_poll / create_booking_page / create_event with the SAME brief. Also pass back the returned `draftToken` as `draftToken` on that create call: it rebuilds EXACTLY the preview you were shown instead of composing the sentence again (a fresh compile of the same words can land on a different title or question set). The token is bound to this brief and timezone and expires in 24 hours; a create tool that does not take one simply composes the brief again, as before. | |
get_pollread | Read a poll's title, time options, and votes so far. | polls:read |
get_booking_pageread | Read a booking page's public config: host, duration, timezone, and availability. | pages:read |
get_eventread | Read an event's public state by id: title, location, notes, the overall start/end window, the DERIVED phase (upcoming / live / ended), the happenings timeline (each with its own location + how-to-get-in note), the announcements feed (host posts and automatic 'now'/feedback updates), the post-event feedback questions (with render descriptors telling you the control/options/bounds), and the live feedback decision block. The secret adminKey is never returned. Read-only — does not modify state; a public read needs no account (scope events:read when a key is presented). Safe to retry. | events:read |
get_my_eventread | The full owner view of one of YOUR OWN events, by event id — authorised by ownership (your connected Booqit account must own it, no adminKey needed). Returns the window, the DERIVED phase, the happenings timeline, the visible announcements feed, the feedback questions (with render descriptors), the feedback decision block, the keep-me-posted subscriber count, and the share link. Returns not_found for an unknown event, or permission_denied if you don't own it. The secret adminKey is never returned. | events:read |
update_my_eventwrite | Update one of YOUR OWN events — authorised by ownership (your connected Booqit account must own it, no adminKey needed). Pass a plain-language `instruction` (the intent path — e.g. 'push the afternoon session to 3pm and drop the feedback survey'); the server COMPILES it against the current event into a precise change. OR pass a structured `changes` patch directly (the deterministic alternative) with any of: title, description, location, notes, timezone; `happenings` (re-time or edit EXISTING agenda items by id — the event window is derived from them, so re-timing happenings is how you move it); and `questions` (replace the post-event feedback set wholesale — keep a question's `id` to preserve its already-collected answers, drop it to remove them). Pass at least one; if you pass both, `changes` wins. A time with no timezone is read in the event's timezone. An edit that would collapse the event window (an inverted happening, or every happening at one instant) is refused before any write. Adding or removing happenings is not available here — an instruction to do so comes back as needs_clarification. Pass `preview: true` to compile + validate + summarize the change WITHOUT applying it. A DESTRUCTIVE edit inferred from an `instruction` (dropping feedback questions/their answers, clearing a field, or changing a question's format so its answers no longer apply) is NOT applied in one shot: it returns a preview with confirmationRequired: true, and you re-send with `confirm: true` to apply it (the explicit `changes` path applies directly). Returns the updated owner view plus a plain-language `summary` of what changed. not_found for an unknown event, permission_denied if you don't own it. The secret adminKey is never returned. | events:write |
get_my_event_feedbackread | The post-event feedback summary for one of YOUR OWN events, by event id — authorised by ownership (your connected Booqit account must own it, no adminKey needed). Returns the respondent count plus, per feedback question, a decision block (winner/leaders, sum/mean/count, or — for a text question — the guests' written answers). Decisions are computed deterministically. No respondent name or email is ever returned; free-text answers ARE, since they are your own event's data (the public get_event hides them). Returns not_found for an unknown event, or permission_denied if you don't own it. | events:read |
get_slotsread | List a booking page's available slots by slug. Returns the page timezone, slot duration (minutes), and days each with an array of open {start,end} slots (ISO 8601 UTC). Availability already accounts for weekly hours, lookahead, minimum notice, buffers around existing bookings, blackout dates, and slots taken by confirmed bookings. Use to find a valid `start` before calling book_slot. Returns a structured not_found error envelope if the slug is unknown. Read-only — does not modify state; a public read needs no account (scope bookings:read when a key is presented). Safe to retry. | bookings:read |
list_bookingsread | List all bookings (confirmed and cancelled) for a booking page, for the page owner. Requires the page's slug and its secret adminKey. Returns the page config and the bookings, each with guest name, guest email, start/end (ISO 8601), notes, custom-question answers, status, and source. This is the owner's admin view — the adminKey authorizes it. Returns a structured error if the slug is unknown or the adminKey is wrong. | bookings:read |
list_my_bookingsread | List all bookings (confirmed and cancelled) for one of YOUR OWN booking pages, by page id. Authorised by ownership: your connected Booqit account must own the page — no adminKey needed. Returns the page config and the bookings, each with guest name, guest email, start/end (ISO 8601), notes, custom-question answers, status, and source. Returns not_found if the page id is unknown, or permission_denied if you don't own it. | bookings:read |
list_my_eventsread | List YOUR OWN events, by ownership — your connected Booqit account must own them, no adminKey needed. Each event returns its title, the overall start/end window, the DERIVED phase (upcoming / live / ended), counts (happenings, happenings still missing an end time, feedback questions, collected feedback, keep-me-posted subscribers, pending scheduled updates), and its /e/<id> share link. Paginated with `limit`/`offset` (defaults 50/0) and a `total`. No secret adminKey is ever returned. | events:read |
create_pollwrite | Create a group poll with a title and proposed time options. | polls:write |
create_booking_pagewrite | Create a booking page with fixed weekly availability guests book slots on. | pages:write |
create_eventwrite | Create an event page: a titled agenda of one or more fixed happenings (each a stated date + time, with its own location and how-to-get-in note), plus an announcements feed and optional post-event feedback questions. Provide EITHER a plain-language `brief` (one sentence — the server COMPILES it into a title, agenda, and feedback questions) OR pass `happenings[]`/`questions[]` directly; anything you pass explicitly overrides the compiled value. `timezone` is always required (a wrong zone misplaces every happening). At least one happening must result. Feedback questions are choose/text/number (never `when` — event times are fixed). STATE-CHANGING and PUBLICLY PUBLISHED: returns the new event id and a public share URL anyone with the link can open (no secret credential is returned to a caller; announce and read feedback through the owner tools). Requires an active subscription on the calling account — unlike polls/pages there is no one-time single-event credit (scope events:write). Retry-safety: pass an `idempotencyKey` and a retry with the same key returns the ORIGINAL event instead of creating a duplicate; without one, a retry creates another event — so retry only with the key, or only after a transport failure that returned no id. | events:write |
resolve_pollwrite | Lock the winning time on a poll (the organiser's decision). Authorised by ownership (your connected account owns it) OR the poll's secret adminKey. Pass a plain-language `instruction` (e.g. 'lock Thursday 2pm') — the server compiles it against the poll's own options — OR pass `winningOptionId` directly (a TimeOption id from get_poll); if you pass both, winningOptionId wins. Idempotent: re-locking the SAME option returns already_resolved. Re-locking a DIFFERENT option is allowed until the poll has become an event, after which the decision is frozen. This records the decision only — call create_event_from_poll to spin up the living event page and carry the voters forward. not_found for an unknown poll, permission_denied if you neither own it nor hold its adminKey. Requires scope polls:write. | polls:write |
set_poll_deadlinewrite | Set, move, clear, or trigger a poll's voting DEADLINE — the moment the poll closes itself, fixes the winning time from the replies already in, and (when the plan allows) creates the event page and tells everyone. Say it in plain words with `instruction`: 'give them until Friday evening', 'push it back a day', 'close it now', 'no deadline'. The structured `closesAt` (an ISO datetime) and `clear` are the explicit alternatives; if you pass one, the instruction is not compiled. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. A moment that has already passed closes the poll ON THIS CALL and returns what it decided. Moving the deadline reschedules the close; clearing it leaves the poll open indefinitely. A poll that closed without fixing a time can be reopened by giving it a new deadline; once a time IS fixed the poll is settled — change the event with update_my_event instead, so the people carried across are told. not_found for an unknown poll, permission_denied if you neither own it nor hold its adminKey. Requires scope polls:write. | polls:write |
create_event_from_pollwrite | Turn a decided poll into its living event page and carry the voters forward. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. If the poll isn't locked yet, pass a plain-language `instruction` ('lock Thursday 2pm') or the structured `winningOptionId` and it is resolved inline; a poll with no decision and no lock instruction returns needs_clarification. The event's single happening is the winning time; title/location/timezone are carried from the poll unless overridden. Every voter who gave an email AND answered yes or maybe to the winning time becomes a keep-me-posted subscriber and receives the outcome announcement (no|maybe-to-that-time and names-only voters are not subscribed). STATE-CHANGING and PUBLICLY PUBLISHED: returns the new event id and a public share URL. Idempotent — resolving the same poll twice returns the ORIGINAL event, never a duplicate — and says so explicitly: `status` is 'created' on the call that minted it and 'already_exists' (with `alreadyExisted:true`) on every later call for the same poll, reporting the SAME carriedSubscribers count, so a retry is safe and its data doesn't change under you. Requires an active subscription (scope events:write); pass `idempotencyKey` for retry-safety. | events:write |
resume_workflowwrite | Finish a paused create request. When create_poll / create_booking_page / create_event refused because you weren't connected, subscribed, or were at your plan limit, it handed back a `continuationId` capturing exactly what you asked for. After you complete the step it named (connect your account, or pay at the checkout link), call this with that id to REPLAY the original request and get its result. Single-use: once it succeeds the id is spent. Returns the created artifact's result, or a structured reason if it expired, was already used, or still needs payment. | |
announcewrite | Post an announcement to an event's feed and notify its keep-me-posted subscribers. Requires the event's secret adminKey. Pass `scheduledFor` (ISO 8601) to schedule it for later (it stays hidden until then); omit for an immediate post. Returns the new announcement id plus an honest reach report: the current subscriber count and whether anyone will actually be emailed (guests are anonymous, so an event with no subscribers still shows the post but reaches no inbox). | events:write |
announce_to_my_eventwrite | Post an announcement to one of YOUR OWN events' feed and notify its keep-me-posted subscribers — authorised by ownership (your connected Booqit account must own the event, no adminKey needed). Pass `scheduledFor` (ISO 8601) to schedule it for later (it stays hidden until then); omit for an immediate post. Returns the new announcement id plus an honest reach report: the current subscriber count and whether anyone will actually be emailed (guests are anonymous, so an event with no subscribers still shows the post but reaches no inbox). Returns not_found for an unknown event, or permission_denied if you don't own it. | events:write |
submit_event_feedbackwrite | Submit post-event feedback for an event, by event id — no account needed (guest). participantName is optional (anonymous is fine). answers is a map of { questionId: answer } validated against the event's feedback questions (choose/text/number). Feedback is a post-event act: it returns feedback_not_open before the event has ended and feedback_closed once the post-event window has passed. Returns not_found for an unknown event, or a structured answer error (required_missing / invalid_option / out_of_range / unknown_question) carrying the offending questionId. No names or answers are echoed back. | events:read |
subscribe_to_eventwrite | Subscribe an email to an event's keep-me-posted updates, by event id — no account needed (guest). Idempotent: subscribing the same email twice is a no-op. Returns only { ok: true } — no email, token, or id is ever returned, and it does not reveal whether the email was already subscribed. Returns not_found for an unknown event. | events:read |
unsubscribe_from_eventwrite | Unsubscribe from an event's keep-me-posted updates using the opaque unsubscribe token from the email — no account needed (guest). Idempotent: an unknown or already-used token is a no-op. Always returns { ok: true } and never reveals whether the token matched. | events:read |
unsubscribe_me_from_eventwrite | Unsubscribe YOURSELF from an event's keep-me-posted updates, by event id — using your connected Booqit account (no token or email argument needed). Idempotent: if you weren't subscribed it is a no-op. Always returns { ok: true } and never reveals whether you were on the list. Returns not_found for an unknown event. To unsubscribe via the one-click link in an email instead, use unsubscribe_from_event with its token. | events:read |
reschedule_bookingwrite | Move a confirmed booking to a new open slot on the same page. | bookings:write |
reschedule_my_bookingwrite | Move a confirmed booking on one of YOUR OWN booking pages to a new start time, by booking id. Authorised by ownership: your connected Booqit account must own the page — no adminKey needed. The new `start` must exactly match a currently-available slot (ISO 8601); the page duration sets the new end. The move can never double-book. Reschedule creates a NEW booking at the new slot and returns its new `id`; `replacesBookingId` is the old id (the input `bookingId`), now cancelled. Returns the moved booking, not_found if the booking id is unknown, or permission_denied if you don't own it. | bookings:write |
book_slotwrite | Book a slot on a booking page by slug. `start` must exactly match an available slot (ISO 8601) from get_slots; the page duration sets the end. Provide guestName and guestEmail; `notes` is optional; `answers` maps the page's custom-question ids to values (required questions are enforced). Pass an optional `idempotencyKey` so a retry returns the original booking instead of a duplicate. Returns the created (or replayed) booking id and start/end, or a structured error envelope: not_found (unknown page), invalid_start (a start that never matched a generated slot), outside_availability (a time outside the page's window), slot_taken (a genuinely offered slot already claimed), or an answer error (required_missing, invalid_option, out_of_range, unknown_question). Side effect: on success the guest is emailed a booking confirmation (write, open-world). Requires scope bookings:write; idempotent when you pass `idempotencyKey`, so safe to retry. | bookings:write |
cancel_bookingwrite | Cancel a booking. Requires the booking id, the page's slug, and the page's secret adminKey (the booking must belong to that page). Cancelling frees the slot and notifies the guest. Returns { ok: true, status } on success, or a structured error if the booking/page is not found or the adminKey is wrong. This is destructive: a cancelled booking cannot be restored, only re-booked. | bookings:write |
cancel_my_bookingwrite | Cancel a booking on one of YOUR OWN booking pages, by booking id. Authorised by ownership: your connected Booqit account must own the page the booking belongs to — no adminKey needed. Cancelling frees the slot and notifies the guest. Returns { ok: true, status } on success, not_found if the booking id is unknown, or permission_denied if you don't own it. Destructive: a cancelled booking cannot be restored, only re-booked. | bookings:write |
vote_on_pollwrite | Cast a vote on a poll. `responses` maps time-option ids (from get_poll) to 'yes', 'maybe', or 'no' (responses for unknown ids are ignored). `answers` maps custom-question ids (from get_poll) to that question's answer: an option-id or array of option-ids for a choice, a string for text, a number for a quantity, or a {slotId: yes|maybe|no} map for a time question. Answers are validated against each question's primitive; a bad or missing-required answer returns a structured error (with questionId). Provide the voter's name and an optional note. Returns { ok: true } on success. A poll can carry a voting DEADLINE (get_poll reports `closesAt`): once it passes, this returns poll_closed and the time is fixed from the replies already in — read get_poll for the decision and the event link. A repeat vote under the same voter name REPLACES that voter's previous responses rather than adding a duplicate, so it is safe to retry. Requires scope polls:write. | polls:write |
summarize_pollread | Host-only smart results for a poll. ALWAYS returns `decisions` — the same deterministic decision block get_poll returns (per-time-option yes/maybe/no counts, and per question the winner/leaders, sum/mean/headcount, collected notes, or best slot) — so you can act on the outcome without a second call. On top of that floor it adds a plain-language `summary` and, for free-text (text) questions ONLY, the answers grouped into decision-ready clusters (label + count + which answers belong) with flags for notes that need attention. Requires the poll's adminKey. Counts are computed deterministically from answer membership. If no free-text answers exist or the grouping is unavailable, the decisions block is still returned. Nothing is stored. | polls:read |
summarize_my_pollread | Smart results for one of YOUR OWN polls, by poll id. ALWAYS returns `decisions` — the same deterministic decision block get_poll returns (per-time-option yes/maybe/no counts, and per question the winner/leaders, sum/mean/headcount, collected notes, or best slot) — so you can act on the outcome without a second call. On top of that floor it adds a plain-language `summary` and, for free-text (text) questions ONLY, the answers grouped into decision-ready clusters (label + count + which answers belong) with flags for notes that need attention. Authorised by ownership — your connected Booqit account must own the poll, no adminKey needed. Counts are computed deterministically. Returns not_found for an unknown poll, permission_denied if you don't own it, or payment_required if the plan isn't active. Nothing is stored. | polls:read |
list_plansread | List the whole pricing ladder: the genuinely-free floor (`free`: €0, one booking page, no subscription), the two self-serve subscription plans (personal, business), and the one-time single-artifact purchases (buy a single poll or a single booking page WITHOUT a subscription). Each priced entry carries its Stripe price id (null when not yet configured), amount in minor units (cents; null when unset), and currency. Public and read-only — no authentication needed. Pass an option's `id` to create_checkout_session to get a payment link. There are no trials. `flexOptions` lists options we're gauging demand for that are NOT yet purchasable (pay-per-booking): each carries its anchor amount but no price id and available:false — register interest via register_flex_interest rather than checkout. | |
register_flex_interestwrite | Register interest in the pay-per-booking option (a small per-confirmed-booking charge, no subscription) — an INTEREST signal, not a purchase. Public and anonymous-friendly: `email` is optional (omit for an anonymous signal), and `context` is an optional non-sensitive tag for segmentation. Records one interest row and returns { ok: true }. Use this instead of create_checkout_session for the pay-per-booking option surfaced in list_plans.flexOptions (it is not yet purchasable). | |
get_subscriptionread | Get the calling account's billing state: effective subscription status (active | past_due | canceled | inactive | unknown — there are no trials), the plan, whether an active subscription is in force, any UNUSED one-time credits (a single poll / single booking page bought without a subscription), and whether the caller can currently create each artifact (active subscription OR an unused credit). Also reports usage: polls + booking pages created vs the plan's resource cap. Includes the billing URL to subscribe or buy. Authenticated; does not itself require a subscription. | |
get_dashboardread | Get the calling account's dashboard digest: what needs attention now (polls with responses waiting on a decision, confirmed bookings within 24 hours, quiet polls, billing lapses, events awaiting feedback or with a scheduled update but no audience), the upcoming-bookings agenda merged across all booking pages, a per-poll summary (leading time, response count, tie hint), a per-page summary (upcoming and confirmed booking totals), a per-event summary (derived phase, happening/feedback/subscriber counts, share link), and billing state with usage against the plan cap. Authenticated; does not itself require a subscription. | |
create_checkout_sessionwrite | Get a Stripe Checkout payment LINK. `plan` is one of: 'personal' or 'business' (a subscription) — or 'single_poll' / 'single_booking_page' (a ONE-TIME payment that grants a single-use credit to create one poll or one booking page without a subscription). Returns { checkoutUrl } to open in a browser. This only returns a link — it never charges a card or stores card data; payment happens on Stripe's hosted page. Works for lapsed accounts (no active subscription needed to pay). If the account already has an active STRIPE-BACKED subscription this returns { error: 'already_subscribed', nextTool: 'change_plan' } instead of a link — switching plans is change_plan, never a second checkout. An account on a COMPLIMENTARY (manually granted, non-Stripe) entitlement is DIFFERENT: it still gets a real link, plus { compEntitlement: true, currentPlan, message } explaining that paying starts a real subscription which replaces the complimentary grant — such an account has no Stripe subscription, so change_plan cannot serve it. Use list_plans to see prices first. | |
change_planwrite | Change the calling account's EXISTING subscription: `target` is 'personal' or 'business' (upgrade/downgrade the plan) or 'cancel' (schedule cancellation at the end of the current period). Routes the change through Stripe and settles via the billing webhook, so every surface converges on one truth. Requires an existing STRIPE subscription (active or lapsed) — to START a subscription, use create_checkout_session instead. An account whose access is COMPLIMENTARY (granted directly, no Stripe subscription behind it) has nothing to change here and gets { error: 'comp_entitlement', nextTool: 'create_checkout_session' }, which can genuinely start a paid subscription for it. Returns a confirmation and a billing-portal link to review the change. | |
get_accountread | Read the calling account's self-serve state in one call: profile (name, email, verified, created, pending-deletion), billing snapshot (effective status active|past_due|canceled|inactive|unknown, plan, tier, active subscription, any unused single-purchase credits, whether a Stripe customer exists, current period end), and usage (polls, booking pages, confirmed bookings, resources used vs the plan cap). Read-only — use manage_account to open the billing portal, edit the profile, or request deletion, and create_checkout_session to subscribe or change plan. Authenticated; not subscription-gated (a lapsed account can still read its own state). | |
manage_accountwrite | Initiate a self-serve account action for the calling account. `action` is one of: 'open_billing_portal' (returns a Stripe Billing Portal URL to manage payment method / cancel / view invoices — requires an existing customer, so subscribe via create_checkout_session first); 'update_profile' (set the display `name`); or 'request_deletion' (GDPR self-serve deletion — deactivates the account and schedules erasure; requires `confirm: true`). To subscribe or change plan, use create_checkout_session (payment routes through Stripe), NOT this op. Authenticated; not subscription-gated. | |
export_account_dataread | Export the calling account's data as one portable JSON package (GDPR subject-access / portability): profile, subscription, single purchases, tenants, API key metadata (NO key secrets), owned polls (with questions + votes) and booking pages (with bookings) — PII decrypted as the owner sees it — plus bookings made as a guest on other hosts' pages, referrals, and refunds. Internal operational/audit records and surrogate tenant/owner ids are omitted; no token or key-hash secrets are ever included. Read-only; authenticated (exports only the caller's OWN data); not subscription-gated. | |
delete_accountwrite | Delete the calling account (GDPR self-serve erasure). Deactivates the account immediately and schedules an irreversible hard-erase of ALL its data at the end of a grace window; logging back in before then cancels it. Requires an explicit `confirm: true` (destructive). Use export_account_data first to take a copy of the data out. Authenticated; not subscription-gated. Returns `eraseDueAt` — when the erase becomes irreversible. | |
archive_my_eventwrite | Archive one of YOUR OWN events by id — tidy up an event you no longer need. Authorised by ownership (your connected Booqit account must own it). Requires confirm:true. Archiving removes the event from your active-resource count (it stops counting toward your plan limit) and hides it from your default lists (list_my_events and the dashboard return active events only). The public event link KEEPS WORKING — archiving is not a takedown, so guests who already hold the link are never stranded. There is no self-serve unarchive tool: contact support to restore it. Idempotent — archiving an already-archived event is a safe no-op. Returns not_found for an unknown event, permission_denied if you don't own it, or a validation error if confirm isn't set. | events:write |
archive_my_pollwrite | Archive one of YOUR OWN polls by id. Authorised by ownership; requires confirm:true. Archiving removes the poll from your active-resource count and from your default lists (the dashboard returns active polls only). The public poll link KEEPS WORKING — archiving is not a takedown, so people who already hold the link can still open and vote. There is no self-serve unarchive tool: contact support to restore it. Idempotent. Returns not_found for an unknown poll, permission_denied if you don't own it, or a validation error if confirm isn't set. | polls:write |
archive_my_booking_pagewrite | Archive one of YOUR OWN booking pages by id. Authorised by ownership; requires confirm:true. Archiving removes the page from your active-resource count and from your default lists (the dashboard returns active pages only). The public booking link KEEPS WORKING and the page still accepts bookings — archiving is not a takedown, so confirmed bookings and invitees holding the link are never stranded. There is no self-serve unarchive tool: contact support to restore it. Idempotent. Returns not_found for an unknown page, permission_denied if you don't own it, or a validation error if confirm isn't set. | pages:write |
delete_my_bookingwrite | PERMANENTLY delete a booking on one of YOUR OWN booking pages, by booking id. Authorised by ownership: your connected Booqit account must own the page the booking belongs to. Requires confirm:true. Unlike cancel_my_booking (which keeps a cancelled record), this hard-deletes the row — it cannot be restored. The slot is freed. Idempotent: deleting an already-gone booking returns not_found (the correct terminal state). Returns not_found for an unknown booking, permission_denied if you don't own it, or a validation error if confirm isn't set. | bookings:write |
remove_event_subscriberwrite | Remove a keep-me-posted subscription from one of YOUR OWN events — the event owner removing a subscriber. Identify the subscriber by email, by connected-account userId, or by subscriberId (at least one). Works for BOTH guest-email and connected-account subscriptions. NO unsubscribe token is needed or ever returned. Requires confirm:true. Returns { removed, subscriberCount } — the count reflects the removal immediately. Idempotent: removing an already-gone or never-subscribed target returns removed:false with the unchanged count. Returns not_found for an unknown event, permission_denied if you don't own it, or a validation error if confirm isn't set or no identifier is given. | events:write |
list_event_subscribersread | List the keep-me-posted subscribers on one of YOUR OWN events — the event owner reviewing their recipient list before removing someone. Authorised by ownership: your connected Booqit account must own the event. Each entry carries { subscriberId, type, email, createdAt }: `type` is 'guest_email' or 'connected_account' (a subscriber whose address matches a real account), and `email` is ALWAYS MASKED (e.g. j***@e***.com) — the raw email, the email_key, and the unsubscribe token are NEVER returned. Read-only. Pass a subscriberId from here to remove_event_subscriber. Returns not_found for an unknown event or permission_denied if you don't own it. | events:read |
get_profileread | Read the calling account's scheduling profile — the details a poll, booking page, or event is created from so you are never asked for them again: the organizer name shown on your artifacts (hostName), organization, timezone, locale, a short bio, your avatar URL, your contact email and phone, and your defaults (default meeting length, location, meeting link) and notification preferences. Owner-only and read-only — use update_profile to change any of it. Returns hasProfile:false with a null profile when you have not set one yet. Authenticated; not subscription-gated. | |
update_profilewrite | Update your scheduling profile from a plain-language description of yourself — e.g. 'I run Katoa GmbH, I'm in Zurich, call me Tristan, default to 30-minute calls'. The server turns that into the right fields (organizer name, organization, timezone, locale, bio, contact email/phone, default meeting length/location/link, notification preferences). You can also pass an explicit `patch` object to set fields directly; anything in `patch` overrides the value compiled from your description. By default this returns a PREVIEW of what would change and saves nothing — re-call with confirm:true to save. Once set, your polls, booking pages, and events are created from these details so you are never asked for your name or timezone again. Owner-only; authenticated; not subscription-gated. | |
list_my_organizationsread | List the organizations you belong to, with the organizationId every other organization tool needs. Each entry carries the org's name, your role in it, the owner's plan, seat usage, and the member count. Call this FIRST when you need an organizationId — for get_organization, list_members, invite_member, change_member_role, remove_member, update_organization, transfer_ownership, set_org_seats or open_org_billing_portal. Returns only organizations you are a member of; it never enumerates other people's organizations. | org:read |
get_organizationread | Read one of your organizations — its name, plan, your role in it, and its seat usage (members + pending invites vs the plan's included seats). You must be a member of the organization. Pass its organizationId. | org:read |
list_membersread | List the members of an organization (name, email, role) and its pending invitations (masked email, role). You must be a member of the organization. Pass its organizationId. Invite tokens and raw emails are never returned. | org:read |
update_organizationwrite | Update an organization's settings. Pass a plain-language `instruction` (e.g. 'rename the org to Acme Team') OR a structured `changes` patch with `name`. Today only the organization name can be changed. Requires the admin or owner role. Pass `preview: true` to validate without writing. | org:write |
invite_memberwrite | Invite people to an organization. Pass a plain-language `instruction` (e.g. 'invite alex@acme.io as an admin and sam@acme.io') OR a structured `invites` array of {email, role}. Roles are admin or member (never owner — use transfer_ownership). Requires the admin or owner role. Each invite uses a seat on the owner's Business plan; running out of seats returns seat_limit_reached, and a non-Business owner is asked to upgrade. Pass `preview: true` to resolve the invites without sending them. | org:write |
change_member_rolewrite | Change a member's role in an organization (admin or member). Pass a plain-language `instruction` (e.g. 'make alex@acme.io an admin') OR a structured `change` of {memberRef, role}, where memberRef is the member's email, name, or user id. Requires the admin or owner role. The owner's role can't be changed here (use transfer_ownership), and you can't demote the last remaining admin. Pass `preview: true` to resolve without applying. | org:write |
remove_memberwrite | Remove a member from an organization, freeing their seat. Pass a plain-language `instruction` (e.g. 'remove sam@acme.io') OR a structured `memberRef` (email, name, or user id). Requires the admin or owner role. The owner can't be removed (use transfer_ownership). This is destructive: without `confirm: true` it returns a preview; re-send with `confirm: true` to apply. `preview: true` also returns the preview without writing. | org:write |
transfer_ownershipwrite | Transfer ownership of an organization to another member. Only the current owner can do this. Pass `newOwnerRef` (the member's email, name, or user id) and `confirm: true` to apply — without confirm you get a preview. The former owner becomes an admin. This is irreversible except by the new owner transferring it back. | org:write |
accept_invitewrite | Accept an organization invitation and join the org. You must be signed in with your Booqit account. Pass the invite `token` you were sent. Idempotent — accepting twice just returns the organization. An invalid or expired token returns not_found. | |
submit_support_requestwrite | Raise a support request with the Booqit team — a ticket lands in the same queue the website contact form and the support inbox use, and you can track it with list_my_support_requests / get_support_request. Pass a `subject` and a `message`. If you are signed in, the reply goes to your account email automatically; if not, pass an `email` so the team can reply. Returns the created ticket with its id and status (open). | |
list_my_support_requestsread | List YOUR OWN support requests, newest first, with each ticket's subject, status (open|answered|closed), and when it was raised. Authorised by your connected Booqit account — you only ever see your own tickets. Use get_support_request for the full detail of one. | |
get_support_requestread | Read one of YOUR OWN support requests by id — its subject, full message, status (open|answered|closed), and when it was raised. Authorised by ownership: your connected Booqit account must own the ticket. Returns not_found for an unknown id, or permission_denied if the ticket is not yours. | |
close_support_requestwrite | Close one of YOUR OWN support requests by id — mark a ticket you no longer need help with as resolved so it leaves the support team's open queue. Authorised by ownership: your connected Booqit account must own the ticket. Idempotent — closing an already-closed request is a safe no-op that returns alreadyClosed:true. Closing does NOT delete the request: it stays readable via list_my_support_requests / get_support_request with status 'closed'. There is no reopen tool — if you need help again, reply to the team's email or raise a new request with submit_support_request. Returns not_found for an unknown id, or permission_denied if the request is not yours. | |
set_org_seatswrite | Set how many extra (paid) seats your organization has on top of the Business plan's included seats. Pass a plain-language `instruction` (e.g. 'buy 3 more seats' or 'set us to 10 seats total') OR a structured `seats` number (the absolute count of PURCHASED add-on seats). Only the organization owner can do this. Pass `confirm: true` to apply the charge — without it you get a preview. Requires an active Business subscription; running below your current members is refused. | org:write |
open_org_billing_portalwrite | Open the Stripe billing portal for your organization to manage the subscription that funds its seats — invoices, payment method, plan, cancellation. Only the organization owner can do this. Returns a URL to open in a browser. | org:write |
invite_to_pollwrite | Invite people to a poll by email so you can later see who hasn't replied and remind only them. Pass a plain-language `instruction` (e.g. 'invite alex@acme.io and sam@acme.io') OR a structured `emails` array. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. Idempotent — re-inviting the same address is a no-op (reported as alreadyInvited). The invited email is stored encrypted and is never returned on any read. Pass `preview: true` to resolve who WOULD be invited without sending. not_found for an unknown poll, permission_denied if you neither own it nor hold its adminKey. Requires scope polls:write. | polls:write |
list_poll_inviteesread | List the people invited to your poll and whether each has responded. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. Returns each invitee's email, when they were invited, whether they've responded, and any reminder history. not_found for an unknown poll, permission_denied otherwise. Requires scope polls:read. | polls:read |
list_poll_non_respondersread | List the invitees to your poll who have NOT yet responded — the exact people to remind. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. A match is by email, so someone who voted counts as a responder; note an invitee who voted without giving an email can't be matched and will still appear here. not_found for an unknown poll, permission_denied otherwise. Requires scope polls:read. | polls:read |
remind_poll_non_responderswrite | Send a reminder to ONLY the people who haven't responded to your poll (never the whole invitee list), via email. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. Pass an optional `message` shown in the reminder. The reminder is recomputed when it sends, so anyone who replies in the meantime is automatically left out. Returns a truthful delivery report (queued / no_recipient when everyone answered / no_transport when email isn't configured). Pass `preview: true` to see who would be reminded without sending. not_found for an unknown poll, permission_denied otherwise. Requires scope polls:write. | polls:write |
finalize_pollwrite | Finalize a poll in one step: pick the winning time, create its event, carry the voters AND invitees into the event as subscribers, and optionally notify them. Authorised by ownership (your connected account owns the poll) OR the poll's secret adminKey. Pass `optionId` (a TimeOption id from get_poll) OR a plain-language `instruction` ('lock Thursday 2pm'); if the poll is already decided that decision is used. Set `createEvent: false` to only lock the decision. Set `notifyInvitees: true` to include invitees in the outcome announcement (otherwise they're subscribed for future updates but not back-blasted). Idempotent — re-running returns the ORIGINAL event. Returns the decision, the event id + public share URL, how many people were transferred, and how many notifications were queued. not_found for an unknown poll, permission_denied otherwise, needs_clarification if no winning time can be resolved. Requires scopes polls:write + events:write. | polls:writeevents:write |
get_capabilitiesread | Discover what this Booqit endpoint can do. Returns the API version (semver), the MCP protocol version, and a map of feature flags (true/false) reflecting which capabilities are ACTUALLY available here — polls, events, booking pages, event feedback/subscriptions, email + push notifications, organizations, seats, user profile, social login, poll invitations, and support. Call it before assuming a feature exists. Also returns releaseNotesUrl (the human changelog); call get_release_notes for the notes in-band. Public and read-only; no authentication needed. | |
get_release_notesread | Return the most recent Booqit release notes (the published changelog) in-band: each entry carries its version, date, and grouped changes (Added / Changed / Fixed / Security). Pass `limit` (default 5, max 20) to control how many recent releases you get. Public and read-only. Also see get_capabilities for what is available now, and releaseNotesUrl for the human page. |
REST
Every tool is also reachable as a plain REST route under /api, authenticated with the same bearer key. Creating a poll over REST is a direct POST — the request body is the tool's arguments, and the response is its result:
curl -X POST https://booqit.online/api/polls \
-H "Authorization: Bearer $BOOQIT_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Team sync",
"options": [
{ "start": "2026-07-10T09:00:00Z", "end": "2026-07-10T09:30:00Z" },
{ "start": "2026-07-10T14:00:00Z", "end": "2026-07-10T14:30:00Z" }
]
}'A booking page's open slots are a public read — list them by slug without a key:
curl https://booqit.online/api/bookings/pages/$SLUG/slotsScopes & rotation
Keys carry scopes; each tool above names the scopes it needs. Mint narrow keys for servers that only read (polls:read, pages:read) and separate keys that can write (polls:write, pages:write, bookings:write). A dashboard-issued key can also manage other keys (keys:write). Rotate a key to issue a replacement while the old one keeps working through a short grace window, then revoke the old one once your services have switched over.
Keys and OAuth grants use the same scope names. A tool that needs polls:write needs it whether the caller is your server with a key or an assistant a user authorized.
New here? Start with what intent-first means and how you actually use it.
Stuck? See support or review the terms and privacy policy.