Skip to main content

Lobby Booking for AI Agents

A public, unauthenticated API lets external AI agents discover and book Lobby Link scheduling pages without a Roam account. The booker is identified solely by the email in the request — no bearer token is required.

Enable agents on a lobby

In the lobby's settings (Advanced tab), turn on Enable AI Agents. The toggle is gated by a feature flag; when agents are disabled (or the lobby is inactive), the public endpoints respond as if the lobby does not exist (404 / lobby_not_found).

SEO / agent discovery also respects account-level SEO opt-out: lobbies that are not publicly listed will not appear in site-wide discovery surfaces.

Discovery

Agents can find bookable pages without hard-coding handles:

SurfaceWhat it provides
https://ro.am/sitemap.xmlSEO-enabled booking pages
https://ro.am/llms.txt"Bookable People and Scheduling Links" section for LLM agents
{pageUrl}.jsonMachine-readable booking-form schema, duration options, hosts, and actions pointing at the API endpoints below
{pageUrl}.llmProse representation of the same page for language models

Example JSON discovery URL: https://ro.am/{handle}/{slug}.json.

Each booking page also advertises these representations via HTTP Link: rel="alternate" headers, in-page <link rel="alternate"> tags, schema.org ReserveAction / ScheduleAction JSON-LD, and a small on-page note for agents that read the rendered DOM.

Use the field ids from {pageUrl}.json when submitting custom-question responses on book.

Endpoints

Base URL: https://api.ro.am (no /v0 or auth). Routes are mounted under /v1/public/lobby/… on the public handler (separate from bearer-authenticated API routes).

Get availability

GET /v1/public/lobby/availability?handle={handle}&slug={slug}&from={iso8601}&to={iso8601}&timeZone={iana}&duration={minutes}
ParameterRequiredDescription
handleyes*Lobby handle
slugyes*Page slug under the handle
from / tonoISO-8601 window. Accepts a timestamp with offset or date-only YYYY-MM-DD interpreted in timeZone. Default is roughly the next month; the window is capped at ~2 months (a further-out to is clamped, not rejected)
timeZonenoIANA zone (default UTC). Slots are always returned as absolute Unix times either way
durationnoMeeting length in minutes; must be one of the lobby's offered durations

*Missing handle/slug yields 400 / missing_parameter; unknown or agent-disabled lobbies yield 404 / lobby_not_found.

Response:

{
"slots": [
{ "start": 1784552400, "end": 1784554200, "spotsRemaining": 1 }
]
}
  • start / end are Unix epoch seconds as JSON numbers.
  • Pass a chosen slot's start/end back to book unchanged. An altered span is rejected (409 conflict); hosts are re-resolved server-side.

Book a meeting

POST /v1/public/lobby/book
Content-Type: application/json
{
"handle": "acme",
"slug": "intro",
"bookingSlot": { "start": 1784552400, "end": 1784554200 },
"timeZone": "America/New_York",
"booker": { "email": "ada@example.com", "name": "Ada" },
"additionalInvitees": [{ "email": "grace@example.com", "name": "Grace" }],
"responses": [{ "fieldId": "…", "value": { } }],
"notes": "optional"
}
FieldRequiredNotes
handle, slugyesSame as availability
bookingSlot.start / .endyesUnix seconds from availability; whole-minute spans only
timeZoneyesIANA zone for the booking
booker.emailyesIdentifies the booker
booker.namenoDisplay name
additionalInviteesnoExtra guests
responsesnoCustom fields only — booker name/email/notes auto-fill reserved form fields; use field ids from {pageUrl}.json
notesnoFree-text notes

Unknown JSON members are rejected (400 / invalid_json) so typos surface clearly. Max body size is 64 KiB.

Response (excerpt): booking id, start/end as RFC 3339 strings localized to the requested timeZone (unlike the unix-seconds request fields), status, optional meetingLink, hosts (display names only — no host emails), invitees.

Response codes

StatusMeaning
200Availability returned / booking created
400Missing/invalid parameters, invalid JSON, or invalid slot/duration
404Unknown handle/slug, inactive lobby, or agents not enabled
405Wrong HTTP method
409Slot no longer available (or does not match availability)
500Backend / calendar failure

Error bodies use the standard catalog shape, e.g. {"error":"…","code":"lobby_not_found"} on v0-style public error rendering where applicable — see Error Codes.

Behavior and caveats

  • Booking is committed immediately. A calendar event is created and confirmation emails are sent to the host and booker before the request returns. There is no pending confirmation step.
  • Rate limiting is by client IP (no API client to key on). Further abuse controls (email confirmation, per-lobby caps) may follow.
  • Network permissions: agents calling this API from hosted runtimes need outbound HTTPS to api.ro.am and, for discovery, ro.am.

Time formats (quick reference)

ContextFormatCommon mistakes
Availability query from / toISO-8601 with offset (e.g. 2026-07-20T09:00:00-07:00) or date-only YYYY-MM-DD interpreted in timeZoneZ-only timestamps are fine if you mean UTC; bare local times without offset are not
Slot start / end (availability response & book request)Unix epoch seconds as a JSON numberDo not send milliseconds, strings, or RFC 3339 here
Booking response start / endRFC 3339 string localized to the requested timeZoneDifferent from the request slot units — convert carefully

Pass availability slots back to book byte-for-byte on bookingSlot.start / bookingSlot.end. Rounding or unit conversion will fail slot re-validation (409).

Network permissions for hosted agents

Agents running in sandboxed environments (MCP hosts, automation runtimes, etc.) need outbound HTTPS to:

  • https://api.ro.am — availability + book
  • https://ro.am — discovery (.json / .llm / llms.txt / sitemap)

Allowlisting only api.ro.am is not enough if the agent first fetches the booking page metadata from ro.am.