Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

ClickUp

Connect FlutterFlow to ClickUp via a FlutterFlow API Call using ClickUp's personal API token in an Authorization header without a 'Bearer' prefix. ClickUp's deep hierarchy — Workspace, Space, Folder, List, Task — requires chained calls to drill down to tasks. Cache Team and Space IDs in App State on first load, then lazy-load tasks only for the opened List to stay within the 100-request-per-minute limit.

What you'll learn

  • Why ClickUp's Authorization header does not use a Bearer prefix and how to set it correctly
  • How ClickUp's Workspace-Space-Folder-List-Task hierarchy works and why it requires chained API calls
  • How to store hierarchy IDs in App State to avoid re-fetching on every navigation
  • How to bind task lists to a ListView with status badges and due dates
  • How to handle ClickUp's 100 req/min rate limit through lazy loading and caching
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read45 minutesProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to ClickUp via a FlutterFlow API Call using ClickUp's personal API token in an Authorization header without a 'Bearer' prefix. ClickUp's deep hierarchy — Workspace, Space, Folder, List, Task — requires chained calls to drill down to tasks. Cache Team and Space IDs in App State on first load, then lazy-load tasks only for the opened List to stay within the 100-request-per-minute limit.

Quick facts about this guide
FactValue
ToolClickUp
CategoryProductivity
MethodFlutterFlow API Call
DifficultyIntermediate
Time required45 minutes
Last updatedJuly 2026

Building a Mobile ClickUp Task Client in FlutterFlow

ClickUp organizes everything in a strict hierarchy: Workspace (Team) → Space → Folder → List → Task. You cannot fetch tasks without knowing the List ID, which requires knowing the Folder and Space IDs first. This means your FlutterFlow app walks the hierarchy on first launch and caches the IDs in App State so subsequent navigation doesn't repeat the chain.

The most distinctive aspect of ClickUp's API is its Authorization header format. The personal API token goes directly in the header with no prefix: `Authorization: pk_12345abcde` — not `Authorization: Bearer pk_12345abcde`. Adding 'Bearer' causes a 401 with no helpful error message. This is the single most common ClickUp integration mistake.

ClickUp's token is available from Settings → Apps → API Token. It works on all plans including Free Forever. The 100 req/min rate limit is tight — the tightest in this series — making lazy loading and ID caching essential.

Integration method

FlutterFlow API Call

ClickUp uses a personal API token placed directly in the Authorization request header — critically, without the 'Bearer ' prefix that most APIs require. This raw-token format is the most common source of 401 errors for developers used to Bearer authentication. You then chain GET calls through ClickUp's Workspace-Space-Folder-List hierarchy before reaching tasks, storing IDs in App State to avoid refetching the hierarchy on every navigation.

Prerequisites

  • A ClickUp account (Free Forever plan includes API access)
  • Your ClickUp personal API token from Settings → Apps → API Token
  • At least one Space and List in your ClickUp workspace with some tasks to display
  • A FlutterFlow project open and ready to add API Calls
  • Basic familiarity with FlutterFlow App State variables for storing the hierarchy IDs

Step-by-step guide

1

Retrieve your ClickUp API token and understand the hierarchy

Log into ClickUp and open your profile settings by clicking your avatar in the bottom-left corner, then selecting 'Settings'. Scroll down to the 'Apps' section on the left sidebar and click it. At the bottom of the Apps page you will see an 'API Token' section with a 'Generate' or copy button. Click to reveal your token — it begins with `pk_` followed by a long alphanumeric string. Copy it. Before building anything, it helps to understand the ID chain you will navigate. Open your ClickUp workspace and look at the URL when you are viewing a task: `https://app.clickup.com/{teamId}/v/li/{listId}`. The `teamId` (Workspace ID) and `listId` are both visible in the URL. You can also find the List ID by right-clicking a List in the left sidebar and selecting 'Copy Link'. These IDs are what you will drill to via API calls. In FlutterFlow, go to App Settings → App Constants and add a constant named `clickupToken` set to your API token. Also add `clickupTeamId` — you can find this from the URL or from the GET /team call in the next step. In App State, create these variables to cache the hierarchy: `clickupSpaces` (type JSON, stores the spaces list), `clickupFolders` (type JSON), `clickupLists` (type JSON), and `clickupSelectedListId` (type String). You will populate these as the user navigates down the hierarchy. If you know exactly which List you want to display (for example, a client-specific deliverables tracker), you can hardcode that List ID as an App Constant and skip the hierarchy navigation entirely — jump straight to calling `GET /list/{listId}/task`.

Pro tip: The fastest path to a working integration is to hardcode the List ID as an App Constant and go straight to GET /list/{listId}/task. Reserve the full hierarchy navigation for apps where users need to browse across multiple Spaces and Lists.

Expected result: You have your ClickUp API token in an App Constant, understand the Team-Space-Folder-List-Task ID chain, and have App State variables ready to cache each level.

2

Create a ClickUp API Group with the raw Authorization header

In FlutterFlow, click 'API Calls' in the left navigation panel. Click '+ Add' → 'Create API Group'. Name it 'ClickUp'. Set the Base URL to `https://api.clickup.com/api/v2`. This next part is the most important configuration detail in the entire tutorial. Switch to the Headers section and add one header: - Key: `Authorization` - Value: `{{token}}` Notice there is NO 'Bearer' or 'Basic' prefix — just `{{token}}` by itself. ClickUp validates the token directly in the Authorization header value without any scheme prefix. If you add 'Bearer ' in front of the token, ClickUp returns 401 Unauthorized with an error that looks identical to a wrong-token error, making it very difficult to diagnose. Switch to the Variables tab and add a variable named `token` (type String, default: `FFAppConstants.clickupToken`). This propagates the token to every child API Call automatically. To verify the setup, add a quick test API Call inside the ClickUp group: name it 'Get Teams', method GET, endpoint `/team`. Run it in the Response tab. A successful response returns `{ "teams": [{ "id": "12345678", "name": "Your Workspace Name", ... }] }`. The `id` value is your Team ID — copy this and save it to the `clickupTeamId` App Constant. If you see a 401 response, double-check the Authorization header has no prefix before `{{token}}`.

typescript
1// API Group configuration reference:
2// Name: ClickUp
3// Base URL: https://api.clickup.com/api/v2
4//
5// Headers:
6// Authorization: {{token}} <-- NO 'Bearer ' prefix
7//
8// Group Variable:
9// token (String) — default: FFAppConstants.clickupToken
10//
11// Test call:
12// GET /team — returns { "teams": [{ "id": "...", "name": "..." }] }
13

Pro tip: If your test call returns 401, the first thing to check is whether you have 'Bearer ' in front of the token value. Remove it. The second thing to check is that the token starts with 'pk_' and was copied from Settings → Apps → API Token, not from any other ClickUp settings page.

Expected result: Running 'Get Teams' returns your ClickUp workspace name and Team ID as JSON. You can see the ID to store in the clickupTeamId App Constant.

3

Navigate the Space-Folder-List hierarchy and cache IDs

Add three more API Calls to the ClickUp group to navigate the hierarchy. You will chain these calls using FlutterFlow's Action Flow — each call's result updates an App State variable and triggers a widget rebuild. API Call 1 — 'Get Spaces': Method GET, endpoint `/team/{{teamId}}/space`. Variable: `teamId` (String, default: `FFAppConstants.clickupTeamId`). Key JSON Paths: `$.spaces[*].id`, `$.spaces[*].name`. API Call 2 — 'Get Folders': Method GET, endpoint `/space/{{spaceId}}/folder`. Variable: `spaceId` (String). Key JSON Paths: `$.folders[*].id`, `$.folders[*].name`. API Call 3 — 'Get Lists': Method GET, endpoint `/folder/{{folderId}}/list`. Variable: `folderId` (String). Key JSON Paths: `$.lists[*].id`, `$.lists[*].name`, `$.lists[*].task_count`. Also add a 'Get Folderless Lists' call using `/space/{{spaceId}}/list` for Lists that sit directly in a Space without a Folder — this is common in smaller ClickUp setups. In FlutterFlow, create a navigation screen called 'Browse ClickUp'. Use a ListView bound to 'Get Spaces' as the backend query — tap a Space row to navigate to a Folder view. On the Folder screen, the Backend Query is 'Get Folders' with the selected Space ID passed as a Route Parameter. On the List screen, the Backend Query is 'Get Lists' with the Folder ID as a Route Parameter. For performance: when the user first taps a Space, store the Space ID in App State and store the entire folders response in `clickupFolders` App State. When they then tap a Folder, filter the locally cached folders list rather than making another API call. This converts three sequential API calls into one per navigation level, cutting requests significantly. Rate limit arithmetic: each navigation level costs one API call. Loading Spaces (1 call) + tapping a Space to load Folders (1 call) + tapping a Folder to load Lists (1 call) + loading Tasks for a List (1 call) = 4 calls total to reach any task. At 100 req/min, you can support 25 users simultaneously navigating without hitting the limit — comfortable for most team apps.

typescript
1// API Call reference — hierarchy navigation:
2//
3// Get Spaces:
4// GET /team/{{teamId}}/space
5// $.spaces[*].id, $.spaces[*].name
6//
7// Get Folders:
8// GET /space/{{spaceId}}/folder
9// $.folders[*].id, $.folders[*].name
10//
11// Get Lists (in folder):
12// GET /folder/{{folderId}}/list
13// $.lists[*].id, $.lists[*].name, $.lists[*].task_count
14//
15// Get Folderless Lists (directly in space):
16// GET /space/{{spaceId}}/list
17// $.lists[*].id, $.lists[*].name
18

Pro tip: Many ClickUp workspaces have Lists sitting directly in a Space (not inside any Folder). Always add the folderless-list call alongside the folder call to avoid missing Lists that appear in the ClickUp sidebar but not in the folder hierarchy.

Expected result: The Browse ClickUp screen shows a list of Spaces. Tapping a Space navigates to Folders. Tapping a Folder shows Lists. Each navigation level passes the parent ID as a Route Parameter to the next screen.

4

Fetch tasks for a List and bind to a task ListView

Add one final API Call to the ClickUp group. Name it 'Get Tasks'. Set the method to GET. Add a variable named `listId` (type String). Set the endpoint to `/list/{{listId}}/task`. Optional but highly recommended query parameters to add as Variables: - `subtasks=true` — include subtasks - `include_closed=false` — hide completed tasks (defaults to false anyway) - `order_by=due_date` — sort by due date - `reverse=true` — show most-urgent first (earliest due date at top) Append these as: `/list/{{listId}}/task?order_by=due_date&reverse=true`. Key JSON Paths from the tasks response: - `$.tasks[*].id` — task ID for linking to detail or marking complete - `$.tasks[*].name` — task name - `$.tasks[*].status.status` — status name (e.g., 'to do', 'in progress', 'complete') - `$.tasks[*].status.color` — hex color code for the status badge - `$.tasks[*].due_date` — Unix timestamp in milliseconds (convert in a Custom Function) - `$.tasks[*].assignees[0].username` — first assignee's name - `$.tasks[*].priority.priority` — 'urgent', 'high', 'normal', 'low', or null Create a Task List screen in FlutterFlow. Pass the `listId` as a Route Parameter from the Lists screen. Add a ListView with Backend Query = 'Get Tasks', passing the listId Route Parameter. Inside each ListView item, create a Row containing: 1. A Container with background color set to `$.tasks[*].status.color` (as a hex color) for a visual status indicator 2. A Column with the task name (Text), assignee username (Text, smaller font), and due date (formatted Text) 3. A Checkbox or Icon Button to mark the task complete For the due date, ClickUp returns Unix timestamps in milliseconds. Add a Custom Function to convert this: `DateTime.fromMillisecondsSinceEpoch(int.parse(dueDateMs)).toString()` — or format it more nicely with a date format string. For the 'mark complete' action on the Checkbox: add a Backend API Call action that calls `PATCH /task/{{taskId}}` with a JSON body of `{"status": "complete"}`. Because this is a write operation, consider routing it through a Firebase Cloud Function to keep your API token out of the client. For internal team apps with trusted users, a direct PATCH call from FlutterFlow is acceptable.

typescript
1// Get Tasks API Call:
2// Method: GET
3// Endpoint: /list/{{listId}}/task?order_by=due_date&reverse=true
4// Variable: listId (String)
5//
6// Key JSON Paths:
7// $.tasks[*].id
8// $.tasks[*].name
9// $.tasks[*].status.status
10// $.tasks[*].status.color
11// $.tasks[*].due_date (Unix ms — convert with Custom Function)
12// $.tasks[*].assignees[0].username
13// $.tasks[*].priority.priority
14//
15// Custom Function — formatDueDate:
16// String formatDueDate(String dueDateMs) {
17// if (dueDateMs.isEmpty) return 'No due date';
18// final dt = DateTime.fromMillisecondsSinceEpoch(int.parse(dueDateMs));
19// return '${dt.month}/${dt.day}/${dt.year}';
20// }
21

Pro tip: ClickUp's `due_date` field is a Unix timestamp in milliseconds stored as a String, not a number. Parse it with `int.parse(dueDateMs)` before passing to `DateTime.fromMillisecondsSinceEpoch()`. Tasks with no due date return null for this field — guard with an empty-string check.

Expected result: The Task List screen shows tasks from the selected ClickUp List with color-coded status badges, formatted due dates, and assignee names. The list is sorted by due date with most-urgent tasks at the top.

5

Add tasks and manage rate limits with caching

To add a task from FlutterFlow, create a simple form screen with fields for Task Name, Assignee (a dropdown populated from `GET /list/{listId}/member`), and Due Date (a Date Picker). The 'Add Task' button action calls `POST /list/{{listId}}/task` with a JSON body. For the task body, ClickUp expects specific field names. The minimum required body is `{ "name": "Task name" }`. Additional fields: `"due_date"` (Unix timestamp in ms as a number), `"assignees": [userId]`, `"status": "to do"`, and `"priority": 2` (1=urgent, 2=high, 3=normal, 4=low). In FlutterFlow's API Call configuration for POST /task, switch to the Body tab, set the body type to JSON, and add the fields as Variables. Use a `due_date` variable of type Integer to pass the Unix timestamp. You will need a Custom Function to convert the Date Picker's DateTime value to a Unix millisecond timestamp: `date.millisecondsSinceEpoch`. For rate limit management at 100 req/min: the key practices are (1) do not call hierarchy endpoints on every navigation — cache Space/Folder/List data in App State after the first load; (2) do not auto-refresh the task list on a timer — refresh only when the user pulls to refresh or navigates back to the screen; (3) if you need to load tasks across multiple Lists simultaneously (for a dashboard view), sequence the calls with a small delay between them using a Wait action in the Action Flow rather than firing them all in parallel. Also note that Custom Fields on tasks are returned as an array keyed by field ID, similar to Smartsheet's column IDs — use a Custom Function to extract specific custom field values by ID if needed.

typescript
1// POST /list/{listId}/task body reference:
2// {
3// "name": "{{taskName}}",
4// "assignees": [{{assigneeId}}],
5// "due_date": {{dueDateMs}},
6// "status": "{{status}}",
7// "priority": {{priorityLevel}}
8// }
9//
10// Custom Function — dateToClickUpTimestamp:
11// int dateToClickUpTimestamp(DateTime date) {
12// return date.millisecondsSinceEpoch;
13// }
14

Pro tip: If you'd rather not build the full hierarchy navigation and just want tasks from a known List, hardcode the List ID as an App Constant and go straight to GET /list/{listId}/task. This is the most common pattern for client portals and team dashboards where the List is fixed.

Expected result: The Add Task form submits successfully and the new task appears in the ClickUp web app immediately. Navigating back to the Task List and refreshing shows the new task in the ListView.

Common use cases

Mobile ClickUp task client for remote teams

A FlutterFlow app that lets team members navigate their ClickUp workspace from a phone — browse Spaces and Lists, view tasks with status badges and due dates, mark tasks complete with a tap, and add quick tasks on the go. The hierarchy loads once on first open and is cached for the session.

FlutterFlow Prompt

Build a ClickUp task viewer where I can browse my Spaces, tap into a List, see all tasks with their status and due date, and mark tasks as done by tapping a checkbox.

Copy this prompt to try it in FlutterFlow

Daily standup digest screen

A screen that shows each team member's tasks that are due today or overdue across all Lists in a specific Space. The app drills from Space → Folders → Lists in the background and aggregates tasks with `due_date` before today, displaying them grouped by assignee for the standup meeting.

FlutterFlow Prompt

Create a standup screen showing all team tasks due today or overdue, grouped by assignee. Pull from all lists in our main Space and show task name, status, and due date.

Copy this prompt to try it in FlutterFlow

Client deliverables tracker

A read-only FlutterFlow app that clients access to see the status of deliverables in a dedicated ClickUp List — task name, status, due date, and assignee visible without the full ClickUp interface. Drills directly to the known List ID stored as an App Constant, skipping the hierarchy navigation entirely.

FlutterFlow Prompt

Build a read-only client portal showing tasks from a specific ClickUp List with task name, assignee, due date, and status. Clients should not see other workspaces or lists.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Every API call returns 401 Unauthorized

Cause: The Authorization header contains 'Bearer ' before the token value. ClickUp expects the raw token without any prefix — 'Authorization: pk_12345...' not 'Authorization: Bearer pk_12345...'.

Solution: Open the ClickUp API Group in FlutterFlow and check the Authorization header value. It should contain only `{{token}}` with no text before it. Remove any 'Bearer ' or 'Basic ' prefix. Verify the token App Constant starts with 'pk_' and was copied from Settings → Apps → API Token.

Task list is empty despite navigating to the correct List

Cause: The List ID is incorrect, the List has no tasks, or the `include_closed` parameter is excluding tasks that are in a 'closed' status. Also possible: the JSON Path starts with `$[*]` instead of `$.tasks[*]`.

Solution: Check that the List ID in the Route Parameter matches the actual List ID from the ClickUp URL or the Get Lists API response. In the Response tab, run the Get Tasks call with that List ID and inspect the raw JSON — tasks are under `$.tasks[*]`, not at the root. Add `&include_closed=true` to the endpoint temporarily to confirm whether tasks exist but are filtered out by status.

429 Too Many Requests when navigating the hierarchy quickly

Cause: Each navigation tap fires a new API call and the user or the app is exceeding 100 requests per minute per token.

Solution: Cache hierarchy responses in App State after the first fetch. On subsequent visits to the same Spaces or Folders screen, serve from App State rather than making a new API call. Only refresh the cache when the user explicitly pulls to refresh. For task lists, use a 'last loaded' timestamp and skip the refresh if the data is less than 2 minutes old.

Due date shows as a large number like '1752364800000' instead of a readable date

Cause: ClickUp returns due dates as Unix timestamps in milliseconds stored as a String. Binding the raw value to a Text widget displays the numeric string without any date formatting.

Solution: Add a Custom Function named `formatDueDate` that parses the string as an integer, converts it to a DateTime using `DateTime.fromMillisecondsSinceEpoch()`, and returns a formatted string. Bind the Text widget to the output of this Custom Function rather than directly to the JSON Path value.

typescript
1String formatDueDate(String dueDateMs) {
2 if (dueDateMs.isEmpty) return 'No due date';
3 final dt = DateTime.fromMillisecondsSinceEpoch(int.parse(dueDateMs));
4 return '${dt.month}/${dt.day}/${dt.year}';
5}

Best practices

  • Never add 'Bearer ' before the ClickUp token in the Authorization header — it is the single most common cause of 401 errors in ClickUp API integrations.
  • Cache Team ID, Space IDs, and Folder IDs in App State after the first load and navigate using cached IDs rather than re-fetching the hierarchy on every screen visit.
  • Lazy-load tasks only when a user taps into a specific List — never fan out calls across all Lists on initial page load, as this quickly exceeds the 100 req/min rate limit.
  • Handle null due_date fields gracefully — many tasks have no due date and the field will be null or absent, so guard your due-date Custom Function against null/empty input.
  • For apps where users can add or update tasks, consider routing write calls through a Firebase Cloud Function to avoid shipping the API token in the compiled app binary.
  • Store status colors from `$.tasks[*].status.color` and use them as widget background colors for visual status differentiation rather than hardcoding color values that don't match the user's ClickUp workspace theme.
  • When building for a fixed List (client portal, standup digest), bypass the hierarchy navigation and store the List ID as an App Constant — this reduces setup complexity and API calls significantly.

Alternatives

Frequently asked questions

Why doesn't ClickUp use 'Bearer' before the token like other APIs?

ClickUp implemented its own API key authentication before OAuth Bearer tokens became the universal standard for REST APIs. Rather than breaking existing integrations by switching to Bearer format, they kept the raw-token scheme. The behavior is the same — a token in an Authorization header — but the format differs from RFC 6750 Bearer authentication. Always check the ClickUp API docs for any endpoint rather than assuming Bearer format.

Can I access tasks from multiple Lists in one screen without hitting rate limits?

Yes, but carefully. Loading tasks from 5 Lists simultaneously costs 5 API calls. At 100 req/min per token, you have a budget of roughly 100 calls per minute across all users. For a team dashboard, cache task counts at the List level (available from the Lists endpoint) and load the full task list only when a user drills into a specific List. For summary views, use ClickUp's filtered task endpoints with date or assignee parameters to get a curated view in a single call.

Does ClickUp's Free Forever plan include full API access?

Yes — ClickUp's Free Forever plan includes personal API token access with the same endpoints as paid plans. The primary limitation is that rate limits may be lower on free plans compared to Business or Enterprise plans. Check the current ClickUp API documentation for plan-specific rate limit details.

How do I handle Custom Fields in tasks?

Custom Fields on tasks are returned as an array under `$.tasks[*].custom_fields`, where each element has a `id` (field UUID), `name` (field title), and `value` (the field's value for that task). This is similar to Smartsheet's column-ID system. Build a Custom Function that takes the custom_fields array and a field name or ID, then returns the matching value. Store field IDs in App Constants for the fields you display most often.

Can I receive ClickUp webhooks in my FlutterFlow app to update task status in real time?

Not directly in FlutterFlow — the app does not expose a public URL for incoming webhooks. To receive real-time ClickUp events, set up a Firebase Cloud Function as the webhook receiver (ClickUp sends a POST to your function URL when tasks change), store the updated data in Firestore, and use FlutterFlow's Firestore real-time listener to update your app's UI when the Firestore document changes.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.