Public API reference

IT / Technical

Preb exposes a small set of public HTTP endpoints under /api/public/*. Most power the booking widget and are unauthenticated; one (transactions) records revenue and is authenticated with an API key. All endpoints return JSON, run over https://app.preb.co, and are rate limited.

Authentication & signing

Each endpoint uses one of three authentication models:

EndpointMethodAuth
/api/public/capturePOSTNone (public widget)
/api/public/bookPOSTNone (public widget)
/api/public/transactionsPOSTAPI key (HMAC-signed)
/api/public/month-availabilityGETNone (public widget)
/api/public/booking-summaryGETSigned token
/api/public/funnel-eventsPOSTNone (public widget)

Only /api/public/transactions uses an API key. It is signed with HMAC — see API keys → Authentication for the exact header format. booking-summary is read with a short-lived signed token that Preb mints for an existing booking. The remaining endpoints are public widget endpoints with no credential.

All times are UTC ISO-8601

Timestamps in requests and responses use UTC ISO-8601 (e.g. 2026-06-29T10:00:00Z). Timezones for availability and booking are passed separately as IANA names (e.g. Europe/Berlin).

POST /api/public/capture

Creates or updates a contact ("lead") from the booking form's first step. Public; rate limited to 20 requests / 60s per IP and event type.

Request body

FieldTypeRequiredNotes
eventTypeIdUUIDThe event being booked
emailstringMax 254 chars
namestring1–120 chars (omit when empty; don't send "")
phonestringMax 40 chars
attributionJsonbobjectUTM / click-id attribution payload
customFieldsobjectUp to 50 keys; serialises to ≤ 8 KB
step1Completebooleantrue only when the visitor presses Continue; gates qualification + routing
priorContactIdUUIDUsed to rename a session's lead in place after a typo correction

Response 200 — shape varies by outcome:

// normal capture
{ "contactId": "…", "wasCreated": true, "disqualified": false,
  "redirectUrl": null, "allowedHostIds": ["…"] }

// disqualified by a rule
{ "contactId": "…", "wasCreated": true, "disqualified": true,
  "redirectUrl": "https://…", "fullyBooked": false,
  "disqualifiedTitle": "…", "guestMessage": "…",
  "ctaLabel": "…", "ctaUrl": "…" }

Notable: first-touch attribution is preserved across updates, custom fields are deep-merged, and unknown custom-field keys are dropped (not an error).

POST /api/public/book

Commits a booking for a chosen slot. Public, but requires an Idempotency-Key header (8–512 characters) so retries can't double-book. Rate limited to 5 requests / 60s per IP and event type.

Request body

FieldTypeRequiredNotes
eventTypeIdUUID
startTimeUtcstringUTC ISO-8601 with offset
bookerTimezonestringIANA timezone
attendeesarray1–20 items, each { name, email }
responsesobjectAnswers to booking-form questions (string → string)
selectedLocationKindstringThe location the booker chose, when the event type offers several. Must match a configured kind. Omit to use the event's first location.
attendeeAddressstringThe booker's meeting address. Required when the chosen location is in_person_attendee (max 500 chars).
consentbooleanRequired when the event type requires consent
attributionJsonbobjectAttribution payload
contactIdstringLinks the booking to a known contact
rescheduledFromUidstringThe booking this one replaces
embedbooleanSuppresses the confirmation redirect inside an iframe

Response 200

{
  "uid": "bk_…",
  // present only when a confirmation redirect is configured:
  "redirectUrl": "https://…",
  "redirectDelaySeconds": 3,
  "redirectNewTab": false,
  "redirectCountdown": true,
  "redirectFireTracking": true
}

A retry with the same Idempotency-Key (or a concurrent duplicate) returns the existing booking's uid. Host selection, routing enforcement, and calendar sync all happen server-side; the client's host filter is re-derived from the stored contact and is not trusted.

POST /api/public/transactions

Records a payment against a contact — the bridge for reporting revenue from an external billing system (Make, Zapier, a custom script). Authenticated with an API key (the only endpoint that is). Rate limited to 1000 requests / hour per key.

Request body

FieldTypeRequiredNotes
emailstringResolves / creates the contact
amountnumberPositive, up to 9999999999.99
currencystring3-letter code (e.g. EUR); stored upper-cased
transaction_atstringUTC ISO-8601; backfill allowed
preb_booking_uidstringAttach to a specific booking (max 64)
descriptionstringMax 2000 chars

You can also send an optional Idempotency-Key header (must match [A-Za-z0-9_.:-]{1,255}) to make replays safe.

Response 201

{ "transactionId": "…", "dealId": "…", "contactId": "…" }

A replay of a previously seen idempotency key returns 200 with "idempotentReplay": true. The transaction is attached to the contact's most recent open deal where possible, and closes that deal on an exact amount match.

GET /api/public/month-availability

Returns the bookable days in a date range, in the booker's timezone. Public; rate limited to 60 requests / 60s per IP. Used by the widget's calendar to gray out fully-booked days.

Query parameters

ParamRequiredNotes
eventTypeId
tzIANA timezone
fromYYYY-MM-DD; defaults to today
toYYYY-MM-DD; defaults to the end of the booking window
hRestrict to specific routed hosts

Response 200

{ "availableDates": ["2026-07-01", "2026-07-03"], "noHostsAvailable": false }

A range wider than the booking window (plus a small margin) returns 400 range_too_large.

GET /api/public/booking-summary

Returns a public summary of one booking (used by confirmation / reschedule screens). Authenticated with a short-lived signed token, not an API key — Preb mints the token for a specific booking and includes it where it's needed. Rate limited to 60 requests / 60s per IP.

Request: no body. Send the token in the header:

Authorization: Bearer <token>

Response 200

{
  "uid": "bk_…",
  "title": "Intro call",
  "start_time": "2026-07-01T09:00:00Z",
  "end_time": "2026-07-01T09:30:00Z",
  "location": { "kind": "google_meet", "conferencingLink": "https://…" },
  "host_name": "Jane Host",
  "host_username": "jane",
  "avatar_url": "https://…",
  "attendees": [{ "name": "…", "email": "…", "role": "guest" }],
  "is_routed": true
}

This endpoint is intentionally not blocked for workspaces with a lapsed subscription — it's a read of an already-completed booking.

POST /api/public/funnel-events

Reports how long a visitor spent on each booking-form field and whether they abandoned it — the data behind form drop-off analysis. Public; rate limited to 50 requests / 60s per IP and event type.

Request body

FieldTypeRequiredNotes
eventTypeIdUUID
eventsarray1–20 items

Each item: { "field": string, "dwell_seconds": number, "abandoned": boolean } (dwell_seconds 0–3600).

Response 200

{ "inserted": 3 }

Events for unknown fields are dropped silently; { "inserted": 0 } is a normal response, not an error.

Errors & status codes

Errors are JSON: { "error": "<code>" }, sometimes with an extra message or issues (validation detail). Common codes:

StatusExample codesMeaning
400invalid_json, invalid_input, invalid_timezone, missing_idempotency_key, consent_required, range_too_large, phone_required, address_required, invalid_locationMalformed or rejected request
401unauthorized, missing_token, invalid_tokenAuth failed (key signature or token)
403email_not_allowedEmail blocked by the event's policy
404event_type_not_found, not_foundTarget doesn't exist or is hidden
409booking_unavailable, slot_unavailable, per_invitee_limitThe booking can't proceed right now
422contact_not_foundCouldn't resolve a contact (transactions)
429rate_limitedOver the limit — see Retry-After
500internal, no_location_configuredServer-side error

See Rate limits & security for the rate headers and the fail-open behaviour.