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.
PUT /v3/appointment/:id/bookConvert a hold 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.
PUT /v3/appointment/:id/confirmMark the appointment as confirmed (status BK, confirmed: true).
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. Confirm / Book: Either call PUT /v3/appointment/:id/book to finalize a hold or skip straight to POST /v3/appointment to create a booked record in one step.
  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: When resources have connected calendars, events are created automatically after the appointment succeeds. Cancelling or rescheduling removes or updates those events.

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.

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.

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.

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