Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with ClickUp

Integrate Bolt.new with ClickUp using your personal API token from Settings → Apps and the ClickUp REST API v2. Build custom task boards, fetch workspace hierarchies, create and update tasks, and track time — all through a Next.js API route that keeps your token server-side. ClickUp's API token auth is simpler than OAuth, making it one of the more approachable productivity tool integrations.

What you'll learn

  • How to find your ClickUp personal API token in Settings → Apps
  • How to fetch your ClickUp workspace hierarchy — teams, spaces, folders, and lists
  • How to build a custom task board view using ClickUp API data in a React component
  • How to create and update tasks programmatically via the ClickUp REST API
  • How to log time entries using the ClickUp time tracking API
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate14 min read30 minutesProductivityApril 2026RapidDev Engineering Team
TL;DR

Integrate Bolt.new with ClickUp using your personal API token from Settings → Apps and the ClickUp REST API v2. Build custom task boards, fetch workspace hierarchies, create and update tasks, and track time — all through a Next.js API route that keeps your token server-side. ClickUp's API token auth is simpler than OAuth, making it one of the more approachable productivity tool integrations.

Building Custom ClickUp Dashboards and Automations with Bolt.new

ClickUp's REST API v2 gives you programmatic access to the full workspace hierarchy: teams (workspaces), spaces, folders, lists, and tasks. With this API you can build views and automations that ClickUp's own interface does not offer — custom dashboards combining data from multiple spaces, automated task creation triggered by events in other systems, time tracking reports formatted exactly for your billing workflow, or read-only task boards for stakeholders who do not need a ClickUp account.

Unlike many productivity tool integrations, ClickUp uses a simple bearer token for API authentication rather than OAuth 2.0. Your personal API token — available in Settings → Apps — grants access to all workspaces connected to your account. This means you can build and test the full integration in Bolt's WebContainer without needing to deploy first, since there is no OAuth callback URL requirement. For team-level access in a production app where different users authenticate with their own ClickUp accounts, the OAuth 2.0 flow is available but not required for personal-use tools.

ClickUp enforces a rate limit of 100 requests per minute per token. For most dashboard use cases this is ample. If you are building a sync tool that processes large task lists, implement simple throttling with a small delay between API calls to avoid 429 rate limit errors.

Integration method

Bolt Chat + API Route

Bolt generates the ClickUp integration through conversation — you describe the task board or automation you want and Bolt writes the API route and React component code. ClickUp uses a simple API token for authentication (no OAuth required for personal use), so the full integration can be built and tested in Bolt's development environment without needing to deploy first. API calls flow through server-side Next.js routes to keep the token out of the browser.

Prerequisites

  • A ClickUp account with at least one workspace containing spaces, lists, and tasks
  • Your ClickUp personal API token (found at app.clickup.com/settings/apps — scroll to API Token)
  • A Next.js project in Bolt.new (prompt 'Create a Next.js app' to get started)
  • Your workspace ID and at least one list ID (visible in the URL when you open a list in ClickUp: app.clickup.com/{workspaceId}/v/li/{listId})

Step-by-step guide

1

Get your ClickUp API token and find your workspace IDs

ClickUp provides a personal API token that gives access to all your workspaces. To find it, log into ClickUp and go to Settings (bottom-left corner, click your avatar) → Apps → scroll down to the API Token section. Click Generate if you haven't yet, then copy the token. Store it immediately in a safe place — ClickUp only shows it once per generation, though you can regenerate it at any time. Next, find your workspace and list IDs. The easiest way is to look at the URL while browsing ClickUp. When you open a Space or List, the URL pattern is: app.clickup.com/{teamId}/v/li/{listId}. The teamId is your workspace ID (also called team ID in the ClickUp API). You can also fetch these programmatically: the ClickUp API endpoint GET /team returns all teams your token has access to, and from there you can traverse the hierarchy down to spaces and lists. Create a .env.local file in your Bolt project root with the values below. The VITE_* prefix is for client-side access in Vite projects — but for security, never expose your ClickUp API token client-side. Use process.env (no VITE_ prefix) only in server-side API routes. The CLICKUP_TEAM_ID and CLICKUP_LIST_ID values are safe to expose as they are just identifiers, not credentials.

Bolt.new Prompt

Create a Next.js app with a ClickUp integration setup. Add a .env.local file with CLICKUP_API_TOKEN, CLICKUP_TEAM_ID, and CLICKUP_LIST_ID as placeholder variables. Create a /api/clickup/workspace route that fetches all teams and spaces for my workspace using the ClickUp REST API v2 and returns the hierarchy as JSON.

Paste this in Bolt.new chat

.env.local
1// .env.local
2CLICKUP_API_TOKEN=pk_your_personal_api_token_here
3CLICKUP_TEAM_ID=your_workspace_id
4CLICKUP_LIST_ID=your_default_list_id

Pro tip: Your ClickUp workspace ID appears in the URL as the number after app.clickup.com/ — for example, in app.clickup.com/12345678/home, your team ID is 12345678.

Expected result: Your .env.local file is created with placeholder values, and calling /api/clickup/workspace returns your workspace name, spaces, and lists as JSON.

2

Fetch the workspace hierarchy and display lists

The ClickUp API hierarchy is: Team (Workspace) → Spaces → Folders → Lists. Tasks live inside lists. To build any task view, you first need to know which lists exist. The API provides endpoints for each level: GET /team returns all workspaces, GET /team/{teamId}/space returns spaces in a workspace, GET /space/{spaceId}/folder returns folders, and GET /space/{spaceId}/list returns folderless lists. For most use cases, you need the list IDs up front to query tasks. Rather than traversing the full hierarchy on every page load, fetch the hierarchy once during app setup and store the list IDs in your app state or database. For a personal tool, you can even hardcode the list IDs you care about after finding them through a one-time API call. The API route below fetches all spaces and their lists in one operation by chaining the calls. It returns a simplified structure that is easy to render in a React dropdown or sidebar navigation. All ClickUp API calls use the same Authorization header format: the raw token string — not 'Bearer {token}', just the token itself. This is different from most other APIs.

Bolt.new Prompt

Create a ClickUp workspace explorer API route at /api/clickup/spaces that fetches all spaces and their lists for my team ID. Then build a React sidebar component that displays the spaces as expandable sections with their lists inside. Clicking a list should set it as the active list and trigger a task fetch.

Paste this in Bolt.new chat

app/api/clickup/spaces/route.ts
1// app/api/clickup/spaces/route.ts
2import { NextResponse } from 'next/server';
3
4const CLICKUP_API = 'https://api.clickup.com/api/v2';
5const API_TOKEN = process.env.CLICKUP_API_TOKEN;
6const TEAM_ID = process.env.CLICKUP_TEAM_ID;
7
8type ClickUpList = { id: string; name: string; task_count: number };
9type ClickUpFolder = { id: string; name: string; lists: ClickUpList[] };
10type ClickUpSpace = { id: string; name: string; folders: ClickUpFolder[]; lists: ClickUpList[] };
11
12async function clickupFetch(path: string) {
13 const response = await fetch(`${CLICKUP_API}${path}`, {
14 headers: { Authorization: API_TOKEN! },
15 });
16 if (!response.ok) throw new Error(`ClickUp API error: ${response.status}`);
17 return response.json();
18}
19
20export async function GET() {
21 try {
22 const { spaces } = await clickupFetch(`/team/${TEAM_ID}/space?archived=false`);
23
24 const spacesWithLists: ClickUpSpace[] = await Promise.all(
25 spaces.map(async (space: { id: string; name: string }) => {
26 const [{ folders }, { lists: folderlessLists }] = await Promise.all([
27 clickupFetch(`/space/${space.id}/folder?archived=false`),
28 clickupFetch(`/space/${space.id}/list?archived=false`),
29 ]);
30 return { id: space.id, name: space.name, folders, lists: folderlessLists };
31 })
32 );
33
34 return NextResponse.json({ spaces: spacesWithLists });
35 } catch (error) {
36 return NextResponse.json(
37 { error: error instanceof Error ? error.message : 'Unknown error' },
38 { status: 500 }
39 );
40 }
41}

Pro tip: Note that the ClickUp Authorization header uses just the raw token string — not 'Bearer token'. Using 'Bearer' will result in 401 errors. This is a common gotcha when coming from other APIs.

Expected result: Calling /api/clickup/spaces returns a JSON array of your ClickUp spaces, each containing their folders and lists with names and task counts.

3

Build a task list view with filtering and sorting

With list IDs available, you can fetch tasks from any list using GET /list/{listId}/task. The ClickUp task endpoint supports extensive query parameters for filtering: assignees (comma-separated user IDs), statuses (comma-separated status names), due_date_gt and due_date_lt (Unix timestamps in milliseconds for date range filtering), order_by (created, updated, or due_date), and reverse (true/false for sort direction). The task response includes all task fields: id, name, description, status, priority, due_date, assignees, tags, custom fields, and parent (for subtasks). Priority is returned as an object with priority (integer 1-4) and color fields. Status is an object with status (the name string) and type fields. Map these to your UI components accordingly. Pagination is handled through the page query parameter (0-indexed) with 100 tasks per page. Always check if the response indicates more pages are available (last_page field in the response) and implement a Load More button or automatic pagination for lists with more than 100 tasks. Because ClickUp API calls work fine as outbound requests from Bolt's WebContainer, you can build and test this entire task board in the Bolt preview without deploying. Incoming webhooks from ClickUp (for real-time task updates) require a deployed URL, but polling-based task fetching works in development.

Bolt.new Prompt

Create a ClickUp task board page that fetches tasks from a given list ID using /api/clickup/tasks?listId=. Display tasks as cards with: task name, status badge (color-coded), priority badge (urgent=red, high=orange, normal=blue, low=gray), assignee avatar, and due date. Add filter buttons for status and priority at the top. Use React state for filtering — no additional API calls needed after the initial fetch.

Paste this in Bolt.new chat

app/api/clickup/tasks/route.ts
1// app/api/clickup/tasks/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4const CLICKUP_API = 'https://api.clickup.com/api/v2';
5const API_TOKEN = process.env.CLICKUP_API_TOKEN;
6
7export async function GET(request: NextRequest) {
8 const { searchParams } = new URL(request.url);
9 const listId = searchParams.get('listId') ?? process.env.CLICKUP_LIST_ID;
10 const page = searchParams.get('page') ?? '0';
11 const orderBy = searchParams.get('order_by') ?? 'due_date';
12
13 const params = new URLSearchParams({
14 page,
15 order_by: orderBy,
16 subtasks: 'true',
17 include_closed: 'false',
18 });
19
20 const response = await fetch(
21 `${CLICKUP_API}/list/${listId}/task?${params.toString()}`,
22 { headers: { Authorization: API_TOKEN! } }
23 );
24
25 if (!response.ok) {
26 return NextResponse.json(
27 { error: `ClickUp returned ${response.status}` },
28 { status: response.status }
29 );
30 }
31
32 const data = await response.json();
33 return NextResponse.json({
34 tasks: data.tasks,
35 lastPage: data.last_page,
36 });
37}

Pro tip: ClickUp due dates are stored as Unix timestamps in milliseconds. Convert them with new Date(parseInt(task.due_date)).toLocaleDateString() for display.

Expected result: The task board page displays ClickUp tasks as cards with color-coded status and priority badges, and the filter buttons update the visible tasks using client-side filtering.

4

Create and update tasks, and log time entries

Creating a task requires a POST to /list/{listId}/task with at minimum a name field. Optional fields include description (plain text or Markdown), status (must match an existing status name in the list), priority (1=urgent, 2=high, 3=normal, 4=low), assignees (array of user IDs), due_date (Unix timestamp in milliseconds), and tags (array of tag names). The task creation response includes the full task object with the newly assigned task ID. Updating an existing task uses PUT /task/{taskId} with the same field structure. Only include the fields you want to change — omitted fields are left unchanged. To update an assignee, use the special add or rem arrays inside the assignees object: { assignees: { add: [userId], rem: [] } }. Time tracking in ClickUp uses the Time Tracking API. To log time, POST to /task/{taskId}/time with a start (Unix ms), duration (in milliseconds), and optional description. To start a timer (live tracking), POST to /team/{teamId}/time_entries/start. Fetch time entries for a task with GET /task/{taskId}/time. These mutation endpoints all work from Bolt's WebContainer since they are outbound HTTP requests. No deployment is needed to test task creation and updates — only ClickUp webhook receipt (for real-time change notifications pushed FROM ClickUp) requires a deployed URL.

Bolt.new Prompt

Add task creation and time logging to my ClickUp integration. Create a /api/clickup/tasks/create route that POSTs a new task to a list with name, description, priority, and due date. Create a /api/clickup/time/log route that logs a time entry for a task ID with start time, duration in hours, and description. Add a simple form UI for creating tasks and a time log form.

Paste this in Bolt.new chat

app/api/clickup/tasks/create/route.ts
1// app/api/clickup/tasks/create/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4const CLICKUP_API = 'https://api.clickup.com/api/v2';
5const API_TOKEN = process.env.CLICKUP_API_TOKEN;
6const DEFAULT_LIST_ID = process.env.CLICKUP_LIST_ID;
7
8export async function POST(request: NextRequest) {
9 const { listId, name, description, priority, dueDate, assignees } = await request.json();
10
11 const taskBody: Record<string, unknown> = {
12 name,
13 description: description ?? '',
14 priority: priority ?? 3, // 1=urgent, 2=high, 3=normal, 4=low
15 assignees: assignees ?? [],
16 };
17
18 if (dueDate) {
19 taskBody.due_date = new Date(dueDate).getTime(); // Convert to Unix ms
20 taskBody.due_date_time = true;
21 }
22
23 const response = await fetch(
24 `${CLICKUP_API}/list/${listId ?? DEFAULT_LIST_ID}/task`,
25 {
26 method: 'POST',
27 headers: {
28 Authorization: API_TOKEN!,
29 'Content-Type': 'application/json',
30 },
31 body: JSON.stringify(taskBody),
32 }
33 );
34
35 if (!response.ok) {
36 const error = await response.json();
37 return NextResponse.json({ error }, { status: response.status });
38 }
39
40 const task = await response.json();
41 return NextResponse.json({ id: task.id, name: task.name, url: task.url });
42}
43
44// app/api/clickup/time/log/route.ts — log a time entry
45export async function logTime(taskId: string, durationHours: number, description: string) {
46 const durationMs = Math.round(durationHours * 3600 * 1000);
47 const start = Date.now() - durationMs;
48
49 return fetch(`${CLICKUP_API}/task/${taskId}/time`, {
50 method: 'POST',
51 headers: {
52 Authorization: API_TOKEN!,
53 'Content-Type': 'application/json',
54 },
55 body: JSON.stringify({ start, duration: durationMs, description }),
56 });
57}

Pro tip: Task priority in the ClickUp API uses integers, not strings: 1 = urgent, 2 = high, 3 = normal, 4 = low. The GET task response returns priority as an object with both the integer and a color hex code.

Expected result: Submitting the task creation form creates a new task in your ClickUp list, and the time log form successfully records a time entry against an existing task.

Common use cases

Cross-space task dashboard

Build a consolidated view showing tasks assigned to a specific team member across multiple ClickUp spaces, with priority coloring and due date countdown. Team leads can see workload at a glance without opening each space separately in ClickUp.

Bolt.new Prompt

Create a ClickUp task dashboard in Next.js. Fetch all tasks assigned to a user ID across multiple list IDs from the ClickUp API. Display them in a table sorted by due date with priority color badges (urgent = red, high = orange, normal = blue, low = gray). Include a filter by space.

Copy this prompt to try it in Bolt.new

Automated task creation from form submissions

When a user submits a bug report or feature request form in your app, automatically create a ClickUp task in the appropriate list with the form data as the task description, assign it to the right team member, and set the priority based on the severity selected in the form.

Bolt.new Prompt

Add a ClickUp integration to my feedback form. When a user submits the form with a bug title, description, and severity (low/medium/high/critical), create a task in ClickUp list ID [LIST_ID] with the form data. Map severity to ClickUp priorities: critical = urgent, high = high, medium = normal, low = low.

Copy this prompt to try it in Bolt.new

Time tracking report exporter

Pull time entries logged in ClickUp for a selected date range and format them into a billable hours report by client (space) and project (list). Export the report as a CSV that can be imported into an invoicing tool.

Bolt.new Prompt

Build a time tracking report page that calls the ClickUp API to fetch all time entries for a given date range. Group entries by space name and list name, calculate total hours per group, and display a summary table. Add a Download CSV button that exports the data with columns: Date, Space, List, Task Name, Duration (hours).

Copy this prompt to try it in Bolt.new

Troubleshooting

All ClickUp API calls return 401 OAUTH_027: OAuth token not found

Cause: The Authorization header is incorrectly formatted. ClickUp's API uses the raw token string as the Authorization header value — not 'Bearer {token}'. Additionally, the API token may have been regenerated after you stored it, or the .env variable name is wrong.

Solution: Verify the Authorization header in your fetch call is set to exactly the token value: { Authorization: process.env.CLICKUP_API_TOKEN }. Do not add 'Bearer' or 'Token' prefix. Confirm the token in your .env matches the current token in ClickUp Settings → Apps.

typescript
1// Correct — no Bearer prefix:
2headers: { Authorization: process.env.CLICKUP_API_TOKEN! }
3
4// Wrong — do NOT use this:
5headers: { Authorization: `Bearer ${process.env.CLICKUP_API_TOKEN}` }

Task creation returns 400 with 'List not found' even though the list ID looks correct

Cause: The list ID in your request does not match any list accessible to your API token. This can happen if the list is in a workspace your token does not have access to, or if the list was deleted. ClickUp list IDs are numeric — a non-numeric ID or a space ID used instead of a list ID will also cause this error.

Solution: Use the /api/clickup/spaces route you built to fetch the current list of spaces and lists. Find the correct list ID in that response and update CLICKUP_LIST_ID in your .env. Remember that list IDs and space IDs look similar but are different — tasks live in lists, not spaces.

API calls work in development but return CORS errors in the browser console when called directly from a component

Cause: The ClickUp API call is being made directly from a client-side React component using fetch(), which triggers CORS. ClickUp's API does not allow cross-origin requests from browser environments.

Solution: Move all ClickUp API calls to server-side Next.js API routes (app/api/clickup/*/route.ts). Your React components should call your own API routes (e.g., fetch('/api/clickup/tasks')), which then call ClickUp server-to-server without CORS restrictions.

Best practices

  • Never put your ClickUp API token in client-side code or in environment variables prefixed with NEXT_PUBLIC_ — always access it only in server-side API routes using process.env.CLICKUP_API_TOKEN.
  • Cache the workspace hierarchy (spaces, folders, lists) on the server side and refresh it periodically rather than fetching on every page load — the hierarchy rarely changes and the calls count against your 100 req/min rate limit.
  • Add rate limit handling to your API routes: if ClickUp returns a 429 status, read the X-RateLimit-Reset header and retry after that time, or implement exponential backoff.
  • Use the include_closed=false query parameter when fetching tasks to exclude completed tasks by default, significantly reducing response size for active workspaces with long histories.
  • Fetch user IDs from the GET /team/{teamId}/member endpoint once and store them if your app needs to assign tasks or filter by assignee — avoid hardcoding user IDs which will break if team membership changes.
  • For webhook-based real-time updates (task created, status changed), you must deploy your app first since ClickUp webhooks push events to your server's public URL, which the Bolt WebContainer cannot receive.
  • Use the ClickUp Custom Fields API to read and set custom field values if your workflow relies on them — custom fields have separate field IDs that you retrieve from the list's field configuration endpoint.

Alternatives

Frequently asked questions

Do I need OAuth for the ClickUp integration or is the API token enough?

For personal tools and single-user dashboards, the personal API token from Settings → Apps is sufficient and much simpler to implement. OAuth 2.0 is only needed if you want other users to connect their own ClickUp accounts to your app — for example, if you are building a SaaS product where each customer logs in with their own ClickUp credentials.

Can I test the ClickUp integration in Bolt's preview before deploying?

Yes, almost entirely. Outbound API calls to ClickUp — fetching tasks, creating tasks, updating tasks, and logging time — all work in Bolt's WebContainer preview. The only limitation is incoming webhooks: if you want ClickUp to notify your app in real-time when tasks change, you need a deployed public URL. For polling-based approaches (refreshing every few seconds), no deployment is needed.

How do I find the user IDs needed to assign tasks?

Call GET https://api.clickup.com/api/v2/team/{teamId}/member with your API token. The response contains an array of member objects, each with an id (the user ID for task assignment), username, and email. Store these to build an assignee picker in your UI.

What is the ClickUp API rate limit and how do I handle it?

ClickUp limits API calls to 100 requests per minute per personal token. If you exceed this, you receive a 429 response with a X-RateLimit-Reset header indicating when the limit resets. For dashboard apps making a few dozen calls on page load this is rarely an issue. For bulk operations, add a 700ms delay between requests to stay safely under the limit.

Can I access ClickUp Docs and Whiteboards through the API?

ClickUp Docs have limited API support — you can create and fetch docs, but editing rich content has constraints. Whiteboards are not currently accessible via the public API. For task, time tracking, and workspace hierarchy operations (the most common use cases), the API coverage is comprehensive.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.