Appointment Guide

Learn how to create, hold, confirm, and manage appointments through the v3 API without exposing internal booking logic.

Environment tip: All examples use v3.onsched.com for production. Replace the host with api-stage.onsched.com when calling the staging environment.

When to Use

  • Build customer-facing booking flows (create holds, finalize bookings, cancel or reschedule).
  • Power operational tooling that needs filtered appointment lists or per-record inspection.
  • Sync appointments to downstream systems via polling or in combination with webhooks.

Prerequisites

  • Authentication: Either OAuth2 client credentials (POST /v3/oauth/token) or the dashboard trio of Authorization: Bearer <JWT>, x-api-key, and x-client-id.
  • Company context: Requests are scoped to the authenticated company. Location and service IDs must belong to that company.
  • Valid availability check: Always call GET /v3/availability first to present conflict-free slots.

Endpoint Overview

EndpointPurpose
GET /v3/appointmentsPaginated list with optional filters (limit, page).
GET /v3/appointments/filterFilter by status, date range, resource, or location via query params.
GET /v3/appointment/:idRetrieve a single appointment (public routes only return minimal fields).
POST /v3/appointmentCreate a booked appointment immediately.
POST /v3/appointment/holdCreate a short-lived hold (status: IN) so customers can finish intake steps.
POST /v3/appointment/reserveReserve a slot (status: RS) without hold expiration; finalize with PUT .../book or delete when no longer needed.
PUT /v3/appointment/:id/bookConvert a hold or reserved slot to a booked record without changing slot times.
PUT /v3/appointment/:idUpdate mutable properties (notes, metadata, CustomFields, etc.).
PUT /v3/appointment/:id/rescheduleMove an appointment to a new slot. The request must pass availability validation. The new record keeps IN/RS if the original was a hold or reserved; otherwise it is booked (BK).
PUT /v3/appointment/:id/confirmSet confirmed: true only; does not change status or external calendars.
PUT /v3/appointment/:id/cancelCancel and trigger notifications + webhooks.
DELETE /v3/appointment/:idHard delete; typically reserved for internal tooling.

All write operations are validated server-side (see files under api/validations/appointment/). Invalid IDs or overlapping times return 400 errors without revealing internal scheduling rules.

Booking Flow

  1. Hold (optional): POST /v3/appointment/hold with Unavailability.startTime/endTime and optional Customer, CustomFields, and ResourceIds. Holds respect expiration windows defined per location (expirationDelay seconds).
  2. Book (and optional confirm): Call PUT /v3/appointment/:id/book to finalize a hold into BK with calendar sync, or use POST /v3/appointment to book in one step. Use PUT /v3/appointment/:id/confirm only when you need confirmed: true without changing status or calendars.
  3. Resource Assignment: Provide ResourceIds as repeated query string parameters (e.g., ResourceIds=id1&ResourceIds=id2). When omitted, the service’s eligible resources are auto-assigned based on the round robin mode you requested during availability.
  4. Notifications/Webhooks: The platform automatically dispatches notifications and webhooks unless you pass skip_notifications=true in the request body—use sparingly and send your own alerts if you opt out.
  5. Calendar Events: Google Calendar / Outlook events are created when a booked appointment is created (POST /v3/appointment), a hold or reserve is finalized (PUT /v3/appointment/:id/book), or an appointment is rescheduled (new slot). Holds and reserves (POST /v3/appointment/hold, POST /v3/appointment/reserve) do not create external events until booked. PUT /confirm does not sync calendars.

Status Lifecycle

api/enums/models.js defines the canonical status values:

  • IN – Initial hold. Auto-expires if not promoted.
  • BK – Booked appointment (most common steady state).
  • RS – Reserved hold that is kept longer than a quick IN.
  • RE – Rescheduled placeholder left behind when a booking is moved.
  • CN – Cancelled appointment.

The API prevents conflicting state transitions (for example, cancelling a CN appointment returns a 400). Use holds for multi-step checkout; skip them for instant bookings.

Status transitions and side effects

  • POST /v3/appointment/holdIN status. The slot is locked until expirationDelay (seconds) on the location elapses or you finalize it.
  • POST /v3/appointment/reserveRS status. The slot stays locked without that hold timer; finalize with PUT /v3/appointment/:id/book or remove the appointment when done.
  • PUT /v3/appointment/:id/book or POST /v3/appointmentBK status. Confirmation emails/SMS and confirmation webhooks fire unless you pass skip_notifications=true.
  • PUT /v3/appointment/:id/confirm sets confirmed: true and does not change status or sync external calendars. To finalize a hold into a booked record with calendar sync, use PUT /v3/appointment/:id/book.
  • PUT /v3/appointment/:id/reschedule leaves the original record behind in RE status (for audit/history) and creates a new appointment for the new slot; status on the new row follows the same hold/reserved vs booked rules as POST /v3/appointment / #buildAppointmentData (not always BK).
  • PUT /v3/appointment/:id/cancel moves the booking to CN, stops reminder cadence, and emits cancellation webhooks/notifications.

Custom Fields & Metadata

  • Send CustomFields as an object. Keys must exist in your schema configuration.
  • Customer accepts either an existing id or the fields required to create a new record (firstName, email at minimum).
  • Unavailability represents the time block; padding is computed server-side when the service has non-zero padding.

Build appointments from availability results

Use the values returned by GET /v3/availability to avoid validation errors:

  • LocationId and ServiceId must match the request you used to find the slot.
  • ResourceIds should mirror the slot returned (or the resources you requested when roundRobin=NONE).
  • Unavailability.startTime/endTime should come directly from the chosen availableTimes entry.
  • Customer is required for immediate bookings; holds (POST /v3/appointment/hold) can be created without a customer and later promoted with PUT /v3/appointment/:id/book once customer details are ready.

If you need extra time for intake, use holds plus a higher expirationDelay on the location; otherwise, book directly to keep the flow simple.

Example: Create a Hold Then Book It

# Step 1: create hold (expires automatically if untouched)
curl -X POST https://v3.onsched.com/v3/appointment/hold \
  -H "Authorization: Bearer <token>" \
  -H "x-api-key: <company-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "LocationId": "<location-id>",
    "ServiceId": "<service-id>",
    "ResourceIds": ["<resource-id>"],
    "Customer": {
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "[email protected]"
    },
    "Unavailability": {
      "startTime": "2025-03-14T15:00:00Z",
      "endTime": "2025-03-14T15:30:00Z"
    }
  }'

# Step 2: finalize
curl -X PUT https://v3.onsched.com/v3/appointment/<hold-id>/book \
  -H "Authorization: Bearer <token>" \
  -H "x-api-key: <company-key>" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Confirmed via concierge"}'

Troubleshooting

  • “The selected time slot is not available.” Confirm you are passing the same ResourceIds that were returned by the availability search and that no one else booked the slot in the meantime.
  • Invalid Customer errors: Ensure either Customer.id exists or you include required creation fields. Holds without customer context only work when isHold is true.
  • Notifications skipped unintentionally: Avoid setting skip_notifications unless you intentionally handle downstream messages.
  • Missing fields from public routes: Public endpoints deliberately trim fields like notes, CustomFields, and contact info. Use authenticated company routes for internal tooling.
  • External calendar missing after “confirm”: PUT /confirm does not call Google or Outlook. Use PUT /book (from a hold) or POST /v3/appointment (immediate book) so the API can create calendar events.
  • Investigating calendar issues in logs: Search API logs for the appointment id and for [Calendar] warnings (failed create/delete) or messages from GoogleCalendar (for example invalid_grant). OnSched-tagged Google events include extendedProperties.private.createdBy = ONSCHED in the Calendar API; events without that tag were not created by this sync path.

With these patterns you can compose reliable booking flows while keeping OnSched’s scheduling engine as the source of truth.