ClickUp Integration Playbook — Solanasis
Last updated: 2026-03-24 Architecture: Hybrid — MCP Connector (primary) + ClickUp API v2 (gap-filler) Related: AI-Native Time Logging Playbook
Table of Contents
- Architecture Overview
- MCP Connector — Complete Tool Reference
- MCP Connector — Gaps & Limitations
- ClickUp API v2 — Complete Reference
- ClickUp API v2 — Endpoint Deep Dive
- Python Integration Patterns
- Webhooks & Automation
- Troubleshooting & Maintenance
1. Architecture Overview
Integration Paths
PRIMARY PATH (daily operations):
┌─────────────┐ ┌───────────────────┐ ┌──────────┐
│ Claude Code │───>│ MCP Connector │───>│ ClickUp │
│ (tool calls) │ │ (claude.ai) │ │ API │
└─────────────┘ └───────────────────┘ └──────────┘
FALLBACK PATH (when MCP can't do it):
┌─────────────┐ ┌───────────────────┐ ┌──────────┐
│ Claude Code │───>│ Python Script │───>│ ClickUp │
│ (Bash tool) │ │ (requests lib) │ │ API v2 │
└─────────────┘ └───────────────────┘ └──────────┘
When to Use Which Path
| Scenario | Use |
|---|---|
| Create/update/delete tasks | MCP |
| Search for tasks, docs, anything | MCP |
| Track time (start/stop/manual) | MCP |
| Read/write comments | MCP |
| Create/update docs and pages | MCP |
| Navigate workspace hierarchy | MCP |
| Manage tags, dependencies, links | MCP |
| Send chat messages | MCP |
| Set custom field values | MCP |
| Goals / OKR management | API script |
| Task checklists (sub-items) | API script |
| Create/manage templates | API script |
| Create/manage saved views | API script |
| Set up webhooks | API script |
| Manage guests/permissions | API script |
| Manage user groups | API script |
| Bulk reporting/analytics | API script |
Current Setup Status
The ClickUp MCP connector is connected via Claude.ai’s built-in connectors (Settings > Connected Apps). It works with both Claude.ai (Cowork) and Claude Code sessions. No local MCP server setup is required.
To verify the connector is working:
Ask Claude to call clickup_get_workspace_hierarchy — if it returns your spaces/folders/lists, the connector is live.
If the connector stops working:
- Go to claude.ai > Settings > Connected Apps
- Check that ClickUp shows as “Connected”
- If disconnected, click “Connect” and re-authorize via OAuth
- Restart your Claude Code session
Alternative: Self-hosted MCP server (not currently used):
claude mcp add --transport http clickup https://mcp.clickup.com/mcpThis adds ClickUp’s official MCP server directly to Claude Code. Useful if the Claude.ai connector is unavailable. Requires OAuth re-auth on first use.
2. MCP Connector — Complete Tool Reference
The Claude.ai ClickUp connector provides 49 tools across 12 categories.
2.1 Search (1 tool)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_search | Universal search across entire workspace — tasks, docs, dashboards, attachments, whiteboards, chat messages, forms | keywords, filters.asset_types (task/doc/whiteboard/dashboard/attachment/chat), filters.assignees, filters.task_statuses (unstarted/active/done/closed/archived), filters.location (projects=Space IDs, categories=Folder IDs, subcategories=List IDs), filters.created_date_from/to, filters.due_date_from/to, sort, count, cursor |
Example use cases:
- “Find all tasks tagged ‘urgent’ assigned to me” →
clickup_searchwith assignee filter - “Search for the onboarding doc” →
clickup_searchwithkeywords: "onboarding",asset_types: ["doc"] - “What tasks are due this week?” →
clickup_searchwithdue_date_from/tofilters
2.2 Tasks (7 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_create_task | Create a task in a specific list | name (required), list_id (required), description, markdown_description, assignees, priority (urgent/high/normal/low), due_date, start_date, tags, status, task_type, parent (for subtasks), custom_fields |
clickup_get_task | Get task details by ID (supports custom IDs like ‘DEV-1234’) | task_id (required), subtasks (true to include), detail_level (summary/detailed) |
clickup_update_task | Update any task property | task_id (required), plus any field to change: name, description, status, priority, due_date, assignees, custom_fields, time_estimate, task_type |
clickup_delete_task | Delete a task (always confirm with user first) | task_id (required) |
clickup_filter_tasks | Filter tasks with combined criteria (AND across types, OR within) | tags, list_ids, folder_ids, space_ids, statuses, assignees (numeric IDs), due_date_from/to, date_done_from/to, include_closed, order_by, page |
clickup_move_task | Move task to a different home list | task_id, list_id (destination) |
clickup_add_task_to_list | Add task to an additional list (keeps original) | task_id, list_id |
Also: clickup_remove_task_from_list — remove from additional list (not home list)
Common workflow — Create a client task:
clickup_get_listwithlist_name: "Client Tasks"→ get list_idclickup_create_taskwithname,list_id,assignees: ["me"],priority: "high",due_date: "2026-04-01"
Common workflow — Find and update a task:
clickup_searchwithkeywords: "website redesign"→ get task_idclickup_update_taskwithtask_id,status: "in progress"
2.3 Time Tracking (6 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_start_time_tracking | Start a timer on a task (only one at a time) | task_id (required), description, billable, tags |
clickup_stop_time_tracking | Stop the running timer | description, tags |
clickup_add_time_entry | Add a manual time entry | task_id (required), start (required, YYYY-MM-DD HH:MM), duration (e.g., “1h 30m”) or end_time, description, billable, tags |
clickup_get_task_time_entries | Get time entries for one task | task_id (required), start_date, end_date, is_billable |
clickup_get_current_time_entry | Check if a timer is running | (no required params) |
clickup_get_time_entries | Get time entries across all tasks in a date range (reporting) | start_date (required), end_date (required), assignee_id, is_billable |
Common workflow — Track time on a task:
clickup_searchwithkeywords: "security assessment"→ get task_idclickup_start_time_trackingwithtask_id,description: "Initial review"- (work happens)
clickup_stop_time_tracking
Common workflow — Weekly time report:
clickup_get_time_entrieswithstart_date: "2026-03-17",end_date: "2026-03-23"
2.4 Comments (3 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_task_comments | Get all comments on a task (includes reply_count) | task_id (required), start (timestamp for pagination), start_id |
clickup_create_task_comment | Add a comment to a task | task_id (required), comment_text (required), notify_all, assignee |
clickup_get_threaded_comments | Get replies to a specific comment | comment_id (required) |
2.5 Documents (4 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_create_document | Create a doc in a space/folder/list | name (required), parent.id + parent.type (required — 4=space, 5=folder, 6=list, 7=everything, 12=workspace), visibility (PUBLIC/PRIVATE/PERSONAL/HIDDEN), create_page (boolean) |
clickup_create_document_page | Add a page to a document | document_id (required), name (required), content (required), content_format (text/md or text/plain), parent_page_id (for sub-pages) |
clickup_update_document_page | Update page content (WARNING: replaces entire content) | document_id (required), page_id (required), content, name, content_format |
clickup_list_document_pages | List all pages in a document | document_id (required), max_page_depth |
Pro Tip:
update_document_pagedoes a full replace of content. Always read the page first, then send the full content with your changes included.
2.6 Hierarchy (8 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_workspace_hierarchy | Get spaces → folders → lists tree | max_depth (0=spaces, 1=+folders, 2=+lists), limit (max 50), cursor, space_ids |
clickup_create_list | Create a list in a space | name (required), space_name or space_id, content, due_date, priority, assignee |
clickup_create_list_in_folder | Create a list inside a folder | name (required), folder_id (required), content, status |
clickup_get_list | Look up a list by name or ID | list_name or list_id |
clickup_update_list | Update list name/content/status | list_id (required), name, content, status |
clickup_create_folder | Create a folder in a space | name (required), space_name or space_id, override_statuses |
clickup_get_folder | Look up a folder by name or ID | folder_id or folder_name + space_name/space_id |
clickup_update_folder | Update folder name/statuses | folder_id (required), name, override_statuses |
2.7 Members (3 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_workspace_members | List all workspace members | (no required params) |
clickup_find_member_by_name | Find a member by name or email | name_or_email (required) |
clickup_resolve_assignees | Convert names/emails/“me” to user IDs | assignees array (required) — e.g., ["me"] or ["john@example.com"] |
Pro Tip: Most task tools auto-resolve assignees. Use
resolve_assigneesonly when you need IDs separately (e.g., for search filters).
2.8 Tags (2 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_add_tag_to_task | Add an existing tag to a task | task_id (required), tag_name (required) — tag must already exist in space |
clickup_remove_tag_from_task | Remove a tag from a task | task_id (required), tag_name (required) |
Limitation: MCP cannot create new tags — only add/remove existing ones. To create a new tag, use the API or create it manually in ClickUp first.
2.9 Dependencies & Links (4 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_add_task_dependency | Set a dependency between tasks | task_id (required), depends_on (required), type: waiting_on or blocking |
clickup_remove_task_dependency | Remove a dependency | task_id, depends_on, type |
clickup_add_task_link | Create bidirectional link between tasks | task_id (required), links_to (required) |
clickup_remove_task_link | Remove a task link | task_id, links_to |
Dependency types:
waiting_on— task_id cannot start until depends_on is doneblocking— task_id is blocking depends_on from starting
2.10 Chat (4 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_chat_channels | List chat channels with members | limit (1-100), cursor |
clickup_send_chat_message | Send a message to a channel | channel_id (required), content (required), content_format (text/md), type (message/post), assignee, followers |
clickup_get_chat_channel_messages | Get messages from a channel | channel_id (required), limit, cursor |
clickup_get_chat_message_replies | Get threaded replies to a message | message_id (required), limit, cursor |
2.11 Custom Fields (1 tool)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_custom_fields | Get field definitions at any hierarchy level | list_id, folder_id, space_id, include_workspace (boolean) |
Note: To set custom field values, use
clickup_create_taskorclickup_update_taskwith thecustom_fieldsarray:[{"id": "field_id", "value": "field_value"}]
2.12 Reporting (2 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_get_task_time_in_status | Time spent in each status for one task | task_id (required) — requires “Total time in Status” ClickApp |
clickup_get_bulk_tasks_time_in_status | Time in status for up to 100 tasks | task_ids array (required, 1-100) |
2.13 Reminders (3 tools)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_create_reminder | Create a personal reminder | title (required), due_date (required, YYYY-MM-DD or YYYY-MM-DD HH:MM), description |
clickup_search_reminders | Search/filter reminders | is_overdue, is_completed, reminder_type (REMINDER/ASSIGNED_COMMENT/APPROVAL/etc.), due_date_status (TODO/LATER/DELETED), since, limit, cursor |
clickup_update_reminder | Update a reminder | reminder_id (required), title, description, due_date, is_completed |
2.14 File Attachments (1 tool)
| Tool | What It Does | Key Parameters |
|---|---|---|
clickup_attach_task_file | Attach a file to a task | task_id (required), then either: file_data (base64) + file_name, or file_url (http/https) + optional auth_header |
3. MCP Connector — Gaps & Limitations
What MCP Cannot Do
| Missing Capability | Solanasis Impact | Workaround |
|---|---|---|
| Goals / OKR management | Low — not currently using Goals | Use API if/when adopting OKRs |
| Task checklists (sub-items in tasks) | Medium — useful for multi-step tasks | Use subtasks instead (parent param in create_task), or use task description with markdown checklists |
| Templates (task/list/folder) | Low-Medium — would speed up recurring setups | Create tasks manually from a “template” task description stored in docs; or use API |
| Views (create/manage saved views) | Low — views are set up once in the UI | Create views manually in ClickUp UI |
| Webhooks (event subscriptions) | Medium — needed for real-time automation | Use API for webhook setup; or use ClickUp’s built-in automations |
| Guest/permissions management | Low — one-person operation | Manage in ClickUp UI when needed |
| User groups | None — one person | N/A |
| Space deletion | None — destructive, rare | Do it in UI |
| Tag creation | Low — create in UI, apply via MCP | Create tags once in ClickUp UI, then use add_tag_to_task |
| Checklist items (sub-checklists within tasks) | Medium — useful for detailed task breakdowns | Use markdown checklists in task description, or subtasks |
Free Plan Constraints (if applicable)
- 100 uses for core time-tracking actions (create, edit, delete)
- 60 total Custom Field writes (cumulative, never resets)
- 5 active automations, 100 automation actions/month
- No native AI on Free (trial only)
See the AI-Native Time Logging Playbook for detailed Free plan analysis.
4. ClickUp API v2 — Complete Reference
Base URL
https://api.clickup.com/api/v2
Authentication
Personal API Token (recommended for Solanasis — single-user):
- Go to ClickUp > Settings > Apps > API Token
- Click “Generate” or copy existing token
- Store in
.envfile asCLICKUP_API_TOKEN - Include in requests as:
Authorization: <your_token>
OAuth 2.0 (for multi-user apps — not needed now):
- Authorization URL:
https://app.clickup.com/api - Token URL:
https://api.clickup.com/api/v2/oauth/token - Standard authorization code grant flow
Rate Limits
| Plan | Requests/Minute/Token |
|---|---|
| Free Forever | 100 |
| Unlimited | 100 |
| Business | 100 |
| Business Plus | 1,000 |
| Enterprise | 10,000 |
Headers returned:
X-RateLimit-Limit— max requests per minuteX-RateLimit-Remaining— requests left in current windowX-RateLimit-Reset— Unix timestamp when window resets
When rate limited: HTTP 429 response. Back off and retry after reset time.
Request Format
- Content-Type:
application/json - All dates in request bodies: Unix timestamp in milliseconds
- All dates in query params: Unix timestamp in milliseconds
- Pagination: Most list endpoints support
page(0-indexed) parameter
Error Codes
| Code | Meaning |
|---|---|
| 400 | Bad request — check parameters |
| 401 | Unauthorized — check API token |
| 403 | Forbidden — insufficient permissions |
| 404 | Not found — check IDs |
| 429 | Rate limited — back off and retry |
| 500 | Server error — retry with backoff |
All 23 Endpoint Categories
| # | Category | Base Path | CRUD | MCP Coverage |
|---|---|---|---|---|
| 1 | Authorization | /oauth/token | Token management | N/A |
| 2 | Attachments | /task/{task_id}/attachment | Upload | YES |
| 3 | Comments | /task/{task_id}/comment | CRUD | YES |
| 4 | Custom Task Types | /team/{team_id}/custom_item | Read | Partial |
| 5 | Custom Fields | /list/{list_id}/field | Read + Set | YES |
| 6 | Folders | /space/{space_id}/folder | CRUD | YES (no delete) |
| 7 | Goals | /team/{team_id}/goal | CRUD | NO |
| 8 | Guests | /team/{team_id}/guest | CRUD | NO |
| 9 | Lists | /folder/{folder_id}/list | CRUD | YES (no delete) |
| 10 | Members | /task/{task_id}/member | Read | YES |
| 11 | Roles | /team/{team_id}/customroles | Read | NO |
| 12 | Shared Hierarchy | /team/{team_id}/shared | Read | NO |
| 13 | Spaces | /team/{team_id}/space | CRUD | Partial (no delete) |
| 14 | Tags | /space/{space_id}/tag | CRUD | Partial (no create/delete) |
| 15 | Tasks | /list/{list_id}/task | CRUD | YES |
| 16 | Task Checklists | /task/{task_id}/checklist | CRUD | NO |
| 17 | Task Relationships | /task/{task_id}/dependency | CRUD | YES |
| 18 | Templates | /team/{team_id}/taskTemplate | Read + Apply | NO |
| 19 | Time Tracking | /team/{team_id}/time_entries | CRUD | YES |
| 20 | User Groups | /team/{team_id}/group | CRUD | NO |
| 21 | Users | /team/{team_id}/user | Invite/Edit/Remove | NO |
| 22 | Views | /team/{team_id}/view | CRUD | NO |
| 23 | Webhooks | /team/{team_id}/webhook | CRUD | NO |
5. ClickUp API v2 — Endpoint Deep Dive
5.1 Authorization
When to use API instead of MCP: Only needed if building a multi-user OAuth app.
GET https://app.clickup.com/api?client_id={id}&redirect_uri={uri}
POST https://api.clickup.com/api/v2/oauth/token
For personal use, just use the API token (no OAuth needed).
5.2 Attachments
When to use API instead of MCP: MCP covers this via clickup_attach_task_file.
POST /api/v2/task/{task_id}/attachment
- Multipart form upload
?custom_task_ids=trueto use custom IDs
5.3 Comments
When to use API instead of MCP: MCP covers task comments. API adds list-level and chat-view comments.
GET /api/v2/task/{task_id}/comment
POST /api/v2/task/{task_id}/comment
PUT /api/v2/comment/{comment_id}
DELETE /api/v2/comment/{comment_id}
GET /api/v2/list/{list_id}/comment
POST /api/v2/list/{list_id}/comment
GET /api/v2/view/{view_id}/comment
POST /api/v2/view/{view_id}/comment
import requests, os
TOKEN = os.getenv("CLICKUP_API_TOKEN")
HEADERS = {"Authorization": TOKEN, "Content-Type": "application/json"}
# Create a comment
resp = requests.post(
f"https://api.clickup.com/api/v2/task/{task_id}/comment",
headers=HEADERS,
json={"comment_text": "Status update: Phase 1 complete", "notify_all": True}
)5.4 Custom Task Types
When to use API instead of MCP: To list all available task types in workspace.
GET /api/v2/team/{team_id}/custom_item
5.5 Custom Fields
When to use API instead of MCP: MCP can read fields and set values. API needed for field-level CRUD (rare).
GET /api/v2/list/{list_id}/field
Free plan warning: 60 total custom field writes (cumulative, never resets).
5.6 Folders
When to use API instead of MCP: MCP covers create/read/update. API adds delete.
GET /api/v2/space/{space_id}/folder
POST /api/v2/space/{space_id}/folder
GET /api/v2/folder/{folder_id}
PUT /api/v2/folder/{folder_id}
DELETE /api/v2/folder/{folder_id}
# Create a folder
resp = requests.post(
f"https://api.clickup.com/api/v2/space/{space_id}/folder",
headers=HEADERS,
json={"name": "Q2 2026 Client Work"}
)5.7 Goals (MCP Gap)
Why you need the API: MCP has no goal management at all.
GET /api/v2/team/{team_id}/goal
POST /api/v2/team/{team_id}/goal
GET /api/v2/goal/{goal_id}
PUT /api/v2/goal/{goal_id}
DELETE /api/v2/goal/{goal_id}
POST /api/v2/goal/{goal_id}/key_result
PUT /api/v2/key_result/{key_result_id}
DELETE /api/v2/key_result/{key_result_id}
# Create a goal
resp = requests.post(
f"https://api.clickup.com/api/v2/team/{team_id}/goal",
headers=HEADERS,
json={
"name": "Q2 Revenue Target",
"due_date": 1719792000000, # Unix ms
"description": "Close 3 new fCIO clients",
"multiple_owners": False,
"owners": [user_id],
"color": "#1DB954"
}
)
# Add a key result
resp = requests.post(
f"https://api.clickup.com/api/v2/goal/{goal_id}/key_result",
headers=HEADERS,
json={
"name": "Pipeline value > $50K",
"type": "number",
"steps_start": 0,
"steps_end": 50000,
"unit": "USD"
}
)5.8 Guests (MCP Gap)
Why you need the API: MCP cannot manage external user access.
POST /api/v2/team/{team_id}/guest
GET /api/v2/team/{team_id}/guest/{guest_id}
PUT /api/v2/team/{team_id}/guest/{guest_id}
DELETE /api/v2/team/{team_id}/guest/{guest_id}
POST /api/v2/task/{task_id}/guest/{guest_id}
DELETE /api/v2/task/{task_id}/guest/{guest_id}
# Invite a guest (e.g., a 1099 contractor)
resp = requests.post(
f"https://api.clickup.com/api/v2/team/{team_id}/guest",
headers=HEADERS,
json={"email": "contractor@example.com", "can_edit_tags": False, "can_see_time_spent": True}
)5.9 Lists
When to use API instead of MCP: MCP covers create/read/update. API adds delete and folderless lists.
GET /api/v2/folder/{folder_id}/list
POST /api/v2/folder/{folder_id}/list
GET /api/v2/space/{space_id}/list # folderless lists
POST /api/v2/space/{space_id}/list # create folderless
GET /api/v2/list/{list_id}
PUT /api/v2/list/{list_id}
DELETE /api/v2/list/{list_id}
5.10 Members
When to use API instead of MCP: MCP covers workspace-level members. API adds task/list-level membership.
GET /api/v2/task/{task_id}/member
GET /api/v2/list/{list_id}/member
5.11 Roles (MCP Gap)
Why you need the API: To read custom role definitions.
GET /api/v2/team/{team_id}/customroles
5.12 Shared Hierarchy (MCP Gap)
Why you need the API: To see shared/inherited permissions across the hierarchy.
GET /api/v2/team/{team_id}/shared
5.13 Spaces
When to use API instead of MCP: MCP can read hierarchy. API adds create/update/delete.
GET /api/v2/team/{team_id}/space
POST /api/v2/team/{team_id}/space
GET /api/v2/space/{space_id}
PUT /api/v2/space/{space_id}
DELETE /api/v2/space/{space_id}
5.14 Tags
When to use API instead of MCP: MCP can add/remove tags from tasks. API adds tag creation and deletion at space level.
GET /api/v2/space/{space_id}/tag
POST /api/v2/space/{space_id}/tag
PUT /api/v2/space/{space_id}/tag/{tag_name}
DELETE /api/v2/space/{space_id}/tag/{tag_name}
# Create a new tag in a space
resp = requests.post(
f"https://api.clickup.com/api/v2/space/{space_id}/tag",
headers=HEADERS,
json={"tag": {"name": "client-acme", "tag_fg": "#FFFFFF", "tag_bg": "#1DB954"}}
)5.15 Tasks
When to use API instead of MCP: MCP covers most operations. API adds task merging, task from template, and filtered get by list.
GET /api/v2/list/{list_id}/task # filtered task list
POST /api/v2/list/{list_id}/task # create
GET /api/v2/task/{task_id} # get by ID
PUT /api/v2/task/{task_id} # update
DELETE /api/v2/task/{task_id} # delete
GET /api/v2/team/{team_id}/task # filtered across workspace
Task from template (MCP gap):
# Create a task from a template
resp = requests.post(
f"https://api.clickup.com/api/v2/list/{list_id}/taskTemplate/{template_id}",
headers=HEADERS,
json={"name": "Security Assessment — Acme Corp"}
)5.16 Task Checklists (MCP Gap)
Why you need the API: MCP has no checklist support at all.
POST /api/v2/task/{task_id}/checklist
PUT /api/v2/checklist/{checklist_id}
DELETE /api/v2/checklist/{checklist_id}
POST /api/v2/checklist/{checklist_id}/checklist_item
PUT /api/v2/checklist/{checklist_id}/checklist_item/{checklist_item_id}
DELETE /api/v2/checklist/{checklist_id}/checklist_item/{checklist_item_id}
# Create a checklist on a task
resp = requests.post(
f"https://api.clickup.com/api/v2/task/{task_id}/checklist",
headers=HEADERS,
json={"name": "Security Assessment Steps"}
)
checklist_id = resp.json()["checklist"]["id"]
# Add items to the checklist
for item_name in ["Run vulnerability scan", "Review firewall rules", "Check access logs", "Draft findings report"]:
requests.post(
f"https://api.clickup.com/api/v2/checklist/{checklist_id}/checklist_item",
headers=HEADERS,
json={"name": item_name}
)5.17 Task Relationships
When to use API instead of MCP: MCP covers this fully.
POST /api/v2/task/{task_id}/dependency
DELETE /api/v2/task/{task_id}/dependency
POST /api/v2/task/{task_id}/link/{links_to}
DELETE /api/v2/task/{task_id}/link/{links_to}
5.18 Templates (MCP Gap)
Why you need the API: MCP cannot list or apply templates.
GET /api/v2/team/{team_id}/taskTemplate?page=0
POST /api/v2/list/{list_id}/taskTemplate/{template_id}
# List all task templates
resp = requests.get(
f"https://api.clickup.com/api/v2/team/{team_id}/taskTemplate?page=0",
headers=HEADERS
)
templates = resp.json()["templates"]
# Apply a template to create a task
resp = requests.post(
f"https://api.clickup.com/api/v2/list/{list_id}/taskTemplate/{template_id}",
headers=HEADERS,
json={"name": "DR Verification — Client XYZ"}
)5.19 Time Tracking
When to use API instead of MCP: MCP covers most operations. API adds edit/delete of individual entries and tag management.
GET /api/v2/team/{team_id}/time_entries
POST /api/v2/team/{team_id}/time_entries
GET /api/v2/team/{team_id}/time_entries/{timer_id}
PUT /api/v2/team/{team_id}/time_entries/{timer_id}
DELETE /api/v2/team/{team_id}/time_entries/{timer_id}
GET /api/v2/team/{team_id}/time_entries/current
POST /api/v2/team/{team_id}/time_entries/start/{task_id}
POST /api/v2/team/{team_id}/time_entries/stop
GET /api/v2/team/{team_id}/time_entries/tags
POST /api/v2/team/{team_id}/time_entries/tags
PUT /api/v2/team/{team_id}/time_entries/tags
5.20 User Groups (MCP Gap)
Why you need the API: To create/manage teams within the workspace.
GET /api/v2/team/{team_id}/group
POST /api/v2/team/{team_id}/group
PUT /api/v2/group/{group_id}
DELETE /api/v2/group/{group_id}
5.21 Users (MCP Gap)
Why you need the API: To invite, edit roles, or remove workspace members programmatically.
POST /api/v2/team/{team_id}/user
PUT /api/v2/team/{team_id}/user/{user_id}
DELETE /api/v2/team/{team_id}/user/{user_id}
GET /api/v2/team/{team_id}/user/{user_id}
5.22 Views (MCP Gap)
Why you need the API: To programmatically create or manage saved views.
GET /api/v2/team/{team_id}/view
POST /api/v2/team/{team_id}/view
GET /api/v2/view/{view_id}
PUT /api/v2/view/{view_id}
DELETE /api/v2/view/{view_id}
GET /api/v2/view/{view_id}/task
Also available at space, folder, and list levels:
GET/POST /api/v2/space/{space_id}/view
GET/POST /api/v2/folder/{folder_id}/view
GET/POST /api/v2/list/{list_id}/view
# Create a "My Open Tasks" view
resp = requests.post(
f"https://api.clickup.com/api/v2/space/{space_id}/view",
headers=HEADERS,
json={
"name": "My Open Tasks",
"type": "list",
"grouping": {"field": "status"},
"filters": {
"op": "AND",
"fields": [{"field": "assignee", "op": "=", "value": [user_id]}]
}
}
)5.23 Webhooks (MCP Gap)
Why you need the API: To set up event-driven automation.
GET /api/v2/team/{team_id}/webhook
POST /api/v2/team/{team_id}/webhook
PUT /api/v2/webhook/{webhook_id}
DELETE /api/v2/webhook/{webhook_id}
# Create a webhook for task status changes
resp = requests.post(
f"https://api.clickup.com/api/v2/team/{team_id}/webhook",
headers=HEADERS,
json={
"endpoint": "https://your-endpoint.example.com/clickup-webhook",
"events": ["taskStatusUpdated", "taskCreated", "taskDeleted"],
"space_id": space_id # scope to a specific space
}
)
webhook_secret = resp.json()["webhook"]["secret"]
# Store webhook_secret securely for signature verificationSee Section 7 for full webhook details.
6. Python Integration Patterns
Project Structure
solanasis-scripts/
└── clickup/
├── __init__.py
├── client.py # Base client with auth + rate limiting
├── goals.py # Goal/OKR operations
├── checklists.py # Checklist operations
├── templates.py # Template operations
└── webhooks.py # Webhook management
Auth Setup
.env file (in solanasis-scripts/):
CLICKUP_API_TOKEN=pk_12345678_ABCDEFGHIJKLMNOPQRSTUVWXYZ
CLICKUP_TEAM_ID=12345678
Security: Never commit
.envto git. Verify.gitignoreincludes it.
Base Client
"""solanasis-scripts/clickup/client.py — ClickUp API base client"""
import os
import time
import requests
from dotenv import load_dotenv
load_dotenv()
CLICKUP_API_TOKEN = os.getenv("CLICKUP_API_TOKEN")
CLICKUP_TEAM_ID = os.getenv("CLICKUP_TEAM_ID")
BASE_URL = "https://api.clickup.com/api/v2"
def _headers():
return {
"Authorization": CLICKUP_API_TOKEN,
"Content-Type": "application/json",
}
def clickup_request(method, path, **kwargs):
"""Make a ClickUp API request with automatic rate limit handling.
Args:
method: HTTP method (get, post, put, delete)
path: API path (e.g., "/task/abc123")
**kwargs: Passed to requests.request (json, params, etc.)
Returns:
Response JSON or None for 204s
Raises:
requests.HTTPError: On non-retryable errors
"""
url = f"{BASE_URL}{path}"
max_retries = 3
for attempt in range(max_retries):
resp = requests.request(method, url, headers=_headers(), **kwargs)
if resp.status_code == 429:
reset_time = int(resp.headers.get("X-RateLimit-Reset", time.time() + 60))
wait = max(reset_time - time.time(), 1)
print(f"Rate limited. Waiting {wait:.0f}s...")
time.sleep(wait)
continue
resp.raise_for_status()
if resp.status_code == 204:
return None
return resp.json()
raise Exception("Rate limit retries exhausted")
# Convenience wrappers
def get(path, **kwargs):
return clickup_request("GET", path, **kwargs)
def post(path, **kwargs):
return clickup_request("POST", path, **kwargs)
def put(path, **kwargs):
return clickup_request("PUT", path, **kwargs)
def delete(path, **kwargs):
return clickup_request("DELETE", path, **kwargs)Example: Goals Script
"""solanasis-scripts/clickup/goals.py — Goal/OKR management"""
from client import get, post, put, delete, CLICKUP_TEAM_ID
def list_goals():
return get(f"/team/{CLICKUP_TEAM_ID}/goal")
def create_goal(name, description="", due_date_ms=None):
payload = {"name": name, "description": description}
if due_date_ms:
payload["due_date"] = due_date_ms
return post(f"/team/{CLICKUP_TEAM_ID}/goal", json=payload)
def add_key_result(goal_id, name, target_type="number", start=0, end=100, unit=""):
return post(f"/goal/{goal_id}/key_result", json={
"name": name,
"type": target_type,
"steps_start": start,
"steps_end": end,
"unit": unit
})
if __name__ == "__main__":
goals = list_goals()
for g in goals.get("goals", []):
print(f" {g['name']} — {g.get('percent_completed', 0)}%")Example: Checklists Script
"""solanasis-scripts/clickup/checklists.py — Checklist operations"""
from client import post, put, delete
def create_checklist(task_id, name):
resp = post(f"/task/{task_id}/checklist", json={"name": name})
return resp["checklist"]["id"]
def add_item(checklist_id, name, assignee_id=None):
payload = {"name": name}
if assignee_id:
payload["assignee"] = assignee_id
return post(f"/checklist/{checklist_id}/checklist_item", json=payload)
def check_item(checklist_id, item_id, resolved=True):
return put(f"/checklist/{checklist_id}/checklist_item/{item_id}",
json={"resolved": resolved})
def create_checklist_from_list(task_id, checklist_name, items):
"""Create a checklist and populate it with items.
Args:
task_id: ClickUp task ID
checklist_name: Name for the checklist
items: List of item name strings
"""
cl_id = create_checklist(task_id, checklist_name)
for item in items:
add_item(cl_id, item)
return cl_idDate Conversion Helper
The ClickUp API uses Unix timestamps in milliseconds. Python helper:
from datetime import datetime
def to_clickup_ms(date_str):
"""Convert 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM' to Unix ms."""
for fmt in ("%Y-%m-%d %H:%M", "%Y-%m-%d"):
try:
dt = datetime.strptime(date_str, fmt)
return int(dt.timestamp() * 1000)
except ValueError:
continue
raise ValueError(f"Invalid date format: {date_str}")
def from_clickup_ms(ms):
"""Convert Unix ms to 'YYYY-MM-DD HH:MM'."""
return datetime.fromtimestamp(ms / 1000).strftime("%Y-%m-%d %H:%M")7. Webhooks & Automation
How ClickUp Webhooks Work
- You register a webhook URL with ClickUp (via API)
- ClickUp sends POST requests to your URL when events occur
- Each request includes a signature for verification
- You process the event and take action
Available Event Types (30+)
Task events:
taskCreated,taskUpdated,taskDeletedtaskStatusUpdated,taskPriorityUpdatedtaskAssigneeUpdated,taskDueDateUpdatedtaskTagUpdated,taskMovedtaskTimeEstimateUpdated,taskTimeTrackedUpdated
Comment events:
taskCommentPosted,taskCommentUpdated
List/Folder/Space events:
listCreated,listUpdated,listDeletedfolderCreated,folderUpdated,folderDeletedspaceCreated,spaceUpdated,spaceDeleted
Goal events:
goalCreated,goalUpdated,goalDeletedkeyResultCreated,keyResultUpdated,keyResultDeleted
Webhook Registration
from client import post, get, delete, CLICKUP_TEAM_ID
def create_webhook(endpoint_url, events, space_id=None, folder_id=None, list_id=None):
"""Register a webhook. Scope with space/folder/list IDs, or omit for workspace-wide."""
payload = {"endpoint": endpoint_url, "events": events}
if space_id:
payload["space_id"] = space_id
if folder_id:
payload["folder_id"] = folder_id
if list_id:
payload["list_id"] = list_id
resp = post(f"/team/{CLICKUP_TEAM_ID}/webhook", json=payload)
return resp["webhook"] # includes "id" and "secret"
def list_webhooks():
return get(f"/team/{CLICKUP_TEAM_ID}/webhook")
def delete_webhook(webhook_id):
return delete(f"/webhook/{webhook_id}")Signature Verification
ClickUp signs webhook payloads with HMAC-SHA256 using the webhook secret:
import hmac
import hashlib
def verify_webhook_signature(payload_body, signature_header, webhook_secret):
"""Verify that a webhook payload is authentic.
Args:
payload_body: Raw request body (bytes)
signature_header: Value of X-Signature header
webhook_secret: Secret from webhook creation response
Returns:
True if signature is valid
"""
expected = hmac.new(
webhook_secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)When Webhooks Make Sense vs. Polling
| Approach | Use When |
|---|---|
| Webhooks | You need real-time reactions (e.g., auto-notify when a task is completed) |
| Polling (MCP) | You need information on-demand during Claude Code sessions |
| Polling (API script) | Scheduled reports or batch processing (e.g., daily time summary) |
For Solanasis right now: Polling via MCP is sufficient. Webhooks become valuable when you need automated reactions without Claude Code being active (e.g., a task completion triggers an email to a client). This requires a server/endpoint to receive webhooks — consider Cloudflare Workers (fits your existing Cloudflare stack).
8. Troubleshooting & Maintenance
MCP Connector Issues
| Problem | Fix |
|---|---|
| MCP tools not appearing | Check claude.ai > Settings > Connected Apps > ClickUp is “Connected" |
| "Unauthorized” errors | Re-authorize: disconnect and reconnect ClickUp in claude.ai settings |
| Tools appear but calls fail | ClickUp may be having an outage — check status.clickup.com |
| Slow responses | Normal — MCP routes through claude.ai servers. Large workspace queries take longer. |
| Missing workspace data | Check clickup_get_workspace_hierarchy — you may need to paginate (cursor param) |
API Script Issues
| Problem | Fix |
|---|---|
| 401 Unauthorized | Check CLICKUP_API_TOKEN in .env — regenerate if expired |
| 429 Rate Limited | The base client auto-retries. If persistent, reduce request frequency |
| 404 Not Found | Verify task/list/space IDs. Custom IDs need ?custom_task_ids=true param |
| Missing data in responses | Check pagination — most list endpoints default to page 0 only |
| SSL/connection errors | Check network connectivity. ClickUp API requires HTTPS. |
Rate Limit Strategy
- The MCP connector handles rate limits internally — no action needed
- For API scripts, the base client (Section 6) includes automatic retry with backoff
- If hitting limits frequently:
- Batch operations where possible
- Cache responses that don’t change often (workspace hierarchy, member lists)
- Consider upgrading ClickUp plan for higher limits (Business+: 1,000/min)
API Version Migration (v2 → v3)
ClickUp is incrementally migrating endpoints to v3. As of March 2026:
- Most endpoints remain v2
- Some endpoints have v3 equivalents
- No hard cutover date announced
- The v2 endpoints continue to work
- Monitor ClickUp developer docs for migration timeline
Action: No immediate changes needed. When building new scripts, check if a v3 endpoint exists. The base URL for v3 is https://api.clickup.com/api/v3.
ClickUp Plan Considerations
| Plan | Price | Key Limits for Solanasis |
|---|---|---|
| Free Forever | $0 | 100 time tracking uses, 60 custom field writes (never resets), 5 automations |
| Unlimited | $10/user/mo | Unlimited time tracking, unlimited custom fields, unlimited automations |
| Business | $19/user/mo | + advanced features, same API rate limits |
| Business Plus | $29/user/mo | + 1,000 req/min API rate limit |
Recommendation from time-logging playbook: Upgrade to Unlimited when the workflow sticks. The Free plan constraints are real, especially the 60-write custom field cap.
Appendix: Quick Reference Card
Most-Used MCP Tools
Search: clickup_search keywords="..." filters.asset_types=["task"]
Create: clickup_create_task name="..." list_id="..."
Update: clickup_update_task task_id="..." status="in progress"
Find: clickup_filter_tasks tags=["client-acme"] statuses=["open"]
Timer: clickup_start_time_tracking task_id="..."
Stop: clickup_stop_time_tracking
Comment: clickup_create_task_comment task_id="..." comment_text="..."
Hierarchy: clickup_get_workspace_hierarchy max_depth=2
API Authentication Header
Authorization: pk_12345678_ABCDEFGHIJKLMNOPQRSTUVWXYZ
Content-Type: application/json
Date Formats
- MCP tools:
YYYY-MM-DDorYYYY-MM-DD HH:MM(human-readable) - API requests: Unix timestamp in milliseconds (e.g.,
1711238400000) - Conversion:
int(datetime.strptime("2026-03-24", "%Y-%m-%d").timestamp() * 1000)
Python SDK Options (for reference)
| Package | Version | Status | License |
|---|---|---|---|
clickup-python-sdk | 2.0.1 | Beta, single maintainer | GPLv3 |
pyclickup | older | Documented but less maintained | MIT |
Raw requests | stable | Recommended — most resilient | N/A |
Recommendation: Use raw requests with the base client from Section 6. More resilient than SDKs with single maintainers.