To integrate Acuity Scheduling with V0 by Vercel, generate a booking interface with V0, create a Next.js API route that calls the Acuity API using your credentials, store credentials in Vercel environment variables, and deploy. Your app can display available times, fetch appointments, and create bookings without exposing your API key to the browser.
Build Custom Appointment Booking Experiences with V0 and Acuity Scheduling
Acuity Scheduling is the go-to booking platform for coaches, consultants, therapists, fitness trainers, and service businesses of all sizes. While Acuity provides a hosted booking page out of the box, many founders want to embed scheduling directly in their V0-generated app with a custom look and feel that matches their brand. The Acuity API makes this possible — you can fetch appointment types, check real-time availability, and create bookings programmatically while Acuity handles calendar sync, email confirmations, and reminders in the background.
The integration has two primary approaches. The simpler path is embedding Acuity's own scheduling widget via an iframe — this requires no API credentials and works in about five minutes. The more powerful path uses Acuity's REST API to build a fully custom booking flow: display your own calendar UI, style the time picker to match your design, and integrate booking confirmation into your existing app flow without redirecting users to a separate hosted page.
Acuity's API uses HTTP Basic Authentication with your User ID and API key, making it straightforward to call from a Next.js API route. The API is well-documented and covers appointment types, available times, appointment creation, rescheduling, and cancellation. For service businesses that want their scheduling embedded in their main product rather than outsourced to a third-party URL, this integration with V0 and Vercel provides an elegant path from design to deployed booking system.
Integration method
Acuity Scheduling integrates with V0-generated Next.js apps through server-side API routes that call the Acuity REST API using HTTP Basic Authentication. Your Acuity User ID and API key are stored as server-only Vercel environment variables and never reach the browser. The UI components V0 generates fetch availability and submit booking requests through your Next.js routes, which proxy calls to Acuity's API to retrieve appointment types, check open slots, and create bookings.
Prerequisites
- An Acuity Scheduling account with at least one appointment type configured — sign up at acuityscheduling.com
- Your Acuity User ID and API key — found in Acuity under Integrations → API → API Credentials
- Your Acuity appointment type IDs — visible in the URL when you edit an appointment type in Acuity, or retrieve them via the API
- A V0 account at v0.dev for generating the booking UI components
- A Vercel account for deployment and environment variable management
Step-by-step guide
Generate the Booking UI with V0
Generate the Booking UI with V0
Open V0 at v0.dev and describe the appointment booking interface you want to create. For an Acuity integration, you have two visual options: a multi-step wizard (service selection → date/time picker → contact form → confirmation) or a simpler single-page layout with all fields visible. Multi-step wizards work well for service businesses with multiple appointment types because they guide users through the decision process without overwhelming them. Describe the appointment types you offer and the color scheme or style that matches your brand. When prompting V0, specify the API routes your component will call: /api/acuity/types for fetching appointment types, /api/acuity/availability for open time slots, and /api/acuity/book for creating the appointment. V0 will generate the component with fetch calls already stubbed out to these routes. Include the request body shapes in your prompt so V0 generates the correct data structures — for booking, Acuity requires at minimum a datetime (ISO 8601 format), appointmentTypeID, firstName, lastName, and email. After generating the component, use V0's preview to check the layout on both desktop and mobile. Acuity is used heavily on mobile by clients booking while on the go, so a responsive design is essential. Use V0's Git panel to push the generated code to your GitHub repository before moving to the API route creation step.
Build a two-step appointment booking form. Step 1: Show three service cards (30-min Consultation at $50, 60-min Deep Dive at $100, 15-min Check-in at $25) as selectable cards that highlight on click. A Next button advances to Step 2. Step 2: Show a date picker (calendar grid) and a list of time slots for the selected date, loaded from /api/acuity/availability?appointmentTypeID={id}&date={date}. Below the time slots, show fields for First Name, Last Name, and Email, plus a Book Appointment button that POSTs to /api/acuity/book. After successful booking, show a green confirmation card with the booking details.
Paste this in V0 chat
Pro tip: If you want a quick integration without building a custom UI, Acuity provides an embeddable iframe widget. In Acuity go to Integrations → Embedding, copy the iframe code, and paste it into your V0 component. This skips the API route entirely and still matches your branding through Acuity's appearance settings.
Expected result: A multi-step booking form renders in V0's preview with service selection cards, date/time pickers, and a contact form. The component references /api/acuity/availability and /api/acuity/book for data fetching and submission.
Create the Acuity API Routes
Create the Acuity API Routes
Create three Next.js API routes to handle the three main operations: fetching appointment types, checking availability, and creating bookings. All three use HTTP Basic Authentication with your Acuity User ID and API key. Basic Auth encodes credentials as base64(userId:apiKey) and passes them in the Authorization header as 'Basic {encoded}'. In Node.js, you can generate this with Buffer.from(`${userId}:${apiKey}`).toString('base64'). The GET /api/acuity/types route fetches all appointment types from https://acuityscheduling.com/api/v1/appointment-types. This returns an array of objects with id, name, duration, price, and description fields that your frontend uses to populate the service selection step. The GET /api/acuity/availability route accepts appointmentTypeID and date as query parameters and calls https://acuityscheduling.com/api/v1/availability/times. The date should be in YYYY-MM-DD format. The response is an array of available time slots as ISO 8601 datetime strings. The POST /api/acuity/book route receives the booking details from your form and calls https://acuityscheduling.com/api/v1/appointments to create the appointment. Required fields are datetime (the selected slot from the availability response), appointmentTypeID, firstName, lastName, and email. On success, Acuity returns the created appointment object with a confirmation ID. Acuity automatically sends a confirmation email to the client and adds the appointment to your connected calendar.
1// app/api/acuity/book/route.ts2import { NextRequest, NextResponse } from 'next/server';34const ACUITY_BASE = 'https://acuityscheduling.com/api/v1';56function getAuthHeader() {7 const userId = process.env.ACUITY_USER_ID;8 const apiKey = process.env.ACUITY_API_KEY;9 if (!userId || !apiKey) throw new Error('Acuity credentials not configured');10 const encoded = Buffer.from(`${userId}:${apiKey}`).toString('base64');11 return `Basic ${encoded}`;12}1314export async function POST(request: NextRequest) {15 let body: {16 datetime: string;17 appointmentTypeID: number;18 firstName: string;19 lastName: string;20 email: string;21 phone?: string;22 notes?: string;23 };2425 try {26 body = await request.json();27 } catch {28 return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });29 }3031 const { datetime, appointmentTypeID, firstName, lastName, email } = body;3233 if (!datetime || !appointmentTypeID || !firstName || !lastName || !email) {34 return NextResponse.json(35 { error: 'datetime, appointmentTypeID, firstName, lastName, and email are required' },36 { status: 400 }37 );38 }3940 try {41 const response = await fetch(`${ACUITY_BASE}/appointments`, {42 method: 'POST',43 headers: {44 Authorization: getAuthHeader(),45 'Content-Type': 'application/json',46 },47 body: JSON.stringify(body),48 });4950 const data = await response.json();5152 if (!response.ok) {53 return NextResponse.json(54 { error: data.message || 'Booking failed' },55 { status: response.status }56 );57 }5859 return NextResponse.json({60 success: true,61 appointmentId: data.id,62 confirmationPage: data.confirmationPage,63 datetime: data.datetime,64 });65 } catch (error) {66 const msg = error instanceof Error ? error.message : 'Unknown error';67 return NextResponse.json({ error: msg }, { status: 500 });68 }69}Pro tip: Create a separate route file for each operation (types, availability, book) rather than combining them into one route with query parameter branching. This keeps each route file focused and makes debugging easier when one operation fails.
Expected result: Three API routes exist at /api/acuity/types, /api/acuity/availability, and /api/acuity/book. Calling /api/acuity/types returns your Acuity appointment type list as JSON. Calling /api/acuity/book with valid data creates an appointment in Acuity.
Also Create the Availability and Types Routes
Also Create the Availability and Types Routes
The booking route handles appointment creation, but you also need routes for fetching appointment types and checking availability. Create app/api/acuity/types/route.ts to return your appointment type catalog, and app/api/acuity/availability/route.ts to return open time slots for a given type and date. Both are GET routes that read query parameters, authenticate with Acuity's API using the same Basic Auth pattern from the previous step, and return the raw Acuity response to your frontend. For the availability route, validate the appointmentTypeID and date query parameters before calling Acuity. A missing or malformed date will cause a confusing Acuity error instead of a clear 400 response to your frontend. The date must be YYYY-MM-DD format — validate with a simple regex or Date.parse check before proxying the request. For the types route, you may want to filter or transform the Acuity response before returning it to the frontend. Acuity returns many fields per appointment type (including internal metadata), and your frontend only needs id, name, duration, price, and description. Filtering the response reduces payload size and simplifies the frontend component's data handling. Cache the types response for a few minutes since appointment types rarely change, using Vercel's fetch cache headers.
Update the booking form Step 1 to fetch service cards from /api/acuity/types on component mount. While loading show skeleton placeholder cards. When a user selects a service type, store its id. Update Step 2 to call /api/acuity/availability?appointmentTypeID={selectedId}&date={selectedDate} when the user picks a date from the calendar. Show 'No availability on this date' if the response is empty. Display loading spinners during both fetches.
Paste this in V0 chat
1// app/api/acuity/availability/route.ts2import { NextRequest, NextResponse } from 'next/server';34const ACUITY_BASE = 'https://acuityscheduling.com/api/v1';56function getAuthHeader() {7 const userId = process.env.ACUITY_USER_ID;8 const apiKey = process.env.ACUITY_API_KEY;9 if (!userId || !apiKey) throw new Error('Acuity credentials not configured');10 const encoded = Buffer.from(`${userId}:${apiKey}`).toString('base64');11 return `Basic ${encoded}`;12}1314export async function GET(request: NextRequest) {15 const { searchParams } = new URL(request.url);16 const appointmentTypeID = searchParams.get('appointmentTypeID');17 const date = searchParams.get('date');1819 if (!appointmentTypeID || !date) {20 return NextResponse.json(21 { error: 'appointmentTypeID and date are required' },22 { status: 400 }23 );24 }2526 // Validate date format (YYYY-MM-DD)27 if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {28 return NextResponse.json(29 { error: 'date must be in YYYY-MM-DD format' },30 { status: 400 }31 );32 }3334 try {35 const url = new URL(`${ACUITY_BASE}/availability/times`);36 url.searchParams.set('appointmentTypeID', appointmentTypeID);37 url.searchParams.set('date', date);3839 const response = await fetch(url.toString(), {40 headers: { Authorization: getAuthHeader() },41 next: { revalidate: 60 }, // Cache availability for 60 seconds42 });4344 const data = await response.json();4546 if (!response.ok) {47 return NextResponse.json(48 { error: data.message || 'Failed to fetch availability' },49 { status: response.status }50 );51 }5253 return NextResponse.json(data);54 } catch (error) {55 const msg = error instanceof Error ? error.message : 'Unknown error';56 return NextResponse.json({ error: msg }, { status: 500 });57 }58}Pro tip: Acuity availability is timezone-sensitive. The returned time slots are in the timezone configured on your Acuity account. Make sure your frontend displays times in the user's local timezone using JavaScript's Intl.DateTimeFormat or a library like date-fns-tz to avoid showing incorrect appointment times.
Expected result: GET /api/acuity/availability?appointmentTypeID=12345&date=2026-05-10 returns an array of available time slots as ISO 8601 datetimes. GET /api/acuity/types returns your appointment type catalog.
Add Credentials to Vercel and Deploy
Add Credentials to Vercel and Deploy
Push your code to GitHub and add your Acuity credentials to Vercel. Open the Vercel Dashboard, select your project, go to Settings → Environment Variables, and add two variables: ACUITY_USER_ID (your numeric Acuity user ID) and ACUITY_API_KEY (your API key). Both are found in your Acuity account under Integrations → API → API Credentials. Neither variable should have the NEXT_PUBLIC_ prefix — these are server-side secrets used only in your API routes and must never reach the browser. Add both variables for Production, Preview, and Development environments. For local development, add them to your project's .env.local file as well (this file is gitignored by default in Next.js projects and is safe for local secrets). Set ACUITY_USER_ID=your_user_id and ACUITY_API_KEY=your_api_key in .env.local, then run npm run dev to test locally before deploying. After adding the variables, trigger a redeployment from the Vercel Deployments tab by clicking Redeploy on the latest deployment. Once deployed, test the complete booking flow from your live URL: check that appointment types load, availability shows open slots, and submitting the booking form creates an appointment visible in your Acuity calendar. Acuity sends confirmation emails automatically to the client email address provided in the booking form, so you can verify delivery by using your own email address during testing. For complex scheduling setups with multiple service providers or locations, RapidDev's team can help configure the Acuity API routing logic.
Pro tip: Test your Acuity credentials before deploying by making a simple curl request: curl -u 'YOUR_USER_ID:YOUR_API_KEY' https://acuityscheduling.com/api/v1/appointment-types — you should see your appointment types returned as JSON.
Expected result: The deployed app on Vercel shows real appointment types from your Acuity account, displays accurate availability when a date is selected, and successfully creates bookings that appear in your Acuity calendar dashboard.
Common use cases
Custom Appointment Booking Page
Replace the generic Acuity hosted booking page with a custom interface that matches your brand. Display your service types, let users pick a date and time, and submit the booking — all within your V0-generated app without redirecting to an Acuity subdomain.
Build a multi-step appointment booking flow with three steps: Step 1 shows service type cards (Consultation, Strategy Session, Follow-up) with duration and price. Step 2 shows a calendar date picker and available time slots for the selected service. Step 3 is a form for name, email, and phone. Show a confirmation screen with booking details after submission to /api/acuity/book.
Copy this prompt to try it in V0
Client Dashboard with Upcoming Appointments
Build an internal or client-facing dashboard that displays upcoming and past appointments fetched from Acuity. Show appointment details, allow clients to reschedule or cancel directly in the dashboard, and display booking status.
Create an admin dashboard with a table of upcoming appointments showing client name, email, service type, appointment date/time, and status badge (Confirmed/Cancelled/Rescheduled). Include a search bar to filter by client name. Add Cancel and Reschedule action buttons per row. Data fetched from /api/acuity/appointments. Use a clean SaaS dashboard style with teal as the primary color.
Copy this prompt to try it in V0
Service Provider Availability Widget
Embed a lightweight availability checker on a landing page that lets visitors see the next available slots without going through the full booking flow. Drive conversions by showing real-time availability before asking for contact details.
Design a compact availability widget for a landing page that shows the next 5 available appointment slots for a 30-minute consultation (fetched from /api/acuity/availability). Display each slot as a clickable card with the date and time. When a user clicks a slot, show a minimal form for their name and email to confirm the booking. Keep the widget under 400px wide and use a professional, trustworthy design.
Copy this prompt to try it in V0
Troubleshooting
API route returns 401 Unauthorized from Acuity
Cause: The ACUITY_USER_ID or ACUITY_API_KEY environment variable is missing, incorrect, or the Basic Auth header is being constructed incorrectly.
Solution: Verify your User ID and API key in Acuity under Integrations → API. User ID is a number (not your email). Confirm both environment variables are set in Vercel without the NEXT_PUBLIC_ prefix, and redeploy after adding them. Test the credentials locally by running npm run dev with .env.local configured.
1// Correct Basic Auth construction2const encoded = Buffer.from(`${process.env.ACUITY_USER_ID}:${process.env.ACUITY_API_KEY}`).toString('base64');3const authHeader = `Basic ${encoded}`;Availability endpoint returns an empty array even when slots should be available
Cause: The date format is wrong (Acuity requires YYYY-MM-DD), the appointment type ID is incorrect, or the Acuity calendar has no available slots configured for that day.
Solution: Confirm the date is formatted as YYYY-MM-DD (not MM/DD/YYYY or a timestamp). Verify the appointmentTypeID matches an existing appointment type from your /api/acuity/types response. In your Acuity calendar, check that you have availability set up for the requested date under Availability → Set Your Hours.
Booking fails with 'No available times' error from Acuity
Cause: The datetime sent in the booking request no longer appears in the availability list, usually because the slot was taken between the user viewing availability and submitting the booking.
Solution: Handle race conditions gracefully in your UI: after a failed booking, re-fetch availability for the same date and show the user an updated list of open slots with a message like 'That time was just taken — please choose another slot.' Implement optimistic UI locking on the selected slot to discourage double-submissions.
Confirmation emails are not being sent to clients after booking
Cause: Acuity email notifications may be disabled for the appointment type, or the email address in the booking request contains a typo.
Solution: In Acuity, go to Customize Appearance → Client Email Notifications and verify notifications are enabled for the appointment type. Also check your Acuity spam/sent folder to confirm emails are being dispatched. Validate the email field in your booking form with a basic email regex before submitting to the API.
Best practices
- Validate the appointmentTypeID and date parameters server-side before forwarding to Acuity — malformed inputs produce confusing API errors that are hard to debug
- Cache the appointment types response for at least a few minutes using Next.js fetch caching since types rarely change, reducing unnecessary API calls on each page load
- Always show a loading skeleton when fetching availability — Acuity API calls can take 500-1500ms and users need feedback that something is happening
- Handle the time slot race condition by re-fetching availability if a booking fails, and show the user updated open slots rather than just an error message
- Store your Acuity User ID and API key only in server-side environment variables without NEXT_PUBLIC_ prefix — these credentials give full read/write access to your Acuity account
- Display appointment times in the user's local timezone using the Intl API or date-fns-tz — Acuity returns times in your account's configured timezone, not the visitor's
- Test cancellation and rescheduling flows before launch — use the Acuity API's DELETE /appointments/{id} and PUT /appointments/{id}/reschedule endpoints to handle post-booking changes
Alternatives
Choose Calendly over Acuity if you prioritize simplicity and a faster setup — Calendly's embed widget requires zero API code and its scheduling page is cleaner for one-on-one meetings.
Use ScheduleOnce (now Oncehub) if you need complex multi-person scheduling, round-robin assignment, or enterprise SSO integrations that Acuity does not support.
Choose Doodle instead of Acuity if your primary use case is group poll-based scheduling (finding a time that works for multiple people) rather than individual service appointment booking.
Frequently asked questions
Can I embed Acuity scheduling without building a custom API integration?
Yes — Acuity provides an iframe embed widget that works without any API code. In your Acuity account, go to Integrations → Embedding and copy the iframe snippet. Paste it into a React component using dangerouslySetInnerHTML or directly in a div. This is the fastest path and Acuity handles all the booking logic, but you lose the ability to customize the appearance beyond Acuity's built-in theme settings.
Where do I find my Acuity User ID and API key?
Log into Acuity Scheduling and go to Integrations (in the left sidebar) → API → API Credentials. Your User ID is a numeric value (not your email address), and the API key is a long alphanumeric string. Copy both exactly — the User ID especially is often confused with an account email by new users.
Does Acuity Scheduling have a sandbox or test mode?
Acuity does not offer a separate sandbox environment like Stripe does. All API calls interact with your live account data. To test without creating real appointments, create a dedicated test appointment type in your Acuity account named something like 'API Test' and use that type ID for development. Delete test appointments manually from your Acuity dashboard after testing.
Can I accept payments through the Acuity API?
Acuity supports Stripe and Square for payment collection on the hosted booking page, but payment processing through the API itself is limited. If you need to collect payment in your custom booking UI, implement Stripe separately in your V0 app and create the Acuity appointment only after the payment is confirmed. This gives you more control over the payment UX and error handling.
What appointment fields can I collect through the Acuity API?
Beyond the required fields (datetime, appointmentTypeID, firstName, lastName, email), you can pass phone, notes, and custom form fields defined in your Acuity appointment type. Custom fields use the fields array in the booking request body with objects containing id (the field ID from your Acuity form configuration) and value. Fetch the field IDs from the /api/v1/forms endpoint.
How do I handle appointment cancellations from my app?
Send a DELETE request to https://acuityscheduling.com/api/v1/appointments/{id} from a Next.js API route. You need the Acuity appointment ID returned when the booking was created (store it in your database or return it to the client). Acuity automatically sends a cancellation email to the client. You can also use PUT /appointments/{id}/cancel to mark an appointment as cancelled without deleting it from the system.
Is the Acuity API rate-limited?
Acuity imposes rate limits on API requests, though the exact limits are not publicly documented in detail. In practice, typical V0-built booking apps with normal usage patterns will not hit rate limits. If you need to fetch availability for many dates simultaneously (e.g., displaying a full month calendar), fetch dates sequentially or in small batches with slight delays rather than making dozens of parallel requests.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation