Acuity Scheduling

Lightweight, dependency-free, in-memory Acuity Scheduling API fake for testing scheduling code.

Default port: 4850

Quick start

import { AcuitySchedulingServer } from "./services/acuity-scheduling/src/server.js";

const server = new AcuitySchedulingServer(4850);
await server.start();
// ... run your app/tests ...
await server.stop();

Point an Acuity client at http://127.0.0.1:4850. Authenticate with HTTP Basic auth using userId:apiKey (any non-empty Basic credentials accepted):

const basic = Buffer.from("parlel:parlel").toString("base64");
const res = await fetch("http://127.0.0.1:4850/api/v1/me", {
  headers: { Authorization: `Basic ${basic}` },
});

Implemented operations

All /api/v1/* routes require HTTP Basic auth (numeric userId : apiKey), matching the real Acuity Scheduling API. Requests and responses use JSON. State is in-memory. Created appointments use the real Acuity appointment shape:

{ "id": 1000001, "firstName": "Alice", "lastName": "Smith", "phone": "",
  "email": "alice@parlel.dev", "date": "June 1, 2024", "time": "9:00am",
  "endTime": "9:30am", "datetime": "2024-06-01T09:00:00-0000",
  "price": "0.00", "paid": "no", "amountPaid": "0.00",
  "type": "Initial Consultation", "appointmentTypeID": 1, "classID": null,
  "category": "", "duration": "30", "calendar": "Parlel", "calendarID": 1,
  "location": "", "certificate": null, "confirmationPage": "https://...",
  "formsText": "", "notes": "", "timezone": "UTC", "canceled": false,
  "forms": [], "labels": [] }

Service & inspection (parlel extensions)

Access via MCP / preview URL

The emulator is reachable at ACUITY_SCHEDULING_BASE_URL (http://127.0.0.1:4850). When running in the parlel pool, an MCP tool / preview URL proxies to this base URL — point your Acuity client at that URL with Basic auth and every /api/v1/* endpoint above works as documented.

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
GET /api/v1/meta (public, no auth)✅ Supported
GET /api/v1/me (parlel convenience)✓ By design — Acuity has no /me; convenience stub
Appointment types✅ Supported
Appointments list (excludes canceled unless canceled=true/showall=true)✅ Supported
Appointment create (validates datetime, appointmentTypeID, firstName, lastName, email, type existence)✅ Supported
Appointment get / update / cancel✅ Supported
PUT update white-list (firstName,lastName,phone,email,certificate,notes,fields,labels,smsOptIn)✅ Supported
Real appointment fields (classID,category,location,confirmationPage,formsText,labels,forms)✅ Supported
forms[].values shape from POST/PUT fields✅ Supported
Basic auth (userId:apiKey)✅ Supported
Availability dates/times◐ Static deterministic stub
OAuth2 flow⟳ Roadmap (Basic accepted)
Forms / certificates / blocks / clients / calendars endpoints⟳ Roadmap
Reschedule endpoint (PUT /appointments/:id/reschedule)⟳ Roadmap
Real availability computation✓ By design — static, zero-cost local emulator
Certificate / coupon validation✓ By design — accepted, not validated
Credential validity✓ By design — any non-empty Basic accepted
Rate limiting (429)✓ By design — never throttles; local tests run at full speed, zero cost

Error codes & shapes

Errors use the real Acuity envelope { status_code, message, error }, where error is a stable machine-readable code:

Statuserror codeWhen
400required_datetimePOST /appointments missing datetime
400required_appointment_type_idmissing appointmentTypeID
400required_first_namemissing firstName
400required_last_namemissing lastName
400required_emailmissing email
400invalid_appointment_typeappointmentTypeID does not exist
400invalid_jsonmalformed JSON body
401unauthorizedmissing Basic credentials ({ "message": "Unauthorized" })
404not_foundunknown appointment or endpoint

Manifest

See services/acuity-scheduling/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

ACUITY_SCHEDULING_USER_ID=parlel
ACUITY_SCHEDULING_API_KEY=parlel
ACUITY_SCHEDULING_BASE_URL=http://parlel-bridge:4850
<!-- parlel:testenv:end -->