---
name: sundial-org/clickup
source: https://app.decimal.ai/s/sundial-org-clickup@1/SKILL.md
source_sha256: 41649afe794b
---

# ClickUp Skill

Interact with ClickUp's REST API for task management, reporting, and workflow automation.

## Configuration

Before using this skill, ensure the following are configured in `TOOLS.md`:

- **API Token:** `CLICKUP_API_KEY`
- **Team/Workspace ID:** `CLICKUP_TEAM_ID`
- **Space IDs** (optional, for filtering)
- **List IDs** (optional, for creating tasks)

## Quick Start

### Using the Helper Script

The fastest way to query ClickUp:

```bash
# Set environment variables
export CLICKUP_API_KEY="pk_..."
export CLICKUP_TEAM_ID="90161392624"

# Get all open tasks
./scripts/clickup-query.sh tasks

# Get task counts (parent vs subtasks)
./scripts/clickup-query.sh task-count

# Get assignee breakdown
./scripts/clickup-query.sh assignees

# Get specific task
./scripts/clickup-query.sh task <task-id>
```

### Direct API Calls

For custom queries or operations not covered by the helper script:

```bash
# Get all open tasks (with subtasks and pagination)
curl "https://api.clickup.com/api/v2/team/{team_id}/task?include_closed=false&subtasks=true" \
  -H "Authorization: {api_key}"
```

## Critical Rules

### 1. ALWAYS Include Subtasks

**Never** query tasks without `subtasks=true`:

```bash
# ✅ CORRECT
?subtasks=true

# ❌ WRONG
(no subtasks parameter)
```

**Why:** Without this parameter, you miss potentially 70%+ of actual tasks. Parent tasks are just containers; real work happens in subtasks.

### 2. Handle Pagination

ClickUp API returns max 100 tasks per page. **Always** loop until `last_page: true`:

```bash
page=0
while true; do
    result=$(curl -s "...&page=$page" -H "Authorization: $CLICKUP_API_KEY")
    
    # Process tasks
    echo "$result" | jq '.tasks[]'
    
    # Check if done
    is_last=$(echo "$result" | jq -r '.last_page')
    [ "$is_last" = "true" ] && break
    
    ((page++))
done
```

**Why:** Workspaces with 300+ tasks need 3-4 pages. Missing pages = incomplete data.

### 3. Distinguish Parent Tasks vs Subtasks

```bash
# Parent tasks have parent=null
jq '.tasks[] | select(.parent == null)'

# Subtasks have parent != null
jq '.tasks[] | select(.parent != null)'
```

## Common Operations

### Get Task Counts

```bash
# Using helper script (recommended)
./scripts/clickup-query.sh task-count

# Direct API with jq
curl -s "https://api.clickup.com/api/v2/team/{team_id}/task?subtasks=true" \
  -H "Authorization: {api_key}" | \
jq '{
    total: (.tasks | length),
    parents: ([.tasks[] | select(.parent == null)] | length),
    subtasks: ([.tasks[] | select(.parent != null)] | length)
}'
```

### Get Assignee Breakdown

```bash
# Using helper script (recommended)
./scripts/clickup-query.sh assignees

# Direct API
curl -s "https://api.clickup.com/api/v2/team/{team_id}/task?subtasks=true" \
  -H "Authorization: {api_key}" | \
jq -r '.tasks[] | 
    if .assignees and (.assignees | length) > 0 
    then .assignees[0].username 
    else "Unassigned" 
    end' | sort | uniq -c | sort -rn
```

### Create a Task

```bash
curl "https://api.clickup.com/api/v2/list/{list_id}/task" \
  -X POST \
  -H "Authorization: {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Task Name",
    "description": "Description here",
    "assignees": [user_id],
    "status": "to do",
    "priority": 3
  }'
```

### Update a Task

```bash
curl "https://api.clickup.com/api/v2/task/{task_id}" \
  -X PUT \
  -H "Authorization: {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Name",
    "status": "in progress",
    "priority": 2
  }'
```

### Get Specific Task

```bash
# Using helper script
./scripts/clickup-query.sh task {task_id}

# Direct API
curl "https://api.clickup.com/api/v2/task/{task_id}" \
  -H "Authorization: {api_key}"
```

## Advanced Queries

### Filter by Space

```bash
curl "https://api.clickup.com/api/v2/team/{team_id}/task?space_ids[]={space_id}&subtasks=true" \
  -H "Authorization: {api_key}"
```

### Filter by List

```bash
curl "https://api.clickup.com/api/v2/list/{list_id}/task?subtasks=true" \
  -H "Authorization: {api_key}"
```

### Include Closed Tasks

```bash
curl "https://api.clickup.com/api/v2/team/{team_id}/task?include_closed=true&subtasks=true" \
  -H "Authorization: {api_key}"
```

## Reference Documentation

For detailed API documentation, query patterns, and troubleshooting:

**Read:** `references/api-guide.md`

Covers:
- Full API endpoint reference
- Response structure details
- Common gotchas and solutions
- Rate limits and best practices
- Task object schema

## Workflow Patterns

### Daily Standup Report

```bash
# Get all open tasks grouped by assignee
./scripts/clickup-query.sh assignees

# Get specific team member's tasks (use user ID, not username!)
curl "https://api.clickup.com/api/v2/team/{team_id}/task?subtasks=true&assignees[]={user_id}" \
  -H "Authorization: {api_key}"
```

### Task Audit

```bash
# Count tasks by status
./scripts/clickup-query.sh tasks | \
  jq -r '.tasks[].status.status' | sort | uniq -c | sort -rn

# Find unassigned tasks
./scripts/clickup-query.sh tasks | \
  jq '.tasks[] | select(.assignees | length == 0)'
```

### Priority Analysis

```bash
# Count by priority
./scripts/clickup-query.sh tasks | \
  jq -r '.tasks[] | .priority.priority // "none"' | sort | uniq -c | sort -rn
```

## Tips

- **Helper script first:** Use `scripts/clickup-query.sh` for common operations
- **Direct API for custom:** Use curl when you need specific filters or updates
- **Always read api-guide.md:** Contains full endpoint reference and troubleshooting
- **Check TOOLS.md:** For workspace-specific IDs and configuration
- **Test with small queries:** When unsure, test with `| head -n 5` first
- **Filter by user ID:** Use `assignees[]={user_id}` parameter, not jq username matching

## Troubleshooting

- **Missing tasks?** → Add `subtasks=true`
- **Only 100 tasks returned?** → Implement pagination loop
- **401 Unauthorized?** → Check `CLICKUP_API_KEY` is set correctly
- **Rate limit error?** → Wait 1 minute (100 requests/min limit)
- **Empty assignees array?** → Task is unassigned (not an error)
- **Assignee filter returns fewer tasks than expected?** → Use user ID in `assignees[]` param, not jq text matching