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/openapi.json
{
"openapi": "3.1.0",
"info": {
"title": "Book by OpenApps",
"version": "1.1.0",
"summary": "A free booking page for one-on-one meetings, a free Calendly alternative.",
"description": "No key and no account. Anyone can read a page's open times and book one. The ownerKey from POST /pages manages a page (header x-owner-key); the cancelKey from each booking reads, moves and cancels it (header x-cancel-key). Both come back once. Book does not read calendars or send emails. Errors are JSON: {\"error\": \"<sentence>\"}. Guide: https://book-free.openapps.xyz/llms.txt"
},
"servers": [
{
"url": "https://functions.bounded.sh/apps/7385a6c1c47153b1728160ff/api"
}
],
"externalDocs": {
"url": "https://book-free.openapps.xyz/llms.txt"
},
"components": {
"securitySchemes": {
"ownerKey": {
"type": "apiKey",
"in": "header",
"name": "x-owner-key",
"description": "The page's owner key, from POST /pages."
},
"cancelKey": {
"type": "apiKey",
"in": "header",
"name": "x-cancel-key",
"description": "The booking's cancel key, from POST /pages/{handle}/bookings."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
]
},
"PageInput": {
"type": "object",
"properties": {
"handle": {
"type": "string",
"description": "The page address. Leave it out to get one made from the name."
},
"name": {
"type": "string",
"maxLength": 80,
"description": "Who people meet."
},
"timezone": {
"type": "string",
"description": "IANA zone the hours and days off are in.",
"example": "America/New_York"
},
"meetingUrl": {
"type": "string",
"description": "An https link to a Google Meet, Zoom or Teams room. Shown only to people who book."
},
"title": {
"type": "string",
"maxLength": 120,
"default": "30 minute call"
},
"description": {
"type": "string",
"maxLength": 1000
},
"durationMin": {
"type": "integer",
"enum": [
15,
30,
45,
60
],
"default": 30
},
"hours": {
"type": "object",
"description": "Weekday (mon..sun, weekdays, weekend, everyday) to ranges \"HH:MM-HH:MM\" in 15-minute steps, as a list or one comma-separated string. Days left out are closed.",
"additionalProperties": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"example": {
"weekdays": [
"09:00-12:00",
"13:00-17:00"
]
}
},
"daysOff": {
"type": "array",
"maxItems": 50,
"items": {
"oneOf": [
{
"type": "string",
"description": "\"YYYY-MM-DD\" or \"YYYY-MM-DD to YYYY-MM-DD\""
},
{
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date"
},
"to": {
"type": "string",
"format": "date"
}
},
"required": [
"from"
]
}
]
},
"description": "Dates nobody can book, inclusive, in the page's time zone.",
"example": [
"2026-12-24",
"2026-12-28 to 2026-12-31"
]
},
"noticeHours": {
"type": "integer",
"minimum": 0,
"maximum": 720,
"default": 4
},
"horizonDays": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"default": 60
},
"bufferMin": {
"type": "integer",
"enum": [
0,
15,
30,
45,
60
],
"default": 0
},
"questions": {
"type": "array",
"maxItems": 5,
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"label": {
"type": "string",
"maxLength": 200
},
"required": {
"type": "boolean"
}
},
"required": [
"label"
]
}
]
}
}
},
"required": [
"name",
"timezone",
"meetingUrl"
]
},
"PagePatch": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 80,
"description": "Who people meet."
},
"timezone": {
"type": "string",
"description": "IANA zone the hours and days off are in.",
"example": "America/New_York"
},
"meetingUrl": {
"type": "string",
"description": "An https link to a Google Meet, Zoom or Teams room. Shown only to people who book."
},
"title": {
"type": "string",
"maxLength": 120,
"default": "30 minute call"
},
"description": {
"type": "string",
"maxLength": 1000
},
"durationMin": {
"type": "integer",
"enum": [
15,
30,
45,
60
],
"default": 30
},
"hours": {
"type": "object",
"description": "Weekday (mon..sun, weekdays, weekend, everyday) to ranges \"HH:MM-HH:MM\" in 15-minute steps, as a list or one comma-separated string. Days left out are closed.",
"additionalProperties": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"example": {
"weekdays": [
"09:00-12:00",
"13:00-17:00"
]
}
},
"daysOff": {
"type": "array",
"maxItems": 50,
"items": {
"oneOf": [
{
"type": "string",
"description": "\"YYYY-MM-DD\" or \"YYYY-MM-DD to YYYY-MM-DD\""
},
{
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date"
},
"to": {
"type": "string",
"format": "date"
}
},
"required": [
"from"
]
}
]
},
"description": "Dates nobody can book, inclusive, in the page's time zone.",
"example": [
"2026-12-24",
"2026-12-28 to 2026-12-31"
]
},
"noticeHours": {
"type": "integer",
"minimum": 0,
"maximum": 720,
"default": 4
},
"horizonDays": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"default": 60
},
"bufferMin": {
"type": "integer",
"enum": [
0,
15,
30,
45,
60
],
"default": 0
},
"questions": {
"type": "array",
"maxItems": 5,
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"label": {
"type": "string",
"maxLength": 200
},
"required": {
"type": "boolean"
}
},
"required": [
"label"
]
}
]
}
}
},
"description": "Any fields of PageInput except handle. Fields left out stay as they are; questions and daysOff replace the list."
},
"Page": {
"type": "object",
"properties": {
"handle": {
"type": "string"
},
"url": {
"type": "string"
},
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"durationMin": {
"type": "integer"
},
"meeting": {
"type": "string",
"description": "The kind of call, like \"Google Meet\"."
},
"timezone": {
"type": "string"
},
"hours": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"hoursSummary": {
"type": "array",
"items": {
"type": "string"
}
},
"daysOff": {
"type": "array",
"items": {
"type": "object",
"properties": {
"from": {
"type": "string",
"format": "date"
},
"to": {
"type": "string",
"format": "date"
}
}
}
},
"noticeHours": {
"type": "integer"
},
"horizonDays": {
"type": "integer"
},
"bufferMin": {
"type": "integer"
},
"questions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"required": {
"type": "boolean"
}
}
}
}
}
},
"Booking": {
"type": "object",
"properties": {
"bookingId": {
"type": "string"
},
"start": {
"type": "string",
"format": "date-time"
},
"end": {
"type": "string",
"format": "date-time"
},
"label": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"booked",
"cancelled"
]
},
"cancelledBy": {
"type": [
"string",
"null"
],
"enum": [
"owner",
"guest",
null
]
},
"movedTo": {
"type": "string",
"description": "Set when the guest moved the booking: the id of the booking that replaced it."
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"note": {
"type": [
"string",
"null"
]
},
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"q": {
"type": "string"
},
"a": {
"type": "string"
}
}
}
}
}
},
"BookingCreated": {
"type": "object",
"properties": {
"bookingId": {
"type": "string"
},
"start": {
"type": "string",
"format": "date-time"
},
"end": {
"type": "string",
"format": "date-time"
},
"label": {
"type": "string"
},
"with": {
"type": "string"
},
"title": {
"type": "string"
},
"meetingUrl": {
"type": "string"
},
"icsUrl": {
"type": "string"
},
"googleCalendarUrl": {
"type": "string"
},
"manageUrl": {
"type": "string",
"description": "The person's private page to see, move or cancel the booking."
},
"cancelKey": {
"type": "string"
},
"movedFrom": {
"type": "string",
"description": "Only on a move: the booking this one replaced."
},
"note": {
"type": "string"
}
}
}
}
},
"paths": {
"/": {
"get": {
"operationId": "listRoutes",
"summary": "List the routes",
"responses": {
"200": {
"description": "The routes",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
},
"/pages": {
"post": {
"operationId": "createPage",
"summary": "Make a booking page",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PageInput"
},
"example": {
"handle": "sam-lee",
"name": "Sam Lee",
"timezone": "America/New_York",
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"durationMin": 30,
"hours": {
"weekdays": "09:00-17:00"
},
"daysOff": [
"2026-12-24"
]
}
}
}
},
"responses": {
"201": {
"description": "The page, its admin link and keys",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"handle": {
"type": "string"
},
"url": {
"type": "string"
},
"adminUrl": {
"type": "string"
},
"ownerKey": {
"type": "string"
},
"calendarFeedUrl": {
"type": "string"
},
"share": {
"type": "string"
},
"page": {
"$ref": "#/components/schemas/Page"
}
}
},
"example": {
"handle": "sam-lee",
"url": "https://book-free.openapps.xyz/sam-lee",
"adminUrl": "https://book-free.openapps.xyz/e/sam-lee#k=OWNER_KEY",
"ownerKey": "OWNER_KEY",
"calendarFeedUrl": "https://functions.bounded.sh/apps/7385a6c1c47153b1728160ff/api/pages/sam-lee/calendar.ics?f=FEED_KEY"
}
}
}
},
"400": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"409": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/pages/{handle}": {
"get": {
"operationId": "getPage",
"summary": "Read a page (not the meeting link)",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
}
],
"responses": {
"200": {
"description": "The page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Page"
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
},
"patch": {
"operationId": "updatePage",
"summary": "Change a page",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
}
],
"security": [
{
"ownerKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PagePatch"
},
"example": {
"daysOff": [
"2026-12-28 to 2026-12-31"
],
"bufferMin": 15
}
}
}
},
"responses": {
"200": {
"description": "The page and its meeting link",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Page"
},
{
"type": "object",
"properties": {
"meetingUrl": {
"type": "string"
}
}
}
]
}
}
}
},
"400": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/pages/{handle}/availability": {
"get": {
"operationId": "getAvailability",
"summary": "Open start times",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
},
{
"name": "tz",
"in": "query",
"schema": {
"type": "string"
},
"description": "Zone to show times in (default: the page's).",
"example": "Europe/London"
},
{
"name": "from",
"in": "query",
"schema": {
"type": "string",
"format": "date"
},
"description": "First date, in tz (default: today)."
},
{
"name": "days",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 62,
"default": 14
}
}
],
"responses": {
"200": {
"description": "Open times",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"handle": {
"type": "string"
},
"title": {
"type": "string"
},
"name": {
"type": "string"
},
"durationMin": {
"type": "integer"
},
"timezone": {
"type": "string"
},
"zone": {
"type": "string"
},
"from": {
"type": "string"
},
"days": {
"type": "integer"
},
"slots": {
"type": "array",
"items": {
"type": "string",
"format": "date-time"
}
},
"book": {
"type": "string"
}
}
},
"example": {
"handle": "sam-lee",
"durationMin": 30,
"timezone": "Europe/London",
"zone": "London time (GMT+1)",
"from": "2026-10-21",
"days": 7,
"slots": [
"2026-10-21T14:00:00+01:00",
"2026-10-21T14:30:00+01:00"
]
}
}
}
},
"400": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/pages/{handle}/bookings": {
"post": {
"operationId": "createBooking",
"summary": "Book an open time",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"start": {
"type": "string",
"description": "One of the open slots. Without an offset it is read in timezone, else in the page's zone."
},
"timezone": {
"type": "string"
},
"name": {
"type": "string",
"maxLength": 80
},
"email": {
"type": "string",
"format": "email"
},
"note": {
"type": "string",
"maxLength": 1000
},
"answers": {
"oneOf": [
{
"type": "object",
"additionalProperties": {
"type": "string"
}
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
}
},
"required": [
"start",
"name",
"email"
]
},
"example": {
"start": "2026-10-21T15:00:00+01:00",
"name": "Alex Kim",
"email": "alex@example.com",
"answers": {
"Company": "Acme"
}
}
}
}
},
"responses": {
"201": {
"description": "The booking, the meeting link and the cancel key",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingCreated"
},
"example": {
"bookingId": "dh6syZouMtw5XA55DX7h",
"start": "2026-10-21T15:00:00+01:00",
"end": "2026-10-21T15:30:00+01:00",
"label": "Wed, Oct 21, 3:00 PM to 3:30 PM GMT+1",
"with": "Sam Lee",
"title": "Intro call",
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"icsUrl": "https://functions.bounded.sh/apps/7385a6c1c47153b1728160ff/api/bookings/dh6syZouMtw5XA55DX7h/ics?c=CANCEL_KEY",
"googleCalendarUrl": "https://calendar.google.com/calendar/render?action=TEMPLATE&...",
"manageUrl": "https://book-free.openapps.xyz/b/sam-lee/dh6syZouMtw5XA55DX7h#c=CANCEL_KEY",
"cancelKey": "CANCEL_KEY",
"note": "Book does not send emails. Give the person the meeting link, the calendar file and the manageUrl (it is how they move or cancel the booking)."
}
}
}
},
"400": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"409": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
},
"get": {
"operationId": "listBookings",
"summary": "The page's bookings (owner)",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
}
],
"security": [
{
"ownerKey": []
}
],
"responses": {
"200": {
"description": "Bookings in the page's time zone",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"handle": {
"type": "string"
},
"timezone": {
"type": "string"
},
"calendarFeedUrl": {
"type": "string"
},
"upcoming": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Booking"
}
},
"past": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Booking"
}
},
"cancelled": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Booking"
}
}
}
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/pages/{handle}/calendar.ics": {
"get": {
"operationId": "getCalendarFeed",
"summary": "The page's bookings as a calendar to subscribe to",
"parameters": [
{
"name": "handle",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$"
},
"example": "sam-lee"
},
{
"name": "f",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "The feed key, part of calendarFeedUrl. It can only read."
}
],
"responses": {
"200": {
"description": "An iCalendar feed",
"content": {
"text/calendar": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/bookings/{bookingId}": {
"get": {
"operationId": "getBooking",
"summary": "A booking and its meeting link",
"parameters": [
{
"name": "bookingId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "dh6syZouMtw5XA55DX7h"
}
],
"security": [
{
"cancelKey": []
}
],
"responses": {
"200": {
"description": "The booking",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Booking"
},
{
"type": "object",
"properties": {
"page": {
"type": "object"
},
"meetingUrl": {
"type": [
"string",
"null"
]
},
"icsUrl": {
"type": "string"
},
"manageUrl": {
"type": "string"
}
}
}
]
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/bookings/{bookingId}/ics": {
"get": {
"operationId": "getBookingIcs",
"summary": "A calendar file for the meeting",
"parameters": [
{
"name": "bookingId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "dh6syZouMtw5XA55DX7h"
},
{
"name": "c",
"in": "query",
"schema": {
"type": "string"
},
"description": "The cancel key (or send x-cancel-key)."
}
],
"responses": {
"200": {
"description": "An iCalendar file",
"content": {
"text/calendar": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/bookings/{bookingId}/reschedule": {
"post": {
"operationId": "rescheduleBooking",
"summary": "Move a booking to another open time",
"description": "Books the new time with the same details and frees the old one in one step. The new booking keeps the cancel key.",
"parameters": [
{
"name": "bookingId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "dh6syZouMtw5XA55DX7h"
}
],
"security": [
{
"cancelKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"start": {
"type": "string"
},
"timezone": {
"type": "string"
}
},
"required": [
"start"
]
},
"example": {
"start": "2026-10-22T10:00:00+01:00"
}
}
}
},
"responses": {
"201": {
"description": "The new booking",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingCreated"
},
"example": {
"bookingId": "dh6syZouMtw5XA55DX7h",
"start": "2026-10-21T15:00:00+01:00",
"end": "2026-10-21T15:30:00+01:00",
"label": "Wed, Oct 21, 3:00 PM to 3:30 PM GMT+1",
"with": "Sam Lee",
"title": "Intro call",
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"icsUrl": "https://functions.bounded.sh/apps/7385a6c1c47153b1728160ff/api/bookings/dh6syZouMtw5XA55DX7h/ics?c=CANCEL_KEY",
"googleCalendarUrl": "https://calendar.google.com/calendar/render?action=TEMPLATE&...",
"manageUrl": "https://book-free.openapps.xyz/b/sam-lee/dh6syZouMtw5XA55DX7h#c=CANCEL_KEY",
"cancelKey": "CANCEL_KEY",
"note": "Book does not send emails. Give the person the meeting link, the calendar file and the manageUrl (it is how they move or cancel the booking).",
"movedFrom": "OLD_BOOKING_ID"
}
}
}
},
"400": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"409": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
},
"/bookings/{bookingId}/cancel": {
"post": {
"operationId": "cancelBooking",
"summary": "Cancel a booking and free its time",
"parameters": [
{
"name": "bookingId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "dh6syZouMtw5XA55DX7h"
}
],
"security": [
{
"cancelKey": []
},
{
"ownerKey": []
}
],
"responses": {
"200": {
"description": "The cancelled booking",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Booking"
},
{
"type": "object",
"properties": {
"freed": {
"type": "boolean"
}
}
}
]
}
}
}
},
"401": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"403": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"404": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
},
"409": {
"description": "A plain sentence saying what to change",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That time is not open. GET .../pages/sam-lee/availability lists the open times."
}
}
}
}
}
}
}
}
}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