Zapier API reference
Every endpoint the TimeTuna Zapier integration calls, with authentication details, request parameters, response shapes, and error codes. For a guide to using the integration, see the Zapier page.
All endpoints live under the base URL https://timetuna.com. Requests and responses are JSON unless noted otherwise. For setup instructions and Zap-building walkthroughs, see the Zapier guide.
Authentication
The integration uses OAuth 2.0 authorization code flow with refresh tokens. Every Zapier-facing endpoint expects the access token as a Bearer token:
Authorization: Bearer <access_token>- Scope: a single scope,
bookings. It grants read access to bookings and booking pages the authorizing user owns, plus create, cancel, and reschedule on their behalf. It never grants access to other accounts. - Access tokens expire after 1 hour (
expires_in: 3600). - Refresh tokens are long-lived and rotate: each refresh revokes the old refresh token and returns a new pair.
- Plan requirement: the authorizing account must be on the Executive plan. Endpoints marked Executive below return
402with{ "error": "upgrade_required" }if the account has downgraded.
GET /api/oauth/authorize
Starts the flow. Renders a consent screen if the user has a TimeTuna session, or redirects to sign-in first. On approval, redirects back to redirect_uri with ?code=...&state=...; on denial, with ?error=access_denied.
GET /api/oauth/authorize
?response_type=code (required, must be "code")
&client_id=<client id> (required)
&redirect_uri=<uri> (required, must match the registered prefix)
&state=<opaque value> (recommended, echoed back)Errors: 400 invalid_request (missing or malformed parameters), 400 invalid_client (unknown client), 400 invalid_redirect_uri (URI does not match the registered prefix). Authorization codes are single-use and expire after 10 minutes.
POST /api/oauth/token
Exchanges an authorization code for tokens, or refreshes an expired access token. Accepts application/x-www-form-urlencoded (preferred) or JSON. Client credentials are passed in the body.
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<code from the redirect>
&redirect_uri=<same uri as the authorize call>
&client_id=<client id>
&client_secret=<client secret>POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=<current refresh token>
&client_id=<client id>
&client_secret=<client secret>Both grants return the same shape:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "bookings"
}Errors: 401 invalid_client (bad client id or secret), 400 invalid_grant (expired, used, or unknown code or refresh token), 400 unsupported_grant_type.
GET /api/zapier/auth/me
Identifies the connected account. Zapier calls this to test a connection and label it with the account email. Requires a valid Bearer token; no plan check.
{ "id": "…", "email": "host@example.com", "name": "Ada Host" }Errors: 401 (missing, expired, or revoked token), 404 (user no longer exists).
Common error responses
All Zapier-facing endpoints share these error conventions:
401{ "error": "Unauthorized" }: missing, expired, or revoked access token. Refresh the token and retry.402{ "error": "upgrade_required" }: the account is not on the Executive plan.400{ "error": "<message>" }: missing or invalid parameters; the message names the field.403{ "error": "Forbidden" }: the resource exists but belongs to a different account.404{ "error": "<resource> not found" }.409: the operation conflicts with the booking's current state (for example cancelling an already-cancelled booking).
Searches (dynamic dropdowns)
GET /api/zapier/actions/booking-pages
Executive. Lists every booking page the account owns, sorted by name. Powers the booking-page dropdowns in the Zap editor and the Find booking pages search.
[
{
"id": "…",
"name": "Intro call",
"url": "https://timetuna.com/ada/intro",
"slug": "ada/intro",
"timezone": "Europe/Berlin",
"duration_minutes": 30
}
]GET /api/zapier/actions/available-times
Executive. Returns the open slots on one booking page for one calendar date, in the page's timezone. Powers the Find available times search.
GET /api/zapier/actions/available-times
?booking_page_id=<id> (required, from Find booking pages)
&date=YYYY-MM-DD (required)
&duration=<minutes> (optional, defaults to the page's duration)[
{
"id": "2026-07-02T09:00:00.000Z",
"start": "2026-07-02T09:00:00.000Z",
"end": "2026-07-02T09:30:00.000Z"
}
]Times are ISO 8601 UTC. Errors: 400 (missing parameters or a date not in YYYY-MM-DD format), 404 (booking page not found or not owned by the account), 502 (availability lookup failed upstream).
GET /api/zapier/actions/bookings
Executive. Lists the account's next 50 upcoming confirmed bookings, soonest first. Powers the booking dropdown in the Cancel and Reschedule actions. The name field is a human-readable label for the dropdown.
[
{
"id": "…",
"name": "Grace Guest (Jul 2, 2026, 11:00 AM)",
"guest_name": "Grace Guest",
"guest_email": "grace@example.com",
"start_time": "2026-07-02T09:00:00.000Z",
"booking_page_name": "Intro call",
"status": "confirmed"
}
]Actions
POST /api/zapier/actions/bookings
Executive. Creates a booking on a page the account owns, exactly as if the guest had booked it: calendar events, meeting links, and confirmation emails all fire.
{
"booking_page_id": "…", // required
"start_time": "2026-07-02T09:00:00Z", // required, ISO 8601 with offset
"guest_email": "grace@example.com", // required
"guest_name": "Grace Guest", // optional
"purpose": "Portfolio review", // optional
"duration": 30, // optional, minutes
"form_responses": { "company": "…" } // optional, page form fields
}Returns 201 with the booking object (same shape as the trigger payloads below). Errors: 400 (missing required fields, a start time without a UTC offset, or a slot that is unavailable, taken, or in the past).
POST /api/zapier/actions/bookings/{id}/cancel
Executive. Cancels a booking by ID. No request body. Cancellation emails are sent and any calendar events are removed, exactly as if cancelled from the dashboard.
{ "success": true, "booking_id": "…" }Errors: 404 (unknown booking), 403 (booking belongs to another account), 409 (already cancelled).
POST /api/zapier/actions/bookings/{id}/reschedule
Executive. Moves a booking to a new start time. Reschedule notifications and calendar updates fire as usual.
{
"new_start_time": "2026-07-03T14:00:00Z", // required, ISO 8601 with offset
"duration": 45 // optional, minutes
}{ "success": true, "booking_id": "…" }Errors: 400 (missing new_start_time or unavailable slot), 404, 403, 409 (booking is cancelled).
Webhook subscriptions (REST hooks)
The three triggers are REST hooks: Zapier registers a hook URL once, and TimeTuna posts events to it as they happen.
POST /api/zapier/hooks/subscribe
Executive. Registers a hook URL for one event type. Subscribing the same URL and event twice returns the existing subscription instead of creating a duplicate.
{
"hookUrl": "https://hooks.zapier.com/…", // required
"event": "new_booking" // required: new_booking |
// booking_cancelled | booking_rescheduled
}Returns 201 (or 200 when reusing an existing subscription) with { "id": "…" }. Errors: 400 (missing fields or unknown event type).
DELETE /api/zapier/hooks/unsubscribe
Removes the subscription for a hook URL, optionally narrowed to one event type. Requires a valid Bearer token; no plan check, so a downgraded account can still clean up.
{
"hookUrl": "https://hooks.zapier.com/…", // required
"event": "new_booking" // optional
}Returns { "success": true }. Deleting an already-removed subscription still succeeds.
Event delivery
TimeTuna posts each event to the registered hook URL as JSON, with the event type in a header:
POST <hookUrl>
Content-Type: application/json
X-Webhook-Event: new_booking | booking_cancelled | booking_rescheduledThe body is the bare payload object described under trigger payloads. If the hook URL answers 410 Gone, TimeTuna deactivates that subscription automatically.
Polling triggers and payloads
Each REST-hook trigger has a matching polling endpoint that Zapier uses to fetch sample data in the Zap editor and to validate payload shapes. The polling responses use exactly the same fields as the live webhook deliveries.
GET /api/zapier/triggers/new-booking
Executive. Returns the 3 most recently created confirmed bookings, newest first.
[
{
"id": "…",
"booking_page_id": "…",
"booking_page_name": "Intro call",
"booking_page_url": "https://timetuna.com/ada/intro",
"guest_name": "Grace Guest",
"guest_email": "grace@example.com",
"start_time": "2026-07-02T09:00:00.000Z",
"end_time": "2026-07-02T09:30:00.000Z",
"timezone": "Europe/Berlin",
"purpose": "Portfolio review",
"meeting_link": "https://zoom.us/j/…",
"form_responses": { "name": "Grace Guest", "email": "grace@example.com" },
"status": "confirmed",
"created_at": "2026-07-01T16:20:00.000Z"
}
]GET /api/zapier/triggers/booking-cancelled
Executive. Returns the 3 most recently cancelled bookings. Same fields as new-booking minus booking_page_url, meeting_link, and form_responses, with status set to "cancelled".
GET /api/zapier/triggers/booking-rescheduled
Executive. Returns up to 3 recently rescheduled bookings. Same fields as new-booking minus booking_page_url and form_responses, plus previous_start_time and previous_end_time. Live webhook deliveries carry the real previous times; the polling samples return null for both, because previous times are not stored after the move.