To integrate Replit with Acuity Scheduling, generate an API key from your Acuity account, store your User ID and API key in Replit Secrets (lock icon π), and call the Acuity Scheduling API from Python or Node.js server-side code to manage appointments, availability, and client records. Deploy with Autoscale for webhook-driven booking workflows.
Why Connect Replit to Acuity Scheduling?
Acuity Scheduling powers appointment booking for thousands of service businesses β therapists, coaches, salons, tutors, and consultants. Its API gives you programmatic control over availability, bookings, and client data, enabling you to build custom booking flows, sync appointments with your own database, or automate follow-up communications from a Replit backend.
The most common integration patterns are embedding custom booking logic inside a larger web application, syncing Acuity appointments to an external CRM or database, and triggering automated messages (SMS, email) when appointments are booked or cancelled. The Acuity API v1 covers all appointment types, appointment types, calendar availability, and client management with a straightforward REST interface.
Replit's Secrets system (lock icon π in the sidebar) keeps your Acuity API key encrypted and out of your codebase. Because the API key grants access to all appointments and client data in your Acuity account, treat it like a database password β never commit it to code or expose it on the client side. All Acuity API calls should originate from your server-side backend running in Replit.
Integration method
You connect Replit to Acuity Scheduling by retrieving your User ID and API key from the Acuity developer settings, storing them in Replit Secrets, and calling the Acuity Scheduling API v1 from your server-side Python or Node.js code. The API uses HTTP Basic Authentication with your User ID as the username and API key as the password. Webhooks can be registered to receive real-time notifications when appointments are scheduled, rescheduled, or cancelled.
Prerequisites
- A Replit account with a Python or Node.js project created
- An Acuity Scheduling account (free trial available, paid plan required for API access)
- At least one appointment type created in your Acuity account
- Basic familiarity with REST APIs and HTTP Basic Authentication
- Node.js 18+ or Python 3.10+ (both available on Replit by default)
Step-by-step guide
Find Your Acuity User ID and Generate an API Key
Find Your Acuity User ID and Generate an API Key
Log in to your Acuity Scheduling account and navigate to Integrations in the left sidebar. Scroll down to find the section labeled 'API' or search for 'API Integrations'. Click on 'API Credentials'. On this page you will see your User ID β a numeric value like 12345678 β and an option to generate an API key. Click 'Generate API Key' if you have not already created one. The API key is a long alphanumeric string. Copy both your User ID and API key immediately β Acuity only shows the full API key once after generation. Note that Acuity API access requires a paid plan. If you are on the free trial, you may have limited API access. The full API including webhooks and calendar management requires the Powerhouse plan or higher. Check your plan in Acuity under Account > Billing to confirm API access is enabled. You will also want to note your appointment type IDs. In Acuity, go to Appointment Types and click on any type β the ID appears in the URL as a number. These IDs are used when querying availability for specific appointment types.
Pro tip: Create a dedicated API key labeled 'replit-integration' in Acuity rather than reusing an existing key. This lets you revoke access for one integration without affecting any other connected tools.
Expected result: You have your Acuity User ID (numeric) and API key copied and ready to store in Replit Secrets.
Store Acuity Credentials in Replit Secrets
Store Acuity Credentials in Replit Secrets
Open your Replit project and click the lock icon π in the left sidebar to open the Secrets pane. Add the following secrets using the 'Add a new secret' form: - Key: ACUITY_USER_ID β Value: your numeric Acuity User ID (e.g., 12345678) - Key: ACUITY_API_KEY β Value: your full Acuity API key Click 'Add Secret' after each entry. Replit encrypts these values with AES-256 encryption at rest and injects them as environment variables at runtime. They are never visible in your file tree or committed to Git. In Python, access these with os.environ['ACUITY_USER_ID'] and os.environ['ACUITY_API_KEY']. In Node.js, use process.env.ACUITY_USER_ID and process.env.ACUITY_API_KEY. Do not use Deno.env.get() β that pattern is specific to Supabase Edge Functions and does not work in Replit's Node.js environment. Replit's Secret Scanner automatically flags any API keys accidentally written to code files. If you paste an API key directly into your source code by mistake, Replit will warn you and prompt you to move it to Secrets instead.
Pro tip: Store your most frequently queried appointment type ID as ACUITY_APPOINTMENT_TYPE_ID in Secrets as well, so you can easily swap it between environments without modifying code.
Expected result: Two Secrets (ACUITY_USER_ID and ACUITY_API_KEY) appear in the Replit Secrets pane, each showing a masked value.
Query Availability and Create Appointments with Python
Query Availability and Create Appointments with Python
The Acuity Scheduling API v1 uses HTTP Basic Authentication where the username is your User ID and the password is your API key. The base URL is https://acuityscheduling.com/api/v1/. No official Python SDK is available, so you will use the standard requests library, which comes pre-installed on Replit. The workflow for booking an appointment has three steps: first fetch available dates for an appointment type, then fetch available times for a specific date, and finally create the appointment with the client's details. The code below implements all three steps along with a function to list upcoming appointments and cancel them. Note that appointment type IDs are numeric integers in Acuity β not strings. The calendarID field in the appointments endpoint refers to the staff member or resource calendar. If you only have one calendar in your account, you can fetch the calendar ID from the /calendars endpoint and use it directly.
1import os2import requests3from datetime import date, timedelta45USER_ID = os.environ["ACUITY_USER_ID"]6API_KEY = os.environ["ACUITY_API_KEY"]7BASE_URL = "https://acuityscheduling.com/api/v1"89# Basic Auth: User ID as username, API key as password10AUTH = (USER_ID, API_KEY)1112def get_appointment_types() -> list:13 """Fetch all active appointment types from the Acuity account."""14 response = requests.get(f"{BASE_URL}/appointment-types", auth=AUTH)15 response.raise_for_status()16 return response.json()1718def get_available_dates(appointment_type_id: int, months: int = 1) -> list:19 """Get dates with available slots for the next N months."""20 today = date.today()21 end_date = today + timedelta(days=30 * months)22 params = {23 "appointmentTypeID": appointment_type_id,24 "month": today.strftime("%Y-%m")25 }26 response = requests.get(f"{BASE_URL}/availability/dates", params=params, auth=AUTH)27 response.raise_for_status()28 return response.json()2930def get_available_times(appointment_type_id: int, date_str: str) -> list:31 """Get available time slots for a specific date. date_str format: YYYY-MM-DD"""32 params = {33 "appointmentTypeID": appointment_type_id,34 "date": date_str35 }36 response = requests.get(f"{BASE_URL}/availability/times", params=params, auth=AUTH)37 response.raise_for_status()38 return response.json()3940def create_appointment(41 appointment_type_id: int,42 datetime_str: str,43 first_name: str,44 last_name: str,45 email: str,46 phone: str = ""47) -> dict:48 """49 Book an appointment.50 datetime_str format: '2026-04-15T14:00:00-0500'51 """52 data = {53 "appointmentTypeID": appointment_type_id,54 "datetime": datetime_str,55 "firstName": first_name,56 "lastName": last_name,57 "email": email,58 "phone": phone59 }60 response = requests.post(f"{BASE_URL}/appointments", json=data, auth=AUTH)61 response.raise_for_status()62 return response.json()6364def get_upcoming_appointments(max_results: int = 20) -> list:65 """Fetch upcoming appointments sorted by start time."""66 params = {"max": max_results, "direction": "ASC"}67 response = requests.get(f"{BASE_URL}/appointments", params=params, auth=AUTH)68 response.raise_for_status()69 return response.json()7071def cancel_appointment(appointment_id: int) -> bool:72 """Cancel an appointment by ID."""73 response = requests.delete(f"{BASE_URL}/appointments/{appointment_id}", auth=AUTH)74 return response.status_code == 2007576# Example usage77if __name__ == "__main__":78 # List appointment types79 types = get_appointment_types()80 print("Appointment types:")81 for t in types:82 print(f" [{t['id']}] {t['name']} β {t['duration']} min")8384 if types:85 type_id = types[0]["id"]86 # Get available dates this month87 dates = get_available_dates(type_id)88 print(f"\nAvailable dates: {[d['date'] for d in dates[:5]]}")8990 if dates:91 # Get times on the first available date92 times = get_available_times(type_id, dates[0]["date"])93 print(f"Available times on {dates[0]['date']}: {[t['time'] for t in times[:3]]}")Pro tip: The Acuity API rate limit is 10 requests per second per account. For batch operations like fetching availability across multiple appointment types, add a small delay between requests using time.sleep(0.1) to stay within the limit.
Expected result: Running the script prints your appointment types with their IDs, available dates for the first type, and the first few available time slots on the nearest available date.
Build a Node.js Booking API with Express
Build a Node.js Booking API with Express
For Node.js projects, use the built-in https module or the popular axios package (npm install axios) to call the Acuity API. The Express server below exposes REST endpoints for your frontend to check availability and create bookings, with all Acuity credentials kept server-side in Replit Secrets. The server implements four endpoints: GET /appointment-types to list available service types, GET /availability to check open slots for a given date, POST /appointments to book an appointment, and DELETE /appointments/:id to cancel. This server-side proxy pattern is important because it prevents your Acuity User ID and API key from being exposed to browser clients. Install dependencies by running npm install express axios in the Replit Shell, or add them to package.json and let Replit install on startup. The .replit config file should set the run command to node server.js.
1const express = require('express');2const axios = require('axios');34const app = express();5app.use(express.json());67const ACUITY_USER_ID = process.env.ACUITY_USER_ID;8const ACUITY_API_KEY = process.env.ACUITY_API_KEY;9const BASE_URL = 'https://acuityscheduling.com/api/v1';1011// Axios instance with Basic Auth pre-configured12const acuity = axios.create({13 baseURL: BASE_URL,14 auth: {15 username: ACUITY_USER_ID,16 password: ACUITY_API_KEY17 }18});1920// List all appointment types21app.get('/appointment-types', async (req, res) => {22 try {23 const response = await acuity.get('/appointment-types');24 res.json(response.data);25 } catch (err) {26 console.error('Acuity error:', err.response?.data);27 res.status(err.response?.status || 500).json({ error: err.message });28 }29});3031// Check available times for an appointment type on a date32// Query params: appointmentTypeID, date (YYYY-MM-DD)33app.get('/availability', async (req, res) => {34 const { appointmentTypeID, date } = req.query;35 if (!appointmentTypeID || !date) {36 return res.status(400).json({ error: 'appointmentTypeID and date are required' });37 }38 try {39 const response = await acuity.get('/availability/times', {40 params: { appointmentTypeID, date }41 });42 res.json(response.data);43 } catch (err) {44 res.status(err.response?.status || 500).json({ error: err.message });45 }46});4748// Book an appointment49app.post('/appointments', async (req, res) => {50 const { appointmentTypeID, datetime, firstName, lastName, email, phone } = req.body;51 if (!appointmentTypeID || !datetime || !firstName || !email) {52 return res.status(400).json({ error: 'appointmentTypeID, datetime, firstName, and email are required' });53 }54 try {55 const response = await acuity.post('/appointments', {56 appointmentTypeID,57 datetime,58 firstName,59 lastName: lastName || '',60 email,61 phone: phone || ''62 });63 res.status(201).json(response.data);64 } catch (err) {65 console.error('Booking error:', err.response?.data);66 res.status(err.response?.status || 500).json({67 error: err.response?.data?.message || err.message68 });69 }70});7172// Cancel an appointment73app.delete('/appointments/:id', async (req, res) => {74 try {75 await acuity.delete(`/appointments/${req.params.id}`);76 res.json({ success: true, message: 'Appointment cancelled' });77 } catch (err) {78 res.status(err.response?.status || 500).json({ error: err.message });79 }80});8182// Get upcoming appointments83app.get('/appointments', async (req, res) => {84 try {85 const response = await acuity.get('/appointments', {86 params: { max: req.query.max || 20, direction: 'ASC' }87 });88 res.json(response.data);89 } catch (err) {90 res.status(err.response?.status || 500).json({ error: err.message });91 }92});9394app.listen(3000, '0.0.0.0', () => {95 console.log('Acuity Scheduling proxy server running on port 3000');96});Pro tip: Add a CORS middleware (npm install cors) if your frontend is served from a different origin than your Replit server. Call app.use(cors({ origin: 'https://your-frontend.com' })) before your route definitions.
Expected result: The server starts on port 3000, and a GET request to /appointment-types returns a JSON array of your Acuity appointment types.
Set Up Acuity Webhooks and Deploy
Set Up Acuity Webhooks and Deploy
Acuity Scheduling can send webhook notifications to your Replit app when appointments are scheduled, rescheduled, cancelled, or when a client is created or updated. This enables real-time workflows like sending custom confirmations, syncing with a CRM, or triggering downstream processes without polling the API. To register a webhook in Acuity, go to Integrations in the sidebar, find the API section, and click 'Webhooks'. Enter your deployed Replit server URL plus the webhook path (e.g., https://your-app.replit.app/acuity/webhook). Select the event types you want: 'scheduled' (new booking), 'rescheduled', 'cancelled', and 'changed' cover most use cases. Webhooks only work against a deployed Replit URL β not the development preview URL. Click the 'Deploy' button in Replit and choose Autoscale deployment for web applications that receive webhooks. Autoscale spins up instances to handle incoming requests and automatically scales back down during quiet periods. If your app must process webhooks 24/7 with no cold-start delay, choose Reserved VM instead. Acuity does not send a verification challenge when registering webhooks β it simply starts sending POST requests immediately after you save the URL. Make sure your endpoint is deployed and accepting requests before registering.
1from flask import Flask, request, jsonify2import os3import hmac4import hashlib56app = Flask(__name__)78# Acuity webhook secret (set in Acuity Integrations > API > Webhooks)9WEBHOOK_SECRET = os.environ.get("ACUITY_WEBHOOK_SECRET", "")1011@app.route('/acuity/webhook', methods=['POST'])12def acuity_webhook():13 # Verify webhook signature if secret is configured14 if WEBHOOK_SECRET:15 signature = request.headers.get('X-Acuity-Signature', '')16 expected = hmac.new(17 WEBHOOK_SECRET.encode(),18 request.data,19 hashlib.sha25620 ).hexdigest()21 if not hmac.compare_digest(signature, expected):22 return jsonify({'error': 'Invalid signature'}), 4012324 payload = request.json25 if not payload:26 return jsonify({'error': 'No payload'}), 4002728 action = payload.get('action') # 'scheduled', 'rescheduled', 'cancelled', 'changed'29 appointment = payload.get('appointment', {})3031 appt_id = appointment.get('id')32 client_email = appointment.get('email')33 appt_type = appointment.get('type')34 appt_time = appointment.get('datetime')3536 print(f"Acuity webhook: action={action}, id={appt_id}, client={client_email}")3738 if action == 'scheduled':39 # New appointment booked β sync to database, send custom confirmation, etc.40 print(f"New booking: {appt_type} at {appt_time} for {client_email}")41 elif action == 'cancelled':42 # Appointment cancelled β update database, trigger refund workflow, etc.43 print(f"Cancelled: appointment {appt_id} for {client_email}")44 elif action == 'rescheduled':45 print(f"Rescheduled: appointment {appt_id} to {appt_time}")4647 return jsonify({'received': True}), 2004849if __name__ == '__main__':50 app.run(host='0.0.0.0', port=3000)Pro tip: Acuity sends webhook payloads as JSON with Content-Type: application/json. Use request.json in Flask or express.json() middleware in Express to parse the body. Do not use request.form for Acuity webhooks (unlike Mailchimp).
Expected result: After deployment, Acuity can reach your webhook endpoint. New appointments trigger a POST to your server and appear in the console output.
Common use cases
Custom Booking Portal with Database Sync
Build a branded booking portal in Replit that shows real-time availability from Acuity, lets users book appointments, and simultaneously writes the booking record to your own PostgreSQL database. This gives you full control over the UI and allows you to store additional data fields that Acuity does not support natively.
Build a Flask web app with a /availability endpoint that fetches open slots from the Acuity API for the next 7 days and a /book endpoint that creates an appointment in Acuity and saves it to a PostgreSQL database, using ACUITY_USER_ID and ACUITY_API_KEY from Replit Secrets.
Copy this prompt to try it in Replit
Automated Appointment Reminders
Use Acuity webhooks to trigger automated SMS or email reminders from your Replit app when an appointment is booked. When Acuity posts a webhook event to your server, your app reads the appointment details and sends a reminder via Twilio or SendGrid 24 hours before the scheduled time.
Create an Express server with a POST /acuity/webhook endpoint that receives Acuity booking confirmation events, reads the appointment datetime and client email, and schedules a reminder email to be sent 24 hours before the appointment using SendGrid.
Copy this prompt to try it in Replit
Availability Analytics Dashboard
Pull appointment history and availability data from Acuity on a schedule to generate utilization reports β showing which time slots are booked most often, average lead time between booking and appointment, and cancellation rates. This data can inform pricing decisions and operating hours adjustments.
Write a Python script that fetches all appointments from Acuity for the past 30 days using the Acuity API, groups them by day of week and time slot, and outputs a CSV report showing booking frequency and cancellation rate per slot.
Copy this prompt to try it in Replit
Troubleshooting
API returns 401 Unauthorized when making requests
Cause: The User ID or API key stored in Replit Secrets is incorrect, or the Basic Auth credentials are being passed incorrectly. A common mistake is passing the User ID as a string when it must be used as the username in Basic Auth.
Solution: Verify your User ID and API key in Acuity under Integrations > API Credentials. In Replit Secrets, make sure ACUITY_USER_ID contains only the numeric ID (no quotes, no extra spaces). In Python, the Auth tuple is (USER_ID, API_KEY) where both are strings β if USER_ID is stored as an integer, convert it with str(os.environ['ACUITY_USER_ID']).
1# Python: ensure User ID is a string for Basic Auth2import os3USER_ID = str(os.environ["ACUITY_USER_ID"]) # Convert to string explicitly4API_KEY = os.environ["ACUITY_API_KEY"]5AUTH = (USER_ID, API_KEY)GET /availability/times returns an empty array even for dates shown as available
Cause: The appointment type ID or date format is incorrect, or the calendar has no hours configured for that day. Acuity availability depends on the calendar's business hours, which must be set in the Acuity calendar settings.
Solution: Verify the appointment type ID is correct by calling /appointment-types first and checking the IDs. Confirm the date format is YYYY-MM-DD. In Acuity, go to Business Hours under your calendar settings and make sure hours are enabled for the day you are querying. Also check that the appointment type is not blocked by a time block on that date.
1# Python: verify appointment type ID before querying availability2types = get_appointment_types()3print("Valid type IDs:", [t['id'] for t in types])4# Use the numeric ID from this list when calling get_available_times()Webhook events are never received by the Replit server
Cause: The webhook URL points to the Replit development server (which goes offline when the browser tab is closed) instead of the deployed app URL, or the Replit app is not deployed.
Solution: Click 'Deploy' in Replit and wait for the deployment to complete. Copy the deployed URL (ending in .replit.app) and update the webhook URL in Acuity under Integrations > API Credentials > Webhooks. Development preview URLs that contain 'replit.dev' are temporary and only work while the editor is open.
POST /appointments returns 400 with 'That time is no longer available'
Cause: Another booking was made for the same time slot between when you fetched available times and when you submitted the booking. This race condition is expected behavior β availability is not held during the selection process.
Solution: Implement retry logic that re-fetches available times and presents updated options when a booking attempt fails with a 400 error. Communicate clearly to users that slot selection does not guarantee availability until the booking is confirmed.
1# Python: retry booking with fresh availability on 4002import time34def book_with_retry(type_id, date_str, client_data, max_retries=2):5 for attempt in range(max_retries):6 times = get_available_times(type_id, date_str)7 if not times:8 return None, "No times available"9 try:10 return create_appointment(type_id, times[0]['time'], **client_data), None11 except Exception as e:12 if attempt < max_retries - 1:13 time.sleep(1)14 else:15 return None, str(e)Best practices
- Always store ACUITY_USER_ID and ACUITY_API_KEY in Replit Secrets (lock icon π) β never hardcode them or commit them to Git.
- Use a server-side proxy in Replit so frontend code never directly contacts the Acuity API, keeping your credentials hidden from browser clients.
- Validate appointment type IDs and datetime formats before sending booking requests β Acuity returns unhelpful 400 errors for format mismatches.
- Register a webhook ACUITY_WEBHOOK_SECRET in Acuity and verify the X-Acuity-Signature header on every incoming webhook event to prevent fake booking notifications.
- Deploy with Autoscale for web apps that handle bookings via a frontend; use Reserved VM if your app processes high-volume webhooks and cannot tolerate cold-start latency.
- Always call /availability/times immediately before presenting booking options to users β do not cache availability for more than a few minutes, as slots can fill quickly.
- Handle the 'That time is no longer available' 400 error gracefully in your UI by re-fetching availability and showing updated options rather than displaying a raw error message.
- Use the Acuity sandbox/test environment during development if available on your plan, to avoid creating real test appointments that affect your live calendar.
Alternatives
Doodle is a group scheduling poll tool rather than a full appointment booking system, making it a better fit when coordinating meeting times between multiple participants rather than client-facing bookings.
Clockify focuses on time tracking and reporting rather than appointment booking, making it a better fit when you need to measure how time is spent after appointments rather than manage booking workflows.
Zoom includes basic scheduling features via its API, making it a simpler option if your main goal is creating video meeting links rather than managing a full appointment booking system with availability rules.
Frequently asked questions
How do I store my Acuity API key in Replit?
Click the lock icon π in the left sidebar of your Replit project to open the Secrets pane. Add ACUITY_USER_ID with your numeric Acuity User ID and ACUITY_API_KEY with your API key. Access them in Python with os.environ['ACUITY_API_KEY'] and in Node.js with process.env.ACUITY_API_KEY. Never paste credentials directly into your code files.
Does Replit work with Acuity Scheduling on the free tier?
Replit's free tier supports outbound API calls, so you can call the Acuity API from a Replit project without a paid Replit plan. However, the Acuity API itself requires a paid Acuity plan β the free Acuity trial has limited API access. You will also need Replit's paid plan (Replit Core) for always-on deployments needed to receive Acuity webhooks reliably.
How do I find my Acuity appointment type ID?
In Acuity, navigate to Appointment Types in the left sidebar and click on any appointment type. The ID appears as a number in the page URL (e.g., /appointments/types/12345678). Alternatively, call the /appointment-types endpoint via the API and read the 'id' field from each object in the response array.
Can I book appointments on behalf of clients from Replit?
Yes. The Acuity API's POST /appointments endpoint lets you create appointments programmatically with any client email and details. This is called an 'owner booking' and uses your account credentials. The client receives the standard Acuity confirmation email unless you disable notifications in the appointment type settings.
Why does Acuity return no available times even though the calendar is open?
Acuity availability depends on three things: business hours must be enabled for that day, the specific appointment type must be assigned to a calendar, and there must not be a blocking time event on that date. Check your calendar's Business Hours settings in Acuity and confirm the appointment type is linked to an active calendar. Also verify the date format in your API request is exactly YYYY-MM-DD.
How do I handle Acuity webhooks in Replit?
Deploy your Replit app first to get a stable URL (ending in .replit.app), then register that URL plus your webhook path in Acuity under Integrations > API Credentials > Webhooks. Acuity sends JSON POST requests when appointments change. Use Flask's request.json or Express's express.json() middleware to parse the payload. Webhooks do not work against temporary development URLs.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation