P
PepoSmart Docs

REST API (v1)

The PepoSmart REST API lets your own backend list event types, read live availability, and create bookings on behalf of your users. Bookings made through the API are identical to bookings made on the booking page — the same confirmation emails, calendar invites, video links, reminders and AI meeting notes all apply.

Base URL

https://app.peposmart.com/api/v1 — all requests are HTTPS and all request and response bodies are JSON.

Authentication

Every endpoint requires an API key, sent as a Bearer token. Create one in the app under Account → API Keys:

  1. Go to app.peposmart.com/account/api-keys
  2. Give the key a name that identifies the system using it, then Create key
  3. Copy the key immediately — the full value is shown only once

Keys look like psk_live_…. Send them on every request:

Authorization: Bearer psk_live_xxxxxxxxxxxxxxxxxxxxxxxx

Server-side only

An API key acts on behalf of the account that created it. Never ship it to a browser, a mobile app, or any client you do not control. Call the API from your backend and expose only what your own users need. A key can be revoked at any time from the same screen.

Scope of a key

A key is tied to the user account that created it. It can only see and book that account's event types. If you need to book across several hosts, either use a team event type (where PepoSmart assigns the host) or issue one key per host account.

Private event types are never returned or bookable through the API, even if you know the event id.

Rate limits

Endpoint groupLimit
Reads (/event-types, /availability)60 requests per minute, per key
Bookings (POST /bookings)10 requests per minute, per key

Exceeding a limit returns 429 with a Retry-After header giving the seconds to wait. Read and booking budgets are counted separately, so polling availability never eats into your booking allowance. If a production portal needs more headroom, contact support and we will raise it.

Booking flow

The three read-then-write calls, in order:

  1. List event types to find the event id you want to book, and to learn what that event requires from the attendee.
  2. Fetch availability for that event id and show the slots to your user.
  3. Create the booking with the chosen slot and the attendee's details.

Timezones: pass slots back verbatim

The date and time you send to POST /bookings must be exactly the values returned by GET /availability — they are expressed in the host's timezone, which the response reports as timezone. Do not convert them. To display slots in your user's timezone, pass ?tz= and use the visitorDate / visitorTime fields for display only.

GET /api/v1/event-types

Lists the public, non-deleted event types belonging to the key's account.

curl https://app.peposmart.com/api/v1/event-types \
  -H "Authorization: Bearer psk_live_xxx"

Response 200

{
  "host": { "name": "Jordan Lee", "username": "jordan" },
  "eventTypes": [
    {
      "id": "evt_123",
      "title": "Discovery Call",
      "description": "A 30 minute intro call.",
      "durationMinutes": 30,
      "locations": ["google_meet"],
      "needsPhoneNumber": false,
      "needsPreferredLocation": false,
      "requiresPayment": false,
      "paymentAmountCents": null,
      "paymentCurrency": null,
      "eventType": "one-on-one",
      "hostCount": 1,
      "hostNames": ["Jordan Lee"],
      "bookingUrl": "https://app.peposmart.com/book/jordan/evt_123"
    }
  ]
}
FieldWhy it matters
locationsIf more than one, you must pass location when booking
needsPhoneNumberThe host calls the attendee — attendee.phoneNumber is required
needsPreferredLocationThe event asks the attendee where to meet — suggestedLocation is required
requiresPaymentCannot be booked via the API — send the user to bookingUrl
eventTypecollective means every host attends; round-robin means one is assigned at booking time. Do not describe one as the other to your users.
hostCountAuthoritative count. hostNames can be shorter when a host has no display name, so never infer the number of hosts from it.

GET /api/v1/availability/{eventId}

Returns bookable slots, with host schedules merged and existing bookings and external calendar busy blocks already removed.

Query parameterDefaultNotes
days14Number of days with availability to return. Capped at 30.
tzAn IANA timezone (e.g. Europe/London). Adds visitorDate / visitorTime to each slot for display. Invalid values return 400.
curl "https://app.peposmart.com/api/v1/availability/evt_123?days=7&tz=Europe/London" \
  -H "Authorization: Bearer psk_live_xxx"

Response 200

{
  "eventId": "evt_123",
  "title": "Discovery Call",
  "durationMinutes": 30,
  "timezone": "America/New_York",
  "visitorTimezone": "Europe/London",
  "days": [
    {
      "date": "2026-09-01",
      "slots": [
        {
          "time": "09:00",
          "iso": "2026-09-01T13:00:00.000Z",
          "visitorDate": "2026-09-01",
          "visitorTime": "14:00"
        }
      ]
    }
  ]
}

Only days with at least one free slot are returned. timezone is the host's — date and time are expressed in it, and those are the two values you send back when booking. iso is the same instant in UTC, handy for sorting or rendering yourself.

POST /api/v1/bookings

Creates a confirmed booking.

curl -X POST https://app.peposmart.com/api/v1/bookings \
  -H "Authorization: Bearer psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "evt_123",
    "date": "2026-09-01",
    "time": "09:00",
    "attendee": {
      "name": "Jane Doe",
      "email": "[email protected]",
      "timezone": "Europe/London",
      "phoneNumber": "+44 20 7123 4567"
    },
    "location": "google_meet",
    "guestEmails": ["[email protected]"],
    "notes": "Wants to discuss the enterprise tier."
  }'
FieldRequiredNotes
eventIdYesFrom GET /event-types
dateYesYYYY-MM-DD, in the host timezone
timeYesHH:mm, in the host timezone
attendee.nameYesMax 200 characters. Split into first and last name automatically when the event collects them separately.
attendee.emailYesWhere the confirmation and reminders are sent
attendee.timezoneNoIANA name. Defaults to the host's. Used for the times shown in the attendee's emails, so pass it when you know it.
attendee.phoneNumberConditionalRequired when needsPhoneNumber is true
locationConditionalRequired when the event offers more than one. Must be one of its locations.
suggestedLocationConditionalRequired when needsPreferredLocation is true. Max 300 characters.
guestEmailsNoUp to 10. Ignored unless the event allows guests.
notesNoMax 2000 characters. Stored with the booking.

Response 201

{
  "booking": {
    "id": "bkg_789",
    "eventTitle": "Discovery Call",
    "startTimeIso": "2026-09-01T13:00:00.000Z",
    "durationMinutes": 30,
    "attendeeLocalTime": "Tuesday, September 1, 2026 at 14:00 BST",
    "location": "google_meet",
    "meetingUrl": "https://meet.google.com/abc-defg-hij"
  },
  "confirmationEmail": "A confirmation email will be sent to [email protected] if this event has email notifications configured."
}

meetingUrl is the Google Meet, Zoom or Teams link, whichever the event is configured for. It is null for in-person or phone meetings.

Errors

Errors return a JSON body with an error string, sometimes with extra context to help you recover.

StatusMeaning & what to do
400Validation failed. Field-level messages arrive in details. A missing location also returns the valid options; a paid event returns its bookingUrl so you can redirect the user there.
401Missing, malformed, unknown or revoked key. Check the Authorization: Bearer header.
404The event type does not exist, is private, was deleted, or belongs to a different account than the key.
409The slot was taken between your availability call and the booking. The body includes sameDayAlternatives — offer those and retry.
429Rate limited. Wait the number of seconds in Retry-After.
502Availability could not be computed. Retry shortly.

Always handle 409

Availability is a snapshot. Somebody can book the same slot through the booking page while your user is still deciding, so a 409 is normal operation rather than a bug — refresh the slots and let the user pick again.

Paid event types

Event types that collect payment cannot be booked through the API, because payment is taken during checkout on the booking page. Such a request returns 400 along with the bookingUrl — send the user there to finish. You can detect these ahead of time via requiresPayment on GET /event-types.

Prefer the embed if you do not need your own UI

The API is the right choice when you want full control of the interface or a tamper-proof attendee identity. If you mainly want to save your users from retyping their details, the embed route is far less work and supports paid events too.