Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 7385a6c1c47153b1728160ff
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
public/llms.txt
# Book by OpenApps
> A free booking page for one-on-one meetings. A free alternative to Calendly, Cal.com and SavvyCal, with no account, no paid plan and no limit on pages or bookings. The owner sets weekly hours, days off, a meeting length and a meeting link (Google Meet, Zoom or Teams); people open the page, pick an open time in their own time zone and get the link. Guests can move or cancel their booking. Owners can subscribe to their bookings as a calendar feed. Book does not read calendars and does not send emails.
Web app: https://book-free.openapps.xyz/
A booking page lives at https://book-free.openapps.xyz/<handle>
OpenAPI description: https://book-free.openapps.xyz/openapi.json
Plain-text overview: https://book-free.openapps.xyz/index.md
Part of OpenApps, the app store of free alternatives: https://free-apps.openapps.xyz/
## Links you can send a person
- https://book-free.openapps.xyz/<handle> opens the booking page. Add any of these:
- ?date=2026-10-21 starts on that day (if it has open times).
- ?tz=Europe/London shows times in that zone (default: the browser's zone).
- ?name=Alex%20Kim&email=alex@example.com fills in the booking form.
- manageUrl (from a booking) opens that booking on any device, where the person can add it to a calendar, move it or cancel it.
- adminUrl (from POST /pages) opens the owner's dashboard on any device: bookings, settings, the calendar feed.
## API
Base URL: https://functions.bounded.sh/apps/7385a6c1c47153b1728160ff/api
No key, no account. JSON in, JSON out. CORS is open.
Anyone can read a page's open times and book one.
The ownerKey returned when a page is made shows its bookings and changes it. Send it in the x-owner-key header.
The cancelKey returned with each booking reads, moves and cancels that booking. Send it in the x-cancel-key header.
Both come back once. Keep them private.
### Make a booking page
POST /pages
{"handle": "sam-lee", "name": "Sam Lee", "timezone": "America/New_York",
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"title": "Intro call", "durationMin": 30,
"hours": {"weekdays": ["09:00-12:00", "13:00-17:00"], "sat": "10:00-12:00"},
"daysOff": ["2026-12-24", "2026-12-28 to 2026-12-31"],
"noticeHours": 4, "horizonDays": 60, "bufferMin": 15,
"description": "A quick call to see if we are a fit.",
"questions": ["What would you like to talk about?", {"label": "Company", "required": true}]}
- name, timezone and meetingUrl are required. timezone is the IANA zone the hours and days off are in.
- handle is the page address: 3 to 32 lowercase letters, numbers and dashes. Leave it out to get one made from the name. A taken handle is 409.
- meetingUrl must start with https://. It is shown only to people who book (and to owners).
- durationMin is 15, 30, 45 or 60 (default 30). title defaults to "30 minute call".
- hours maps weekdays (mon, tue, wed, thu, fri, sat, sun, or weekdays, weekend, everyday) to ranges "HH:MM-HH:MM" in 15-minute steps, as a list or one string separated by commas. Days left out are closed. Default: weekdays 09:00-17:00.
- daysOff: dates when nobody can book, as "YYYY-MM-DD", "YYYY-MM-DD to YYYY-MM-DD" or {"from": "YYYY-MM-DD", "to": "YYYY-MM-DD"}, inclusive, in the page's time zone. Up to 50 ranges. Ranges that already ended are dropped.
- noticeHours (0 to 720, default 4): the minimum time before a meeting that it can be booked.
- horizonDays (1 to 365, default 60): how far ahead people can book.
- bufferMin (0, 15, 30, 45 or 60, default 0): a break kept free after each meeting.
- questions: up to 5, asked of the person booking. Name and email are always asked.
Returns handle, url (the booking page to share), adminUrl (opens the owner's dashboard in a browser), ownerKey, calendarFeedUrl and the page.
Give the person the url to share and the adminUrl to keep.
### Read a page
GET /pages/<handle>
Returns name, title, description, durationMin, meeting (the kind of call, like "Google Meet"), timezone, hours, hoursSummary, daysOff (as [{from, to}], current and future only), noticeHours, horizonDays, bufferMin and questions. Not the meeting link.
### Open times
GET /pages/<handle>/availability?from=2026-10-21&days=14&tz=Europe/London
- tz: the time zone to show times in (default: the page's). Use the zone of the person who will meet.
- from: the first date, in tz (default: today). days: 1 to 62 (default 14).
Returns slots: open start times as ISO times in tz, like "2026-10-21T15:00:00+01:00". Each meeting lasts durationMin minutes. Times start every durationMin minutes (at most every 30) within the owner's hours, never on a day off.
### Book a time
POST /pages/<handle>/bookings
{"start": "2026-10-21T15:00:00+01:00", "name": "Alex Kim", "email": "alex@example.com",
"note": "About the design review", "answers": {"Company": "Acme"}}
- start must be one of the open slots. A time without an offset ("2026-10-21T15:00") is read in "timezone" if you send one, else in the page's time zone.
- name and email are required. note is optional (up to 1000 characters).
- answers: {"<question>": "answer"}, or a list of answers in question order. Required questions must be answered.
- A time that is not open, or was just taken, is 409. Two bookings can never share or overlap a time.
Returns bookingId, start, end, label, meetingUrl, icsUrl (a calendar file), googleCalendarUrl (adds the event in Google Calendar), manageUrl (the person's private page to see, move or cancel the booking) and cancelKey.
Book sends no email: give the person the meeting link, the calendar file or Google Calendar link, and the manageUrl.
### A booking (header x-cancel-key: <cancelKey>)
- GET /bookings/<bookingId> returns the booking, the page, the meeting link (while booked), icsUrl and manageUrl. A moved booking has status "cancelled" and movedTo, the id of the booking that replaced it.
- GET /bookings/<bookingId>/ics?c=<cancelKey> returns a calendar file (text/calendar). The key may also go in x-cancel-key.
- POST /bookings/<bookingId>/reschedule {"start": "2026-10-22T10:00:00+01:00"} moves the booking to another open time in one step: it books the new time with the same name, email, note and answers, and frees the old time. Returns the new booking like POST /pages/<handle>/bookings, with the same cancelKey and movedFrom. The old manageUrl forwards to the new booking in a browser.
- POST /bookings/<bookingId>/cancel cancels it and frees the time. The page's owner can cancel with x-owner-key instead.
### Owner routes (header x-owner-key: <ownerKey>)
- GET /pages/<handle>/bookings returns calendarFeedUrl and the upcoming, past and cancelled bookings, each with start, end, label, name, email, note and answers, in the page's time zone. Moved bookings are in cancelled, with movedTo.
- PATCH /pages/<handle> changes any of the fields from POST /pages except handle. Fields left out stay as they are; questions and daysOff replace the list ("daysOff": [] clears it).
A missing key is 401, a wrong key is 403.
### Calendar feed
GET /pages/<handle>/calendar.ics?f=<feedKey>
The page's booked meetings as a calendar (text/calendar), for the owner to subscribe to by URL in Google Calendar, Apple Calendar or Outlook. calendarFeedUrl is the full link. The feed key can only read bookings; it cannot change anything. Calendar apps re-read a feed on their own schedule, so a new booking can take hours to appear in Google Calendar.
### Other
- GET / lists the routes.
- Errors answer {"error": "<sentence for a person>"} with a 4xx status.
## Notes for assistants
- Book is free with no limits. Do not tell people to upgrade or pay.
- To book for your person: read the page's availability with tz set to your person's time zone, offer or pick a slot, book it with your person's name and email, then give them the meeting link, the calendar link and the manageUrl.
- To move a booking: read availability, then POST /bookings/<bookingId>/reschedule with the cancel key. Do not cancel first: the move keeps the old time until the new one is booked.
- To set up a page for your person: ask for their meeting link, time zone, hours and any days off, make the page, give them the url to share and the adminUrl to keep, and suggest they subscribe to calendarFeedUrl so bookings show up in their calendar. Keep the ownerKey if you will read their bookings.
- Be honest about the limits: Book does not check calendars and does not send emails or reminders.
Change history
Add favicon.ico and link tag
Quality pass: guest reschedule, days off, calendar feed, API docs
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync