Scheduling & booking pages

Overview
Scheduling lets other people book time with you — without back and forth. It has four parts: event types (what can be booked), availability (when), booking pages (where booking happens) and connections (which external calendars count).
The key sentence about busy time: a known appointment takes the slot — even without an external calendar. What is in the system counts. A connected calendar adds to that, it does not replace it.
Core tasks
Create an event type. Duration, buffer, notice, location or video link, and who is invited.
Set availability. A schedule describes the regular hours; overrides for individual days take precedence.
Drag availability in the weekly grid. In the schedule dialog a grid shows the week in half hours. Drag across free cells with the mouse to make the time available; start on an available cell to remove the time again — across several days at once. The grid writes the same time windows as the list below; enter finer times (09:15) there. By keyboard: arrow keys move, the space bar toggles a cell, shift + arrow marks a range that is applied when you release shift. Overnight windows (22:00–02:00) appear lighter on the following day.
See the bookable slots in advance. Next to the grid, Preview of bookable slots lists the start times per weekday for the chosen meeting length, calculated like the booking page — before saving, without busy times and exceptions.
Publish a booking page. The page is publicly reachable and shows only genuinely free times.
Route enquiries. A routing form asks questions and then directs to the matching event type or person.
Book resources along. Where a room or device belongs to an appointment, it is booked with it and blocked for others.
Connect calendars. A connection brings external appointments in as busy time.
Book on behalf of a customer. Under Appointments → Book on behalf choose the event type, day and free time, optionally the host (round robin), plus the channel (phone, walk-in, email) and — for "ask the booker" — the location. The guest receives the same confirmation as for a self-booking.
Embed on your own website. Under Embed on a booking page you find the octibiz-booking element — it encapsulates the booking completely; language and colour are set via attributes.
Automate appointment follow-ups. The central automations know the actions Book appointment, Reschedule appointment and Cancel appointment; the AI assistant finds free times and books after confirmation.
Booked appointments block the host's time in time tracking and workload as well — without an external calendar. A day on which project planning has used up the entire capacity cannot be booked.
Fields in detail
The What it does column answers what changes in the system — not what the field is called. The grey name behind the label is the API field: the same thing runs under that name through automation, import and AI tools.
Event type
| Field | Required | Values / format | What it does | |
|---|---|---|---|---|
Name name | yes | text | What can be booked. The invitee reads this name. | |
URL slug slug | yes | lowercase letters, digits, hyphens | The part of the public booking URL. Do not change it once shared — every link sent out runs into nothing. | |
Description description | no | text | What the invitee reads before choosing. | |
Colour color | no | colour value | Colour of the marker in list and calendar. | |
Duration durationMinutes | no | minutes, greater than 0 | How long the appointment lasts. It blocks exactly this time — buffers come on top. | |
Selectable durations durationOptions | no | list of minute values | Lets the visitor pick between several lengths, 15, 30 and 60 minutes for instance. The default duration is always included, even if it is not listed here. | |
Type kind | no | one_on_one, group, collective, round_robin, managed | Who takes the appointment and how many may book. 1:1 is one host per booking, group several invitees on one appointment, collective all hosts at once, round robin distributed in turn, managed set by the team only. This field decides which of the following settings apply at all. | |
Seats capacity | no | number, greater than 0 | Group only: how many invitees may take the same appointment. Below 2 it is not a group appointment. | |
Location locationKind | no | video_meet, video_teams, video_zoom, video_custom, in_person, phone, ask_invitee, custom | Where the appointment takes place. The video kinds create the link automatically; with ask invitee the guest states it. | |
Location details locationConfig | no | address, phone number, link or free text — depending on the location | The addition to the location: the address for On site, your number for Phone (empty = you call the guest's number), the address of your own video or Zoom link, free text for Custom. It appears in the confirmation, the calendar invitation and the host's calendar. With Zoom, Zoom meeting per appointment (autoCreate) creates a meeting of its own for each appointment — requires the integration “zoom” (server-to-server app); the link then appears in confirmation, invitation and calendar. | |
Travel buffer travelTime | no | `{mode: fixed \ | routed, minutes: 1–480}`; empty = off | Only for On site and Ask invitee: time for travelling stays free before and after the host’s other on-site appointments. Routed additionally checks the real travel time between the addresses when booking (services under Module settings). |
Buffer before bufferBeforeMinutes | no | minutes, 0 or more | Time blocked before the appointment — travel, preparation. | |
Buffer after bufferAfterMinutes | no | minutes, 0 or more | The same afterwards. Without buffers appointments sit edge to edge and the first delay topples the day. | |
Minimum notice minNoticeMinutes | no | minutes, 0 or more | How short-notice booking may be. The value is in minutes, not hours — one day of notice is 1440, not 24. | |
Bookable ahead horizonDays | no | days, greater than 0 | How far into the future booking is allowed. | |
Booking horizon type horizonKind | no | rolling (default), fixed range or unlimited | What "bookable until" is measured against: rolling counts days from today, "fixed range" opens exactly one span, "unlimited" allows any date. | |
Fixed booking range dateRange | with a fixed range | from date and to date, both inclusive | The one span bookings are possible in — for a trade fair, a campaign week or an office-hours window. An empty value clears the span again. | |
Slot increment slotIncrementMinutes | no | minutes, greater than 0 | The grid the free times are offered on. At 15 a 60-minute appointment may also start at 9:15; at 60 only on the hour. This field decides how dense the day gets. | |
Daily limit dailyLimit | no | number; 0 or empty = no limit | How many appointments of this type may happen on one day. Once reached, the day disappears from the offer. | |
Visibility visibility | no | public, secret | Public appears on booking pages, secret does not — it stays bookable through the direct link. This is restraint, not access control. | |
Payment required requiresPayment | no | yes/no | Whether payment is taken before confirmation. | |
Price priceAmount | no | amount in cents, 0 or more | What the appointment costs. Kept in cents: 4500 is 45.00 EUR. | |
Form questions questions | no | list of questions | What the invitee must state in addition. Every required question costs bookings — ask for what you really need. | |
Confirmation page confirmationConfig | no | your own text or a redirect | What the visitor sees after booking: your own thank-you text or a redirect to an address of your choice, a page that counts the conversion for instance. | |
Availability scheduleId | no | a schedule | Which schedule provides the bookable hours. Empty means the host's default schedule. | |
Hosts hostEmployeeIds | no | employees | Who takes the appointment. Only times the person is genuinely free are offered. Round robin and collective need at least two. | |
Resources resourceIds | no | resources | Required rooms and devices. A slot is free only if host and all resources are free. | |
Recurrence recurrenceRule | no | {freq: daily/weekly/monthly, interval, count}; empty = single appointment | The invitee books all occurrences in one go. It only happens if every occurrence is free — otherwise none. | |
Follow-up parts additionalParts | no | list of {offsetMinutes, durationMinutes}, at most 10; empty = single part | Multi-part appointment, e.g. a 60-minute first meeting and a 30-minute follow-up two weeks later. The offset counts from the start of the first part, on the host's wall clock (also across daylight saving changes). A time is offered only if all parts are free; all are booked in one go. Not combinable with a recurrence. | |
Create lead createLead | no | yes/no | Whether a booking by an unknown visitor creates a lead in the CRM. Without the switch an appointment stays an appointment and the contact is lost. | |
Convert lead right away convertLead | no | yes/no (default no) | The booking counts as qualification: the lead immediately becomes customer (a private customer without a company), contact and deal in the first open stage of the default pipeline; the appointment is linked to them. Without a default pipeline it stays a lead. | |
Work item per booking workItemKind | no | task, ticket or none | Whether every booking also creates a work item — a task to prepare or a ticket to work on. Empty = no work item. | |
Team distribution roundRobinStrategy | no | rotation (default), load | Round robin only: Rotation gives the booking to whoever went longest without one; By workload to whoever has the least booked appointment time in the seven days before and after. | |
Trigger a quote createQuote | no | yes/no | Creates a draft quote for a linked deal. Requires the quotes module; without a deal nothing happens. | |
Invoice on payment invoiceOnPayment | no | yes/no (default yes) | For paid event types: the incoming payment creates an issued invoice recorded as paid for the amount collected. A booker who is not yet a customer is created as a private customer. Requires the invoicing module. | |
Active active | no | yes/no | Off means no longer bookable. Existing bookings remain. | |
Brand brandId | no | one of your brands | Under which brand the event type is kept and offered. | |
Template (centrally managed) managedTemplate | no | yes/no | Turns the event type into a template: never bookable itself, distributed to employees who each get their own copy with themselves as host. A copy cannot become a template. | |
Locked fields lockedFields | no | list of fields (name, duration, location, buffers, notice, horizon, questions, price, follow-ups …) | Templates only: these fields follow every change of the template into all copies and cannot be changed on a copy. Slug, hosts, schedule and “active” always belong to the copy. |
Distribute a template. In the event type list, Distribute to employees opens the selection:
| Field | Required | Values / format | What it does |
|---|---|---|---|
Employees employees | yes | one or more employees | Each gets a copy (1:1, themselves as host, address <template>-<name>). Whoever already has one gets no second. If the template is deleted, the copies remain as independent event types. |
Event kinds
The kinds themselves (1:1, group, collective, round robin, managed) are fixed because booking calculates by them — none can be created or deleted. Event types → Event kinds maintains what belongs to you: what a kind is called, its colour and order, whether the event type editor offers it and which one is preselected on creation. Permission to change: scheduling.event_type.manage.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Key key | – | one_on_one, group, collective, round_robin, managed | Fixed — booking branches on it; therefore read-only. |
Label label | yes | text | The name of the kind in list and editor. |
Colour color | no | colour value | Marker colour of the kind. |
Order sortOrder | no | number | Order in the editor's selection field. |
Preselected isDefault | no | yes/no | This kind is preselected when creating an event type. |
Offered active | no | yes/no | Only offered kinds can be chosen in the editor; existing event types keep their kind. |
Booking page
| Field | Required | Values / format | What it does |
|---|---|---|---|
Name title | yes | text | The heading of the public page. |
URL slug slug | yes | lowercase letters, digits, hyphens | The part of the public address /public/booking/…. Do not change it once shared. |
Owner type ownerType | no | employee, resource | Whether the page belongs to a person or to a resource. |
Owner ownerId | no | employee or resource | Whose times the page offers. |
Welcome text welcomeText | no | text | What stands at the top of the page. The sentence that explains what the guest is signing up for. |
Image avatarFileId | no | file | The photo or logo of the page. |
Styling branding | no | object | Colours and logo for the white-label appearance. |
Custom domain customDomain | no | domain, e.g. book.your-brand.com | Serves the page under your own address. Only the host name is stored; a domain belongs to exactly one page. DNS, certificate and redirecting the root address are an operations step — without it the domain points nowhere. |
Event types eventTypeIds | no | event types | What can be booked on the page. Only active, public types are shown. |
Visibility visibility | no | public, secret | Secret takes the page out of overviews and search engines; it stays reachable. |
Brand brandId | no | one of your brands | Under which brand the page is kept. |
Availability schedule
| Field | Required | Values / format | What it does |
|---|---|---|---|
Name name | yes | text | How the schedule is called in the selection, e.g. "Business hours". |
Owner type ownerType | no | employee, resource | Whether the schedule belongs to a person or to a resource. |
Owner ownerId | no | employee or resource | Whose times it describes. |
Time zone timezone | no | time zone (e.g. Europe/Berlin) | Which zone the hours are meant in. Set wrongly, every offered slot shifts — the most common reason for "the times are wrong". |
Weekly hours weekly | no | time windows "HH:MM"–"HH:MM" per weekday | The regular hours. A day without a window is closed. If the end lies before the start (22:00–02:00) or reads "24:00", the window runs past midnight into the next day. Start equal to end is rejected. |
Default schedule isDefault | no | yes/no | The schedule an event type uses when it names none of its own. |
Brand brandId | no | one of your brands | Under which brand the schedule is kept. |
Slot preview
The preview turns a (possibly unsaved) weekly plan into bookable start times. It writes nothing; automations and AI tools can use the same request.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Weekly availability weekly | no | time windows "HH:MM"–"HH:MM" per weekday | The plan the slots are calculated for — the same notation as on the schedule. A window without length or in the wrong form is rejected. |
Time zone timezone | no | time zone; empty = Europe/Berlin | Which zone the times refer to; start times come back in the same zone. |
Meeting length durationMinutes | no | 5–1440 minutes, default 30 | How long a meeting is. A window only offers meetings that fit completely. |
Step incrementMinutes | no | 5–1440 minutes, default = length | How far apart meetings may start, e.g. every 15 minutes for 30-minute meetings. |
Date override
| Field | Required | Values / format | What it does |
|---|---|---|---|
Schedule scheduleUlid | yes | a schedule | Which schedule the override applies to. |
Date date | yes | YYYY-MM-DD | The one day that deviates from the weekly rule. Overrides always take precedence. |
Time windows intervals | no | pairs "HH:MM"–"HH:MM"; empty = blocked all day | The deviating hours of that day. Leaving it empty means blocked, not "as usual". |
Reason reason | no | text | Why the day deviates. The sentence that answers "why were we closed?" half a year later. |
Routing form
| Field | Required | Values / format | What it does |
|---|---|---|---|
Name name | yes | text | How the form is called in administration. |
URL slug slug | yes | lowercase letters, digits, hyphens | The part of the public form address. |
Description description | no | text | What the visitor reads above the form. |
Fields fields | no | list of key, label, type, required | The questions. The rules refer to their keys — rename a key and the rule stops matching. |
Rules rules | no | list of conditions and target event type | Where visitors are sent. The first rule whose conditions all match wins, so the order is part of the logic. |
Fallback event type fallbackEventTypeSlug | no | slug of an event type | Where visitors go when no rule matches. Without a fallback they end in a dead end. |
Active active | no | yes/no | Whether the form can be answered publicly. |
Brand brandId | no | one of your brands | Under which brand the form is kept. |
Resource (room or device)
| Field | Required | Values / format | What it does |
|---|---|---|---|
Name name | yes | text | What the room or device is called. |
Type kind | no | room, equipment, other | Room, device or other — for grouping the list only. |
Capacity capacity | no | number, greater than 0 | How many people fit in. |
Availability scheduleUlid | no | a schedule | When the resource is bookable at all. Empty means around the clock. |
Location location | no | text | Where the resource is. |
Active active | no | yes/no | Off means no longer bookable alongside. |
Brand brandId | no | one of your brands | Under which brand the resource is kept. |
Calendar connection
Google and Microsoft are connected through their own sign-in dialog and have no input fields. The following fields apply to CalDAV (iCloud, Nextcloud, Fastmail …) and to subscribed iCal/ICS feeds.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Calendar URL url | yes | address of the calendar collection or ICS feed | Where the external appointments come from. For CalDAV the direct collection URL, not the server root. |
User name username | yes (CalDAV) | text | The sign-in on the CalDAV server. |
App password password | yes (CalDAV) | text | The application-specific password — mandatory for iCloud and Fastmail, where the account password does not work. It is stored encrypted and never shown again. |
Label label | no | text | How the connection is called in the list. |
Room/equipment ownerResource | no | resource | With the permission “Manage resources”: the connection belongs to the room or equipment. Its appointments take the time from every event type using this resource — from no employee. |
Employee ownerEmployee | no | employee | Only with the permission “Manage calendar connections of all employees”: the ICS/CalDAV connection is created for this employee instead of you. Google and Microsoft are connected by each person themselves (the sign-in belongs to the account holder). |
After connecting you choose which calendars count:
| Field | Required | Values / format | What it does |
|---|---|---|---|
Target calendar targetCalendarId | no | a calendar of the connection | Where confirmed appointments are written. Empty means the account's default calendar. |
Busy calendars conflictCalendarIds | no | calendars of the connection | Which calendars additionally block slots. Empty means the target calendar only. |
Pull appointments in syncInbound | no | yes/no | Whether external appointments count as busy time. |
Push appointments out syncOutbound | no | yes/no | Whether booked appointments are written into the external calendar. |
Import a calendar file (.ics)
For calendars without a connection — say an export from practice software or a room plan. Appointments from yesterday to one year ahead block the owner’s time; only start and end are stored. A new file does not replace the old one — remove old files in the list.
| Field | Required | Values / format | What it does |
|---|---|---|---|
File content | yes | content of an .ics file, at most 2 MB, at most 5000 appointments | The appointments that block. Recurrences are expanded. |
File name fileName | no | text | How the file is called in the list. |
Employee ownerEmployee | no | employee | As for connections: without it yourself, with “Manage calendar connections of all employees” someone else. |
Room/equipment ownerResource | no | resource | With “Manage resources” for a room or equipment. |
Blocked time has one source. A known appointment takes the slot even without a foreign calendar connected — and so does an appointment from a connected calendar. That is why no double booking can arise while the connection stands.
Meeting polls
Under CRM → Meeting polls: propose several times, let people vote via link and book the right time for everyone with one click. Voting needs no account; other participants only see the number of votes, never names or addresses. The personal link shown after voting lets people change their vote.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Event type eventType | yes | an event type | Provides location, brand and confirmation emails; booking applies its follow-ups (lead, work item …). |
Title title | yes | text, up to 150 characters | Heading of the poll. |
Description description | no | text | Shown on the voting page. |
Duration durationMinutes | no | minutes, 5–1440 | Empty = duration of the event type. |
Hosts hostEmployeeIds | no | employees | Empty = hosts of the event type. Busy hosts are shown per proposal. |
Proposals options | yes | 1–30 points in time in the future | People vote on these times (yes, maybe, no). |
Time zone timezone | no | time zone | For emails and display; the voting page shows times in the visitor’s time zone. |
Voting until closesAt | no | point in time in the future | After it, the page accepts no more votes. |
Book proposal optionId | yes (when concluding) | a proposal | Books the appointment for everyone who voted “yes” or “maybe” — they receive the confirmation by email. If the time is taken meanwhile, the poll stays open. |
Import from Calendly
Under Settings → Scheduling → Scheduling settings (permission platform.import.run). Imports event types and all future appointments including guests — without email to the guests and without a new calendar entry (it is already in the host’s calendar). Hosts are matched by their email address. A second run skips what was already imported. Access: integration “calendly” with the personal access token calendly_token.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Import execute | no | yes/no (default no) | No = dry run: only counts what would be imported and names skipped items with a reason. Yes = writes. |
Brand brandId | with several brands | one of your brands | The brand to import into. |
Settings & permissions
- Event types:
scheduling.event_type.view/.manage - Availability:
scheduling.schedule.view/.manage - Booking pages:
scheduling.booking_page.view/.manage - Appointments:
scheduling.appointment.view_own/.view_team/.view_any/.manage/.reschedule—view_teamshows appointments hosted by a colleague from your team - Calendar connections:
scheduling.connection.manage_own(own) /.manage_any(all employees, cut by their brands) - Module settings:
scheduling.settings.manage— changing them requires the permission across all brands, since they apply to every brand - Meeting polls:
scheduling.poll.view/.manage - Calendly import:
platform.import.runin the target brand
Module settings
Under Settings → Scheduling → Scheduling settings.
| Field | Required | Values / format | What it does |
|---|---|---|---|
Public booking publicBookingEnabled | no | yes/no | Off: booking links and embeds stop answering; booked appointments stay manageable via their link. |
Reminder before the appointment reminderLeadMinutes | no | minutes, 0–43200 | When the reminder email goes to the guest. |
Payment deadline holdTtlSeconds | no | seconds, 0–86400 | If payment does not arrive, the appointment is cancelled afterwards. 0 = never. |
Reserve the time while typing slotHoldSeconds | no | seconds, 0–86400 | While a visitor fills in the form, others do not see the chosen time. 0 turns it off. |
Cache free times slotCacheSeconds | no | seconds, 0–86400 | Relieves the server with many visitors; every booking clears it immediately. 0 turns it off. |
Refresh free times live liveUpdatesEnabled | no | yes/no | Reports changes to open booking pages instantly; needs a Mercure hub (operations). |
Block fully planned project days blockFullyPlannedDays | no | yes/no | Days on which project planning uses up the capacity cannot be booked. |
Check external calendars live liveBusyCheck | no | yes/no (default yes) | Queries Google, Microsoft and CalDAV at the moment of booking — an appointment entered moments ago is not overbooked. If the provider is unreachable, the synchronised state applies. |
Payment provider paymentProvider | no | stripe, paypal | For paid event types. Credentials as integration “stripe” (secret_key, webhook_secret) or “paypal” (client_id, client_secret, webhook_id, optional sandbox); secrets are stored encrypted. |
Geocoder travelGeocoderUrl | no | http(s) address with a Nominatim interface; empty = off | For the routed travel buffer: turns addresses into coordinates. Addresses are sent to this service — ideally self-hosted. |
Routing service travelRouterUrl | no | http(s) address with an OSRM interface; empty = off | Calculates the travel time between two coordinates. Without both services the fixed buffer applies. |
Email to the host hostMailEnabled | no | yes/no | The host receives an email (template “Appointment for host”) with a calendar invitation on booking, rescheduling and cancellation — without invitation if their calendar is connected outbound. |
Security check when booking captchaMode | no | off, pow, turnstile | pow invisibly solves a puzzle in the browser without any third-party service. turnstile uses Cloudflare Turnstile (needs the OctiConnect module; otherwise the puzzle applies). |
Turnstile site key turnstileSiteKey | with Turnstile | text | From the Cloudflare account. |
Turnstile secret turnstileSecret | with Turnstile | text | From the Cloudflare account; stored encrypted and never shown again. |
Module flag: module.scheduling. Setup walks you through the order.
FAQ & troubleshooting
No free times are shown. Check schedule, notice and buffer — often the notice period is longer than the displayed range.
An appointment from the external calendar is missing. Check the connection. If it fails, what is in the system still counts — so you are not unprotected, but external appointments are missing.
Two bookings on the same slot. Does not happen: the booking locks host and time until it is written — whoever confirms a second later gets "time already taken" and picks again.
A visitor is filling in the form. As soon as they pick a time it is reserved for them (default five minutes, setting scheduling.slot_hold_seconds, 0 turns it off) — others do not see it meanwhile. If they go back or the hold expires, the time is free again immediately.
Free times refresh by themselves. The booking page asks again every minute. With scheduling.live_updates_enabled and a configured Mercure hub the server reports every change instantly; without a hub it keeps asking. The list of free times is cached briefly (scheduling.slot_cache_seconds, default 60 seconds) and cleared on every booking, cancellation and change — a booking always checks the current state.
A CRM appointment without an owner. It blocks the time of whoever entered it.
The host gets the booking twice in the calendar. Does not happen: if their calendar is connected outbound, scheduling enters the appointment there and the host email comes without an invitation. The bell in the back office reports the booking as well.
Bookings fail with “security check failed”. With Turnstile the browser must be allowed to reach Cloudflare (operations: Content Security Policy). The built-in puzzle (pow) needs no third-party service.