# How to Integrate Doodle with V0

- Tool: V0
- Difficulty: Intermediate
- Time required: 60 minutes
- Last updated: March 2026

## TL;DR

To integrate Doodle with a V0 by Vercel app, create a Next.js API route that calls the Doodle REST API v2 using OAuth2 Bearer token authentication. Generate your scheduling interface with V0, then connect it to API routes that create polls, fetch poll results, and display participant availability. Store your Doodle API credentials in Vercel environment variables to keep them secure.

## Add Meeting Scheduling Polls to Your V0 App with the Doodle API

Doodle is the go-to tool for finding meeting times when multiple people need to align on availability. With millions of polls created every month, Doodle has built-in familiarity — participants know how to use it without instructions. Integrating Doodle into your V0 app lets you embed scheduling capabilities directly in your product without building a full calendar availability system from scratch.

The integration works in two directions: creating polls (for app users who want to schedule a meeting with their clients or team) and displaying poll results (showing which times have the most availability). Common use cases include embedding a 'Schedule a Demo' flow where leads can propose meeting times, a team coordinator tool that creates polls for recurring sync meetings, and client portals where project kickoffs are scheduled without email back-and-forth.

Doodle's API v2 provides endpoints for creating polls with proposed time slots, updating poll options, fetching participant responses, and marking the winning time slot as the final meeting time. Access requires registering as a Doodle partner to get API credentials — this is a formal application process, though Doodle does provide sandbox access for development.

## Before you start

- A Doodle account and API credentials — apply for developer access at doodle.com/api or contact Doodle's partner program (standard Doodle accounts cannot access the API directly)
- Doodle OAuth2 client ID and client secret from your registered application
- Understanding that Doodle's API is gated behind a partner program — sandbox access is available during development
- A V0 project exported to GitHub and deployed on Vercel
- Basic understanding of Next.js API routes and OAuth2 flows

## Step-by-step guide

### 1. Generate the Scheduling UI with V0

Before working with the Doodle API, use V0 to generate the scheduling interface. Scheduling UIs have specific interaction patterns — time slot selection grids, participant availability matrices, and confirmation flows — that V0 handles well when described clearly.

For a poll creation flow, you need: a step to define the meeting title and duration, a step to select proposed time slots (typically a calendar with clickable time blocks), and a step to add participant email addresses. For a poll results view, you need: a grid showing time slots as columns and participant names as rows, with color-coded cells (green = available, red = unavailable, yellow = if need be), and a highlighted best time slot.

Ask V0 to generate the time slot selection UI as a responsive calendar grid. Describe the interaction: clicking a date expands time slot options for that day, clicking a time slot toggles it as proposed. Show selected slots in a summary list on the right side. For mobile, collapse the calendar to a vertical list of date + time options.

For the availability matrix (showing poll results), a CSS grid or table with color-coded cells works well. Ask V0 to generate this with a 'Most available' badge on the time slot with the highest participant count. The participant names column should be sticky on horizontal scroll for wide tables with many time slots.

**Expected result:** A multi-step scheduling wizard UI with time slot selection calendar, participant management, and a poll results availability matrix, all using placeholder data.

### 2. Authenticate with the Doodle API and Create the API Route

Create a Next.js API route at `app/api/doodle/route.ts` that handles Doodle API authentication and proxies poll operations. Doodle uses OAuth2 client credentials flow for server-to-server authentication — exchange your client ID and secret for an access token, then use that token for API calls.

Doodle's token endpoint is `https://api.doodle.com/oauth/token` with parameters: `grant_type=client_credentials`, `client_id={your_id}`, `client_secret={your_secret}`. The token response includes `access_token` (valid for a limited time, typically 1 hour) and `token_type: Bearer`.

Doodle's REST API v2 base URL is `https://api.doodle.com/api/v2/`. Key endpoints:
- `POST /polls` — create a new poll with title, options (time slots), and invitee emails
- `GET /polls/{pollId}` — fetch poll details including all options and participant responses
- `PUT /polls/{pollId}/participants/{participantId}` — update participant availability
- `POST /polls/{pollId}/book` — mark a time slot as the confirmed booking

The poll creation payload requires a `type` (standard, book, or signup), `title`, `options` (array of proposed time slots as ISO datetime strings), and `initiator` (name/email of poll creator). Time slot options are structured as `{ start: ISO_datetime, end: ISO_datetime }` objects.

Cache the access token with expiry tracking to avoid refreshing it on every API call. Use a module-level variable similar to the Zoho CRM pattern — store the token and its expiry timestamp, check if it's still valid before making a new token request.

Note: if you don't yet have Doodle API credentials and are in development, implement mock responses that match the Doodle API response format. This lets you build and test the UI integration before API access is provisioned.

```
// app/api/doodle/route.ts
import { NextRequest, NextResponse } from 'next/server';

const DOODLE_BASE = 'https://api.doodle.com/api/v2';
const DOODLE_TOKEN_URL = 'https://api.doodle.com/oauth/token';
const CLIENT_ID = process.env.DOODLE_CLIENT_ID!;
const CLIENT_SECRET = process.env.DOODLE_CLIENT_SECRET!;

let tokenCache: { token: string; expiresAt: number } | null = null;

async function getToken(): Promise<string> {
  if (tokenCache && Date.now() < tokenCache.expiresAt - 60_000) {
    return tokenCache.token;
  }
  const res = await fetch(DOODLE_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    }),
  });
  if (!res.ok) throw new Error(`Doodle token error: ${res.status}`);
  const data = await res.json();
  tokenCache = {
    token: data.access_token,
    expiresAt: Date.now() + (data.expires_in || 3600) * 1000,
  };
  return data.access_token;
}

async function doodleFetch(method: string, path: string, body?: object) {
  const token = await getToken();
  const res = await fetch(`${DOODLE_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) {
    const error = await res.text();
    throw new Error(`Doodle API error ${res.status}: ${error}`);
  }
  return res.json();
}

export async function POST(request: NextRequest) {
  const { action, ...data } = await request.json();

  try {
    if (action === 'create_poll') {
      const poll = await doodleFetch('POST', '/polls', {
        title: data.title,
        type: 'standard',
        options: data.options, // [{ start: ISO, end: ISO }]
        initiator: data.initiator, // { name, email }
        invitees: data.invitees?.map((email: string) => ({ email })) || [],
      });
      return NextResponse.json(poll);
    }
    return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
  } catch (err: any) {
    return NextResponse.json({ error: err.message }, { status: 500 });
  }
}

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const pollId = searchParams.get('pollId');

  if (!pollId) return NextResponse.json({ error: 'pollId required' }, { status: 400 });

  try {
    const poll = await doodleFetch('GET', `/polls/${pollId}`);
    return NextResponse.json(poll);
  } catch (err: any) {
    return NextResponse.json({ error: err.message }, { status: 500 });
  }
}
```

**Expected result:** The API route creates polls via POST and fetches poll details via GET, with OAuth2 token caching to avoid redundant token requests.

### 3. Configure Vercel Environment Variables

Navigate to your Vercel project → Settings → Environment Variables. Add the credentials for your Doodle API application.

Add `DOODLE_CLIENT_ID` — the Client ID from your registered Doodle API application. Doodle provides this when you register as a partner or receive sandbox access through their developer program.

Add `DOODLE_CLIENT_SECRET` — the Client Secret for your application. This is used with the Client ID to obtain OAuth2 access tokens. Store this as a server-only variable (no `NEXT_PUBLIC_` prefix) — it must never be exposed in the browser.

To get Doodle API credentials, visit Doodle's developer documentation at doodle.com/api or contact Doodle's partner team. Doodle's API is not open to general public sign-up — you need to register as a technology partner. For development, Doodle typically provides sandbox credentials with limited functionality for testing your integration before going live.

For Vercel Preview environments (used for pull request previews), use the same sandbox credentials. Only use production API credentials in the Production environment to avoid polluting your live account with test polls.

After adding the environment variables, trigger a redeployment and test the token acquisition by checking your server logs — a successful token response means the credentials are correct.

If you're building this integration before receiving Doodle API access, add placeholder values and implement a `DOODLE_DEMO_MODE=true` environment variable. When demo mode is on, your API route returns hardcoded mock data instead of calling the Doodle API.

**Expected result:** DOODLE_CLIENT_ID and DOODLE_CLIENT_SECRET set in Vercel environment variables. Token acquisition test passes in server logs on next deployment.

### 4. Connect the Scheduling Wizard to Create Real Polls

Wire the V0-generated scheduling wizard to your Doodle API route. When the user completes the final step of the wizard and clicks 'Create Poll', POST the collected data to `/api/doodle` with `action: 'create_poll'`.

The key data transformation is converting selected time slots from your UI's format to Doodle's ISO datetime format. Your UI likely stores selected slots as date strings and time ranges ('2026-04-15 afternoon'). Convert these to specific ISO start and end times: `{ start: '2026-04-15T14:00:00', end: '2026-04-15T16:00:00' }`.

After successful poll creation, Doodle returns the created poll object including a `participationUrl` — the link for participants to respond. Display this URL prominently as a copyable link. Also show a QR code of this URL using a library like `qrcode.react` so it can be shared in presentations or printed.

For displaying poll results, poll regularly using the `/api/doodle?pollId={id}` endpoint or use a 'refresh' button. Doodle's poll response object includes `participants` (array of respondents with their availability choices) and `options` (the time slots). Map this to your availability matrix component — create a 2D structure where rows are participants and columns are time slots.

To determine the 'best time', count votes per option and highlight the option with the most 'yes' responses. Handle ties by showing all tied options as equally highlighted. RapidDev's team can help implement more sophisticated scheduling logic like 'required attendees vs optional' if your use case requires it.

For the embedded Doodle approach (alternative to the custom UI): Doodle provides an embed URL format where you can iframe a Doodle poll directly into your page. The embed URL is `https://doodle.com/poll/{pollId}` in an iframe — this is simpler but gives you less control over the visual design.

```
// Helper: convert UI time selection to Doodle ISO format
export function slotsToISOOptions(
  selections: { date: string; hour: number; duration: number }[]
): { start: string; end: string }[] {
  return selections.map(({ date, hour, duration }) => {
    const start = new Date(`${date}T${hour.toString().padStart(2, '0')}:00:00`);
    const end = new Date(start.getTime() + duration * 60 * 1000);
    return {
      start: start.toISOString(),
      end: end.toISOString(),
    };
  });
}

// Helper: compute best time from poll response
export function getBestSlot(poll: {
  options: { id: string; start: string; end: string }[];
  participants: { preferences: { optionId: string; availability: string }[] }[];
}): string | null {
  const voteCounts: Record<string, number> = {};
  poll.options.forEach(opt => { voteCounts[opt.id] = 0; });
  poll.participants.forEach(p => {
    p.preferences.forEach(pref => {
      if (pref.availability === 'yes') voteCounts[pref.optionId] = (voteCounts[pref.optionId] || 0) + 1;
    });
  });
  const maxVotes = Math.max(...Object.values(voteCounts));
  if (maxVotes === 0) return null;
  return Object.entries(voteCounts).find(([, v]) => v === maxVotes)?.[0] || null;
}
```

**Expected result:** The scheduling wizard creates real Doodle polls on submission. The confirmation screen shows a working poll URL. The results view updates automatically every 30 seconds with participant responses.

## Best practices

- Cache OAuth2 access tokens in a module-level variable with expiry tracking to minimize token refresh overhead on repeated API calls
- Never expose Doodle client credentials in client-side code — all Doodle API calls must go through server-side Next.js API routes
- Implement a demo mode with mock poll data for development while waiting for Doodle API access to be provisioned
- Always validate time slots are in the future before sending to Doodle — past time slots cause validation errors
- Display poll expiry dates prominently — Doodle polls have a limited participation window and users need to know the deadline
- Provide a 'Copy link' button for the poll participation URL — email delivery of Doodle invites can be unreliable, direct sharing is more dependable
- Handle the case where no participants have responded yet — show an appropriate empty state rather than an empty matrix

## Use cases

### Sales Demo Scheduling Flow

Build a 'Schedule a Demo' page where prospects select their available times from a calendar, triggering a Doodle poll that syncs with the sales team's availability. The sales team responds in Doodle, and the app shows the confirmed meeting time to the prospect.

Prompt example:

```
Create a demo scheduling page with a 2-week calendar where users can click time slots to mark availability (morning, afternoon, evening segments). Show a summary of selected times with a 'Send Availability' button. On submit, display a confirmation message: 'Thanks! We'll confirm your demo within 24 hours.' Include a list of what the demo covers (bullet points).
```

### Team Meeting Coordinator

Build an internal tool that creates Doodle polls for recurring team meetings, displays current poll results with color-coded availability heatmaps, and announces the confirmed meeting time to the team's Slack channel when finalized.

Prompt example:

```
Create a meeting scheduler tool with: a form to set meeting title, description, and duration (30/60/90 mins), a calendar date picker to add proposed time options (multiple dates/times), a list of participants to invite by email, and a 'Create Doodle Poll' button. After creation, show the poll URL and an embed of the current responses as a table with participant names as rows and time slots as columns.
```

### Client Project Kickoff Scheduler

Embed a scheduling component in a client portal where new clients can immediately book their project kickoff call. The component creates a Doodle poll with the agency's available slots and sends the client a direct link to respond.

Prompt example:

```
Create a project kickoff scheduling card that shows 'Schedule your kickoff call' with a description, a set of available time options as selectable pills (e.g., 'Tue Apr 15, 2:00 PM', 'Wed Apr 16, 10:00 AM'), a name and email input, and a 'Submit Availability' button. Show a 'Poll sent!' confirmation with a copy-link button for the Doodle poll URL.
```

## Troubleshooting

### Token request returns 401 or 'invalid_client'

Cause: The DOODLE_CLIENT_ID or DOODLE_CLIENT_SECRET is incorrect, or the application hasn't been approved by Doodle's partner program.

Solution: Verify credentials are copied exactly from the Doodle partner portal with no extra whitespace. Confirm your Doodle developer application status — sandbox access may have different credentials from production. Contact Doodle's developer support if credentials seem correct but authentication still fails.

### Poll creation returns 422 Unprocessable Entity

Cause: The poll creation payload is missing required fields or the time slot format is incorrect. Doodle requires options to have valid ISO datetime strings with a future start time.

Solution: Verify the options array contains objects with 'start' and 'end' as valid ISO 8601 datetime strings (e.g., '2026-04-15T14:00:00Z'). Ensure the start time is in the future. The initiator object must include at least a name field.

```
// Valid poll creation payload example:
const payload = {
  title: 'Team Standup',
  type: 'standard',
  options: [
    { start: '2026-04-15T14:00:00Z', end: '2026-04-15T15:00:00Z' },
    { start: '2026-04-16T10:00:00Z', end: '2026-04-16T11:00:00Z' },
  ],
  initiator: { name: 'Alice', email: 'alice@example.com' },
};
```

### Poll results show no participants despite people responding

Cause: The poll ID used to fetch results may be incorrect, or there's a delay between participant response and when the response appears in the API.

Solution: Verify the poll ID returned from poll creation and used in the fetch. Doodle may have a short processing delay before responses appear. Implement a manual 'Refresh Results' button in addition to the polling interval so users can force an update.

### Access token expires mid-session causing failed API calls

Cause: The module-level token cache is only preserved within a serverless function instance. A new deployment or cold start resets the cache, forcing a new token request.

Solution: The token refresh logic in the code handles expiry checking — a new token is obtained automatically when the cached one expires or when the cache is empty after a cold start. This is expected behavior. Each new token request adds ~200ms latency, which is acceptable.

## Frequently asked questions

### Is the Doodle API publicly available for developers?

Doodle's API requires registering as a technology partner through their official partner program. It's not a self-serve API that anyone can sign up for — you need to apply and be approved. Doodle does provide sandbox credentials for development purposes during the approval process. If you need scheduling features without going through a partner approval, consider alternatives like Calendly's API (which has a public developer program) or building a lightweight custom scheduling feature using your own database.

### Can I embed a Doodle poll directly in my V0 app?

Yes, the simplest embedding approach is using an iframe with the Doodle poll participation URL. Doodle polls are designed to be embeddable and respond well to iframe constraints. However, this means participants see Doodle's own UI rather than your app's custom design. For a fully branded experience, use the API to fetch poll data and render it with your own components as described in this guide.

### What's the difference between Doodle's poll types?

Doodle offers three poll types via the API: 'standard' (classic availability poll where participants mark yes/no/if-need-be for each time slot), 'book' (one-on-one booking where the first person to claim a slot locks it), and 'signup' (volunteers sign up for specific slots like event shifts). For meeting scheduling, 'standard' is the most common type. 'book' is useful for appointment scheduling where each slot can only be taken by one person.

### How many time slot options can a Doodle poll have?

Doodle supports up to 15 options in a standard poll. For typical meeting scheduling (proposing 5-7 time slots across 2 weeks), this is more than sufficient. If you need to propose more than 15 time slots, create multiple polls for different date ranges or use a different scheduling approach.

### Can I send automatic email reminders to poll participants?

Doodle's platform sends automated email notifications to invited participants when you include their email addresses in the poll's invitees list. The notification timing and content are controlled by Doodle's platform, not the API. For custom reminder logic (e.g., nudging non-responders after 24 hours), you'd need to track response status via the API and send reminders through your own email service like Mailgun or SendGrid.

---

Source: https://www.rapidevelopers.com/v0-integrations/doodle
© RapidDev — https://www.rapidevelopers.com/v0-integrations/doodle
