# Add booking to a website with Meetful

For an AI agent building or editing somebody's website. One request makes their booking page and returns the code that puts it on the site. Free. Nobody needs an account to start: the person confirms the page with one Google sign-in, and it takes bookings from then on.

What their visitors get: a booking page at `meetful.com/<name>` where they pick how long and a time, shown in their own time zone. Each booking makes the video meeting room on the spot — nobody needs an app or an account to join it — emails both sides with a calendar file, and lands in the owner's Meetful dashboard. In the meeting, every word is written down under the name of the person who said it.

Human-readable version: https://meetful.com/add-booking · API description: https://meetful.com/openapi.json

## 1. Make the page

```http
POST https://meetful.com/api/setup/booking
Content-Type: application/json

{
  "name": "Ada Lovelace",
  "timezone": "Europe/London",
  "hours": "Mon-Fri 09:00-17:00",
  "lengths": [30, 60],
  "site": "https://ada.dev",
  "email": "ada@example.com"
}
```

Only `name` is required.

| Field | What it is | If left out |
|---|---|---|
| `name` | Whose page it is. Shown on it. | required |
| `handle` | The address wanted: `meetful.com/<handle>`. 3–32 letters, digits or dashes. | made from the name |
| `timezone` | IANA name the hours are in, like `America/New_York`. | UTC, with a warning — send it |
| `hours` | Open hours. `"Mon-Fri 09:00-17:00"`, `"Mon-Thu 9am-5pm; Fri 9am-1pm"`, `{"mon-fri": "09:00-17:00"}`, `{"monday": [["09:00","12:00"],["13:00","17:00"]]}` all work. | Monday to Friday, 9 to 5 |
| `lengths` | How long a meeting can be, in minutes: any of 15, 30, 45, 60, 90. The person booking picks one; the first is shown first. Other lengths become the nearest of those, with a warning. `kinds` — `[{ "title", "minutes" }]` — is read the same way. | 30 minutes |
| `title`, `blurb` | The page's heading and the line under it. | "Book a meeting" |
| `site` | The website it goes on. Shown to the person when they claim it. | — |
| `email` | The person's address. Shown on the claim page so they can see it was made for them. **Nothing is ever sent to it.** | — |
| `buffer`, `notice`, `horizon` | Minutes between meetings (0–60), hours of notice (0–72), days ahead bookable (1–90). | 10, 4, 21 |

The reply (`201`):

```json
{
  "status": "waiting",
  "handle": "ada-lovelace",
  "page": "https://meetful.com/ada-lovelace",
  "claim": "https://meetful.com/claim/…",
  "setup": "stp_…",
  "expires": "…",
  "embed": {
    "button": "<script src=\"https://meetful.com/embed.js\" data-meetful-book=\"ada-lovelace\" async></script>",
    "inline": "<div data-meetful-book=\"ada-lovelace\" data-meetful-inline></div>\n<script src=\"https://meetful.com/embed.js\" async></script>",
    "iframe": "<iframe src=\"https://meetful.com/book/ada-lovelace?embed=1\" …></iframe>",
    "react": "…",
    "link": "https://meetful.com/ada-lovelace",
    "markdown": "[Book a time with me](https://meetful.com/ada-lovelace)"
  },
  "tell": "Your booking page is ready at … Open … and sign in with Google to make it yours …",
  "warnings": ["…only if something you sent could not be read"]
}
```

Keep `setup` (it is shown once) — it is how you check on the page and change it.

## 2. Put it on the site

Use what `embed` gave you. The page opens as being set up until its person claims it, so it is safe to put on the site straight away; nothing on the site changes when they do.

| Where | What to add |
|---|---|
| Any HTML page | `embed.button` anywhere — a "Book a call" button that opens the booking page over the site. Or `embed.inline` where the booking page should sit in the page. |
| React, Next.js, Vue, Svelte | Load `https://meetful.com/embed.js` once (a `<Script>` in Next.js, or `embed.react`), then render `<div data-meetful-book="<handle>" data-meetful-inline></div>` where it should appear. |
| WordPress | A Custom HTML block with `embed.inline`. |
| Webflow, Squarespace, Shopify | An Embed / Code / Custom Liquid block with `embed.inline`. |
| Wix, Framer, site builders without scripts | `embed.iframe`, or an embed-by-URL of `https://meetful.com/book/<handle>?embed=1`. |
| Notion, Carrd, Linktree, an email signature, a PDF | `embed.link`. |

Options on the button or inline element: `data-meetful-minutes="60"` opens straight to that length's times; `data-meetful-label="Book a call"`; `data-meetful-color="#0755EB"`. When somebody books, the page gets a `meetful:booked` event on `window` with `{ at, minutes, join }`.

No cookies, no tracking, nothing loaded until it is shown.

## 3. Hand it to the person

Show them the `tell` sentence — it carries the claim link. They open it, sign in with Google once, and the page is theirs and taking bookings. Until then it says it is being set up and takes none: nobody can open a calendar in somebody else's name. **Nothing is emailed to anybody** — they hear about it from you.

| To | Do |
|---|---|
| See whether they have claimed it | `GET /api/setup/booking` with `Authorization: Bearer <setup>` → `status`: `waiting`, `live`, `expired` or `gone` |
| Change it before they claim it | `PATCH /api/setup/booking` with any of the fields above, same header |
| Get a fresh claim link if the first is lost | `POST /api/setup/booking/link`, same header — the old link stops working |
| Take it back | `DELETE /api/setup/booking`, same header, while it is waiting |

After they claim it, it is theirs: they change hours, lengths, their own questions and the address at https://meetful.com/scheduling, and the setup key only reads its status. An unclaimed page lets its address go after seven days.

## If the person already uses Meetful

Ask them for a developer key from https://meetful.com/connect, made while they are signed in, and send it as `Authorization: Bearer tmk_…` on the same request. The page is then theirs and live at once — no claim step — and the same request changes it later. If they claim a new page while already having one, they keep theirs, and the new address opens it, so the code you put on the site still works.

## With MCP

The Meetful MCP server (`https://meetful.com/mcp`, `Authorization: Bearer <key>`; set-up for each assistant at https://meetful.com/connect) has the same thing as two tools: `set_up_booking_page` (the fields above) and `booking_page_status` (`{ "setup": "stp_…" }`).

## In code

```js
const page = await fetch('https://meetful.com/api/setup/booking', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ name: 'Ada Lovelace', timezone: 'Europe/London', hours: 'Mon-Fri 09:00-17:00' }),
}).then(r => r.json());

console.log(page.tell);          // show the person this
console.log(page.embed.button);  // put this on the site
```

```sh
curl -s https://meetful.com/api/setup/booking -H 'content-type: application/json' \
  -d '{"name":"Ada Lovelace","timezone":"Europe/London","hours":"Mon-Fri 09:00-17:00"}'
```

## A Start a meeting button, too

If the site or product should also let people start a video meeting there and then — a "Meet now" button in an app's header, "Talk to us" on a help page — that needs no setup, no key and no account:

| Where | What to add |
|---|---|
| Any link or button | Point it at `https://meetful.com/new`, opened in a new tab. Each press makes a new room and the person who pressed is in it. |
| A ready-made button | `<script src="https://meetful.com/embed.js" data-meetful-start async></script>` |
| Their own button | `<button data-meetful-start>Meet now</button>`, with `https://meetful.com/embed.js` loaded once on the page — it keeps its own words and look. |

`#name=Ada` on the end of the link (or `data-meetful-name="Ada"`) is what the room calls them, when the product knows who is signed in — after the `#`, so the name is never sent to a server in the address. `?template=<slug>` (or `data-meetful-template`) opens the room with one of the agendas at https://meetful.com/templates. `data-meetful-label` and `data-meetful-color` work as on the booking button. The person invites others from inside the room; nothing is stored on the site.

## When it says no

| Code | Why |
|---|---|
| `400` | No `name`, or an address that cannot be one. The `error` says which. |
| `401` | A `tmk_…` key this server does not know, or a `GET`/`PATCH`/`DELETE` without the setup key. |
| `409` | The address is taken — the reply's `try` lists free ones — or the page is no longer waiting. |
| `429` | Twelve pages from one address in an hour. |

Every reply carries `docs`, a link back here.
