Connect Bubble to ClickUp using the API Connector with a personal API token in the Authorization header — no Bearer prefix, no Base64 encoding. The token goes in directly. The two major traps: ClickUp date fields use millisecond epoch timestamps (not seconds), and navigating the team → space → list → task hierarchy usually requires 2–3 chained API calls to reach tasks.
| Fact | Value |
|---|---|
| Tool | ClickUp |
| Category | Productivity |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 45–60 minutes |
| Last updated | July 2026 |
Building a Bubble client portal for ClickUp tasks
The most valuable Bubble + ClickUp integration is a branded client portal: customers log into your Bubble app, see their open project tasks from ClickUp, and submit new requests that automatically create ClickUp tasks. The agency or product team works entirely in ClickUp; clients get a polished interface without needing ClickUp access. Two things distinguish ClickUp from other project management APIs in Bubble. First, the personal token authorization: unlike Jira (which uses Base64 Basic Auth) or most APIs (which require a 'Bearer' prefix), ClickUp personal tokens go directly in the Authorization header value — just the raw token starting with pk_. Second, the timestamp format: ClickUp returns and expects all dates as 13-digit millisecond epoch numbers, not ISO date strings. Bubble's date values must be converted to milliseconds before sending.
Integration method
Call the ClickUp API v2 from Bubble's server-side API Connector using a personal API token placed directly in the Authorization header — without any 'Bearer' prefix, marked Private.
Prerequisites
- A ClickUp account on any plan (Free plan includes API access)
- A ClickUp workspace with at least one list containing tasks to test with
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
- Your ClickUp team ID, space ID, and list ID (you will discover these in the setup steps)
Step-by-step guide
Find and copy your ClickUp personal API token
In ClickUp, click your avatar in the bottom-left corner to open your account menu. Click 'Settings'. In the left sidebar of Settings, click 'Apps'. Scroll down to find the 'API Token' section. Click 'Generate' if you don't have a token yet, or 'Regenerate' to create a new one (note: regenerating invalidates the old token). Click the copy icon next to the token to copy it to your clipboard. The personal API token starts with pk_ followed by a long alphanumeric string. This token has the same permissions as your ClickUp account — it can read and modify any workspace, space, list, or task your account has access to. Keep it private. If you need the integration to work on behalf of multiple users (not just your account), consider ClickUp's OAuth 2.0 flow instead — but for a single-user or agency integration, the personal token is the simplest path.
Pro tip: The personal API token starts with pk_ — if it starts with something else, you may be looking at a different credential type. ClickUp also has OAuth app credentials (client_id, client_secret) which are different from personal tokens.
Expected result: You have a ClickUp personal API token starting with pk_ copied to your clipboard.
Configure the API Connector with the token (no Bearer prefix)
In Bubble, go to Plugins → API Connector → click 'Add another API'. Name the group 'ClickUp API'. In the API base URL field, enter: https://api.clickup.com/api/v2. Now add a shared header. This is where most developers make the first mistake: the key is Authorization, and the value is JUST the token — no 'Bearer' prefix, no 'Basic' prefix. For example: pk_1234567890abcdef. Using 'Bearer pk_1234...' or 'Basic pk_1234...' causes HTTP 401 on every call. Check the Private checkbox on the Authorization header. This keeps the token off the client side. You do not need a Content-Type header at the group level for GET requests — add it per-call for POST requests that send a body. The base URL https://api.clickup.com/api/v2 applies to all calls in this group, so individual calls only need the path suffix (e.g., /team for the team list).
1API Connector Group: ClickUp API2 Base URL: https://api.clickup.com/api/v234 Shared header:5 - Authorization: pk_your_actual_token_here [Private: YES]6 (NO 'Bearer' prefix — just the raw token value)Pro tip: Unlike most APIs that require 'Bearer TOKEN' in the Authorization header, ClickUp personal tokens use just the raw token string. Using the Bearer prefix returns HTTP 401 with no other explanation.
Expected result: The ClickUp API group shows the base URL and a single Private Authorization header containing the raw token without any prefix.
Discover your team ID and space IDs
ClickUp's data is organized hierarchically: Workspace (Team) → Spaces → Folders → Lists → Tasks. To query tasks, you need to work down this hierarchy, and the IDs are not visible in the ClickUp UI by default. Start with the team (workspace) level: add a call named 'Get Teams' with Method GET and URL /team. Set 'Use as' to 'Data'. Click 'Initialize call'. The response includes an array of teams, each with an id and name. Your team ID is a numeric ID in the response. Note it down — you will use it in the next call. Next, add a call 'Get Spaces' with Method GET and URL /team/<team_id>/space — set team_id as a parameter. Initialize with your actual team ID. The response lists your spaces (equivalent to departments or clients in ClickUp) each with an id and name. Copy the space ID for the space containing the lists you want to access from Bubble.
1Call 1: GET /team2Returns: { "teams": [{ "id": "9012345678", "name": "My Workspace" }] }34Call 2: GET /team/9012345678/space5Returns: {6 "spaces": [7 { "id": "90120000001", "name": "Client Projects" },8 { "id": "90120000002", "name": "Internal Operations" }9 ]10}Pro tip: You can also find your list ID directly in the ClickUp web URL when viewing a list — click a list in ClickUp and look at the URL: it contains the list ID as a number in the path. This is often faster than the API discovery calls.
Expected result: You have your ClickUp team ID and the space ID for the area you want to access from Bubble. You can now query specific lists within that space.
Add a task list query call
Add a new call named 'Get Tasks'. Set Method to GET and URL to /list/<list_id>/task — set list_id as a dynamic parameter. Set 'Use as' to 'Data'. ClickUp supports optional query parameters for filtering tasks: add parameters for status (filter by status name), page (for pagination — ClickUp returns max 100 tasks per page), order_by (e.g., 'due_date'), and reverse (true to sort descending). These are query string parameters, not body parameters — add them in the 'Parameters' section of the call, not in a body. Initialize with a real list ID that has tasks. After initialization, Bubble detects the 'tasks' array with fields like: tasks[0].id, tasks[0].name, tasks[0].status.status, tasks[0].assignees[0].username, tasks[0].due_date, tasks[0].priority.priority. Note that due_date comes back as a 13-digit millisecond epoch string like '1714003200000' — to display it as a date in Bubble, divide by 1000 and use Bubble's 'formatted as date' operator on the resulting number treated as a Unix timestamp. Use the 'page' parameter to implement pagination if you have more than 100 tasks.
1GET /list/<list_id>/task23Query parameters:4 - list_id: <dynamic: your list ID> (path parameter)5 - status: <dynamic: status_filter> (optional, e.g., 'in progress')6 - page: 0 (0-indexed, increment for pagination)7 - order_by: due_date (sort field)8 - reverse: true (newest/latest first)9 - include_closed: false (exclude completed tasks)Pro tip: ClickUp task statuses are workspace-specific lowercase strings like 'to do', 'in progress', 'complete'. Use the GET /list/{list_id}/task call without a status filter first to see what status values are actually used in your workspace.
Expected result: Initialize call succeeds and Bubble detects the tasks array with id, name, status, assignees, and due_date fields. You can bind this to a Repeating Group.
Add a task creation call with correct millisecond timestamps
Add a new call named 'Create Task'. Set Method to POST and URL to /list/<list_id>/task. Set 'Use as' to 'Action'. Add a Content-Type header at the call level (not the shared headers): key = Content-Type, value = application/json. Check 'Send arbitrary data' in the Body section. The task creation body requires: name (text), and optionally description (plain text string — unlike Jira, ClickUp does NOT require ADF format), due_date (millisecond epoch as a number), and priority (integer: 1=urgent, 2=high, 3=normal, 4=low). The critical issue for due dates: Bubble stores dates in seconds internally but ClickUp expects milliseconds. In the workflow where you call this API, you need to multiply the Bubble date's Unix timestamp by 1000 before passing it as the due_date parameter. In Bubble's formula: Input Due Date's value:unix timestamp * 1000. Initialize the call with a real list ID and example values including a valid millisecond timestamp to confirm Bubble detects the response (the created task with its id).
1POST /list/<list_id>/task23Additional call-level header:4 Content-Type: application/json56Body:7{8 "name": "<dynamic: task_name>",9 "description": "<dynamic: task_description>",10 "due_date": <dynamic: due_date_milliseconds>,11 "due_date_time": false,12 "priority": <dynamic: priority_integer>,13 "assignees": [],14 "status": "to do"15}Pro tip: Priority integers: 1 = Urgent, 2 = High, 3 = Normal, 4 = Low. If you omit the priority field, ClickUp sets no priority. If you send an invalid priority number, the task is created with no priority rather than returning an error.
Expected result: The Initialize call creates a test task in your ClickUp list and returns the new task's id and url. The task appears in your ClickUp list with the correct name and priority.
Wire tasks to a Bubble Repeating Group and handle timestamps
Now connect the ClickUp task query to your Bubble UI. Add a Repeating Group to your page. Set Type of content to the response type from the Get Tasks call. Set Data source to 'Get data from an external API' → select 'ClickUp API - Get Tasks'. In the parameter prompt, enter the list ID you want to display (this can be dynamic based on the current user's stored list ID in Bubble's database). Inside the Repeating Group, add elements for each task field: a text element bound to 'Current cell's tasks's name' for the task name, another for 'Current cell's tasks's status's status' for the status value. For the due date, the ClickUp due_date field is a string of milliseconds — in Bubble, to convert it to a readable date, use a calculation: extract the due_date value, treat it as a number, divide by 1000, and use Bubble's :formatted as date. The easiest way: add a Text element and set its value to 'Current cell's tasks's due_date / 1000 :formatted as MM/DD/YYYY'. Note: be careful not to make per-row API calls inside the Repeating Group — this pattern quickly exhausts the 100 requests/minute rate limit. Load all tasks once for the list, not one call per task. RapidDev has built dozens of ClickUp-powered Bubble client portals — if your data model or hierarchy is complex, book a free scoping call at rapidevelopers.com/contact.
1Repeating Group:2 Type of content: ClickUp API - Get Tasks's tasks3 Data source: Get data from external API → ClickUp API - Get Tasks4 → list_id: [your list ID or dynamic value]56Inside Repeating Group cells:7 Text element 1: Current cell's tasks's name8 Text element 2: Current cell's tasks's status's status9 Text element 3 (due date display):10 → Current cell's tasks's due_date (as number) / 100011 :formatted as MM/DD/YYYYPro tip: If the Repeating Group shows the correct number of rows but due dates display as very old dates (1970s), the timestamp conversion is off — ensure you are dividing by 1000 and treating the result as a Unix timestamp, not raw milliseconds.
Expected result: The Repeating Group displays your ClickUp tasks with correct names, statuses, and formatted due dates. The list loads in one API call rather than one call per row.
Common use cases
Client task portal
Clients log into a Bubble app and see a real-time list of their project tasks from ClickUp, filtered by their project list. Each task shows the name, assignee, status, and due date. Clients can click a task to see full details without ever logging into ClickUp directly.
Show a Repeating Group of tasks from ClickUp list ID 123456789, filtered to 'In Progress' status, displaying task name, assignee, priority, and due date formatted as a readable date.
Copy this prompt to try it in Bubble
Project request submission
A Bubble form lets clients or internal users submit new project requests that create ClickUp tasks with the correct priority, due date, and description. The form returns the new task URL so the user can check the task in ClickUp if needed.
When a user submits the request form, create a ClickUp task in list '987654321' with the task name from the title input, priority set to 'High', due date converted to milliseconds, and description from the details field.
Copy this prompt to try it in Bubble
Project status dashboard
An internal dashboard in Bubble aggregates task counts by status across multiple ClickUp lists for an executive overview. The dashboard shows total tasks in 'Not Started', 'In Progress', 'Complete' states with color-coded indicators and click-through to individual lists.
Build a dashboard showing the count of ClickUp tasks in each status across our three main project lists, with a color-coded status breakdown and a drill-down view for each list.
Copy this prompt to try it in Bubble
Troubleshooting
Every API call returns HTTP 401 Unauthorized
Cause: The most common cause is using 'Bearer pk_...' instead of just 'pk_...' in the Authorization header. ClickUp personal tokens do not use the Bearer prefix — adding it invalidates the auth.
Solution: In Plugins → API Connector → ClickUp API group, check the Authorization shared header value. Remove any 'Bearer ' or 'Basic ' prefix — the value should be just the raw token starting with pk_. Click Save and retry the Initialize call.
Tasks display with due dates showing as years in the 1970s or strange past dates
Cause: ClickUp returns due_date as a 13-digit millisecond timestamp (e.g., 1714003200000). If treated as seconds instead of milliseconds, dividing by 1000 still gives a huge number, but applying a date conversion directly to the 13-digit number interprets it as a second value from epoch — resulting in a date in the year ~55000.
Solution: In your date display formula: take the due_date value, divide by 1000 to convert milliseconds to seconds, then apply Bubble's Unix timestamp to date conversion. In Bubble's expression editor: 'Current cell's tasks's due_date:converted to number / 1000' as a Unix timestamp formatted as your desired date format.
'There was an issue setting up your call' when initializing a GET /list/{id}/task call
Cause: The Initialize call requires a valid list ID with at least one task in it. Using a wrong list ID (or a list ID from a different workspace/token) causes the API to return 401 or 404, and Bubble cannot detect the response schema from an error response.
Solution: Confirm the list ID is correct by visiting ClickUp in the browser, clicking the list, and reading the ID from the URL. Ensure the account whose token you are using has access to that list. Then retry Initialize with the correct ID in the parameter prompt.
Repeating Group shows only 100 tasks even though the list has more
Cause: ClickUp returns a maximum of 100 tasks per API call (one page). If the list has more than 100 tasks, additional pages require separate calls with the 'page' parameter incremented.
Solution: Implement pagination using Bubble Custom States: track the current page number in a custom state, and add a 'Load More' button that increments the page state and re-triggers the API call with the new page value. Alternatively, add server-side filters (status=in progress, due_date range) to reduce the total result set below 100 without pagination.
Creating tasks returns HTTP 400 when the due_date field is included
Cause: The due_date value was sent as a Bubble date object or as a seconds timestamp instead of a milliseconds integer. ClickUp validates that due_date is a 13-digit millisecond epoch number — sending a 10-digit seconds value (like 1714003200) or an ISO string fails.
Solution: In the workflow step that calls Create Task, set the due_date parameter to: Input Due Date's value:unix timestamp * 1000. The :unix timestamp operator gives seconds; multiplying by 1000 converts to milliseconds. Verify the result is 13 digits long before sending.
Best practices
- Always mark the Authorization header as Private in Bubble's API Connector — the ClickUp personal token grants full account access and must never appear in browser network requests.
- Add Privacy rules in Bubble's Data tab for any Things that store ClickUp task data — without Privacy rules, any logged-in Bubble user can access all stored task records via Bubble's data API.
- Avoid per-row API calls inside Repeating Groups — loading tasks for a list in one call and binding the results to the Repeating Group is efficient; firing a separate API call for each row quickly exhausts the 100 requests/minute rate limit.
- Cache ClickUp task data in Bubble's database for scenarios where data changes infrequently — storing a snapshot reduces WU consumption and improves page load speed compared to live API calls on every page view.
- Store the ClickUp list IDs and team/space IDs in Bubble's App Data or in a data type record rather than hardcoding them in API call URLs — makes it easy to update if the workspace structure changes.
- Convert all due dates to milliseconds before sending to ClickUp (multiply Bubble's :unix timestamp by 1000) and divide by 1000 when displaying ClickUp dates in Bubble — the millisecond format is ClickUp's standard and inconsistency causes silent date errors.
- For public-facing Bubble apps that allow anonymous users to submit task requests, move the Create Task API call to a Backend Workflow (paid Bubble plan) — this keeps the API token server-side and prevents it from appearing in the browser's network inspector.
Alternatives
Jira uses Base64-encoded HTTP Basic Auth (more complex setup), requires Atlassian Document Format for descriptions, and uses JQL for queries. ClickUp has simpler auth (raw token, no encoding) and accepts plain strings for descriptions. Choose ClickUp for agencies and product teams wanting simpler setup; choose Jira for software engineering teams with sprint workflows.
Notion is primarily a document/database CMS in Bubble (display content rows in Repeating Groups), while ClickUp is a task and project management tool (tasks with assignees, statuses, due dates, priorities). Choose Notion for content management; choose ClickUp for project and task management with assignments and workflows.
Asana uses a Bearer token (simpler than ClickUp's no-prefix token pattern) with a RESTful API for task management. Asana has a simpler data hierarchy than ClickUp (workspace → project → task vs. team → space → folder → list → task). ClickUp is more feature-rich with custom fields and multiple view types; Asana is simpler to get started with for straightforward task management.
Frequently asked questions
Do I need a paid ClickUp plan to use the API?
No. ClickUp's API is available on all plans, including the Free plan. Personal API tokens can be generated on free accounts. However, some ClickUp features accessible via the API (like time tracking, custom fields, goals) may require paid plans within ClickUp. Check ClickUp's current pricing for the specific features your integration needs.
Why doesn't ClickUp use 'Bearer' in the Authorization header like most APIs?
ClickUp's personal token authentication was designed with the token as a direct authorization value rather than following the OAuth Bearer token standard. The token works similarly to an API key in that sense. ClickUp's OAuth 2.0 flow (for multi-user authorization) does use Bearer tokens — only the personal token auth uses the raw token format.
How do I find the list ID for a specific ClickUp list?
The fastest way is from the ClickUp URL. Open ClickUp in your browser, click on the list you want, and look at the URL — it contains the list ID as a series of digits. Alternatively, use the GET /team call to get your team ID, then GET /team/{team_id}/space, then GET /space/{space_id}/list to discover all lists and their IDs programmatically.
Can I assign tasks to specific ClickUp users from Bubble?
Yes. The Create Task endpoint accepts an 'assignees' array of ClickUp user IDs (numeric). To get user IDs, call GET /team/{team_id}/member — this returns workspace members with their ids. Store these IDs in Bubble's database (e.g., in user profiles) and include the relevant user ID in the 'assignees' array when creating a task.
How do I handle the 100 requests per minute rate limit?
The rate limit applies to the personal token, so all Bubble users sharing the same token share the same quota. To stay within limits: (1) avoid per-row API calls in Repeating Groups, (2) use the 'page' parameter for pagination rather than calling the full list repeatedly, (3) cache task data in Bubble's database for lists that don't change frequently, (4) implement exponential backoff in Backend Workflows if you expect high volume.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation