Appointment Actions API
Appointment action endpoint: confirm/reject/cancel/paid/complete/no_show
Appointment Actions API
This endpoint powers the core lifecycle management of appointments in the Odys platform, allowing professionals and clients to update the status of bookings through a single PATCH route. It handles confirmation, rejection, cancellation, payment capture, completion, and no-show marking—each triggering database updates and automated messaging. Confirmation, rejection, and client-initiated cancellations also trigger in-app notifications.
Overview
The /api/appointments/[id] route is one of 23 API endpoints in the system and the only one dedicated to mutating appointment states. It interacts primarily with the appointments, professionals, clients, and notifications tables—four of the 10 total database tables. The endpoint enforces strict role-based access: actions are permitted only if the authenticated user owns the professional profile or is the associated client. It also integrates with WhatsApp messaging (19 templates available system-wide) and email delivery for real-time communication.
Error Handling
The endpoint provides granular error responses to guide API consumers:
429 Too Many Requests: Returned if the API rate limit is exceeded.401 Unauthorized: If no authenticated user is found for the request.403 Forbidden: For actions where the authenticated user lacks the necessary role (e.g., a client attempting a professional-only action, or an unauthorized user attempting to cancel).404 Not Found: If the requested appointment ID does not correspond to an existing appointment.400 Bad Request: For invalid requests such as an unknownactiontype, missingprofessionalorclientdata associated with the appointment, or an attempt tocancelan appointment that is not inpending_confirmationorconfirmedstatus.500 Server Error: For unexpected server-side issues caught by the general error handler.
State Transitions and Authorization
Each action modifies either status or paymentStatus (or both) in the appointments table, which has predefined defaults for these fields. The ALLOWED_ACTIONS set ensures only valid operations proceed, preventing silent failures from typos. Actions like cancel can be initiated by either party if the appointment is pending or confirmed, while paid, complete, and no_show are restricted to professionals. Specifically, confirm sets status to confirmed and paymentStatus to authorized, while reject sets status to rejected and paymentStatus to refunded.
On confirmation, the system sends WhatsApp and email receipts and inserts a notification for the client; on rejection, it notifies the client via WhatsApp, email, and an in-app notification, and sends a WhatsApp rejection receipt to the professional. During confirmation, if the client is not a registered user, the WhatsApp message includes an invitation to register for the platform.
Cancellation flows differ based on initiator: clients trigger a two-way notification (to pro and themselves), while professionals only notify the client. This reflects an asymmetric responsibility model where client cancellations are treated as events needing acknowledgment by the professional.
Design decisions
The endpoint consolidates multiple actions into a single PATCH handler to reduce API surface area and avoid route sprawl. Early ownership checks (isProfessional, isClient) streamline permission logic across branches. The use of drizzle-orm enables type-safe queries against the appointments schema, while getUser() and getIp() abstract authentication and rate limiting. The endpoint enforces API rate limiting using getApiLimiter() and returns a 429 Too Many Requests response if the limit is exceeded. WhatsApp messages are fire-and-forget (.catch(console.warn)), prioritizing request completion over transient delivery failures.
Potential improvements
-
Duplicate WhatsApp logic: The same
sendWhatsAppcall with{ context }appears in multiple branches (e.g.,confirm,cancel). Extracting this into a reusablenotifyClient()function inlib/whatsappwould reduce repetition and improve maintainability. -
Email sending side effects: Email functions like
sendBookingConfirmedEmailToClientare called directly in the route handler. These should be decoupled via a queue or event system (e.g., using cron or MCP tools likeupdate_appointment_status) to prevent delays during HTTP response. -
Hardcoded status strings: Status values like
"confirmed","cancelled", etc., appear inline in conditionals (e.g.,["pending_confirmation", "confirmed"]). These should be defined as constants or enum-like objects to prevent typos and enable centralized validation.
References
src/app/api/appointments/[id]/route.tslib/db/schema.tslib/whatsapp.tslib/email.ts