The Schedly API is organized around REST. Our API accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
You can use the Schedly API to build powerful scheduling integrations, automate booking workflows, manage services and appointments, and control your entire scheduling infrastructure programmatically.
All API requests are made to:
https://app.schedly.io
The Schedly Services API is available at /api/services/. Routing endpoints are at /api/routing/. Billing endpoints are at /api/billing/. Booking endpoints are at /api/book/.
https://app.schedly.io
# Schedly Services https://app.schedly.io/api/services/services https://app.schedly.io/api/services/appointments https://app.schedly.io/api/services/customers # OAuth https://app.schedly.io/auth/oauth2/authorize https://app.schedly.io/api/auth/oauth/token # Routing https://app.schedly.io/api/routing/config https://app.schedly.io/api/routing/execute
Schedly uses two authentication methods. Choose the one that fits your integration:
Pass your API key in the cal-api-key header. Create API keys from Settings → Developer → API Keys at app.schedly.io.
API keys are best for server-to-server integrations where your application acts on behalf of itself.
Pass an OAuth access token in the Authorization header as a Bearer token. This is the recommended method for third-party apps that act on behalf of Schedly users.
See the OAuth 2.0 section for the complete authorization flow.
curl https://app.schedly.io/api/auth/oauth/me \
-H "Authorization: Bearer sk_live_abc123def456..."const response = await fetch( 'https://app.schedly.io/api/auth/oauth/me', { headers: { 'Authorization': 'Bearer sk_live_abc123def456...' } } ); const data = await response.json();
import requests response = requests.get( "https://app.schedly.io/api/auth/oauth/me", headers={"Authorization": "Bearer sk_live_abc123def456..."} ) data = response.json()
curl https://app.schedly.io/api/auth/oauth/me \
-H "Authorization: Bearer eyJhbGciOi..."const response = await fetch( 'https://app.schedly.io/api/auth/oauth/me', { headers: { 'Authorization': 'Bearer eyJhbGciOi...' } } );
import requests response = requests.get( "https://app.schedly.io/api/auth/oauth/me", headers={"Authorization": "Bearer eyJhbGciOi..."} )
API requests are rate limited to ensure fair usage across all clients.
| Plan | Limit | Window |
|---|---|---|
| Free | 60 requests | per minute |
| Pro | 120 requests | per minute |
| Enterprise | 600 requests | per minute |
Rate limit information is included in response headers:
X-RateLimit-Limit — Maximum requests per windowX-RateLimit-Remaining — Remaining requestsX-RateLimit-Reset — Unix timestamp when window resetsWhen rate limited, you'll receive a 429 status code. Use exponential backoff when retrying.
HTTP/1.1 200 OK X-RateLimit-Limit: 120 X-RateLimit-Remaining: 98 X-RateLimit-Reset: 1708372800
HTTP/1.1 429 Too Many Requests
{
"status": "error",
"error": "RATE_LIMITED",
"message": "Too many requests. Please retry after 60 seconds."
}Schedly uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 2xx range indicate success, 4xx indicate client errors, and 5xx indicate server errors.
| Code | Meaning |
|---|---|
| 200 | OK — Request succeeded |
| 201 | Created — Resource created |
| 400 | Bad Request — Invalid parameters |
| 401 | Unauthorized — Invalid or missing auth |
| 403 | Forbidden — Insufficient permissions |
| 404 | Not Found — Resource doesn't exist |
| 409 | Conflict — Resource conflict |
| 422 | Unprocessable — Validation failed |
| 429 | Rate Limited — Too many requests |
| 500 | Server Error — Something went wrong |
{
"status": "success",
"data": {
"id": 42,
"name": "Consultation"
}
}{
"status": "error",
"error": "NOT_FOUND",
"message": "The requested resource was not found."
}{
"status": "error",
"error": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}Schedly implements the OAuth 2.0 Authorization Code flow (RFC 6749) with optional PKCE extension (RFC 7636). This is the recommended authentication method for third-party integrations that act on behalf of Schedly users.
| Type | Use Case | Secret |
|---|---|---|
| Confidential | Server-side apps (Node.js, Python, etc.) | Uses client_secret |
| Public (PKCE) | Browser apps, mobile apps, SPAs | Uses code_challenge / code_verifier |
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Your App │ │ Schedly │ │ User │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ 1. Redirect to │ │ │ /auth/oauth2/ │ │ │ authorize ─────────┼───────────────────>│ │ │ │ │ │ 2. User approves │ │ │<───────────────────│ │ │ │ │ 3. Callback with │ │ │ authorization code │ │ │<───────────────────│ │ │ │ │ │ 4. Exchange code │ │ │ for tokens ────────> │ │ │ │ │ 5. Access token + │ │ │ refresh token │ │ │<───────────────────│ │ │ │ │
Before starting the OAuth flow, create an OAuth client in your Schedly developer settings:
client_id and client_secretclient_secret secure. Never expose it in client-side code, public repositories, or frontend bundles.# After creating your OAuth client, # you'll receive: Client ID: cl_live_a1b2c3d4e5f6... Client Secret: cs_live_x9y8z7w6v5u4... # Redirect URI (must match exactly) https://yourapp.com/callback
Direct users to the Schedly authorization page to grant your application access.
https://app.schedly.io/auth/oauth2/authorize
| Parameter | Status | Description |
|---|---|---|
| client_id | Required | Your OAuth client ID |
| redirect_uri | Required | Must match a registered redirect URI |
| response_type | Optional | Set to code (default) |
| state | Required | Opaque value for CSRF protection, returned unchanged |
| code_challenge | Optional | PKCE: Base64url-encoded SHA-256 hash of code_verifier |
| code_challenge_method | Optional | Set to S256 when using PKCE |
After the user approves, Schedly redirects to your redirect_uri with an authorization code and your state parameter.
| Error | Description |
|---|---|
| client_not_found | The client_id doesn't match any registered client |
| redirect_uri_mismatch | The redirect_uri doesn't match any registered URI |
| not_approved | The OAuth client has not been approved |
| access_denied | The user denied the authorization request |
# Redirect the user's browser to: https://app.schedly.io/auth/oauth2/authorize?\ client_id=cl_live_a1b2c3d4e5f6&\ redirect_uri=https://yourapp.com/callback&\ response_type=code&\ state=random_csrf_token_xyz
const params = new URLSearchParams({ client_id: 'cl_live_a1b2c3d4e5f6', redirect_uri: 'https://yourapp.com/callback', response_type: 'code', state: crypto.randomUUID() }); window.location.href = `https://app.schedly.io/auth/oauth2/authorize?${params}`;
import secrets from urllib.parse import urlencode params = urlencode({ "client_id": "cl_live_a1b2c3d4e5f6", "redirect_uri": "https://yourapp.com/callback", "response_type": "code", "state": secrets.token_urlsafe(32) }) auth_url = f"https://app.schedly.io/auth/oauth2/authorize?{params}"
# User approves → redirect to: https://yourapp.com/callback? code=auth_code_abc123xyz789& state=random_csrf_token_xyz # User denies → redirect to: https://yourapp.com/callback? error=access_denied& error_description=User+denied+access
POST /api/auth/oauth/token
Exchange the authorization code for access and refresh tokens.
| Parameter | Status | Description |
|---|---|---|
| grant_type | Required | Set to authorization_code |
| code | Required | The authorization code from the callback |
| client_id | Required | Your OAuth client ID |
| client_secret | Conditional | Required for confidential clients |
| redirect_uri | Required | Must match the URI used in authorization |
| code_verifier | Conditional | Required for PKCE flow |
Returns an access token, refresh token, token type, and expiration time.
| Error | Description |
|---|---|
| invalid_grant | The authorization code is invalid or expired |
| invalid_client | Client authentication failed |
| invalid_request | Missing required parameters |
curl -X POST https://app.schedly.io/api/auth/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=auth_code_abc123xyz789" \ -d "client_id=cl_live_a1b2c3d4e5f6" \ -d "client_secret=cs_live_x9y8z7w6v5u4" \ -d "redirect_uri=https://yourapp.com/callback"
const response = await fetch( 'https://app.schedly.io/api/auth/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: 'auth_code_abc123xyz789', client_id: 'cl_live_a1b2c3d4e5f6', client_secret: 'cs_live_x9y8z7w6v5u4', redirect_uri: 'https://yourapp.com/callback' }) } ); const tokens = await response.json();
import requests response = requests.post( "https://app.schedly.io/api/auth/oauth/token", data={ "grant_type": "authorization_code", "code": "auth_code_abc123xyz789", "client_id": "cl_live_a1b2c3d4e5f6", "client_secret": "cs_live_x9y8z7w6v5u4", "redirect_uri": "https://yourapp.com/callback" } ) tokens = response.json()
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "rt_live_m4n5o6p7q8r9...",
"token_type": "Bearer",
"expires_in": 1800
}POST /api/auth/oauth/refreshToken
Use a refresh token to obtain a new access token when the current one expires.
| Parameter | Status | Description |
|---|---|---|
| grant_type | Required | Set to refresh_token |
| refresh_token | Required | The refresh token from the initial exchange |
| client_id | Required | Your OAuth client ID |
| client_secret | Conditional | Required for confidential clients |
curl -X POST https://app.schedly.io/api/auth/oauth/refreshToken \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=rt_live_m4n5o6p7q8r9..." \ -d "client_id=cl_live_a1b2c3d4e5f6" \ -d "client_secret=cs_live_x9y8z7w6v5u4"
const response = await fetch( 'https://app.schedly.io/api/auth/oauth/refreshToken', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: 'rt_live_m4n5o6p7q8r9...', client_id: 'cl_live_a1b2c3d4e5f6', client_secret: 'cs_live_x9y8z7w6v5u4' }) } );
response = requests.post(
"https://app.schedly.io/api/auth/oauth/refreshToken",
data={
"grant_type": "refresh_token",
"refresh_token": "rt_live_m4n5o6p7q8r9...",
"client_id": "cl_live_a1b2c3d4e5f6",
"client_secret": "cs_live_x9y8z7w6v5u4"
}
){
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "rt_live_newtoken123...",
"token_type": "Bearer",
"expires_in": 1800
}GET /api/auth/oauth/me
Verify an access token and retrieve the authenticated user's information.
Returns the user profile if the token is valid, or a 401 error if the token is invalid or expired.
curl https://app.schedly.io/api/auth/oauth/me \
-H "Authorization: Bearer eyJhbGciOi..."const response = await fetch( 'https://app.schedly.io/api/auth/oauth/me', { headers: { 'Authorization': 'Bearer eyJhbGciOi...' } } ); const user = await response.json();
response = requests.get(
"https://app.schedly.io/api/auth/oauth/me",
headers={"Authorization": "Bearer eyJhbGciOi..."}
)
user = response.json(){
"username": "sarahj"
}Legacy booking endpoints for direct event booking.
POST /api/book/event
POST /api/book/recurring-event
POST /api/book/instant-event
POST /api/cancel
curl -X POST https://app.schedly.io/api/book/event \ -H "Content-Type: application/json" \ -d '{ "eventTypeId": 42, "start": "2026-02-25T14:00:00Z", "end": "2026-02-25T14:30:00Z", "responses": { "name": "Maria Garcia", "email": "maria@example.com" }, "timeZone": "America/Los_Angeles", "language": "en" }'
curl -X POST https://app.schedly.io/api/cancel \ -H "Content-Type: application/json" \ -d '{ "uid": "bk_2f8a9c3e", "reason": "Schedule conflict" }'
The Services API powers Schedly's full business management platform — services, appointments, customers, employees, invoices, products, and more.
cal-api-key header or session cookie.GET /api/services/services — List all services
POST /api/services/services — Create a service
PATCH /api/services/services?id=:id — Update a service
DELETE /api/services/services?id=:id — Delete a service
| Parameter | Status | Description |
|---|---|---|
| name | Required | Service name |
| duration | Optional | Duration in minutes (default: 60) |
| price | Optional | Price in cents (default: 0) |
| currency | Optional | Currency code (default: usd) |
| description | Optional | Service description |
| categoryId | Optional | Category ID |
| locationId | Optional | Location ID |
| bufferTimeBefore | Optional | Buffer time before (minutes) |
| bufferTimeAfter | Optional | Buffer time after (minutes) |
| maxCapacity | Optional | Max booking capacity |
| depositType | Optional | none, fixed, or percentage |
| depositAmount | Optional | Deposit amount |
| enableRecurring | Optional | Enable recurring bookings |
| enableGroupBooking | Optional | Enable group bookings |
curl -X POST https://app.schedly.io/api/services/services \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "name": "Haircut", "duration": 30, "price": 3500, "currency": "usd", "description": "Professional haircut service", "bufferTimeAfter": 10 }'
const res = await fetch('https://app.schedly.io/api/services/services', { method: 'POST', headers: { 'cal-api-key': 'sk_live_abc123...', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Haircut', duration: 30, price: 3500, currency: 'usd', description: 'Professional haircut service' }) });
res = requests.post(
"https://app.schedly.io/api/services/services",
headers={"cal-api-key": "sk_live_abc123..."},
json={
"name": "Haircut",
"duration": 30,
"price": 3500,
"currency": "usd",
"description": "Professional haircut service"
}
){
"id": 1,
"name": "Haircut",
"slug": "haircut",
"duration": 30,
"price": 3500,
"currency": "usd",
"description": "Professional haircut service",
"active": true,
"category": null,
"extras": []
}GET /api/services/appointments — List appointments
POST /api/services/appointments — Create appointment
PUT /api/services/appointments?id=:id — Update appointment
DELETE /api/services/appointments?id=:id — Delete appointment
GET /api/services/appointments/export — Export as CSV
POST /api/services/appointments/import — Import from CSV
| Parameter | Status | Description |
|---|---|---|
| serviceId | Required | Service ID |
| startTime | Required | Start time (ISO 8601) |
| endTime | Required | End time (ISO 8601) |
| customerId | Optional | Customer ID |
| employeeId | Optional | Assigned employee ID |
| locationId | Optional | Location ID |
| status | Optional | pending, approved, canceled, completed |
| price | Optional | Price in cents |
| recurrenceRule | Optional | weekly, biweekly, or monthly |
| recurrenceCount | Optional | Number of recurring instances |
| isGroupBooking | Optional | Enable group booking |
| attendees | Optional | Array of attendee objects |
curl -X POST https://app.schedly.io/api/services/appointments \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "serviceId": 1, "startTime": "2026-02-25T10:00:00Z", "endTime": "2026-02-25T10:30:00Z", "customerId": 5, "employeeId": 3, "status": "approved", "price": 3500 }'
const res = await fetch('https://app.schedly.io/api/services/appointments', { method: 'POST', headers: { 'cal-api-key': 'sk_live_abc123...', 'Content-Type': 'application/json' }, body: JSON.stringify({ serviceId: 1, startTime: '2026-02-25T10:00:00Z', endTime: '2026-02-25T10:30:00Z', customerId: 5, employeeId: 3 }) });
res = requests.post(
"https://app.schedly.io/api/services/appointments",
headers={"cal-api-key": "sk_live_abc123..."},
json={
"serviceId": 1,
"startTime": "2026-02-25T10:00:00Z",
"endTime": "2026-02-25T10:30:00Z",
"customerId": 5,
"employeeId": 3
}
){
"id": 201,
"serviceId": 1,
"startTime": "2026-02-25T10:00:00Z",
"endTime": "2026-02-25T10:30:00Z",
"status": "approved",
"price": 3500,
"service": { "name": "Haircut" },
"customer": {
"name": "Emily Chen",
"email": "emily@example.com"
},
"employee": { "name": "James Wilson" }
}GET /api/services/customers — List customers
POST /api/services/customers — Create customer
PUT /api/services/customers?id=:id — Update customer
DELETE /api/services/customers?id=:id — Delete customer
GET /api/services/customers/export — Export as CSV
POST /api/services/customers/import — Import from CSV
| Parameter | Status | Description |
|---|---|---|
| name | Required | Customer name |
| Optional | Email address | |
| phone | Optional | Phone number |
| notes | Optional | Internal notes |
| gender | Optional | Gender |
| dateOfBirth | Optional | Date of birth (ISO date) |
curl -X POST https://app.schedly.io/api/services/customers \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "name": "Emily Chen", "email": "emily@example.com", "phone": "+1-555-0102", "notes": "Prefers morning appointments" }'
{
"id": 5,
"name": "Emily Chen",
"email": "emily@example.com",
"phone": "+1-555-0102",
"notes": "Prefers morning appointments",
"totalBookings": 0,
"totalSpent": 0
}GET /api/services/employees — List employees
POST /api/services/employees — Create employee
PUT /api/services/employees?id=:id — Update employee
DELETE /api/services/employees?id=:id — Delete employee
GET /api/services/employees/export — Export
POST /api/services/employees/import — Import
POST /api/services/employees/invite — Invite employee
GET /api/services/employees/schedule — Get schedule
PATCH /api/services/employees/schedule — Update schedule
curl -X POST https://app.schedly.io/api/services/employees \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "name": "James Wilson", "email": "james@salon.com", "role": "stylist", "serviceIds": [1, 2, 3] }'
GET POST PATCH DELETE /api/services/employee-role
GET POST PATCH DELETE /api/services/invoices
GET /api/services/invoices/pdf — Download PDF
POST /api/services/invoices/send — Send invoice
GET POST PATCH DELETE /api/services/products
GET /api/services/products/export
POST /api/services/products/import
GET POST /api/services/product-orders
POST /api/services/product-orders/charge — Charge order
POST /api/services/product-orders/terminal — Terminal payment
GET POST PATCH DELETE /api/services/categories
GET POST PATCH DELETE /api/services/product-categories
GET POST PATCH DELETE /api/services/extras
GET POST PATCH DELETE /api/services/coupons
POST /api/services/coupons/validate — Validate coupon code
GET POST PATCH DELETE /api/services/gift-cards
POST /api/services/gift-cards/validate — Validate gift card
GET POST PATCH DELETE /api/services/custom-fields
GET /api/services/custom-fields/public — Public fields
GET POST PATCH DELETE /api/services/forms
GET /api/services/forms/public
POST /api/services/forms/submit
GET /api/services/forms/templates
POST /api/services/forms/upload
GET POST PATCH DELETE /api/services/packages
POST /api/services/packages/purchase
POST /api/services/packages/redeem
curl -X POST https://app.schedly.io/api/services/invoices \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "customerId": 5, "items": [ { "description": "Haircut", "quantity": 1, "unitPrice": 3500 }, { "description": "Deep Conditioning", "quantity": 1, "unitPrice": 2000 } ], "dueDate": "2026-03-01" }'
curl -X POST https://app.schedly.io/api/services/coupons/validate \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{"code": "WELCOME20"}'
curl -X POST https://app.schedly.io/api/services/products \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Shampoo", "price": 2499, "sku": "SHMP-001", "stock": 50 }'
POST /api/services/payments/create — Create payment
GET /api/services/payments/success — Payment success callback
POST /api/services/payments/cancel — Cancel payment
POST /api/services/payments/webhook — Payment webhook
GET /api/services/payment-apps — List payment apps
GET POST /api/services/payment-links
GET POST DELETE /api/services/reviews
GET POST /api/services/notifications/templates
POST /api/services/notifications/send — Send notification
POST /api/services/notifications/reminders — Setup reminders
GET POST /api/services/commissions
GET POST PATCH DELETE /api/services/commissions/rules
GET POST PATCH /api/services/waitlist
GET /api/services/availability
GET /api/services/insights — Business insights
GET /api/services/stats — Dashboard stats
GET /api/services/product-analytics — Product analytics
GET POST PATCH DELETE /api/services/locations
GET POST PATCH DELETE /api/services/taxes
GET PATCH /api/services/company-settings
GET POST /api/services/sms-credits
POST /api/services/ai-voice — AI voice assistant
POST /api/services/ai-image — AI image generation
POST /api/services/book — Public service booking
POST /api/services/start-trial — Start trial
GET /api/services/barcode-lookup — Barcode lookup
POST /api/services/barcode-session — Create barcode session
GET /api/services/image-search — Image search
GET /api/services/catalog/export
POST /api/services/catalog/import
GET POST /api/services/booking-products
curl -X POST https://app.schedly.io/api/services/payments/create \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "appointmentId": 201, "amount": 3500, "currency": "usd" }'
curl "https://app.schedly.io/api/services/insights?\ from=2026-01-01&to=2026-02-28" \ -H "cal-api-key: sk_live_abc123..."
{
"totalRevenue": 128500,
"totalBookings": 47,
"averageBookingValue": 2734,
"topServices": [
{ "name": "Haircut", "count": 22 },
{ "name": "Color Treatment", "count": 15 }
],
"newCustomers": 12
}Enable your customers to manage their own appointments through a self-service portal.
POST /api/services/portal/auth — Send magic link
GET /api/services/portal/verify — Verify portal token
GET /api/services/portal/appointments
GET /api/services/portal/purchases
curl -X POST https://app.schedly.io/api/services/portal/auth \ -H "Content-Type: application/json" \ -d '{ "email": "emily@example.com", "subdomain": "sarahssalon" }'
curl "https://app.schedly.io/api/services/portal/verify?\
token=ptk_abc123xyz789"Intelligent lead routing, territory management, qualification, and SLA tracking.
GET POST /api/routing/config
POST /api/routing/execute — Execute routing engine
POST /api/routing/book — Route lead and book
POST /api/routing/preview — Test/preview routing
POST /api/routing/handoff
GET POST /api/routing/qualification
GET POST /api/routing/territories
GET /api/routing/territories/export
POST /api/routing/territories/import
GET POST /api/routing/smart-links
POST /api/routing/smart-links/resolve
GET POST /api/routing/sla
POST /api/routing/sla/check — SLA check (cron)
GET /api/routing/snippet — Get JS embed snippet
GET /api/routing/analytics
curl -X POST https://app.schedly.io/api/routing/execute \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "email": "lead@company.com", "name": "John Doe", "company": "Acme Corp", "formData": { "companySize": "50-100", "industry": "technology" } }'
const res = await fetch('https://app.schedly.io/api/routing/execute', { method: 'POST', headers: { 'cal-api-key': 'sk_live_abc123...', 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'lead@company.com', name: 'John Doe', company: 'Acme Corp', formData: { companySize: '50-100' } }) });
res = requests.post(
"https://app.schedly.io/api/routing/execute",
headers={"cal-api-key": "sk_live_abc123..."},
json={
"email": "lead@company.com",
"name": "John Doe",
"company": "Acme Corp"
}
){
"routedTo": {
"userId": 12345,
"name": "Sarah Johnson",
"bookingUrl": "https://app.schedly.io/sarahj/demo"
},
"matchedTerritory": "North America",
"qualified": true,
"score": 85
}Manage subscriptions, checkout, and billing information.
POST /api/billing/checkout
| Parameter | Status | Description |
|---|---|---|
| plan | Required | pro, services, branding, or hipaa |
| period | Optional | monthly or yearly (default: monthly) |
GET /api/billing/summary
GET /api/billing/invoices
GET /api/billing/seats
POST /api/billing/cancel
Additional billing endpoints:
POST /api/billing/toggle-branding
POST /api/billing/toggle-hipaa
POST /api/billing/start-pro-trial
POST /api/billing/update-payment-method
POST /api/billing/setup-intent
GET /api/billing/stripe-publishable-key
GET /api/billing/trial-analytics
POST /api/billing/webhook — Stripe webhook handler
curl -X POST https://app.schedly.io/api/billing/checkout \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "plan": "pro", "period": "yearly" }'
{
"clientSecret": "cs_test_a1b2c3d4..."
}curl https://app.schedly.io/api/billing/summary \
-H "cal-api-key: sk_live_abc123..."GET /api/extension/event-types
GET /api/extension/slots
curl https://app.schedly.io/api/extension/event-types \
-H "cal-api-key: sk_live_abc123..."Integration endpoints for Google Reserve booking flow.
GET /api/reserve-with-google/availability
POST /api/reserve-with-google/booking
GET /api/reserve-with-google/feeds
GET /api/reserve-with-google/health
curl https://app.schedly.io/api/reserve-with-google/availability \
-H "cal-api-key: sk_live_abc123..."POST /api/compliance/baa — Sign Business Associate Agreement
POST /api/compliance/dpa — Sign Data Processing Agreement
GET /api/compliance/download — Download compliance docs
curl -X POST https://app.schedly.io/api/compliance/baa \ -H "cal-api-key: sk_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "signerName": "Sarah Johnson", "signerTitle": "CEO", "companyName": "Wellness Clinic LLC" }'
Connect Schedly with thousands of apps using Zapier.
# Zapier uses Schedly's OAuth 2.0 flow: Authorization URL: https://app.schedly.io/auth/oauth2/authorize Token URL: https://app.schedly.io/api/auth/oauth/token Refresh URL: https://app.schedly.io/api/auth/oauth/refreshToken # Zapier automatically handles token # refresh and stores credentials securely.
| Event | Description |
|---|---|
| BOOKING_CREATED | New booking confirmed |
| BOOKING_RESCHEDULED | Booking time changed |
| BOOKING_CANCELLED | Booking cancelled |
| BOOKING_CONFIRMED | Pending booking confirmed |
| BOOKING_REJECTED | Pending booking rejected |
| BOOKING_PAYMENT_INITIATED | Payment initiated for booking |
| MEETING_STARTED | Video meeting started |
| MEETING_ENDED | Video meeting ended |
| RECORDING_READY | Meeting recording available |
All webhook payloads include the event type, timestamp, and event-specific data.
If you set a webhook secret, Schedly includes a X-Schedly-Signature header with an HMAC-SHA256 signature of the payload body.
{
"triggerEvent": "BOOKING_CREATED",
"createdAt": "2026-02-19T14:30:00Z",
"payload": {
"id": 501,
"uid": "bk_2f8a9c3e",
"title": "30-Min Consultation",
"type": "consultation",
"startTime": "2026-02-20T10:00:00Z",
"endTime": "2026-02-20T10:30:00Z",
"organizer": {
"name": "Sarah Johnson",
"email": "sarah@example.com"
},
"attendees": [
{
"name": "Alex Rivera",
"email": "alex@example.com",
"timeZone": "America/Chicago"
}
]
}
}const crypto = require('crypto'); function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); }
All errors follow a consistent format with an error code, human-readable message, and HTTP status code.
| Status | Error Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | The request was malformed or missing required parameters |
| 401 | UNAUTHORIZED | Authentication credentials are missing or invalid |
| 403 | FORBIDDEN | You don't have permission to access this resource |
| 404 | NOT_FOUND | The requested resource does not exist |
| 409 | CONFLICT | The request conflicts with the current state (e.g., double booking) |
| 422 | VALIDATION_ERROR | The request body failed validation |
| 429 | RATE_LIMITED | Too many requests; slow down and retry |
| 500 | INTERNAL_ERROR | An unexpected error occurred on the server |
{
"status": "error",
"error": "UNAUTHORIZED",
"message": "Invalid API key provided."
}{
"status": "error",
"error": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "startTime",
"message": "Must be a valid ISO 8601 date"
},
{
"field": "eventTypeId",
"message": "Required field"
}
]
}HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708372800
{
"status": "error",
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 60s."
}