# Salon Appointment Booking Automation (`relishable_woodland/salon-appointment-booking-automation`) Actor

- **URL**: https://apify.com/relishable\_woodland/salon-appointment-booking-automation.md
- **Developed by:** [Gihan Gangadara](https://apify.com/relishable_woodland) (community)
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $70.00 / 1,000 bookings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Salon Appointment Booking Automation

**Short description:** Check availability, book, look up, and cancel salon
appointments programmatically — a JSON-in/JSON-out automation layer for AI
agents, chatbots, websites, and workflow tools, backed by a real salon
booking system.

This is **not** "a salon website uploaded to Apify." It's an automation/API
Actor: other software calls it with structured input and gets a structured
result back, the same way it would call any booking API — it just happens to
run on Apify's infrastructure instead of you hosting your own endpoint.

### Who should use this

- A salon (or a developer building for one) that already runs the connected
  booking backend and wants to accept bookings from channels beyond its own
  website — a WhatsApp bot, an AI receptionist, a Zapier/Make workflow, a
  booking widget embedded elsewhere.
- Anyone building an AI agent or chatbot that needs to check and book real
  salon appointments as a tool call, with structured JSON in and out.
- Internal automation: nightly availability reports, bulk-importing
  appointments from another system, syncing booking status into a CRM.

### Use cases

- An AI receptionist that answers "do you have anything free Thursday
  afternoon?" by calling `checkAvailability` and reading the slot list back.
- A website or mobile app booking widget that calls `createAppointment`
  instead of implementing its own booking backend.
- A workflow tool (Zapier/Make/n8n) node that creates or cancels appointments
  as part of a larger automation.
- A support bot that looks up an appointment's status with `getAppointment`
  before answering "is my booking confirmed yet?"

### Features

- **checkAvailability** — is this service open at this date/time? (or list
  every open slot for a day)
- **createAppointment** — book a service for a customer, subject to the
  salon's real working hours and existing bookings (no double-booking)
- **getAppointment** — look up a previously created appointment's status,
  without exposing the customer's phone/email back out
- **cancelAppointment** — cancel a booking; cancelling something already
  cancelled is a safe no-op, not an error
- Input validation with clear, itemized error messages
- **Idempotent booking**: re-running the exact same `createAppointment`
  request never creates a second appointment — it returns the original
  booking with `duplicate: true`
- Every run's result is written to the Actor's default Dataset
- No hard-coded credentials — the backend API key is a secret input field

### Architecture

```
User / AI Agent
      |
      v
 Apify Actor            (this package — validation, idempotency, HTTP client)
      |  HTTPS + x-api-key
      v
 Salon Booking API       (server/src/routes/actorApi.js — new, API-key-protected)
      |  calls
      v
 bookingService.js       (existing, reused unchanged — availability rules,
      |                   double-booking prevention, the booking model)
      v
 SQLite                  (existing database)
```

This Actor is an **adapter, not a database client**. Apify Actors run in
isolated containers with no shared filesystem, so it can't open the salon's
SQLite file directly (and SQLite isn't safe for concurrent multi-process
writes anyway). Instead it calls a small set of API-key-protected routes
added to the existing Express backend, which call the *same* booking/
availability functions the salon's own customer-facing website uses — no
booking logic is duplicated or reimplemented.

To point the Actor at your own deployment, set `apiBaseUrl` to wherever you
host `server/` (e.g. `https://api.yoursalon.com`) and `apiKey` to that
deployment's `ACTOR_API_KEY`.

### Supported operations

| Operation | Status | Notes |
|---|---|---|
| `checkAvailability` | ✅ Fully supported | Real backend logic (working hours + existing bookings) |
| `createAppointment` | ✅ Fully supported | Idempotent; double-booking prevented server-side |
| `getAppointment` | ✅ Fully supported | Returns only booking-relevant fields, not phone/email |
| `cancelAppointment` | ✅ Fully supported | Reuses the same status column the owner's dashboard already writes to |

Nothing here is faked. Every operation above calls a real, tested code path
in the existing backend (`server/src/services/bookingService.js`).

### Input parameters

| Field | Type | Required for | Notes |
|---|---|---|---|
| `operation` | string | always | `checkAvailability` | `createAppointment` | `getAppointment` | `cancelAppointment` |
| `salonId` | string | optional | This is a single-salon deployment — leave blank. If set, validated against the backend's `SALON_ID` (reserved scaffolding for a future multi-salon version) |
| `serviceId` | string | checkAvailability, createAppointment (or use `service`) | The backend's real numeric service id, e.g. `"1"` — preferred over `service` |
| `service` | string | alternative to `serviceId` | Exact service name, e.g. `"Women's Haircut"` (case-insensitive) |
| `staffId` | string | optional | **Reserved for a future release** — no staff/stylist model exists yet, so this is accepted and recorded in the Dataset but has no effect on booking |
| `appointmentDate` | string | checkAvailability, createAppointment | `YYYY-MM-DD` |
| `appointmentTime` | string | createAppointment (optional for checkAvailability) | 24-hour `HH:MM`. Omit on checkAvailability to list all open slots for the day |
| `customerName` | string | createAppointment | |
| `customerPhone` | string | createAppointment | |
| `customerEmail` | string | optional | |
| `appointmentId` | string | getAppointment, cancelAppointment | ID returned by a prior `createAppointment` call |
| `apiBaseUrl` | string | optional | Defaults to `API_BASE_URL` env var, then `http://localhost:4000` |
| `apiKey` | string (secret) | required in practice | Defaults to `API_KEY` env var. Sent as `x-api-key` |

### Input examples

Check a specific slot (by service id):

```json
{
  "operation": "checkAvailability",
  "serviceId": "1",
  "appointmentDate": "2026-10-15",
  "appointmentTime": "10:30"
}
```

Create a booking:

```json
{
  "operation": "createAppointment",
  "serviceId": "1",
  "appointmentDate": "2026-10-15",
  "appointmentTime": "10:30",
  "customerName": "John",
  "customerPhone": "0771234567",
  "customerEmail": "john@example.com"
}
```

Look up a booking:

```json
{ "operation": "getAppointment", "appointmentId": "12345" }
```

Cancel a booking:

```json
{ "operation": "cancelAppointment", "appointmentId": "12345" }
```

More runnable examples (including every failure case) are in
[`test/inputs/`](test/inputs/).

### Output examples

Availability (specific time):

```json
{
  "success": true,
  "operation": "checkAvailability",
  "available": true,
  "date": "2026-10-15",
  "time": "10:30",
  "service": "Haircut"
}
```

Unavailable:

```json
{
  "success": true,
  "operation": "checkAvailability",
  "available": false,
  "reason": "Requested appointment slot is already booked."
}
```

Successful booking — **`status` reflects the backend's real workflow**. New
bookings start as `"pending"` until the salon owner confirms them in their
dashboard; this Actor does not fabricate instant `"confirmed"` status:

```json
{
  "success": true,
  "operation": "createAppointment",
  "appointmentId": "12345",
  "customerName": "John",
  "service": "Haircut",
  "date": "2026-10-15",
  "time": "10:30",
  "status": "pending"
}
```

Slot already taken:

```json
{
  "success": false,
  "operation": "createAppointment",
  "available": false,
  "reason": "Requested appointment slot is unavailable."
}
```

Get appointment (no phone/email exposed):

```json
{
  "success": true,
  "operation": "getAppointment",
  "appointmentId": "12345",
  "customerName": "John",
  "service": "Haircut",
  "date": "2026-10-15",
  "time": "10:30",
  "status": "confirmed"
}
```

Cancel appointment:

```json
{
  "success": true,
  "operation": "cancelAppointment",
  "appointmentId": "12345",
  "status": "cancelled"
}
```

### How appointment availability works

The connected backend generates 15-minute candidate start times across the
salon's configured working hours for that day of the week, removes any that
overlap an existing `pending` or `confirmed` booking (by service duration),
and removes past times if the date is today. `checkAvailability` either
checks whether a specific time survived that filter, or — if you omit
`appointmentTime` — returns the full list.

### Dataset output

Every run pushes one item to the default Dataset, with fields that don't
apply to a given operation/outcome set to `null` (so every run produces the
same schema for the Dataset's table view):

```json
{
  "success": true,
  "operation": "createAppointment",
  "appointmentId": "12345",
  "salonId": null,
  "serviceId": "1",
  "staffId": null,
  "customerName": "John",
  "service": "Haircut",
  "appointmentDate": "2026-10-15",
  "appointmentTime": "10:30",
  "status": "pending",
  "error": null,
  "reason": null
}
```

No password, API key, JWT secret, or database credential is ever written to
the Dataset, `OUTPUT`, or logs.

### Authentication & security requirements

- The Actor never hard-codes credentials. `apiKey` is a **secret** input
  field (encrypted at rest, redacted from logs by Apify) and also accepts an
  `API_KEY` environment variable for local/CI use.
- Nothing sensitive is ever written to the Dataset, `OUTPUT`, or logs — only
  booking fields and error messages.
- The backend's `/api/actor/*` routes require a matching `x-api-key` header
  and are entirely separate from the salon's admin JWT login and the public
  customer-facing routes — a leaked Actor key cannot log in as the owner.
- `getAppointment` deliberately omits the customer's phone and email from
  its output, even though the backend record has them — the Actor only
  returns what's needed to answer "what's the status of this booking?"

### Example usage (as an automation/API call)

Via the Apify API (replace `<TASK_OR_ACTOR_ID>` and `<TOKEN>`):

```bash
curl -X POST "https://api.apify.com/v2/acts/<TASK_OR_ACTOR_ID>/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "createAppointment",
    "serviceId": "1",
    "appointmentDate": "2026-10-15",
    "appointmentTime": "10:30",
    "customerName": "John",
    "customerPhone": "0771234567",
    "apiBaseUrl": "https://api.yoursalon.com",
    "apiKey": "your-actor-api-key"
  }'
```

An AI agent framework can wrap this same call as a "manage\_salon\_appointment"
tool: pass the structured input, parse the JSON result, and speak the
`success`/`available`/`reason`/`status` fields back to the user.

### Limitations

- **Single salon per backend.** `salonId` is validated against one
  configured id; there's no multi-tenant salon directory yet.
- **No staff/stylist scheduling.** `staffId` is accepted but ignored — the
  connected backend has no staff model.
- **No notifications yet.** No SMS/email/WhatsApp confirmations — status
  changes are visible via `getAppointment` and the owner's dashboard only.
- **No rescheduling operation** — cancel and create a new one instead.
- Requires the salon's backend to be reachable over HTTPS from the Actor;
  it cannot access a SQLite file directly (Actors run in isolated
  containers with no shared filesystem).
- Idempotency is keyed on salon + service + date + time + phone/email; a
  legitimate second booking for the same person at the exact same slot on a
  retry looks identical to an accidental duplicate by design.
- This has been tested locally (unit tests + real runs against the local
  backend) but not yet run on the Apify platform itself — see "Known
  limitations" in the final project report for what that leaves unverified.

### FAQ

**Does this scrape the salon's website?** No. It calls a dedicated,
API-key-protected backend API. Nothing about it depends on the website's
HTML or design.

**Can I point it at my own salon system instead of the demo backend?** Yes —
that's the intended use. Deploy `server/` (or your own implementation of the
same `/api/actor/*` contract) and set `apiBaseUrl`/`apiKey` accordingly.

**What happens if I run the exact same booking request twice?** You get the
same appointment back with `duplicate: true` — no second booking is created.

**Why is a new booking's status `"pending"` and not `"confirmed"`?** Because
that's what actually happens in the connected system — the salon owner
confirms new requests from their dashboard. This Actor reports the real
state rather than a more convenient-looking fake one.

**Can it text or email the customer?** Not yet — see Roadmap below.

### Pricing suggestion

**Pay per event** is the best fit for an early product:

| Tier | Suggested price | Why |
|---|---|---|
| `checkAvailability`, `getAppointment` | Free or ~$0.005/call | Read-only, cheap to serve — the goal is to make these free enough that agents check availability liberally |
| `createAppointment` | ~$0.05–$0.10/booking | This is the actual business value delivered |
| `cancelAppointment` | Free or ~$0.005/call | Housekeeping, not new value creation |

Offer the first ~20–50 events free (Apify's standard free-tier pattern) so a
developer or AI agent builder can integration-test before committing. Avoid
charging for Actor compute time directly — this workload is a handful of
HTTP calls, not a scrape or a crawl, so compute-based pricing would look
disproportionate to what's delivered.

### Roadmap (not implemented yet)

WhatsApp/email notifications, Google Calendar sync, reminders, rescheduling,
multi-salon and multi-staff support, an AI receptionist integration layer,
outbound webhooks on status change, and usage analytics. The `/api/actor/*`
route group and this Actor's operation dispatch were both designed so each
of these can be added as a new operation or backend route without
restructuring what's here — see `server/src/routes/actorApi.js` and
`src/handler.js`'s `switch` statement as the extension points.

# Actor input Schema

## `operation` (type: `string`):

Which appointment operation to perform.

## `salonId` (type: `string`):

Optional. This is a single-salon deployment — leave blank to use it. If set, it's validated against the connected backend's configured SALON\_ID (reserved scaffolding for a future multi-salon version).

## `serviceId` (type: `string`):

Preferred way to identify the service (the backend's real numeric id, e.g. 1). Required for checkAvailability and createAppointment unless `service` (name) is given instead.

## `service` (type: `string`):

Alternative to serviceId: the exact service name, e.g. "Women's Haircut" (case-insensitive). Used only if serviceId is not set.

## `staffId` (type: `string`):

Optional preferred staff member. Reserved for future use — the connected backend has no staff/stylist model yet, so this is accepted and recorded but currently has no effect on booking.

## `appointmentDate` (type: `string`):

Date in YYYY-MM-DD format. Required for checkAvailability and createAppointment.

## `appointmentTime` (type: `string`):

Time in 24-hour HH:MM format, e.g. "10:30". Required for createAppointment; optional for checkAvailability (omit to list all open slots for the day).

## `customerName` (type: `string`):

Required for createAppointment.

## `customerPhone` (type: `string`):

Required for createAppointment.

## `customerEmail` (type: `string`):

Optional contact email for createAppointment.

## `appointmentId` (type: `string`):

Required for getAppointment and cancelAppointment — the ID returned by a previous createAppointment call.

## `apiBaseUrl` (type: `string`):

Base URL of the salon's booking backend, e.g. https://your-salon-api.example.com. Falls back to the API\_BASE\_URL environment variable, then http://localhost:4000 for local testing.

## `apiKey` (type: `string`):

API key for the salon backend's /api/actor/\* endpoints (sent as the x-api-key header). Treated as a secret — falls back to the API\_KEY environment variable if left blank. Never hard-code this in the Actor's source or logs.

## Actor input object example

```json
{}
```

# Actor output Schema

## `result` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("relishable_woodland/salon-appointment-booking-automation").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("relishable_woodland/salon-appointment-booking-automation").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call relishable_woodland/salon-appointment-booking-automation --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,relishable_woodland/salon-appointment-booking-automation"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/i3lKXSfOjBvEtNXK2/builds/c34tosuRMaR3Y91ij/openapi.json
