Doodle's API is restricted to enterprise partners and not available to independent developers. The practical alternatives for group scheduling in Bolt.new are: embed Doodle polls using their JavaScript widget, build a custom scheduling poll with Supabase (participants vote on time slots, app calculates optimal time), or use Calendly's fully open REST API for availability-based meeting booking. For most Bolt use cases, building a custom poll with Supabase is the most practical path.
Group Scheduling in Bolt.new: Doodle Alternatives and Custom Poll Builds
Doodle is a popular group scheduling tool, but its API access is reserved for enterprise-level integrations and is not open to independent developers building web applications. There is no public API documentation, no developer console for generating API keys, and no OAuth flow available on Doodle's standard or premium plans. If you encounter any third-party claims about a Doodle API, they reference an older beta that was discontinued.
That said, there are three practical approaches for group scheduling in Bolt.new, each suited for different use cases. The simplest is to embed Doodle's own scheduling poll using their JavaScript widget — you create the poll manually on Doodle's website and embed it in your Bolt app with a script tag. This requires no API key and works in Bolt's WebContainer preview. The limitation is you cannot programmatically create polls or retrieve vote results from your app's backend.
For full control — creating polls programmatically, collecting participant votes, calculating optimal times, and storing results in your own database — building a custom group scheduling feature with Supabase is the most capable approach. You design the data model, control the user experience, and own all the scheduling data. This is a weekend project worth the investment for apps where scheduling is a core feature. The custom approach also gives you complete freedom over the UI design, notification system, and how results are displayed and acted upon.
Integration method
Doodle's API is not publicly available for independent developers, so direct integration through API routes is not possible. The practical alternatives are embedding Doodle polls via their JavaScript widget (no API key required), building a custom group scheduling poll in Bolt with Supabase for full API control, or using Calendly's open REST API for booking-based scheduling.
Prerequisites
- A Doodle account for creating polls to embed (free account is sufficient for embedding)
- For the Supabase custom poll approach: a Supabase project with your project URL and anon key
- For the Calendly API approach: a Calendly account with API access and a personal access token
- A Next.js project in Bolt.new (prompt 'Create a Next.js app' to get started)
Step-by-step guide
Understand Doodle's API limitations and choose the right approach
Understand Doodle's API limitations and choose the right approach
Before investing time in integration work, it is important to understand exactly what is and is not available with Doodle for independent developers. Doodle's API documentation at developer.doodle.com describes a REST API, but this API is not open for independent developers — it requires an enterprise partnership agreement with Doodle's business team. Individual developers cannot register for API access, generate API keys, or test the API endpoints. The features described in Doodle's API documentation (creating polls, fetching participants, retrieving votes) are not accessible to apps built on Bolt.new. This is a deliberate business decision by Doodle to protect their scheduling data and avoid commoditization of their core product. For Bolt.new integrations, there are three viable paths, each with different trade-offs. The embed approach is zero-effort: create the poll manually on Doodle, copy the embed code or URL, and include it in your app as an iFrame. Participants vote in the embedded Doodle interface, but your app has no access to the vote data. This works for informational pages where you just want to display a scheduling poll. The Supabase custom poll approach requires the most upfront work but gives you full ownership of the feature — create polls programmatically, store votes in your own database, calculate results, send notifications, and integrate with your app's user authentication. This is the recommended path when scheduling is a core feature of your app and you need complete control. The Calendly API approach is the best choice when the scheduling model is one-on-one appointment booking (someone books time on your calendar) rather than group consensus polling. Calendly has a fully open REST API with personal access tokens, making it straightforward to integrate from Bolt.
Create a scheduling feature comparison page in my app that shows three options: (1) Embed Doodle poll (manual setup, no API key needed, no data access), (2) Custom poll with Supabase (full control, requires backend setup), (3) Calendly API (one-on-one booking, open API). Display each option as a card with pros and cons. Add buttons linking to the setup documentation for each option.
Paste this in Bolt.new chat
1// .env.local — for Supabase custom poll approach2NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url3NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key45// For Calendly API approach (added in later steps)6CALENDLY_ACCESS_TOKEN=your_calendly_personal_access_token7CALENDLY_EVENT_TYPE_UUID=your_event_type_uuidPro tip: If your primary need is consensus-style group scheduling (finding a time that works for multiple people), build the custom Supabase poll. If you need booking-style scheduling (someone books a slot on your calendar), use Calendly. The two use cases are fundamentally different.
Expected result: You have a clear understanding of which scheduling approach fits your use case and the environment variables configured for the approach you chose.
Build a custom group scheduling poll with Supabase
Build a custom group scheduling poll with Supabase
A custom scheduling poll needs two database tables: one for polls (with the organizer's details, the list of candidate time slots, and expiration date) and one for votes (recording each participant's name, email, and the slots they selected as available). The time slots are stored as a JSONB array in Supabase, where each slot is an object with a unique ID, a label string, and an ISO datetime value. The poll creation flow: the organizer fills out a form with a poll title, their name, and the candidate time slots (as many as 10 is a reasonable limit). Your app creates a poll record in Supabase and returns the poll ID. The organizer shares a link like your-app.com/schedule/[pollId] with participants. The voting flow: participants open the shared link, see the list of candidate time slots, and check the slots that work for them. They enter their name and email for identification. Your app inserts a vote record linking their selections to the poll. The schema uses a text array for available_slots to store the slot IDs the participant selected. The results flow: once enough participants have voted, the organizer opens the poll URL and sees a results view — each time slot with a count of how many participants selected it, with the highest-vote slot highlighted. Optionally, the organizer clicks 'Confirm meeting time' which marks the poll as decided and sends confirmation emails. Because Supabase uses HTTP-based communication (via the PostgREST API), the entire custom poll feature works in Bolt's WebContainer without any deployment needed during development.
Build a complete group scheduling poll app with Supabase. Create tables: scheduling_polls (id uuid primary key, organizer_name text, organizer_email text, title text, description text, time_slots jsonb, status text default 'open', created_at timestamptz default now(), expires_at timestamptz) and poll_votes (id uuid primary key, poll_id uuid references scheduling_polls, participant_name text, participant_email text, available_slots text[], voted_at timestamptz default now()). Enable RLS with public read access on both tables for participants to vote without logging in. Build the poll creation form, voting page at /poll/[id], and results view showing vote counts per slot.
Paste this in Bolt.new chat
1-- Supabase SQL: Group scheduling tables2CREATE TABLE scheduling_polls (3 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,4 organizer_name TEXT NOT NULL,5 organizer_email TEXT NOT NULL,6 title TEXT NOT NULL,7 description TEXT,8 time_slots JSONB NOT NULL, -- [{id: 'slot1', label: 'Mon Jun 9, 2pm', datetime: '2026-06-09T14:00:00Z'}]9 status TEXT DEFAULT 'open' CHECK (status IN ('open', 'closed', 'decided')),10 decided_slot_id TEXT,11 created_at TIMESTAMPTZ DEFAULT now(),12 expires_at TIMESTAMPTZ13);1415CREATE TABLE poll_votes (16 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,17 poll_id UUID REFERENCES scheduling_polls(id) ON DELETE CASCADE NOT NULL,18 participant_name TEXT NOT NULL,19 participant_email TEXT NOT NULL,20 available_slots TEXT[] NOT NULL, -- Array of slot IDs the participant is available for21 voted_at TIMESTAMPTZ DEFAULT now(),22 UNIQUE(poll_id, participant_email) -- One vote per participant per poll23);2425-- RLS: Anyone can read polls and votes (participants need no account)26ALTER TABLE scheduling_polls ENABLE ROW LEVEL SECURITY;27ALTER TABLE poll_votes ENABLE ROW LEVEL SECURITY;2829CREATE POLICY "Public read polls" ON scheduling_polls FOR SELECT USING (true);30CREATE POLICY "Public insert polls" ON scheduling_polls FOR INSERT WITH CHECK (true);31CREATE POLICY "Public read votes" ON poll_votes FOR SELECT USING (true);32CREATE POLICY "Public insert votes" ON poll_votes FOR INSERT WITH CHECK (true);Pro tip: The UNIQUE constraint on (poll_id, participant_email) prevents a participant from voting twice on the same poll. When a participant submits their vote, upsert (insert or update) based on this constraint to allow them to change their selections.
Expected result: The Supabase tables are created, and the poll creation form generates polls with shareable URLs. The voting page shows candidate time slots with checkboxes. The results page shows vote counts per slot with the most-voted slot highlighted.
Calculate optimal time slots and display poll results
Calculate optimal time slots and display poll results
With votes stored in Supabase, calculating the optimal meeting time is straightforward: count how many votes included each slot ID, then rank the slots by vote count. The slot with the highest count is the best meeting time. In case of a tie, secondary sorting by earlier datetime is a sensible tiebreaker. The results calculation happens client-side using the data fetched from Supabase. Fetch all votes for a poll using the Supabase JavaScript client: supabase.from('poll_votes').select('available_slots, participant_name').eq('poll_id', pollId). Then iterate through votes and count occurrences of each slot ID. Display the results as a ranked list of time slots with: the slot label, a count like '4 of 6 participants available', a visual progress bar showing the percentage, and a 'Best' badge on the top-ranked slot. For slots where everyone is available, show an 'All available' indicator. For sending confirmation notifications when the organizer decides on a time, use the SendGrid or Resend API from a server-side API route. Send an email to each participant with the confirmed meeting time. This requires a deployed app (outbound email works from the WebContainer during development, but production use should go through a proper transactional email service). The UI should also show who voted and which slots they are available for — this social proof helps participants coordinate and sometimes prompts people who have not voted to do so.
Build a poll results component for my scheduling app. Fetch all votes for a poll from Supabase. Calculate the availability count for each time slot (count how many participant available_slots arrays include that slot ID). Display slots ranked by availability count with: slot label, 'X of Y participants available', a progress bar, and a 'Best Time' badge for the top slot. Add a 'Confirm this time' button visible only when accessed with an organizer token, which updates the poll status to 'decided' and sets decided_slot_id.
Paste this in Bolt.new chat
1// lib/scheduling/calculateResults.ts2type TimeSlot = { id: string; label: string; datetime: string };3type PollVote = { participant_name: string; available_slots: string[] };45export type SlotResult = {6 slot: TimeSlot;7 availableCount: number;8 totalVotes: number;9 availabilityPercent: number;10 participants: string[];11 isBest: boolean;12};1314export function calculateSlotResults(15 timeSlots: TimeSlot[],16 votes: PollVote[]17): SlotResult[] {18 const totalVotes = votes.length;1920 const results = timeSlots.map((slot) => {21 const availableVotes = votes.filter((v) =>22 v.available_slots.includes(slot.id)23 );24 return {25 slot,26 availableCount: availableVotes.length,27 totalVotes,28 availabilityPercent:29 totalVotes > 0 ? Math.round((availableVotes.length / totalVotes) * 100) : 0,30 participants: availableVotes.map((v) => v.participant_name),31 isBest: false,32 };33 });3435 // Sort by availability count desc, then by datetime asc for tiebreaker36 results.sort((a, b) => {37 if (b.availableCount !== a.availableCount) return b.availableCount - a.availableCount;38 return new Date(a.slot.datetime).getTime() - new Date(b.slot.datetime).getTime();39 });4041 // Mark the best slot42 if (results.length > 0 && results[0].availableCount > 0) {43 results[0].isBest = true;44 }4546 return results;47}Pro tip: Use the UNIQUE constraint on (poll_id, participant_email) with an upsert operation so participants can update their availability if they change their mind. Pass onConflict: 'participant_email' when inserting votes with the Supabase client.
Expected result: The poll results page shows all time slots ranked by availability with progress bars and participant names. The best time slot is highlighted with a badge. The organizer can confirm a time which updates the poll status.
Deploy the scheduling app and optionally integrate Calendly as an alternative
Deploy the scheduling app and optionally integrate Calendly as an alternative
Deploying your custom scheduling app follows the standard Bolt workflow. Click the Publish button in Bolt's top-right corner to deploy to Netlify with a .netlify.app URL. After deployment, add your Supabase environment variables in Netlify's Site Settings → Environment Variables: NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. These are safe to use with the NEXT_PUBLIC_ prefix since Supabase anon keys are designed to be public — Row Level Security handles authorization at the database level. For participants accessing the poll voting page, no account or authentication is required. The RLS policies set in step 2 allow public read and insert access on both tables, so anyone with the poll link can vote. If you want to restrict who can create new polls (to prevent spam), add authentication to the poll creation form using Supabase Auth while keeping the voting page public. As an alternative to the custom poll build, Calendly provides the best open API in the scheduling space. If your use case is appointment booking (one person books available time on another person's calendar) rather than consensus-style group scheduling, Calendly's REST API is excellent. Get a personal access token from developer.calendly.com and use it to: retrieve available event types, generate one-time scheduling links, and receive webhooks when appointments are booked. The Calendly API webhook notifies your server when a new invitee is created (someone books a meeting) or when an event is canceled. Like all webhooks, this requires a deployed public URL — Bolt's WebContainer cannot receive incoming webhook requests during development. Deploy first, then register your webhook URL in the Calendly developer console with the events you want to subscribe to: invitee.created and invitee.canceled.
Add Calendly integration to my scheduling page as an alternative to the custom poll. Build a /api/calendly/create-link route that calls the Calendly API to generate a single-use scheduling link for an event type. Accept eventTypeUuid from env var. Display the Calendly scheduling link as a 'Book a time' button that opens in a new tab. Also add a Calendly webhook handler at /api/calendly/webhook that logs when someone books or cancels a meeting.
Paste this in Bolt.new chat
1// app/api/calendly/create-link/route.ts2import { NextResponse } from 'next/server';34const CALENDLY_API = 'https://api.calendly.com';5const ACCESS_TOKEN = process.env.CALENDLY_ACCESS_TOKEN;6const EVENT_TYPE_UUID = process.env.CALENDLY_EVENT_TYPE_UUID;78export async function POST() {9 // First, get the current user's URI (needed for single-use links)10 const meRes = await fetch(`${CALENDLY_API}/users/me`, {11 headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },12 });1314 if (!meRes.ok) {15 return NextResponse.json({ error: 'Calendly auth failed' }, { status: 401 });16 }1718 const { resource: user } = await meRes.json();1920 // Create a single-use scheduling link21 const linkRes = await fetch(`${CALENDLY_API}/scheduling_links`, {22 method: 'POST',23 headers: {24 Authorization: `Bearer ${ACCESS_TOKEN}`,25 'Content-Type': 'application/json',26 },27 body: JSON.stringify({28 max_event_count: 1, // Single-use link29 owner: `${CALENDLY_API}/event_types/${EVENT_TYPE_UUID}`,30 owner_type: 'EventType',31 }),32 });3334 if (!linkRes.ok) {35 const error = await linkRes.json();36 return NextResponse.json({ error }, { status: linkRes.status });37 }3839 const { resource } = await linkRes.json();40 return NextResponse.json({ schedulingUrl: resource.booking_url });41}Pro tip: Calendly single-use scheduling links (max_event_count: 1) expire after one booking. For reusable links, omit the max_event_count field. Reusable links point to your standard availability calendar, while single-use links are ideal for sales outreach where you want each link to book exactly one meeting.
Expected result: The deployed app is accessible at a public URL with the custom group scheduling poll fully functional. The optional Calendly integration generates scheduling links that allow one-click meeting booking from your app.
Common use cases
Embed a Doodle poll inside a Bolt app page
Create a Doodle scheduling poll manually on doodle.com, then embed it directly inside a Bolt app page using Doodle's iFrame or link component. Visitors can vote on time slots without leaving your app. This approach requires no API key and works in development.
Add a Doodle poll embed to my meeting scheduling page. Create a React component called DoodlePoll that accepts a doodleUrl prop (the full Doodle poll URL). Render the poll in an iFrame with full width and 600px height. Add a fallback link below the iframe that opens the poll in a new tab for mobile users where iframes may not render well.
Copy this prompt to try it in Bolt.new
Custom group scheduling poll with Supabase
Build a full group scheduling feature where an organizer creates a poll with candidate time slots, shares a link with participants, participants vote on their availability, and the app automatically highlights the time slot with the most availability.
Build a group scheduling app with Supabase. Create two tables: scheduling_polls (id, organizer_email, title, description, time_slots jsonb array, created_at, expires_at) and poll_votes (id, poll_id, participant_name, participant_email, available_slots text array, voted_at). Build: (1) a poll creation form where organizer enters title and adds up to 10 candidate time slots, (2) a participant voting page at /poll/[id] where they check which slots work for them, (3) a results page showing vote counts per slot with the best time highlighted in green.
Copy this prompt to try it in Bolt.new
Automated meeting scheduling from a contact form
When a website visitor submits a contact or demo request form, automatically create a Calendly scheduling link using the Calendly API and include it in the confirmation email, allowing prospects to book immediately without manual follow-up from the sales team.
Add Calendly integration to my demo request form. When a visitor submits with their name and email, call /api/calendly/create-invite to generate a one-time scheduling link using the Calendly API. Return the scheduling URL in the form success message: 'Thanks! Book your 30-minute demo here: [link]'. Use event type slug from CALENDLY_EVENT_TYPE env var.
Copy this prompt to try it in Bolt.new
Troubleshooting
Participants see 'Could not load poll' when accessing the voting page
Cause: The Supabase RLS policy for public read access is not configured correctly, the poll ID in the URL is invalid, or the Supabase environment variables are missing.
Solution: Verify your NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are set correctly in .env.local. In Supabase, check that the scheduling_polls table has a public SELECT policy (USING (true)). Test the query directly in the Supabase SQL editor: SELECT * FROM scheduling_polls WHERE id = 'your-poll-id' to confirm the poll exists and is accessible.
1-- If RLS is blocking public reads, check policies:2SELECT * FROM pg_policies WHERE tablename = 'scheduling_polls';34-- Add public read policy if missing:5CREATE POLICY "Public read polls" ON scheduling_polls FOR SELECT USING (true);Votes are not being saved — form submits successfully but no vote appears in Supabase
Cause: The Supabase RLS INSERT policy is missing or incorrectly configured for anonymous users, the poll ID being inserted does not match any record in scheduling_polls, or the available_slots array is empty (the participant did not select any time slots).
Solution: Check the Supabase RLS policies for poll_votes — confirm there is an INSERT policy WITH CHECK (true). Verify the poll_id being inserted matches an existing poll. Add validation to the voting form to require at least one time slot to be selected before submission.
1// Add validation before submitting votes:2if (selectedSlots.length === 0) {3 setError('Please select at least one time that works for you.');4 return;5}67// Use upsert to handle re-voting by same email:8const { error } = await supabase9 .from('poll_votes')10 .upsert(11 { poll_id: pollId, participant_name: name, participant_email: email, available_slots: selectedSlots },12 { onConflict: 'poll_id, participant_email' }13 );Calendly API calls return 401 Unauthorized
Cause: The Calendly personal access token is invalid, expired, or not included in the correct Bearer token format. Calendly access tokens can expire and need regeneration.
Solution: Verify your CALENDLY_ACCESS_TOKEN in .env.local. Test the token by calling GET https://api.calendly.com/users/me with it. If expired, generate a new token from developer.calendly.com. Ensure the Authorization header uses the exact format: Authorization: `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`.
Best practices
- Be transparent with users when you build a custom scheduling poll instead of using Doodle — label it clearly as your app's scheduling feature rather than implying Doodle integration that does not exist.
- Add poll expiration dates and a cleanup process to delete expired polls and their votes from Supabase — scheduling polls are temporal and accumulate database rows quickly without cleanup.
- Use email-based participant identification with the UNIQUE constraint to allow participants to update their votes, since availability often changes between poll creation and the deadline.
- Send reminder emails 24 hours before poll expiration to participants who have not voted yet — this significantly improves response rates and produces more useful results.
- For the Calendly integration, never expose your personal access token client-side — all Calendly API calls must go through server-side Next.js routes where the token is accessed via process.env.
- Consider adding a maximum participant limit to prevent spam voting on public poll URLs — a limit of 50 participants is reasonable for most group scheduling use cases.
- Cache the Calendly event type UUID rather than fetching event types on every scheduling link request — event types rarely change and an extra API call per link generation adds unnecessary latency.
Alternatives
Calendly has a fully open REST API for appointment booking workflows where someone books time on your calendar, while Doodle focuses on consensus-style group scheduling polls — different use cases but Calendly is the better API integration choice.
Acuity provides a booking and appointment scheduling API with client management features, better for service businesses that need a full appointment platform beyond Doodle's poll-based approach.
Google Meet's Calendar API can create and manage meeting invites programmatically, complementing a custom scheduling poll by automatically creating the meeting link once a time is confirmed.
Zoom's REST API can create meetings and generate join links automatically when a meeting time is confirmed, serving as the conferencing layer after group scheduling determines the best time.
Frequently asked questions
Does Doodle have a public API I can use in Bolt.new?
No. Doodle's API is restricted to enterprise partnership integrations and is not available to independent developers. There is no developer console, no API key registration, and no documentation for public use. The practical alternatives are to embed Doodle polls using their iFrame widget, build a custom scheduling poll with Supabase, or use Calendly's fully open REST API.
How can I embed a Doodle poll in my Bolt app?
Create the poll manually on doodle.com and copy the poll URL. In your Bolt app, render the Doodle poll in an iFrame with the URL as the src. Doodle polls are designed to be embedded and work without any API key. Note that your app cannot access vote data from the embedded poll — you only display the Doodle interface within your app.
How do I build a custom group scheduling poll in Bolt.new?
Use Supabase to create two tables: one for polls (storing the organizer details and candidate time slots as JSONB) and one for votes (storing each participant's selected slots). Build a poll creation form, a participant voting page, and a results view that counts votes per slot and highlights the best time. Enable public RLS policies so participants can vote without creating an account. This entire feature works in Bolt's WebContainer during development.
Is Calendly a good alternative to Doodle for Bolt.new?
Calendly is excellent for appointment booking use cases — someone books a specific time slot on your available calendar. It has a fully open REST API with personal access tokens, allowing you to generate scheduling links, receive booking webhooks, and manage event types programmatically. However, Calendly is designed for one-on-one or structured booking, not group consensus scheduling. If you need the 'best time for the whole group' model, build a custom poll with Supabase instead.
Can I test the Supabase scheduling poll in Bolt's preview before deploying?
Yes. The entire custom scheduling poll — creating polls, submitting votes, fetching results, calculating optimal slots — uses Supabase's HTTP-based API and works perfectly in Bolt's WebContainer preview. You can run the full end-to-end scheduling flow in development. The only feature requiring a deployed URL would be webhook notifications from external services, but the core scheduling functionality needs no deployment.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation