Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with ClickUp

To integrate Replit with ClickUp, generate an API key from your ClickUp profile settings, store it in Replit Secrets (lock icon πŸ”’), and use the ClickUp API v2 from your Python or Node.js backend to create and manage tasks, lists, and spaces. ClickUp's all-in-one structure (spaces, folders, lists, tasks) requires understanding the hierarchy before making API calls.

What you'll learn

  • How to generate a ClickUp API key and understand the workspace hierarchy
  • How to store ClickUp credentials securely in Replit Secrets
  • How to create, update, and query tasks in ClickUp from Python and Node.js
  • How to use ClickUp custom fields and set up webhooks for real-time event handling
  • How to deploy your Replit app for reliable ClickUp webhook reception
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate17 min read30 minutesProductivityMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with ClickUp, generate an API key from your ClickUp profile settings, store it in Replit Secrets (lock icon πŸ”’), and use the ClickUp API v2 from your Python or Node.js backend to create and manage tasks, lists, and spaces. ClickUp's all-in-one structure (spaces, folders, lists, tasks) requires understanding the hierarchy before making API calls.

Why Connect Replit to ClickUp?

ClickUp has grown rapidly to become one of the most feature-rich project management platforms available, with over 10 million users and a comprehensive API that exposes virtually every feature. Unlike task-focused tools, ClickUp's hierarchy β€” workspaces contain spaces, spaces contain folders and lists, and lists contain tasks β€” means there are many integration points where automation can add value.

Connecting Replit to ClickUp enables you to automate task creation when specific events happen in your app (new customer onboarding, form submission, deployment trigger), sync work items between ClickUp and your product's data model, update custom fields programmatically, and build custom dashboards that surface ClickUp data in your own interface. ClickUp's custom fields feature is particularly powerful for integrations β€” you can store app-specific metadata (like customer IDs, order numbers, or external URLs) directly on ClickUp tasks.

Replit's Secrets system (lock icon πŸ”’) keeps your ClickUp API key encrypted and out of your codebase. With Replit's deployment options, you get the stable HTTPS URL needed to receive ClickUp webhook events, enabling two-way sync between your app and your team's project management workflow.

Integration method

Standard API Integration

You connect Replit to ClickUp by generating an API key from your ClickUp profile, storing it in Replit Secrets, and calling the ClickUp API v2 from your Python or Node.js server code. All requests authenticate via the Authorization header with the API key. For multi-user apps where each user connects their own ClickUp workspace, ClickUp also supports OAuth 2.0.

Prerequisites

  • A Replit account with a Python or Node.js project created
  • A ClickUp account with at least one workspace, space, and list
  • Access to ClickUp profile settings to generate an API key
  • Basic familiarity with REST APIs and JSON
  • Node.js 18+ or Python 3.10+ (both available on Replit by default)

Step-by-step guide

1

Generate a ClickUp API Key and Understand the Workspace Hierarchy

In ClickUp, click your profile avatar in the bottom-left corner and select 'Settings'. In the left sidebar, click 'Apps'. Under 'API Token', click 'Generate' (or copy your existing token if one is shown). This is your personal API key β€” it provides access to all workspaces and spaces your ClickUp account can access. Before making API calls, you need to understand ClickUp's hierarchy to target the right objects. The structure is: Team (workspace) β†’ Space β†’ Folder (optional) β†’ List β†’ Task. Every object has a numeric ID that you use in API calls. To find your IDs, make a GET request to https://api.clickup.com/api/v2/team with your API key in the Authorization header. This returns your team ID (workspace ID). Then call /team/{team_id}/space to get space IDs. Then call /space/{space_id}/list or /folder/{folder_id}/list to get list IDs. Store the IDs you plan to use in Replit Secrets. Alternatively, ClickUp embeds IDs in the URL when you navigate the web app. When viewing a list, the URL is https://app.clickup.com/{team_id}/v/l/{list_id}. Copy the numeric list ID from the URL for the list where your integration will create tasks. ClickUp also supports OAuth 2.0 for multi-user integrations where each user connects their own workspace. For single-workspace server-to-server automation, the API key approach is simpler and sufficient.

discover_ids.py
1# Quick discovery script to find your ClickUp IDs
2import requests
3import os
4
5# Temporarily use the key here to discover IDs
6# Then store everything in Replit Secrets
7API_KEY = "your-clickup-api-key-here" # Replace, then move to Secrets
8
9headers = {
10 "Authorization": API_KEY, # ClickUp does NOT use 'Bearer' prefix
11 "Content-Type": "application/json"
12}
13
14# Get teams (workspaces)
15teams_resp = requests.get("https://api.clickup.com/api/v2/team", headers=headers)
16teams = teams_resp.json().get("teams", [])
17for team in teams:
18 print(f"Team: {team['name']} β€” ID: {team['id']}")
19
20 # Get spaces in each team
21 spaces_resp = requests.get(
22 f"https://api.clickup.com/api/v2/team/{team['id']}/space",
23 headers=headers
24 )
25 for space in spaces_resp.json().get("spaces", []):
26 print(f" Space: {space['name']} β€” ID: {space['id']}")
27
28 # Get lists in each space (not in folders)
29 lists_resp = requests.get(
30 f"https://api.clickup.com/api/v2/space/{space['id']}/list",
31 headers=headers
32 )
33 for lst in lists_resp.json().get("lists", []):
34 print(f" List: {lst['name']} β€” ID: {lst['id']}")

Pro tip: Run this script once to map your ClickUp hierarchy, note the IDs you need, then delete the hardcoded API key and store everything in Replit Secrets. The IDs are stable β€” they do not change unless you delete and recreate spaces or lists.

Expected result: The script prints your ClickUp teams, spaces, and lists with their numeric IDs.

2

Store ClickUp Credentials in Replit Secrets

Open your Replit project and click the lock icon πŸ”’ in the left sidebar to open the Secrets pane. Add the following secrets: - Key: CLICKUP_API_KEY β€” Value: your ClickUp API key (the personal token from Settings > Apps) - Key: CLICKUP_TEAM_ID β€” Value: your team (workspace) numeric ID - Key: CLICKUP_LIST_ID β€” Value: the numeric ID of your primary task list If you need to create tasks in multiple lists, add a Secret for each: CLICKUP_BUGS_LIST_ID, CLICKUP_ONBOARDING_LIST_ID, etc. Click 'Add Secret' after entering each key-value pair. Replit encrypts all Secret values with AES-256 at rest and injects them as environment variables at runtime. They never appear in your file tree or Git history. In Python, access them with os.environ['CLICKUP_API_KEY']. In Node.js, use process.env.CLICKUP_API_KEY. Note: ClickUp's Authorization header format is unusual β€” unlike most APIs, it does NOT use 'Bearer' prefix. The header value is just the raw API key: Authorization: pk_your_key_here. This trips up many developers who assume Bearer format. The code examples in the next steps use the correct format.

Pro tip: After adding Secrets in Replit, click 'Deploy' to trigger a new deployment β€” production deployments only pick up Secrets that were present at deploy time.

Expected result: CLICKUP_API_KEY, CLICKUP_TEAM_ID, and CLICKUP_LIST_ID appear in the Replit Secrets pane with values hidden.

3

Create and Manage Tasks with Python

The ClickUp API v2 uses standard HTTP methods with JSON request and response bodies. The base URL is https://api.clickup.com/api/v2/. One important quirk: the Authorization header does not use 'Bearer' β€” you pass the API key directly as the header value. Task creation in ClickUp supports rich metadata: priority levels (1=urgent, 2=high, 3=normal, 4=low), assignees by user ID, tags, custom fields, due dates as Unix timestamps (milliseconds), and time estimates. The example below covers the most common operations: creating a task, updating its status, setting custom fields, and listing tasks with filters. Custom fields require their field ID (UUID) and a value formatted according to the field type. Find custom field IDs by calling GET /list/{list_id}/field which returns all custom fields defined for a list. Store frequently used custom field IDs as Replit Secrets.

clickup_client.py
1import os
2import requests
3import time
4from typing import Optional, List, Dict
5
6API_KEY = os.environ["CLICKUP_API_KEY"]
7TEAM_ID = os.environ["CLICKUP_TEAM_ID"]
8LIST_ID = os.environ["CLICKUP_LIST_ID"]
9BASE_URL = "https://api.clickup.com/api/v2"
10
11# ClickUp uses API key directly β€” NO 'Bearer' prefix
12HEADERS = {
13 "Authorization": API_KEY,
14 "Content-Type": "application/json"
15}
16
17# Priority constants
18URGENT, HIGH, NORMAL, LOW = 1, 2, 3, 4
19
20def create_task(
21 name: str,
22 description: str = "",
23 priority: int = NORMAL,
24 due_date: Optional[str] = None, # Format: 'YYYY-MM-DD'
25 assignees: Optional[List[int]] = None,
26 tags: Optional[List[str]] = None,
27 custom_fields: Optional[List[Dict]] = None
28) -> dict:
29 """Create a task in the configured list."""
30 task_data = {
31 "name": name,
32 "description": description,
33 "priority": priority,
34 "assignees": assignees or [],
35 "tags": tags or []
36 }
37
38 if due_date:
39 # ClickUp requires due_date as Unix timestamp in milliseconds
40 import datetime
41 dt = datetime.datetime.strptime(due_date, "%Y-%m-%d")
42 task_data["due_date"] = int(dt.timestamp() * 1000)
43
44 if custom_fields:
45 task_data["custom_fields"] = custom_fields
46
47 response = requests.post(
48 f"{BASE_URL}/list/{LIST_ID}/task",
49 json=task_data,
50 headers=HEADERS
51 )
52 response.raise_for_status()
53 task = response.json()
54 print(f"Created task '{task['name']}' β€” ID: {task['id']}")
55 return task
56
57def update_task_status(task_id: str, status_name: str) -> dict:
58 """Update a task's status. Status names are case-sensitive and list-specific."""
59 response = requests.put(
60 f"{BASE_URL}/task/{task_id}",
61 json={"status": status_name},
62 headers=HEADERS
63 )
64 response.raise_for_status()
65 return response.json()
66
67def set_custom_field(task_id: str, field_id: str, value) -> None:
68 """Set a custom field value on a task."""
69 response = requests.post(
70 f"{BASE_URL}/task/{task_id}/field/{field_id}",
71 json={"value": value},
72 headers=HEADERS
73 )
74 response.raise_for_status()
75
76def list_tasks(statuses: Optional[List[str]] = None, page: int = 0) -> List[dict]:
77 """List tasks in the configured list with optional status filter."""
78 params = {"page": page}
79 if statuses:
80 # ClickUp accepts repeated query params for multiple statuses
81 for status in statuses:
82 params[f"statuses[]"] = status
83
84 response = requests.get(
85 f"{BASE_URL}/list/{LIST_ID}/task",
86 params=params,
87 headers=HEADERS
88 )
89 response.raise_for_status()
90 return response.json().get("tasks", [])
91
92def add_comment(task_id: str, comment_text: str) -> dict:
93 """Add a comment to a task."""
94 response = requests.post(
95 f"{BASE_URL}/task/{task_id}/comment",
96 json={"comment_text": comment_text, "notify_all": False},
97 headers=HEADERS
98 )
99 response.raise_for_status()
100 return response.json()
101
102if __name__ == "__main__":
103 task = create_task(
104 name="Investigate checkout error for user@example.com",
105 description="User reported a 500 error on checkout. Order ID: 12345.",
106 priority=HIGH,
107 due_date="2026-04-05",
108 tags=["bug", "checkout"]
109 )
110
111 # Add a comment with context
112 add_comment(task["id"], "Error logged at 2026-03-30 14:23 UTC. Stack trace attached.")
113
114 # Get open tasks
115 open_tasks = list_tasks(statuses=["Open", "In Progress"])
116 print(f"Open tasks: {len(open_tasks)}")
117 for t in open_tasks:
118 print(f" [{t.get('priority', {}).get('priority', 'none')}] {t['name']}")

Pro tip: ClickUp status names are defined per-list and are case-sensitive. Call GET /list/{list_id} to see the exact status names ('Open', 'in progress', 'complete', etc.) before using them in update calls.

Expected result: Running the script creates a ClickUp task with priority and tags, adds a comment, and lists open tasks from the configured list.

4

Build a Node.js Server with Webhook Support

For Node.js projects, no official SDK is required β€” fetch or axios handle the ClickUp API cleanly. The Express server below exposes REST endpoints for common ClickUp operations and includes a webhook handler. ClickUp webhooks are registered per team and can target specific events: taskCreated, taskUpdated, taskDeleted, taskStatusUpdated, taskPriorityUpdated, and more. When you register a webhook, ClickUp returns a webhook ID and a secret. The secret is used to verify that incoming webhook requests are genuinely from ClickUp β€” always verify signatures in production. ClickUp signs webhook payloads using HMAC-SHA256 with the webhook secret. The signature is sent in the X-Signature header (base64-encoded). Verify it by computing HMAC-SHA256 of the raw request body using the webhook secret and comparing with the header value. Note that ClickUp webhooks are workspace-level and you can filter by event type and list ID when registering them. This lets you receive only the events relevant to your integration.

server.js
1const express = require('express');
2const crypto = require('crypto');
3
4const app = express();
5
6// Use raw body for webhook signature verification
7app.use('/clickup/webhook', express.raw({ type: 'application/json' }));
8app.use(express.json());
9
10const API_KEY = process.env.CLICKUP_API_KEY;
11const TEAM_ID = process.env.CLICKUP_TEAM_ID;
12const LIST_ID = process.env.CLICKUP_LIST_ID;
13const WEBHOOK_SECRET = process.env.CLICKUP_WEBHOOK_SECRET || '';
14const BASE_URL = 'https://api.clickup.com/api/v2';
15
16// ClickUp does NOT use 'Bearer' prefix β€” raw API key in Authorization header
17const headers = {
18 'Authorization': API_KEY,
19 'Content-Type': 'application/json'
20};
21
22async function clickupRequest(path, method = 'GET', body = null) {
23 const response = await fetch(`${BASE_URL}${path}`, {
24 method,
25 headers,
26 body: body ? JSON.stringify(body) : undefined
27 });
28 if (!response.ok) {
29 const error = await response.text();
30 throw new Error(`ClickUp API error ${response.status}: ${error}`);
31 }
32 return response.json();
33}
34
35// Create a task
36app.post('/tasks', async (req, res) => {
37 const { name, description, priority, dueDate, tags } = req.body;
38 if (!name) return res.status(400).json({ error: 'name is required' });
39
40 const taskData = {
41 name,
42 description: description || '',
43 priority: priority || 3, // 1=urgent, 2=high, 3=normal, 4=low
44 tags: tags || []
45 };
46
47 if (dueDate) {
48 taskData.due_date = new Date(dueDate).getTime(); // Milliseconds timestamp
49 }
50
51 try {
52 const task = await clickupRequest(`/list/${LIST_ID}/task`, 'POST', taskData);
53 res.json({ success: true, id: task.id, url: task.url });
54 } catch (err) {
55 res.status(500).json({ error: err.message });
56 }
57});
58
59// Update task status
60app.patch('/tasks/:id/status', async (req, res) => {
61 const { status } = req.body;
62 try {
63 const task = await clickupRequest(`/task/${req.params.id}`, 'PUT', { status });
64 res.json({ success: true, status: task.status?.status });
65 } catch (err) {
66 res.status(500).json({ error: err.message });
67 }
68});
69
70// Register a webhook (call once after deploying)
71app.post('/register-webhook', async (req, res) => {
72 const callbackUrl = `https://${req.headers.host}/clickup/webhook`;
73 try {
74 const webhook = await clickupRequest(`/team/${TEAM_ID}/webhook`, 'POST', {
75 endpoint: callbackUrl,
76 events: ['taskCreated', 'taskStatusUpdated', 'taskUpdated'],
77 list_id: LIST_ID // Optional: filter by list
78 });
79 res.json({
80 success: true,
81 webhookId: webhook.id,
82 secret: webhook.secret,
83 message: 'Store the secret as CLICKUP_WEBHOOK_SECRET in Replit Secrets'
84 });
85 } catch (err) {
86 res.status(500).json({ error: err.message });
87 }
88});
89
90// Receive ClickUp webhook events
91app.post('/clickup/webhook', (req, res) => {
92 // Verify HMAC signature
93 if (WEBHOOK_SECRET) {
94 const signature = req.headers['x-signature'];
95 const computed = crypto
96 .createHmac('sha256', WEBHOOK_SECRET)
97 .update(req.body) // Raw body buffer
98 .digest('base64');
99 if (signature !== computed) {
100 console.warn('Invalid ClickUp webhook signature');
101 return res.status(403).json({ error: 'Invalid signature' });
102 }
103 }
104
105 const payload = JSON.parse(req.body);
106 const { event, task_id, history_items } = payload;
107
108 console.log(`ClickUp webhook: ${event} for task ${task_id}`);
109
110 if (event === 'taskStatusUpdated') {
111 const newStatus = history_items?.[0]?.after?.status;
112 console.log(`Task ${task_id} status changed to: ${newStatus}`);
113 // Update your database, trigger notifications, etc.
114 }
115
116 res.json({ received: true });
117});
118
119app.listen(3000, '0.0.0.0', () => {
120 console.log('ClickUp integration server running on port 3000');
121});

Pro tip: When you call /register-webhook, save the returned 'secret' value as CLICKUP_WEBHOOK_SECRET in Replit Secrets immediately. ClickUp only shows the webhook secret once at registration time.

Expected result: The server starts, POST /tasks creates ClickUp tasks, and POST /register-webhook returns a webhook ID and secret to store in Secrets.

5

Deploy and Configure Production Webhooks

ClickUp webhooks require a deployed, publicly accessible URL β€” development Replit URLs are temporary and go offline when you close the browser tab. Deploy your Replit app before registering webhooks. Click 'Deploy' in Replit's toolbar and choose Autoscale deployment for web apps that create tasks in response to user actions. Autoscale is cost-efficient and ClickUp retries failed webhook deliveries, so cold starts are acceptable. Choose Reserved VM if you are running a continuous background process that must be always available with no cold-start latency. After deploying, you have a stable URL at https://your-app.replit.app. Make a POST request to /register-webhook (or call the ClickUp API directly) to register your webhook. The response includes a webhook secret β€” save this immediately as CLICKUP_WEBHOOK_SECRET in Replit Secrets, then trigger a new deployment so the webhook handler picks it up. To verify the integration is working, create a task in your ClickUp list through the web app and check your Replit deployment logs for the incoming event. You can also list your registered webhooks via GET https://api.clickup.com/api/v2/team/{team_id}/webhook to confirm registration and see the webhook URL. For production apps, monitor your ClickUp API usage β€” the API has rate limits of 100 requests per minute per token. If your app creates many tasks in bursts, implement a queue to smooth out the request rate.

Pro tip: ClickUp webhook events include a history_items array that shows what changed (before and after values). This is especially useful for status change events β€” you can see both the previous and new status without making an additional API call.

Expected result: Your app is deployed at a stable replit.app URL, the ClickUp webhook is registered, and task events appear in your deployment logs.

Common use cases

Customer Onboarding Task Generation

When a new customer signs up or a deal closes in your CRM, automatically create a ClickUp task list with all the onboarding steps pre-populated. Each step becomes a task with the right assignee, due date, and custom fields filled in with the customer's details. This eliminates manual setup and ensures every customer gets the same onboarding experience.

Replit Prompt

Build a Flask endpoint that receives new customer data (name, email, plan) and creates a ClickUp task in the 'New Customers' list with the customer's name as the task title, their email in a custom field, and a due date 7 days from today using the ClickUp API v2.

Copy this prompt to try it in Replit

Bug Tracking Integration

Connect your Replit app's error monitoring system to ClickUp so that every unhandled exception automatically creates a ClickUp task in the Engineering team's bug list. The task includes the error message, stack trace, and a link back to the error monitoring dashboard, giving engineers everything they need to investigate without switching tools.

Replit Prompt

Create an Express error handler middleware that catches unhandled errors, creates a ClickUp task in a 'Bugs' list with the error message and stack trace, and assigns it to the on-call engineer based on a rotation stored in the database.

Copy this prompt to try it in Replit

Webhook-Driven Status Sync

When a ClickUp task status changes (e.g., from 'In Progress' to 'Done'), receive the webhook event in your Replit app and update the corresponding record in your database or trigger a downstream action like sending a customer notification. This keeps your product's data in sync with your team's workflow without polling.

Replit Prompt

Set up a Flask endpoint at /clickup/webhook that receives ClickUp task status change events, verifies the webhook signature, and updates the corresponding order status in a PostgreSQL database when a fulfillment task is marked complete.

Copy this prompt to try it in Replit

Troubleshooting

API returns 401 ECODE_OAUTH_INVALID β€” authentication fails

Cause: The Authorization header is formatted incorrectly. ClickUp does not use the 'Bearer' prefix β€” the API key is passed directly as the header value. Using 'Bearer pk_your_key' instead of 'pk_your_key' causes authentication failure.

Solution: Check your Authorization header. It must be the raw API key value, not 'Bearer {key}'. Update your HEADERS dictionary to set 'Authorization' to just os.environ['CLICKUP_API_KEY'] without any prefix.

typescript
1# Correct ClickUp authorization β€” NO 'Bearer' prefix
2headers = {
3 'Authorization': os.environ['CLICKUP_API_KEY'], # correct
4 # NOT: 'Authorization': f'Bearer {os.environ["CLICKUP_API_KEY"]}'
5}

Task creation fails with 'List not found' or 400 error on list ID

Cause: The list ID stored in CLICKUP_LIST_ID is wrong, or the list has been deleted or moved. ClickUp list IDs are numeric strings and can change if a list is recreated.

Solution: Re-run the discover_ids.py script from Step 1 to get the current list ID. Update CLICKUP_LIST_ID in Replit Secrets. Verify the list exists in ClickUp and has not been archived. The list ID also appears in the ClickUp URL when you open a list view.

Webhook events are not received even though the webhook is registered

Cause: The webhook was registered against a development URL that goes offline when the browser tab is closed, or the app was not yet deployed when the webhook was registered.

Solution: Deploy your Replit app to get a stable replit.app URL. Then re-register the webhook using the deployed URL. Verify the webhook is active by listing webhooks at GET /team/{team_id}/webhook and checking the 'health' field in the response.

Status update fails with 'Status not found' error

Cause: ClickUp status names are defined per-list and are case-sensitive. Status names vary between lists and workspaces β€” 'In Progress', 'in progress', and 'IN PROGRESS' are all different.

Solution: Call GET /list/{list_id} to see the exact statuses defined for your list. The response includes a 'statuses' array with the exact name strings to use. Store commonly used status names as constants in your code to avoid typos.

typescript
1# Discover statuses for your list
2response = requests.get(f"{BASE_URL}/list/{LIST_ID}", headers=HEADERS)
3list_data = response.json()
4for status in list_data.get('statuses', []):
5 print(f"Status: '{status['status']}' β€” color: {status['color']}")

Best practices

  • Store your ClickUp API key in Replit Secrets (lock icon πŸ”’) β€” never in source code, .env files committed to Git, or client-side JavaScript.
  • Remember that ClickUp's Authorization header does NOT use 'Bearer' prefix β€” use the raw API key value directly.
  • Store workspace IDs, list IDs, and custom field IDs as Replit Secrets so you can change target lists without redeploying code.
  • Use ClickUp's priority levels (1-4) consistently across your integration β€” define constants in your code for URGENT=1, HIGH=2, NORMAL=3, LOW=4.
  • Convert due dates to Unix timestamps in milliseconds (multiply seconds by 1000) since ClickUp uses millisecond timestamps throughout the API.
  • Save the webhook secret returned at registration time immediately β€” store it as CLICKUP_WEBHOOK_SECRET in Replit Secrets, as ClickUp only shows it once.
  • Deploy your app before registering ClickUp webhooks β€” use your stable replit.app URL, not the temporary development URL.
  • Implement rate limit handling (100 requests/minute): queue task creation requests and add 600ms delays between batches to avoid hitting limits.

Alternatives

Frequently asked questions

How do I store my ClickUp API key in Replit?

Click the lock icon πŸ”’ in the left sidebar of your Replit project to open the Secrets pane. Add CLICKUP_API_KEY with your personal API token from ClickUp Settings > Apps. Access it in Python with os.environ['CLICKUP_API_KEY'] or in Node.js with process.env.CLICKUP_API_KEY. Important: ClickUp's Authorization header does not use a Bearer prefix β€” pass the raw key value directly.

Does Replit work with ClickUp on the free plan?

Yes. The ClickUp API v2 is available on all plans including the free tier. The free tier allows unlimited tasks, members, and spaces with basic features. The ClickUp API has a rate limit of 100 requests per minute, which is sufficient for most integrations. Replit's free tier supports outbound API calls without restriction.

How do I find my ClickUp list ID?

Find your list ID in the ClickUp URL when viewing a list: https://app.clickup.com/{team_id}/v/l/{list_id}. The numeric string in the {list_id} position is the ID. You can also use the discovery script in Step 1 to list all spaces and lists with their IDs by calling the ClickUp API programmatically.

Why is my ClickUp API returning 401 even with the correct key?

The most common cause is using 'Bearer' prefix in the Authorization header. ClickUp is unusual in that it does not use the Bearer token format β€” the Authorization header value should be your raw API key (e.g., pk_12345678_ABCDEF) without any prefix. Remove 'Bearer ' from your header value.

How do ClickUp webhooks work with Replit?

Deploy your Replit app to get a stable URL, then register a webhook via POST https://api.clickup.com/api/v2/team/{team_id}/webhook with your callback URL and desired event types. Save the returned webhook secret as CLICKUP_WEBHOOK_SECRET in Replit Secrets. ClickUp signs payloads with HMAC-SHA256 β€” verify the X-Signature header against a computed hash of the raw request body.

What is the ClickUp API rate limit?

ClickUp allows 100 API requests per minute per API token. If you exceed this limit, the API returns 429 Too Many Requests. Implement request queuing and add delays between bulk operations. For high-volume automations, consider batching task creation or using ClickUp's bulk endpoints where available.

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.