Skip to main content
POST
/
api
/
calendar
/
v1
/
public
/
bookings
/
confirmation
/
{confirmation_number}
/
cancel
Cancel Booking
curl --request POST \
  --url https://api.example.com/api/calendar/v1/public/bookings/confirmation/{confirmation_number}/cancel
{
  "400": {},
  "403": {},
  "404": {},
  "422": {},
  "429": {},
  "booking": {
    "id": "<string>",
    "confirmation_number": "<string>",
    "status": "<string>",
    "start_at": "<string>",
    "end_at": "<string>",
    "service_name": "<string>",
    "organization_name": "<string>"
  }
}

Endpoint

POST https://api.kordless.ai/api/calendar/v1/public/bookings/confirmation/{confirmation_number}/cancel
Cancels an existing booking and releases the time slot. This endpoint uses contact verification, allowing customers to cancel their own bookings.

Authentication

This endpoint uses confirmation number + contact verification instead of an API key.
confirmation_number
string
required
The booking confirmation number (case-insensitive)Example: BOOK_ABC123XYZ
contact
string
required
Email or phone number used when bookingExamples: [email protected] or +1234567890

Response

booking
object
The canceled booking

Examples

curl -X POST "https://api.kordless.ai/api/calendar/v1/public/bookings/confirmation/BOOK_ABC123XYZ/[email protected]"

Response Example

{
  "booking": {
    "id": "book_abc123xyz",
    "confirmation_number": "BOOK_ABC123XYZ",
    "status": "canceled",
    "organization_slug": "acme-salon",
    "organization_name": "Acme Salon",
    "start_at": "2025-12-03T14:00:00Z",
    "end_at": "2025-12-03T15:00:00Z",
    "service_id": "haircut",
    "service_slug": "haircut",
    "service_name": "Haircut",
    "contact": {
      "name": "Jane Doe",
      "email": "[email protected]",
      "phone": "+1234567890"
    },
    "location": {
      "type": "onsite",
      "value": "123 Main St, Suite 100"
    },
    "timezone": "America/New_York"
  }
}

Customer Cancellation Flow

async function handleCancelBooking(confirmationNumber, contactInfo) {
  // 1. Look up booking to show details
  const { booking } = await lookupBooking(confirmationNumber, contactInfo);

  // 2. Confirm cancellation with customer
  const confirmed = await showConfirmDialog({
    title: 'Cancel Booking?',
    message: `Are you sure you want to cancel your ${booking.service_name} appointment on ${formatDate(booking.start_at)}?`,
    confirmText: 'Yes, Cancel',
    cancelText: 'Keep Booking'
  });

  if (!confirmed) return;

  // 3. Cancel the booking
  try {
    await cancelBooking(confirmationNumber, contactInfo);

    // 4. Show success message
    showSuccess({
      title: 'Booking Canceled',
      message: 'Your appointment has been canceled. You will receive a confirmation email shortly.'
    });

    // 5. Offer to rebook
    showRebookOption({
      orgSlug: booking.organization_slug,
      serviceSlug: booking.service_slug
    });

  } catch (error) {
    showError('Could not cancel booking. Please try again or contact support.');
  }
}

Integration with Booking Widget

Add a cancel button to your booking confirmation:
function BookingConfirmation({ confirmationNumber, customerEmail }) {
  const [booking, setBooking] = useState(null);
  const [canceled, setCanceled] = useState(false);

  useEffect(() => {
    lookupBooking(confirmationNumber, customerEmail)
      .then(({ booking }) => setBooking(booking));
  }, [confirmationNumber, customerEmail]);

  async function handleCancel() {
    if (!confirm('Are you sure you want to cancel this booking?')) return;

    await cancelBooking(confirmationNumber, customerEmail);
    setCanceled(true);
  }

  if (canceled) {
    return (
      <div className="cancellation-notice">
        <h2>Booking Canceled</h2>
        <p>Your appointment has been canceled.</p>
        <a href={`/book/${booking.organization_slug}`}>Book a new appointment</a>
      </div>
    );
  }

  return (
    <div className="booking-details">
      <h2>Your Appointment</h2>
      <p>Service: {booking?.service_name}</p>
      <p>Date: {formatDate(booking?.start_at)}</p>
      <p>Time: {formatTime(booking?.start_at)}</p>

      <div className="actions">
        <button onClick={handleCancel} className="cancel-btn">
          Cancel Booking
        </button>
        <a href={`/reschedule/${confirmationNumber}`} className="reschedule-link">
          Reschedule
        </a>
      </div>
    </div>
  );
}

Best Practices

Always ask the customer to confirm:
const confirmed = await showConfirmDialog({
  title: 'Cancel Booking?',
  message: `Cancel your ${booking.service_name} on ${formatDate(booking.start_at)}?`,
  confirmText: 'Yes, Cancel',
  destructive: true
});

if (confirmed) {
  await cancelBooking(confirmationNumber, contactInfo);
}
Notify the customer:
await cancelBooking(confirmationNumber, contactInfo);

await sendEmail({
  to: customerEmail,
  subject: 'Booking Canceled',
  body: `
    Your appointment has been canceled.

    Service: ${booking.service_name}
    Original Date: ${formatDate(booking.start_at)}

    Want to rebook? Visit your booking page.
  `
});
Give customers options after canceling:
await cancelBooking(confirmationNumber, contactInfo);

showOptions({
  title: 'What would you like to do?',
  options: [
    {
      label: 'Book a New Appointment',
      action: () => redirectToBooking(booking.organization_slug)
    },
    {
      label: 'Browse Other Services',
      action: () => redirectToServices(booking.organization_slug)
    }
  ]
});
Gracefully handle double-cancellation attempts:
try {
  await cancelBooking(confirmationNumber, contactInfo);
} catch (error) {
  if (error.message.includes('already canceled')) {
    showInfo('This booking was already canceled.');
  } else {
    throw error;
  }
}

Errors

400
Bad Request
Missing contact parameter
{
  "detail": "Contact information is required to manage this booking"
}
403
Forbidden
Contact doesn’t match booking
{
  "detail": "We could not verify that booking with the provided contact information."
}
404
Not Found
Booking not found
{
  "detail": "Booking not found"
}
422
Unprocessable Entity
Booking already canceled
{
  "detail": "Booking is already canceled"
}
429
Too Many Requests
Rate limit exceeded