Download OpenAPI specification:Download
TidyCal's REST API provides a handful of endpoints which can be used to get information about your account and bookings. It uses conventional OAuth 2.0 protocol for authentication.
Create a personal access token from Integrations -> Advanced -> "Manage API keys" -> "Personal tokens" tab at https://tidycal.com/integrations/advanced (the API is included on every TidyCal plan, including Free). Once created, it can be used to authenticate requests by passing it in the Authorization header.
Authorization: Bearer {TOKEN}
If you're building a custom integration to TidyCal which requires users to authenticate in order to get access tokens to make API requests on their behalf, you'll need to create an OAuth 2.0 client. This is easy to do from Integrations -> Advanced -> "Manage API keys" -> "OAuth clients" tab found here https://tidycal.com/integrations/advanced
Using the authorization_code grant type to authenticate users using OAuth 2.0 to retrieve an access token is fairly conventional, more information on that process can be found here: https://www.oauth.com/oauth2-servers/server-side-apps/authorization-code/
TidyCal ships an MCP server that lets AI assistants such as Claude and ChatGPT work with your TidyCal scheduling data through natural language — ask things like "what bookings do I have next week?" and the assistant answers using your live account data. It can read your account and, only if you approve the write permission when you connect, create and change things: book a meeting, cancel a booking, create a booking type, or add a contact.
Full setup guide, OAuth details, and the current tool list: https://tidycal.com/mcp-docs
https://tidycal.com/mcpmcp:scheduling:read (see your bookings, booking types, availability, and teams) and mcp:scheduling:write (create and change bookings and booking types). Approve only the first for a read-only connection.The connection uses OAuth 2.1 with Dynamic Client Registration; you never paste an API key or token into Claude.
https://tidycal.com/mcpThe server exposes tools to read your profile, teams, booking types, bookings, availability, busy times, locations, and Store orders. With the write scope it also exposes tools to book a meeting, cancel a booking, create a booking type, and add a contact. The current list, with what each tool returns, is on the setup page: https://tidycal.com/mcp-docs — it is generated from the server itself, so it is always up to date.
mcp:scheduling:write, tools that create or cancel refuse and say so. Even with it, no tool edits or deletes existing booking types, issues refunds, or changes account settings.list-busy-times covers every connected provider — Google, Microsoft and Apple — for calendars you have enabled for conflict checking.2026-06-01T14:30:00Z); returned times are UTC unless a resource notes otherwise.List endpoints return 100 items per page (booking types: 30, a team's booking types: 50). Use the page query parameter to move through pages. Responses include the data array plus pagination fields: either at the top level (current_page, last_page, per_page, total, next_page_url, prev_page_url, from, to, first_page_url, last_page_url, path, links) or, for endpoints that return resource collections, in links and meta objects. Keep requesting the next page until next_page_url (or links.next) is null.
Date filters (starts_at, ends_at) accept ISO 8601 timestamps, with or without milliseconds, for example 2026-10-20T14:30:00Z, 2026-10-20T14:30:00.000Z or 2026-10-20T09:30:00-05:00.
The API allows 60 requests per minute per user. Requests over the limit return 429 Too Many Requests with a Retry-After header (seconds to wait). Each response includes X-RateLimit-Limit and X-RateLimit-Remaining.
Errors are always JSON. Validation failures return 422 with a message and an errors object keyed by field name.
Webhooks send a signed HTTPS POST to your URL when something happens in your account. Manage them with the /webhooks endpoints below, or subscribe from Zapier or Make, which use the same endpoints. Up to 10 webhooks per account.
Every event has the same envelope. data is a webhook object with exactly the fields shown in the examples below: it never includes the booker's IP address, payment processor ids, or internal host and booking type settings.
{
"id": "6f1b7c52-0f4e-4d0e-9d5e-0a4a8b1f2c33",
"type": "booking.created",
"created_at": "2026-10-21T15:04:05+00:00",
"data": { ... }
}
id is unique per event and is the same on every retry and resend, so use it to dedupe. created_at is when the event happened. data is built by a background worker a few seconds later, so it reflects the record as it was then, and can be newer than created_at suggests if the record changed in between. Headers on every request: Content-Type: application/json, User-Agent: TidyCal-Webhooks/1.0, TidyCal-Event (the event type), TidyCal-Event-Id (the event id, your dedupe key), TidyCal-Delivery-Id (the id of this webhook's delivery record, the id in the delivery log) and TidyCal-Signature.
id, which is also sent in the TidyCal-Event-Id header and never changes across retries and resends. (TidyCal-Delivery-Id is different: it identifies this webhook's copy of the event, so it is the same across retries but not a dedupe key across webhooks.) Store the ids you have handled and skip repeats.2xx and do the real work afterwards, for example in a queue.booking.created can arrive after a later booking.cancelled. Use the timestamps inside data, not arrival order.POST /webhooks/{id}/test, which sends a signed webhook.test event through the same path as real events.Every request carries TidyCal-Signature: t=<unix timestamp>,v1=<hex digest>.
, to get t and v1.HMAC-SHA256 of the string t + "." + raw_body, keyed with the full webhook secret including the whsec_ prefix, as lowercase hex.v1 in constant time: hash_equals in PHP, crypto.timingSafeEqual in Node. Never use == or === on signatures.t is more than 5 minutes (300 seconds) away from your clock. This stops someone replaying a captured request. Each retry is signed again with a fresh t, so a legitimate retry always passes.expected = hex(hmac_sha256(secret, t + "." + raw_body))
The secret is shown once, when you create the webhook or rotate its secret (POST /webhooks/{id}/rotate-secret). Rotating stops the old secret from verifying immediately.PHP:
$secret = getenv('TIDYCAL_WEBHOOK_SECRET'); // the full whsec_... string
$body = file_get_contents('php://input'); // raw bytes, before any json_decode
parse_str(str_replace(',', '&', $_SERVER['HTTP_TIDYCAL_SIGNATURE'] ?? ''), $parts);
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if (!ctype_digit($t) || abs(time() - (int) $t) > 300) {
http_response_code(400); // missing, malformed or stale timestamp
exit;
}
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
if (!hash_equals($expected, $v1)) {
http_response_code(401); // bad signature
exit;
}
$eventId = $_SERVER['HTTP_TIDYCAL_EVENT_ID']; // dedupe on this
$event = json_decode($body, true);
http_response_code(204);
Node.js (Express):
const crypto = require('crypto');
const express = require('express');
const app = express();
const secret = process.env.TIDYCAL_WEBHOOK_SECRET; // the full whsec_... string
// express.raw keeps the body as a Buffer of the exact bytes TidyCal sent.
app.post('/tidycal', express.raw({ type: 'application/json' }), (req, res) => {
const parts = Object.fromEntries(
(req.get('TidyCal-Signature') || '').split(',').map((part) => part.split('=', 2))
);
const t = parts.t || '';
const v1 = parts.v1 || '';
if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > 300) {
return res.sendStatus(400); // missing, malformed or stale timestamp
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.`)
.update(req.body)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(v1);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401); // bad signature
}
const eventId = req.get('TidyCal-Event-Id'); // dedupe on this
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204);
});
Respond with any 2xx within 10 seconds for the whole request (5 seconds to connect). Anything else, or a timeout, is retried up to 8 attempts in all, after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours (about 22 hours from the first attempt to the last). After the 8th failed attempt the delivery is marked failed; you can resend it from the dashboard or with POST /webhooks/deliveries/{id}/resend, which keeps the same event id.
Your URL must be https and must resolve only to public addresses (private, loopback, link-local and cloud metadata addresses are refused). Redirects are not followed, so a 301 or 302 counts as a failure. Only ports 443 (the default) and 8443 are allowed; any other port is rejected when you save the webhook and before every send. Only the status code of your response is used: the body is never read, and the delivery log shows an outcome such as http_5xx, timeout, connection_failed, tls_error or blocked_destination instead. Deliveries to one host are rate limited across all accounts; over the limit they are delayed, not dropped.
410 Gone response turns the webhook off straight away and that delivery is not retried. Use it to tell TidyCal you no longer want events.2xx resets the count, and so does a gap of more than 24 hours between failures. A short outage, or a webhook that gets very few events, is not turned off.enabled becomes false and disabled_reason is gone or failing (manual when you turn it off yourself). Events that happen while a webhook is off are not queued for it. Turn it back on with PATCH /webhooks/{id} {"enabled": true} or from Integrations, Automations in the dashboard.team_access_removed) when the team owner or admin who created it stops managing the team, and a webhook created by a third-party app when you revoke that app (app_revoked). The first can be turned back on once its creator manages the team again; the second cannot: delete it and create a new one.| Event | Sent when | data |
|---|---|---|
booking.created |
A booking is confirmed (paid bookings, after payment) | Booking |
booking.cancelled |
A booking is cancelled | Booking |
booking.rescheduled |
A booking is moved to a new time | Booking |
booking.approved |
A booking that needed approval is approved | Booking |
payment.completed |
A booking payment is completed | Payment |
payment.refunded |
A booking payment is refunded (in full or in part) | Payment, with the refund fields |
contact.created |
A new contact is created | Contact |
store_order.completed |
A Store order is paid | Store order |
subscription.started |
A contact starts a booking subscription | Subscription |
subscription.renewed |
A subscription renews | Subscription |
subscription.cancelled |
A subscription is cancelled | Subscription |
Booking events carry the booking, contact.created the contact and store_order.completed the Store order, in the webhook shapes shown below (hosts are {id, name}, booking_type is a short summary). Payment and subscription events carry their own objects, shown below. Payment amounts are in major units (for example dollars), like bookings. Payment, charge and Stripe or PayPal ids, card details and the subscription manage link are never included.
{
"id": "6f1b7c52-0f4e-4d0e-9d5e-0a4a8b1f2c33",
"type": "booking.created",
"created_at": "2026-10-21T15:04:05+00:00",
"data": {
"id": 101,
"booking_type_id": 7,
"starts_at": "2026-10-28T16:00:00.000000Z",
"ends_at": "2026-10-28T16:30:00.000000Z",
"timezone": "America/Chicago",
"location": null,
"booking_type": { "id": 7, "title": "Intro call", "url_slug": "intro-call", "url": "https://tidycal.com/ada/intro-call",
"duration_minutes": 30, "price": null, "currency_code": "USD", "team": null },
"contact_id": 55,
"contact": { "id": 55, "name": "Ada Lovelace", "email": "[email protected]", "phone_number": null, "timezone": "Europe/London",
"source": null, "created_at": "2026-10-21T15:04:04.000000Z", "updated_at": "2026-10-21T15:04:04.000000Z" },
"payment": null,
"add_ons": [],
"hosts": [{ "id": 3, "name": "Grace Hopper" }],
"questions": [],
"meeting_url": "https://zoom.us/j/949494949494",
"approved_at": null,
"cancelled_at": null,
"created_at": "2026-10-21T15:04:04.000000Z"
}
}
booking.cancelled is the same with cancelled_at set. booking.approved has approved_at set. booking.rescheduled has the new starts_at and ends_at and an extra previous_starts_at.
status is completed, pending or failed. On payment.refunded it is refunded or partially_refunded, and refunded_amount (cumulative, major units) is added; refunded_amount is absent on every other payment event.
{
"id": "2c7e...", "type": "payment.completed", "created_at": "2026-10-21T15:04:05+00:00",
"data": { "id": 9, "booking_id": 101, "type": "booking", "amount": 25.0, "currency": "USD", "status": "completed",
"platform_fee": null, "completed_at": "2026-10-21T15:04:00.000000Z", "created_at": "2026-10-21T15:03:30.000000Z" }
}
{
"id": "0b9c...", "type": "contact.created", "created_at": "2026-10-21T15:04:05+00:00",
"data": { "id": 55, "name": "Ada Lovelace", "email": "[email protected]", "phone_number": null,
"timezone": "Europe/London", "source": null,
"created_at": "2026-10-21T15:04:04.000000Z", "updated_at": "2026-10-21T15:04:04.000000Z" }
}
{
"id": "4d2a...", "type": "store_order.completed", "created_at": "2026-10-21T15:04:05+00:00",
"data": { "id": 31, "booking_id": null, "product_id": 4, "product": { "id": 4, "title": "Planner", "slug": "planner" },
"buyer_name": "Ada Lovelace", "buyer_email": "[email protected]", "amount": 19.0, "currency": "USD",
"formatted_amount": "$19.00", "status": "completed",
"completed_at": "2026-10-21T15:04:00.000000Z", "refunded_at": null, "created_at": "2026-10-21T15:03:30.000000Z" }
}
subscription.renewed and subscription.cancelled have the same shape. renews_at is the end of the current period while the subscription is active and not cancelled, otherwise null.
{
"id": "8e1f...", "type": "subscription.started", "created_at": "2026-10-21T15:04:05+00:00",
"data": { "id": 3, "status": "active",
"plan": { "id": 2, "booking_type_id": 7, "booking_type": "Coaching", "credits_per_period": 4, "period_unit": "month" },
"price": 99.0, "currency": "USD", "credits_used": 0,
"current_period_start": "2026-10-21T15:00:00.000000Z", "current_period_end": "2026-11-21T15:00:00.000000Z",
"renews_at": "2026-11-21T15:00:00.000000Z", "cancelled_at": null, "ends_at": null,
"contact": { "id": 55, "name": "Ada Lovelace", "email": "[email protected]" },
"created_at": "2026-10-21T15:00:00.000000Z" }
}
POST /webhooks/{id}/test sends {"id": "...", "type": "webhook.test", "created_at": "...", "data": {"message": "This is a test event from TidyCal.", "webhook_id": 12}}. You cannot subscribe to webhook.test.
{- "name": "John Doe",
- "lifetime_pro_at": "2022-01-01T00:00:00Z",
- "vanity_path": "johndoe",
- "language": "en",
- "profile_picture_url": "https://www.gravatar.com/avatar/202446b468bec26847368d8a214b80c9?d=blank&s=200",
- "currency_symbol": "$"
}Get a list of bookings.
| starts_at | string <date-time> Get bookings starting from a specific date. |
| ends_at | string <date-time> Get bookings ending before a specific date. |
| cancelled | boolean Get only cancelled bookings. |
| page | number Page number. |
| include_teams | boolean Include team bookings. |
| order_by | string Example: order_by=id desc Sort column, optionally followed by a space and asc or desc (for example "id desc"). Sortable columns: id, starts_at, ends_at, created_at, updated_at, cancelled_at. Any other value returns 422. |
{- "data": [
- {
- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}
], - "current_page": 0,
- "first_page_url": "string",
- "from": 0,
- "last_page": 0,
- "last_page_url": "string",
- "links": [
- {
- "url": "string",
- "label": "string",
- "active": true
}
], - "next_page_url": "string",
- "path": "string",
- "per_page": 0,
- "prev_page_url": "string",
- "to": 0,
- "total": 0
}Get a booking by ID.
| booking required | integer The ID of the booking. |
{- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}Archive (soft-delete) a booking by ID. Only unpaid bookings that are cancelled or have already ended can be deleted; cancel an upcoming booking first. Bookings with a payment, add-on purchase, cancellation fee or subscription cannot be deleted, and neither can a booking in a recurring series or guest group that is still active. Deleting never sends email and never changes any connected calendar. A deleted booking no longer appears in the bookings list or can be fetched.
| booking required | integer The ID of the booking to delete. |
Cancel a booking by ID.
| booking required | integer The ID of the booking to cancel. |
| reason | string Optional reason for cancellation |
| notify | boolean Default: true Email the booker and the host about the cancellation. Send false to cancel without any notification. |
{- "reason": "Client requested cancellation",
- "notify": true
}{- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}Move a booking to a new start time. The new slot is checked exactly as on the public reschedule page: the booking type's availability, conflicts with the host's connected calendars and other bookings (this booking is ignored), and team host assignment (a round-robin booking may be reassigned to another available host). The booking keeps the booking type's current duration. If the booking type requires approval, the booking goes back to pending. The booker and host are notified unless notify is false. Sending the booking's current start time returns 200 and changes nothing. Guest bookings must be rescheduled through their parent booking. Rate limited with booking creation (10 per minute).
| booking required | integer The ID of the booking to reschedule. |
| starts_at required | string <date-time> New start time, ISO 8601. A value without a UTC offset is read as UTC. Use /booking-types/{bookingType}/timeslots to find open slots. |
| notify | boolean Default: true Send the TidyCal reschedule (or re-approval) emails and notifications. When false, TidyCal sends nothing, but the host's calendar provider (Google Calendar invite updates) and meeting provider (Zoom, Microsoft Teams) may still notify the booker of the new time. |
{- "starts_at": "2026-04-22T09:00:00Z",
- "notify": true
}{- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}Get a list of reschedules of your bookings, newest first. Each item has the rescheduled booking (with its hosts) and the time it was previously scheduled for.
| page | number Page number. |
{- "data": [
- {
- "id": 0,
- "booking_id": 0,
- "previous_starts_at": "2019-08-24T14:15:22Z",
- "booking": {
- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}
}
], - "current_page": 0,
- "first_page_url": "string",
- "from": 0,
- "last_page": 0,
- "last_page_url": "string",
- "links": [
- {
- "url": "string",
- "label": "string",
- "active": true
}
], - "next_page_url": "string",
- "path": "string",
- "per_page": 0,
- "prev_page_url": "string",
- "to": 0,
- "total": 0
}Add a guest to an existing booking. The guest is created as its own booking linked to the original one: it shares the original booking's time, location and meeting link (it never gets a meeting link or calendar event of its own) and receives a confirmation email. The number of guests per booking is capped by the booking type's guest invite limit (max_guest_invites_per_booker). Guests do not take a seat on a group booking slot. Only the original booking can take guests, and only while it is upcoming, not cancelled, paid, and (for approval-required booking types) not yet approved. Guests are free: on a priced booking type the guest is not charged, the same as guests invited on the booking form (the original booker has already paid).
| booking required | integer The ID of the booking to add the guest to. |
| name required | string <= 80 characters Guest's name |
| email required | string <email> <= 191 characters Guest's email address. Must not already be the booker or a guest on this booking. |
{- "name": "Sam Guest",
- "email": "[email protected]"
}{- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}{- "data": [
- {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
], - "current_page": 0,
- "first_page_url": "string",
- "from": 0,
- "last_page": 0,
- "last_page_url": "string",
- "links": [
- {
- "url": "string",
- "label": "string",
- "active": true
}
], - "next_page_url": "string",
- "path": "string",
- "per_page": 0,
- "prev_page_url": "string",
- "to": 0,
- "total": 0
}Create a new booking type.
| title required | string <= 191 characters |
| description required | string <html> |
| duration_minutes required | integer >= 1 |
| url_slug required | string <= 191 characters |
| padding_minutes | integer >= 0 Default: 0 |
| latest_availability_days | integer [ 0 .. 36500 ] Default: 60 |
| private | boolean Default: false |
| max_bookings | integer >= 1 Default: 1 |
| max_guest_invites_per_booker | integer [ 0 .. 10 ] Default: 0 |
| display_seats_remaining | boolean Default: false |
| booking_availability_interval_minutes | integer [ 15 .. 1440 ] If omitted, defaults to 30 for meetings of at least 30 minutes owned by new signups assigned to the new-booking-type-interval treatment; otherwise 15. Explicit values are preserved. |
| redirect_url | string or null <= 60000 characters |
| approval_required | boolean or null |
| in_gallery_at | string or null Whether to list the booking type in the public TidyCal directory. Any non-empty value lists it; an explicit null or empty value opts out. When omitted, new booking types are listed by default. A listed booking type only appears (and is only indexed by search engines) once it meets the directory eligibility rules. |
| booking_type_category_id | integer or null |
| price | number <float> [ 0 .. 99999999.99 ] Default: 0 Price for the booking type. Set to 0 for free booking types. How many decimal places are accepted depends on the currency: a currency with no sub-unit (JPY, KRW, CLP, VND and the other zero-decimal currencies) takes whole amounts only, every other currency takes at most 2. Trailing zeros are notation rather than precision, so 2200.00 is a valid JPY price. The currency judged is the one sent in |
| payment_platform | string Enum: "stripe" "paypal" "tidycal" Payment platform to use. The platform must be connected in your account Integrations settings. Required when price > 0. |
| currency_code | string ISO 4217 currency code (e.g. USD, EUR, GBP). Defaults to your account currency. |
| booking_threshold | integer [ 0 .. 15000 ] Minimum notice required before a booking, in minutes. Omit for the default of 120 (2 hours); set to 0 for no minimum notice. |
{- "title": "30 Minute Meeting",
- "description": "Book a 30 minute meeting with me",
- "duration_minutes": 30,
- "url_slug": "30-minute-meeting",
- "padding_minutes": 15,
- "latest_availability_days": 90,
- "private": false,
- "max_bookings": 1,
- "max_guest_invites_per_booker": 0,
- "display_seats_remaining": false,
- "booking_availability_interval_minutes": 30,
- "approval_required": false,
- "in_gallery_at": null,
- "booking_type_category_id": 1,
- "price": 25,
- "payment_platform": "stripe",
- "currency_code": "USD",
- "booking_threshold": 120
}{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Get the ordered custom questions asked on a booking type's booking form.
| bookingType required | integer The ID of the booking type. |
{- "data": [
- {
- "id": 1,
- "question": "What is your company name?",
- "question_type": "text",
- "required": true,
- "question_answers": [
- "string"
], - "sort_order": 0
}
]
}Replace the full ordered set of questions on a booking type. Array order becomes the display order.
Include an existing question's id to update it in place (its id stays stable); questions without
an id are created, and existing questions left out of the array are deleted. Answers already
stored on past bookings are copies of the question text and are not affected. Send an empty array to remove all questions.
| bookingType required | integer |
required | Array of objects <= 50 items |
{- "questions": [
- {
- "id": 0,
- "question": "string",
- "required": true,
- "question_type": "text",
- "question_answers": [
- "string"
]
}
]
}{- "data": [
- {
- "id": 1,
- "question": "What is your company name?",
- "question_type": "text",
- "required": true,
- "question_answers": [
- "string"
], - "sort_order": 0
}
]
}Apply the same ordered question set to up to 50 booking types. All-or-nothing: if any id is not a
booking type you can update, the request fails with 403 and nothing changes. Existing questions with
the same type and text keep their id; others are created or deleted. Question id is not accepted here.
required | Array of objects <= 50 items |
| booking_type_ids required | Array of integers [ 1 .. 50 ] items |
{- "questions": [
- {
- "id": 0,
- "question": "string",
- "required": true,
- "question_type": "text",
- "question_answers": [
- "string"
]
}
], - "booking_type_ids": [
- 0
]
}{- "data": [
- {
- "booking_type_id": 0,
- "questions": [
- {
- "id": 1,
- "question": "What is your company name?",
- "question_type": "text",
- "required": true,
- "question_answers": [
- "string"
], - "sort_order": 0
}
]
}
]
}Get a single booking type.
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Partially update a booking type. Only the fields you send change; omitted fields keep their current value. Accepts the same fields as create. Returns the updated booking type.
| bookingType required | integer The ID of the booking type. |
Any subset of the fields accepted when creating a booking type.
{- "title": "Renamed call"
}{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Disable a booking type. Idempotent: calling it when the booking type is already disabled returns it unchanged. Returns the booking type.
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Enable a booking type. Idempotent: calling it when the booking type is already enabled returns it unchanged. Returns the booking type.
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Get a list of available timeslots for a specific booking type. These are the times a client can book, so when the booking type has "Look busy" turned on (and the owner can use it), the hidden times are left out here too.
| bookingType required | integer The ID of the booking type. |
| starts_at required | string <date-time> Start date to get timeslots from (UTC). |
| ends_at required | string <date-time> End date to get timeslots until (UTC). Must not be before starts_at, and the range may be at most 90 days; a longer range returns 422. |
{- "data": [
- {
- "starts_at": "2024-03-20T10:00:00Z",
- "ends_at": "2024-03-20T10:30:00Z",
- "available_bookings": 1
}
]
}Create a new booking for a specific booking type.
Single vs Package Bookings:
starts_at to specify the booking timebookings array to specify multiple time slotsResponse Format:
{"data": {Booking}}{"data": [{Booking}, ...]}Priced booking types:
A booking type with a price can be booked through the API only with a coupon_code
that brings the price to 0 (a 100%-off coupon, or a fixed coupon worth at least the
price). The booking is then created and confirmed exactly as on the booking page: no
payment is created and the coupon use is recorded. Any other request for a priced type
returns 400, including a coupon that leaves something to pay, a recurring (weekly or
monthly) booking type, add_on_product_ids and contact_subscription_id. There is no
way to record a payment taken outside TidyCal.
| bookingType required | integer The ID of the booking type. |
| starts_at | string <date-time> The start time of the booking in UTC. Required for single bookings. Ignored if |
Array of objects Array of booking times for package/multi-session bookings. If provided, | |
| name required | string <= 191 characters Name of the person making the booking |
| email required | string <email> <= 191 characters Email of the person making the booking |
| timezone required | string <= 191 characters The timezone of the booking |
Array of objects Answers to booking type questions | |
| coupon_code | string <= 191 characters One of the host's coupon codes (case-insensitive). Required to book a priced booking type, and it must bring the price to 0. Ignored for a free booking type. |
| payment_link | boolean Reserved. Holding a priced booking awaiting payment and returning a URL the
booker pays at is not supported yet: |
{- "starts_at": "2024-03-20T10:00:00Z",
- "bookings": [
- {
- "starts_at": "2024-03-20T10:00:00Z"
}, - {
- "starts_at": "2024-03-27T10:00:00Z"
}
], - "name": "John Doe",
- "timezone": "America/Los_Angeles",
- "booking_questions": [
- {
- "booking_type_question_id": 1,
- "answer": "My answer"
}
], - "coupon_code": "VIPFREE",
- "payment_link": false
}{- "data": {
- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}
}Get a single booking type.
| team required | integer The ID of the team. |
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Partially update a booking type. Only the fields you send change; omitted fields keep their current value. Accepts the same fields as create. Returns the updated booking type.
| team required | integer The ID of the team. |
| bookingType required | integer The ID of the booking type. |
Any subset of the fields accepted when creating a booking type.
{- "title": "Renamed call"
}{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Disable a booking type. Idempotent: calling it when the booking type is already disabled returns it unchanged. Returns the booking type.
| team required | integer The ID of the team. |
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Enable a booking type. Idempotent: calling it when the booking type is already enabled returns it unchanged. Returns the booking type.
| team required | integer The ID of the team. |
| bookingType required | integer The ID of the booking type. |
{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Get a list of contacts.
| search | string <= 255 characters Return only contacts whose name or email starts with this text (case-insensitive). |
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Create a new contact.
| name required | string |
| email required | string <email> |
| timezone | string |
{- "name": "John Doe",
- "timezone": "America/Los_Angeles"
}{- "data": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}
}Get a single contact belonging to the authenticated user.
| contact required | integer Contact ID. |
{- "data": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}
}Update a contact belonging to the authenticated user. Only the fields sent are changed.
| contact required | integer Contact ID. |
| name | string |
string <email> | |
| timezone | string or null |
| phone_number | string or null |
{- "name": "John Doe",
- "timezone": "America/Los_Angeles",
- "phone_number": "+15125550100"
}{- "data": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}
}Get Store sales and booking add-ons for the authenticated user, newest completed first (pending and failed orders last), 100 per page with data, links and meta. Use status=completed to pick up new paid sales only.
| status | string Enum: "pending" "completed" "refunded" "failed" Only orders with this status. |
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "booking_id": null,
- "product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "slug": "workbook-pdf"
}, - "buyer_name": "Jane Doe",
- "amount": 25,
- "currency": "USD",
- "formatted_amount": "$25.00",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "download_count": 0,
- "created_at": "2024-01-01T00:00:00Z"
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Get your contacts' subscriptions to your booking type subscription plans, newest first, 100 per page with data, links and meta.
| status | string Enum: "active" "past_due" "paused" "cancelled" Only subscriptions with this status. A subscription set to cancel at period end keeps status active (see cancel_at_period_end) until the period ends. |
| contact_id | integer Only subscriptions of this contact. |
| booking_type_id | integer Only subscriptions to this booking type's plan. |
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "status": "active",
- "cancel_at_period_end": false,
- "plan": {
- "id": 1,
- "booking_type_id": 1,
- "booking_type_title": "Coaching session",
- "price": 49,
- "currency": "usd",
- "period_unit": "month",
- "credits_per_period": 4
}, - "credits_used": 1,
- "credits_remaining": 3,
- "current_period_start": "2026-10-01T00:00:00Z",
- "current_period_end": "2026-11-01T00:00:00Z",
- "renews_at": "2026-11-01T00:00:00Z",
- "cancelled_at": null,
- "ends_at": null,
- "contact_id": 1,
- "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "created_at": "2026-10-01T00:00:00Z",
- "updated_at": "2026-10-01T00:00:00Z"
}
]
}Get one of your contacts' subscriptions by ID.
| subscription required | integer The ID of the subscription. |
{- "data": {
- "id": 1,
- "status": "active",
- "cancel_at_period_end": false,
- "plan": {
- "id": 1,
- "booking_type_id": 1,
- "booking_type_title": "Coaching session",
- "price": 49,
- "currency": "usd",
- "period_unit": "month",
- "credits_per_period": 4
}, - "credits_used": 1,
- "credits_remaining": 3,
- "current_period_start": "2026-10-01T00:00:00Z",
- "current_period_end": "2026-11-01T00:00:00Z",
- "renews_at": "2026-11-01T00:00:00Z",
- "cancelled_at": null,
- "ends_at": null,
- "contact_id": 1,
- "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "created_at": "2026-10-01T00:00:00Z",
- "updated_at": "2026-10-01T00:00:00Z"
}
}Cancel a subscription at the end of its current billing period, the same as the contact cancelling it from their manage page: the contact is not charged again and keeps any remaining credits until current_period_end. The status stays active (with cancel_at_period_end true) until then. Nothing is refunded. The contact is emailed unless notify is false.
| subscription required | integer The ID of the subscription to cancel. |
| notify | boolean Default: true Email the contact that the subscription was cancelled. |
{- "notify": true
}{- "data": {
- "id": 1,
- "status": "active",
- "cancel_at_period_end": false,
- "plan": {
- "id": 1,
- "booking_type_id": 1,
- "booking_type_title": "Coaching session",
- "price": 49,
- "currency": "usd",
- "period_unit": "month",
- "credits_per_period": 4
}, - "credits_used": 1,
- "credits_remaining": 3,
- "current_period_start": "2026-10-01T00:00:00Z",
- "current_period_end": "2026-11-01T00:00:00Z",
- "renews_at": "2026-11-01T00:00:00Z",
- "cancelled_at": null,
- "ends_at": null,
- "contact_id": 1,
- "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "created_at": "2026-10-01T00:00:00Z",
- "updated_at": "2026-10-01T00:00:00Z"
}
}Get a list of teams the authenticated user has access to.
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "name": "Sales Team",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Get a list of bookings for a specific team. The team owner and admins see every team booking; other members see bookings they host or whose booking type they own.
| team required | integer The ID of the team. |
| page | number Page number. |
| start_date | string <date> Get bookings starting from a specific date. |
| end_date | string <date> Get bookings ending before a specific date. |
string <email> Get bookings for a specific email address. | |
| host_id | integer Get bookings for a specific user ID. |
| cancelled | boolean Filter by cancellation status. Use true for cancelled bookings, false for non-cancelled bookings, or omit for all bookings. |
{- "data": [
- {
- "id": 1,
- "contact_id": 1,
- "booking_type_id": 1,
- "starts_at": "2022-01-01T00:00:00Z",
- "ends_at": "2022-02-01T00:00:00Z",
- "cancelled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "timezone": "America/Los_Angeles",
- "meeting_id": "fw44lkj48fks",
- "questions": [
- {
- "id": 1,
- "booking_id": 1,
- "question": "What is your favorite color?",
- "answer": "Blue",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "contact": {
- "id": 1,
- "name": "John Doe",
- "phone_number": "+1234567890",
- "timezone": "America/New_York",
- "created_at": "2022-01-01T00:00:00Z",
- "updated_at": "2022-01-01T00:00:00Z"
}, - "contact_subscription_id": null,
- "payment": {
- "id": 1,
- "booking_id": 1,
- "amount": 1,
- "currency": "USD",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}, - "coupon_code": "SPRING25",
- "add_ons": [
- {
- "id": 1,
- "booking_id": 1,
- "storefront_product_id": 1,
- "product": {
- "id": 1,
- "title": "Workbook PDF",
- "url_slug": "workbook-pdf"
}, - "amount": "25.00",
- "currency": "USD",
- "status": "completed",
- "completed_at": "2024-01-01T00:00:00Z",
- "refunded_at": null,
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "refund": {
- "status": "refunded",
- "amount": "25.00",
- "currency": "USD",
- "refunded_at": "2024-01-01T00:00:00Z"
}, - "hosts": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
]
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Get a list of users in a specific team. Member email addresses (invitation_email and user.email) are returned to the team owner and admins only; other members get null for everyone but themselves, and filtering by email returns 403 for them.
| team required | integer The ID of the team. |
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "name": "John Doe",
- "created_at": "2024-01-01T00:00:00Z",
- "updated_at": "2024-01-01T00:00:00Z"
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Add a user to a team by sending an invitation email.
| team required | integer The ID of the team. |
| email required | string <email> Email address of the user to invite |
| role_name | string Enum: "admin" "user" Role name for the user in the team |
{- "role_name": "user"
}{- "message": "Invitation sent successfully",
- "team_user_id": 123
}Get a list of booking types for a specific team.
| team required | integer The ID of the team. |
| page | number Page number. |
{- "data": [
- {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
], - "links": {
- "first": "string",
- "last": "string",
- "prev": "string",
- "next": "string"
}, - "meta": {
- "current_page": 0,
- "from": 0,
- "last_page": 0,
- "path": "string",
- "per_page": 0,
- "to": 0,
- "total": 0
}
}Create a new booking type for a specific team.
| team required | integer The ID of the team. |
| title required | string <= 191 characters |
| description required | string <html> |
| duration_minutes required | integer >= 1 |
| url_slug required | string <= 191 characters |
| padding_minutes | integer >= 0 Default: 0 |
| latest_availability_days | integer [ 0 .. 36500 ] Default: 60 |
| private | boolean Default: false |
| max_bookings | integer >= 1 Default: 1 |
| max_guest_invites_per_booker | integer [ 0 .. 10 ] Default: 0 |
| display_seats_remaining | boolean Default: false |
| booking_availability_interval_minutes | integer [ 15 .. 1440 ] Default: 15 |
| redirect_url | string or null <= 60000 characters |
| approval_required | boolean or null |
| in_gallery_at | string or null Whether to list the booking type in the public TidyCal directory. Any non-empty value lists it; an explicit null or empty value opts out. When omitted, new booking types are listed by default. A listed booking type only appears (and is only indexed by search engines) once it meets the directory eligibility rules. |
| booking_type_category_id | integer or null |
| price | number <float> [ 0 .. 99999999.99 ] Default: 0 Price for the booking type. Set to 0 for free booking types. How many decimal places are accepted depends on the currency: a currency with no sub-unit (JPY, KRW, CLP, VND and the other zero-decimal currencies) takes whole amounts only, every other currency takes at most 2. Trailing zeros are notation rather than precision, so 2200.00 is a valid JPY price. The currency judged is the one sent in |
| payment_platform | string Enum: "stripe" "paypal" "tidycal" Payment platform to use. The platform must be connected in your account Integrations settings. Required when price > 0. |
| currency_code | string ISO 4217 currency code (e.g. USD, EUR, GBP). Defaults to your account currency. |
| booking_threshold | integer [ 0 .. 15000 ] Minimum notice required before a booking, in minutes. Omit for the default of 120 (2 hours); set to 0 for no minimum notice. |
{- "title": "30 Minute Meeting",
- "description": "Book a 30 minute meeting with me",
- "duration_minutes": 30,
- "url_slug": "30-minute-meeting",
- "padding_minutes": 15,
- "latest_availability_days": 90,
- "private": false,
- "max_bookings": 1,
- "max_guest_invites_per_booker": 0,
- "display_seats_remaining": false,
- "booking_availability_interval_minutes": 30,
- "approval_required": false,
- "in_gallery_at": null,
- "booking_type_category_id": 1,
- "price": 25,
- "payment_platform": "stripe",
- "currency_code": "USD",
- "booking_threshold": 120
}{- "data": {
- "id": 1,
- "user_id": 1,
- "title": "15 Minute Meeting",
- "duration_minutes": 15,
- "padding_minutes": 10,
- "disabled_at": "2022-02-01T00:00:00Z",
- "created_at": "2022-02-01T00:00:00Z",
- "updated_at": "2022-02-01T00:00:00Z",
- "url_slug": "15-minute-meeting",
- "description": "Book a meeting with me for 15 minutes!",
- "price": 10,
- "payment_platform": "stripe",
- "payment_platform_id": "7df81dc2-beb8-48b4-a467-f171eabeca01",
- "currency_code": "USD",
- "private": false,
- "latest_availability_days": 60,
- "booking_threshold_minutes": 120,
- "max_bookings": 1,
}
}Webhook endpoints you own, plus every endpoint scoped to a team you own or administer. 50 per page. The signing secret is never included.
{- "data": [
- {
- "id": 12,
- "events": [
- "booking.created"
], - "description": "string",
- "team_id": 0,
- "booking_type_id": 0,
- "enabled": true,
- "disabled_reason": "string",
- "disabled_at": "2019-08-24T14:15:22Z",
- "failing_since": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Subscribe a URL to events. This is also the REST-hook subscribe call used by Zapier and Make: send {url, events} and keep the returned id; DELETE /webhooks/{id} unsubscribes.
The response includes the signing secret. It is shown only here and by rotate-secret; store it. You can have at most 10 webhooks (deleted ones do not count). The URL must be https and resolve only to public addresses; it is checked again before every delivery.
Scope: leave team_id and booking_type_id out for every event on your account. team_id limits the webhook to that team's booking types and needs team owner or admin. booking_type_id limits it to one booking type of yours, or of a team you administer. Team-scoped webhooks are stored under the team owner (they count toward the owner's 10) and are visible to every team owner and admin. The scope cannot be changed later.
Access follows the team: a team-scoped webhook is turned off when the owner or admin who created it is removed from the team or loses the admin role, and an account-wide webhook receives a team's booking events only while its owner owns or administers that team. A webhook created through a connected app (such as Zapier) is turned off when you revoke that app. One webhook per URL per account.
| url required | string <uri> |
| events required | Array of strings (WebhookEventType) non-empty Items Enum: "booking.created" "booking.cancelled" "booking.rescheduled" "booking.approved" "payment.completed" "payment.refunded" "contact.created" "store_order.completed" "subscription.started" "subscription.renewed" "subscription.cancelled" |
| team_id | integer or null |
| booking_type_id | integer or null |
| description | string or null <= 255 characters |
{- "events": [
- "booking.created"
], - "team_id": 0,
- "booking_type_id": 0,
- "description": "string"
}{- "data": {
- "id": 12,
- "events": [
- "booking.created"
], - "description": "string",
- "team_id": 0,
- "booking_type_id": 0,
- "enabled": true,
- "disabled_reason": "string",
- "disabled_at": "2019-08-24T14:15:22Z",
- "failing_since": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "secret": "whsec_3f1c0d..."
}
}{- "data": {
- "id": 12,
- "events": [
- "booking.created"
], - "description": "string",
- "team_id": 0,
- "booking_type_id": 0,
- "enabled": true,
- "disabled_reason": "string",
- "disabled_at": "2019-08-24T14:15:22Z",
- "failing_since": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Change the URL, events or description, or turn the webhook on or off with enabled. team_id and booking_type_id cannot be changed and are rejected with 422. Setting enabled to true on a webhook that was turned off after failures clears the failure state.
| webhook required | integer The ID of the webhook. |
| url | string <uri> |
| events | Array of strings (WebhookEventType) non-empty Items Enum: "booking.created" "booking.cancelled" "booking.rescheduled" "booking.approved" "payment.completed" "payment.refunded" "contact.created" "store_order.completed" "subscription.started" "subscription.renewed" "subscription.cancelled" |
| description | string or null |
| enabled | boolean |
{- "events": [
- "booking.created"
], - "description": "string",
- "enabled": true
}{- "data": {
- "id": 12,
- "events": [
- "booking.created"
], - "description": "string",
- "team_id": 0,
- "booking_type_id": 0,
- "enabled": true,
- "disabled_reason": "string",
- "disabled_at": "2019-08-24T14:15:22Z",
- "failing_since": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Replace the signing secret. The new secret is returned once and the old one stops verifying immediately. Only the webhook owner, or the team owner or admin who created a team webhook, can rotate it.
| webhook required | integer |
{- "data": {
- "id": 12,
- "events": [
- "booking.created"
], - "description": "string",
- "team_id": 0,
- "booking_type_id": 0,
- "enabled": true,
- "disabled_reason": "string",
- "disabled_at": "2019-08-24T14:15:22Z",
- "failing_since": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "secret": "whsec_3f1c0d..."
}
}Queue a sample webhook.test event to the webhook. It is delivered, signed and retried like any other event and appears in the delivery log. Limited to 10 requests per minute.
| webhook required | integer |
{- "data": {
- "id": 0,
- "webhook_id": 0,
- "event_id": "a7a26ff2-e851-45b6-9634-d595f45458b7",
- "event_type": "booking.created",
- "status": "pending",
- "attempts": 0,
- "response_status": 0,
- "response_body": "string",
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "delivered_at": "2019-08-24T14:15:22Z",
- "failed_at": "2019-08-24T14:15:22Z",
- "payload": { },
- "created_at": "2019-08-24T14:15:22Z"
}
}Delivery log for the webhook, newest first, 50 per page. Deliveries are kept for 30 days.
| webhook required | integer |
{- "data": [
- {
- "id": 0,
- "webhook_id": 0,
- "event_id": "a7a26ff2-e851-45b6-9634-d595f45458b7",
- "event_type": "booking.created",
- "status": "pending",
- "attempts": 0,
- "response_status": 0,
- "response_body": "string",
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "delivered_at": "2019-08-24T14:15:22Z",
- "failed_at": "2019-08-24T14:15:22Z",
- "payload": { },
- "created_at": "2019-08-24T14:15:22Z"
}
]
}Send a finished (delivered or failed) delivery again. The same event id is used so receivers can dedupe. The delivery is reset (attempts, status and response cleared) and queued. Limited to 10 requests per minute.
| delivery required | integer |
{- "data": {
- "id": 0,
- "webhook_id": 0,
- "event_id": "a7a26ff2-e851-45b6-9634-d595f45458b7",
- "event_type": "booking.created",
- "status": "pending",
- "attempts": 0,
- "response_status": 0,
- "response_body": "string",
- "next_attempt_at": "2019-08-24T14:15:22Z",
- "delivered_at": "2019-08-24T14:15:22Z",
- "failed_at": "2019-08-24T14:15:22Z",
- "payload": { },
- "created_at": "2019-08-24T14:15:22Z"
}
}