# How to Integrate Retool with Calendly

- Tool: Retool
- Difficulty: Beginner
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Calendly by creating a REST API Resource with Calendly's API URL and personal access token Bearer authentication. Query events, invitees, and event types to build scheduling dashboards that give your sales or ops team a centralized view of all booked meetings, invitee data, and scheduling metrics — without switching between Calendly and other tools.

## Build a Calendly Scheduling Admin Panel in Retool

Calendly handles the mechanics of scheduling perfectly, but its built-in reporting is limited for operational needs. Sales teams want to see all upcoming meetings across the entire team in one view. Operations teams need to export invitee data for CRM entry. Managers want metrics on which event types are most popular and how far in advance meetings are typically booked.

Connecting Calendly to Retool gives you a custom scheduling panel built around your specific workflows. A sales dashboard can show all meetings booked this week, filtered by rep, with invitee company and email pulled from Calendly's invitee data and matched against your CRM. An operations panel can export all invitees from a specific event type for the last 30 days with one click. A management view can show booking velocity trends over time.

Calendly's API v2 uses personal access tokens for authentication — straightforward Bearer token configuration in Retool with no OAuth flow required. The most useful endpoints are /scheduled_events (all booked meetings), /scheduled_events/{uuid}/invitees (who booked each meeting), and /event_types (your booking page configurations). All data is paginated with cursor-based pagination.

## Before you start

- A Calendly account (Professional plan or higher for API access)
- A Calendly personal access token (Integrations → API & Webhooks → Personal Access Tokens → Create new token)
- Your Calendly user URI (returned by /users/me API call, used to scope queries)
- A Retool account with permission to create Resources
- Basic familiarity with Retool's Table component and query editor

## Step-by-step guide

### 1. Create a Calendly REST API Resource in Retool

Navigate to the Resources tab in Retool and click Add Resource. Select REST API from the resource type list. Name it 'Calendly API'.

In the Base URL field, enter https://api.calendly.com. This is Calendly's API v2 base URL. All endpoint paths will be appended to this base.

For authentication, select Bearer Token from the Authentication dropdown. In the Token field, enter your Calendly personal access token. To store it securely, first create a configuration variable: go to Settings → Configuration Variables, create CALENDLY_ACCESS_TOKEN, mark it as Secret, paste your token. Then use {{ retoolContext.configVars.CALENDLY_ACCESS_TOKEN }} in the Bearer Token field.

Calendly's API v2 doesn't require additional default headers beyond the Bearer token. Click Save Changes.

To verify the resource works, create a quick test query: GET /users/me. This returns your Calendly user information including your user URI (in the format https://api.calendly.com/users/abc123). Copy this URI — you'll need it to scope event queries to your organization or user.

**Expected result:** The Calendly API resource is saved. A test query to /users/me returns your Calendly user information, confirming authentication is working.

### 2. Fetch scheduled events with date range filtering

Create the main query to retrieve scheduled events. Name it getScheduledEvents. Set Method to GET, Path to /scheduled_events.

Calendly's /scheduled_events endpoint requires either an organization or user parameter to scope the results. Use URL parameters:
- user: {{ variable_userUri.value }} (your Calendly user URI from the /users/me call)
- min_start_time: {{ moment(dateRange.value[0]).toISOString() }} (ISO 8601 format)
- max_start_time: {{ moment(dateRange.value[1]).toISOString() }}
- status: active (to exclude cancelled events, or remove for all events)
- count: 50 (page size, max 100)

Drag a Date Range Picker component onto your canvas (default: last 30 days). The min_start_time and max_start_time parameters control the query window.

Calendly uses cursor-based pagination. The response includes a pagination object with next_page_token for the next page. For most use cases, 100 events per page is sufficient. For larger exports, implement pagination using Retool Variables to store and pass the next page token.

Bind the query results to a Table component. The events array has uri, name, start_time, end_time, status, and event_type fields.

```
// JavaScript transformer — format Calendly events for table display
const events = (data?.collection || []);
return events.map(event => {
  const startTime = new Date(event.start_time);
  const endTime = new Date(event.end_time);
  const durationMs = endTime - startTime;
  const durationMin = Math.round(durationMs / 60000);

  return {
    event_uri: event.uri,
    event_uuid: event.uri.split('/').pop(),
    name: event.name || 'Meeting',
    start_time: startTime.toLocaleString('en-US', {
      month: 'short', day: 'numeric',
      hour: '2-digit', minute: '2-digit'
    }),
    end_time: endTime.toLocaleString('en-US', {
      hour: '2-digit', minute: '2-digit'
    }),
    duration: `${durationMin} min`,
    status: event.status,
    event_type_name: event.event_type?.split('/').pop() || 'N/A',
    location: event.location?.location || event.location?.type || 'TBD',
    created_at: event.created_at
      ? new Date(event.created_at).toLocaleDateString()
      : 'N/A'
  };
});
```

**Expected result:** The events table shows scheduled meetings for the selected date range with formatted times, duration, status, and meeting type. The table refreshes when the date range picker changes.

### 3. Fetch invitee details for selected events

Calendly's /scheduled_events endpoint returns event metadata but not invitee information. To get who booked each meeting, you need to call /scheduled_events/{uuid}/invitees for each event.

Create a query named getEventInvitees. Set Method to GET, Path to /scheduled_events/{{ table_events.selectedRow.event_uuid }}/invitees.

This query runs automatically when a user selects a row in the events table (because it references table_events.selectedRow). Set the query trigger to 'Run on component change' for the table selection.

The invitees response returns a collection array where each invitee has:
- name, email: invitee contact info
- status: active or cancelled
- questions_and_answers: responses to custom intake questions you added to the event type
- timezone, created_at: booking context
- cancel_url, reschedule_url: action URLs if you want to add Cancel/Reschedule buttons

Display invitee details in a side panel or second Table below the events table. For meetings with multiple invitees (group events), the table shows all attendees.

Store the invitee's email to power cross-system lookups — use it to query your CRM or customer database for matching records.

```
// JavaScript transformer — format Calendly invitees for display
const invitees = (data?.collection || []);
return invitees.map(invitee => {
  // Extract answers to custom questions
  const answers = {};
  (invitee.questions_and_answers || []).forEach(qa => {
    const key = qa.question.toLowerCase().replace(/\s+/g, '_').slice(0, 30);
    answers[key] = qa.answer || '';
  });

  return {
    name: invitee.name || 'Unknown',
    email: invitee.email || 'N/A',
    status: invitee.status,
    timezone: invitee.timezone || 'N/A',
    booked_at: invitee.created_at
      ? new Date(invitee.created_at).toLocaleString()
      : 'N/A',
    // Custom question answers (varies by event type setup)
    ...answers,
    reschedule_url: invitee.reschedule_url || '',
    cancel_url: invitee.cancel_url || ''
  };
});
```

**Expected result:** Selecting a meeting in the events table triggers the invitees query and shows attendee details including name, email, timezone, booking time, and any custom question answers in a detail panel.

### 4. Combine Calendly data with CRM records

The most valuable Calendly-Retool integration is combining scheduling data with CRM context. When a sales rep sees a meeting in the Calendly table, they want to see the invitee's CRM record alongside the scheduling data.

Add a second resource to your Retool app — your CRM (HubSpot, Salesforce, Pipedrive) or PostgreSQL database. Create a query named getCRMContact that looks up the invitee's email after it's populated by the invitees query.

For a PostgreSQL database: SELECT id, company_name, deal_stage, arr, assigned_rep FROM contacts WHERE email = '{{ getEventInvitees.data?.[0]?.email }}'

For a HubSpot native connector: use the search endpoint to find a contact by email property.

Display the CRM data in a side panel with the invitee's deal stage, company, and any relevant account context. Add action buttons that trigger additional queries — 'Log Meeting' to write the meeting back to CRM, 'Create Follow-up Task' to add a task associated with the contact.

For complex sales operations panels that combine Calendly scheduling with CRM data, deal pipeline, and post-meeting task automation, RapidDev's team can help architect the complete Retool solution.

```
-- SQL query: getCRMContact
-- Look up CRM record by invitee email from Calendly
SELECT
  c.id,
  c.first_name,
  c.last_name,
  c.company_name,
  c.deal_stage,
  c.annual_revenue,
  c.assigned_rep,
  c.last_contact_date,
  c.notes
FROM contacts c
WHERE LOWER(c.email) = LOWER('{{ getEventInvitees.data?.[0]?.email }}')
LIMIT 1;
```

**Expected result:** After selecting a meeting and seeing invitee details, the CRM panel automatically loads the matching contact record with deal stage, company info, and account context — giving the rep all the context they need before the meeting.

### 5. Build scheduling metrics and event type reporting

Add reporting functionality to understand scheduling trends. Create a query to fetch all event types: getEventTypes (GET, /event_types with user={{ variable_userUri.value }}).

Event types represent your different Calendly booking pages — Discovery Call (30 min), Demo (45 min), Follow-up (15 min), etc. Each has a uri, name, duration, and scheduling_url.

Create a metrics transformer that aggregates the scheduled events data by event type to show booking counts:

Display a bar Chart showing bookings per event type for the selected date range. Add a metrics table showing each event type's total bookings, average lead time (days from booking to meeting), and cancellation rate.

Calculate average lead time from the scheduled events: time between created_at (when it was booked) and start_time (when the meeting is). High lead times may indicate booking too far in advance; very low lead times indicate last-minute bookings.

Add a week-over-week comparison by creating a second events query for the previous period. Display percentage change in booking volume with up/down arrows using conditional formatting in Retool's Statistic components.

```
// JavaScript transformer — calculate booking metrics by event type
const events = getScheduledEvents.data || [];
const eventTypes = getEventTypes.data?.collection || [];

// Build event type name map from URIs
const typeMap = {};
eventTypes.forEach(et => {
  typeMap[et.uri] = et.name;
});

// Group events by event type
const grouped = {};
events.forEach(event => {
  const typeName = typeMap[event.event_type_name] || 'Unknown';
  if (!grouped[typeName]) {
    grouped[typeName] = { count: 0, cancelled: 0, lead_times: [] };
  }
  grouped[typeName].count++;
  if (event.status === 'cancelled') grouped[typeName].cancelled++;
  const leadTime = (new Date(event.start_time) - new Date(event.created_at)) / 86400000;
  if (leadTime > 0) grouped[typeName].lead_times.push(leadTime);
});

// Convert to array for display
return Object.entries(grouped).map(([name, stats]) => ({
  event_type: name,
  total_bookings: stats.count,
  cancellations: stats.cancelled,
  cancel_rate: stats.count > 0
    ? `${((stats.cancelled / stats.count) * 100).toFixed(1)}%`
    : '0%',
  avg_lead_days: stats.lead_times.length > 0
    ? (stats.lead_times.reduce((a, b) => a + b, 0) / stats.lead_times.length).toFixed(1)
    : 'N/A'
}));
```

**Expected result:** The event type metrics table shows booking counts, cancellation rates, and average lead times per event type. The bar chart visualizes booking distribution across event types for the selected period.

## Best practices

- Store your Calendly personal access token as a Secret configuration variable — never paste it directly into query header fields.
- Fetch and store the user URI from /users/me when your Retool app first loads rather than hardcoding it — Calendly user URIs are stable but using a dynamic lookup makes the app portable across different Calendly accounts.
- Always include a status parameter in event queries — filtering to 'active' events by default prevents cancelled meetings from cluttering the view unless explicitly needed.
- Use cursor-based pagination for large event exports — Calendly returns a next_page_token for pages beyond the first, which you need to implement for complete data exports.
- Combine invitee email data with your CRM in the same Retool app to give sales teams full context on who they're meeting with without requiring them to switch tools.
- Track custom intake question responses by event type — questions like 'Company name' or 'What's your use case?' provide valuable pre-meeting context visible in Retool even before opening the Calendly event.
- Add cancellation and reschedule URL buttons for each invitee row to give operations teams the ability to manage scheduling exceptions from Retool.
- Cache event type definitions (which change rarely) in a Retool Variable on page load rather than re-fetching them on every query cycle.

## Use cases

### Build a sales team meeting tracker

Create a Retool dashboard showing all upcoming and recent Calendly meetings across your sales team. Filter by date range, event type, and rep. Show invitee name, email, meeting time, and a button to view the invitee's record in Salesforce. Give sales managers a single-page view of scheduling activity.

Prompt example:

```
Build a sales meeting tracker that shows all scheduled_events from Calendly for the next 7 days, displays invitee name, email, event type, and scheduled time in a Table, and includes a 'Find in Salesforce' button that opens a Salesforce contact search for the invitee's email.
```

### Event type performance dashboard

Build a dashboard that shows booking metrics by event type — how many times each event type was booked in the last 30 days, average lead time from booking to meeting, and cancellation rates. Help operations and marketing teams understand which Calendly pages are driving the most engagement.

Prompt example:

```
Create an event type analytics panel that fetches all event_types and scheduled_events, uses a JavaScript transformer to count events per event type for the selected date range, and shows a bar chart of bookings by event type alongside cancellation counts.
```

### Invitee data export and CRM enrichment tool

Build an operations tool that exports all Calendly invitees from a selected event type and date range as a structured dataset. Add buttons to push invitees to a PostgreSQL table or check if they already exist in your CRM. Automate the manual process of pulling Calendly data into other systems.

Prompt example:

```
Build an invitee export panel where selecting an event type and date range fetches all invitees from /scheduled_events with their contact details, checks each email against a PostgreSQL contacts table, and provides 'Add to CRM' buttons for invitees not yet in the database.
```

## Troubleshooting

### API returns 400 Bad Request — 'Required string parameter was not given a value for user'

Cause: Calendly's /scheduled_events endpoint requires either a user or organization parameter. The query is missing the user URI parameter, which must be the full Calendly user URI (not just an ID).

Solution: First run GET /users/me to get your user URI. It looks like https://api.calendly.com/users/XXXXXXXX. Store this in a Retool Variable named calendlyUserUri and pass it as the user URL parameter in all event queries. The full URI (not just the ID suffix) is required.

### Events query returns an empty collection even though there are scheduled meetings

Cause: The date range parameters are in the wrong format. Calendly requires ISO 8601 format with timezone offset or Z suffix. Using date-only strings (YYYY-MM-DD) without time and timezone causes the filter to fail.

Solution: Use moment.js to format dates with timezone: moment(dateRange.value[0]).toISOString() produces the correct format (e.g., 2024-01-15T00:00:00.000Z). Ensure the dateRange picker is correctly initialized with date objects, not strings.

```
// Correct date format for Calendly URL parameters
// min_start_time value:
{{ moment(dateRange.value[0]).startOf('day').toISOString() }}
// max_start_time value:
{{ moment(dateRange.value[1]).endOf('day').toISOString() }}
```

### Invitee name and email show as null in the transformer output

Cause: The response from /scheduled_events/{uuid}/invitees was not yet available when the transformer ran, or the collection array path is incorrect. Calendly wraps results in a collection key.

Solution: Ensure the transformer accesses data?.collection (not data?.data or data directly). Add a null check: if (!data?.collection) return []. Verify the query is targeting the correct path and that the event UUID in the query path is correctly extracted from the selected row.

```
// Safe data access in invitees transformer
const invitees = data?.collection || [];
if (invitees.length === 0) return [];
// ... rest of transformer
```

## Frequently asked questions

### Does Calendly have a native connector in Retool?

Calendly does not have a native Retool connector — you connect it via a REST API Resource with Bearer token authentication. Calendly's v2 API covers all the main operations: events, invitees, event types, and organization data. The personal access token approach is straightforward and doesn't require setting up OAuth flows.

### What Calendly plan do I need to use the API?

Calendly API access requires a Professional plan or higher (paid plans). The free Calendly plan does not include API access. Once on a paid plan, generate a personal access token from your Calendly account under Integrations → API & Webhooks. Organization-level access requires an Enterprise plan to query all organization members' events.

### How do I get scheduling data for my entire team, not just my own calendar?

To access your whole organization's events, first get your organization URI from GET /users/me (look for the current_organization field). Then use the organization parameter instead of user in your /scheduled_events queries: add URL parameter organization={{ variable_orgUri.value }}. This requires admin-level API access to your Calendly organization, which must be enabled in your organization settings.

### Can I receive Calendly webhooks in Retool?

You can receive Calendly webhooks in Retool Workflows. Create a Retool Workflow with a Webhook trigger — the generated webhook URL can be configured as a Calendly webhook endpoint in Settings → Integrations → Webhooks. Calendly sends event payloads when meetings are created, cancelled, or rescheduled. This lets you trigger workflows in real-time rather than polling the API.

---

Source: https://www.rapidevelopers.com/retool-integrations/calendly
© RapidDev — https://www.rapidevelopers.com/retool-integrations/calendly
