소스 정보
- 저장소
- oyi77/1ai-skills
- 최근 소스 활동
- 2026년 7월 31일 15:22
- 감지된 SKILL.md 언어
- 영어
- 스타
- 8
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/oyi77/1ai-skills --skill clickup명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Android and mobile application security testing — emulators, rooting, traffic interception, dynamic instrumentation. Use when testing mobile apps for vulnerabilities, reversing APKs, or bypassing security controls on Android.
Self-reflection + Self-criticism + Auto-learning from corrections + Self-organizing memory. Agent evaluates its own work, catches mistakes, and improves permanently. Use when working with self improving.
Plan and execute a comprehensive red team engagement covering reconnaissance through post-exploitation using MITRE ATT&CK-aligned TTPs to evaluate an organization's detection and response capabilities. Use when working with conducting full scope red team engagement.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | clickup |
| description | Skill: clickup. See SKILL.md body for details. Use when this domain is relevant. |
| domain | operations |
| author | oyi77 |
| license | Apache-2.0 |
| subdomain | business-operations |
| tags | ["business-ops","clickup","management","operations"] |
| version | 1.0.0 |
Trigger phrases:
Situations:
API version reference: This skill targets the ClickUp REST API v2 (/api/v2/). The API base URL is https://api.clickup.com/api/v2.
ClickUp is a unified project management platform built on a hierarchical structure: Workspace → Space → Folder → List → Task. The REST API v2 exposes CRUD operations at every level, plus specialized endpoints for time tracking, goals, docs, dependencies, and custom fields.
API base URL: https://api.clickup.com/api/v2
Authentication: Pass API token via Authorization: pk_XXXXXXXX header, or use OAuth 2.0 for user-installed integrations.
Key design constraints:
Retry-After header.Workspace (team_id)
└── Space (space_id)
├── Folder (folder_id)
│ └── List (list_id)
│ └── Task (task_id)
└── List (list_id) — a list can exist directly in a space without a folder
└── Task (task_id)
Tasks can optionally have:
pk_Authorization: pk_XXXXXXXXXXXXXX
The API token has the same permissions as the user who generated it.
OAuth is required if your integration will be installed by multiple ClickUp users (e.g., a marketplace app).
https://your-app.com/oauth/callbackhttps://app.clickup.com/api?client_id=CLIENT_ID&redirect_uri=REDIRECT_URIPOST https://api.clickup.com/api/v2/oauth/token with client_id, client_secret, codeThe OAuth token is used identically to an API token (passed as Authorization: Bearer <token>).
Before any API call, resolve the workspace and location IDs.
# Get authenticated user's workspaces
import requests
API_TOKEN = "pk_xxxxxxxx"
HEADERS = {"Authorization": API_TOKEN}
resp = requests.get("https://api.clickup.com/api/v2/team", headers=HEADERS)
teams = resp.json()["teams"]
# teams[0]["id"] is your workspace_id
Map from names to IDs. Collect these once and cache them (they rarely change).
def get_spaces(workspace_id: str) -> list[dict]:
resp = requests.get(
f"https://api.clickup.com/api/v2/team/{workspace_id}/space",
headers=HEADERS
)
return resp.json()["spaces"]
def get_folders(space_id: str) -> list[dict]:
resp = requests.get(
f"https://api.clickup.com/api/v2/space/{space_id}/folder",
headers=HEADERS
)
return resp.json()["folders"]
def get_lists(folder_id: str) -> list[dict]:
resp = requests.get(
f"https://api.clickup.com/api/v2/folder/{folder_id}/list",
headers=HEADERS
)
return resp.json()["lists"]
Create, read, update, delete, and query tasks.
Confirm the operation via the ClickUp UI or a GET request.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/task/{task_id} | Get a single task |
POST | /api/v2/list/{list_id}/task | Create a task in a list |
PUT | /api/v2/task/{task_id} | Update a task |
DELETE | /api/v2/task/{task_id} | Delete a task |
GET | /api/v2/list/{list_id}/task | Get tasks in a list (with filters) |
POST | /api/v2/task/{task_id}/checklist/{checklist_id}/checklist_item | Add checklist item |
POST | /api/v2/task/{task_id}/link | Create task dependency |
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/list/{list_id} | Get list details |
POST | /api/v2/folder/{folder_id}/list | Create a list |
PUT | /api/v2/list/{list_id} | Update a list |
GET | /api/v2/space/{space_id} | Get space details |
PUT | /api/v2/space/{space_id} | Update space |
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/task/{task_id}/time | Get time entries for a task |
POST | /api/v2/task/{task_id}/time | Add time entry |
PUT | /api/v2/time/{time_entry_id} | Update time entry |
DELETE | /api/v2/time/{time_entry_id} | Delete time entry |
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/team/{team_id}/goal | List goals in workspace |
POST | /api/v2/team/{team_id}/goal | Create a goal |
PUT | /api/v2/goal/{goal_id} | Update a goal |
POST | /api/v2/goal/{goal_id}/key_result | Add a key result to a goal |
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/list/{list_id}/field | Get custom fields for a list |
POST | /api/v2/task/{task_id}/field/{field_id} | Set a custom field value |
| Method | Endpoint | Purpose |
|---|---|---|
POST | /api/v2/task/{task_id}/link | Link tasks with dependency |
DELETE | /api/v2/task/{task_id}/link/{links_to} | Remove a dependency link |
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/v2/team/{team_id}/webhook | List registered webhooks |
POST | /api/v2/team/{team_id}/webhook | Create a webhook |
DELETE | /api/v2/webhook/{webhook_id} | Delete a webhook |
import requests
import json
API_TOKEN = "pk_xxxxxxxx"
LIST_ID = "901234567890"
HEADERS = {
"Authorization": API_TOKEN,
"Content-Type": "application/json"
}
def create_task(list_id: str, name: str, description: str = "",
assignees: list[int] | None = None,
priority: int | None = None,
due_date: int | None = None,
custom_fields: list[dict] | None = None) -> dict:
"""Create a ClickUp task.
Args:
list_id: ID of the target list
name: Task name (required)
description: Markdown task description
assignees: List of ClickUp user IDs
priority: 1 (urgent), 2 (high), 3 (normal), 4 (low)
due_date: Unix timestamp in milliseconds
custom_fields: List of {id, value} dicts
"""
payload = {"name": name}
if description:
payload["description"] = description
if assignees:
payload["assignees"] = assignees
if priority:
payload["priority"] = priority
if due_date:
payload["due_date"] = due_date
resp = requests.post(
f"https://api.clickup.com/api/v2/list/{list_id}/task",
headers=HEADERS,
json=payload
)
resp.raise_for_status()
task = resp.json()
custom_fields:
field custom_fields:
set_custom_field(task[], field[], field[])
task
():
resp = requests.post(
,
headers=HEADERS,
json={: value}
)
resp.raise_for_status()
():
resp = requests.put(
,
headers=HEADERS,
json={: status}
)
resp.raise_for_status()
task = create_task(
list_id=LIST_ID,
name=,
description=,
assignees=[],
priority=,
due_date=(pd.Timestamp().timestamp() * ),
custom_fields=[{: , : }]
)
()
def get_tasks(list_id: str, status: str | None = None,
assignee: int | None = None,
page: int = 0) -> list[dict]:
"""Get tasks from a list with optional filters.
ClickUp paginates at 100 tasks per page. Use `page` to iterate.
"""
params = {"page": page, "order_by": "updated"}
if status:
params["statuses[0]"] = status
if assignee:
params["assignees[0]"] = str(assignee)
resp = requests.get(
f"https://api.clickup.com/api/v2/list/{list_id}/task",
headers=HEADERS,
params=params
)
resp.raise_for_status()
return resp.json()["tasks"]
# Get all "in progress" tasks
tasks = get_tasks(LIST_ID, status="in progress")
for t in tasks:
print(f"{t['id']}: {t['name']} (updated {t['date_updated']})")
const API_TOKEN = 'pk_xxxxxxxx';
const LIST_ID = '901234567890';
const BASE = 'https://api.clickup.com/api/v2';
async function clickupFetch(endpoint, options = {}) {
const url = `${BASE}${endpoint}`;
const resp = await fetch(url, {
...options,
headers: {
'Authorization': API_TOKEN,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!resp.ok) {
const body = await resp.text();
throw new Error(`ClickUp API ${resp.status}: ${body}`);
}
return resp.json();
}
// Create a task
async function createTask({ name, description, assignees, priority, dueDate }) {
return clickupFetch(`/list/${LIST_ID}/task`, {
method: 'POST',
: .({
name,
description,
assignees,
priority,
: dueDate ? (dueDate).() : ,
}),
});
}
() {
results = [];
( id taskIds) {
task = (, {
: ,
: .({ status }),
});
results.(task);
( (r, ));
}
results;
}
( () => {
task = ({
: ,
: ,
: [],
: ,
});
.();
})();
API_TOKEN="pk_xxxxxxxx"
WORKSPACE_ID="12345678"
LIST_ID="901234567890"
BASE="https://api.clickup.com/api/v2"
# Get all spaces in a workspace
curl -s -H "Authorization: $API_TOKEN" \
"$BASE/team/$WORKSPACE_ID/space" | jq '.spaces[] | {id, name}'
# Create a task
curl -s -X POST \
-H "Authorization: $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Fix login bug","priority":1,"assignees":[123456]}' \
"$BASE/list/$LIST_ID/task" | jq '{id, name, url}'
# Update task status
curl -s -X PUT \
-H "Authorization: $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"in review"}' \
"$BASE/task/9abc1234" | jq '.status.status'
# Add time entry (duration in milliseconds)
curl -s -X POST \
-H "Authorization: $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"duration":3600000,"description":"Code review session"}' \
"$BASE/task/9abc1234/time" | jq '.id'
# Delete a task
curl -s -X DELETE \
-H "Authorization: $API_TOKEN" \
"$BASE/task/9abc1234"
ClickUp webhooks fire HTTP POST requests to your endpoint when specified events occur. They are registered per workspace.
def register_webhook(workspace_id: str, endpoint_url: str,
events: list[str] | None = None) -> dict:
"""Register a ClickUp webhook.
Events: taskCreated, taskUpdated, taskDeleted, taskStatusUpdated,
taskPriorityUpdated, taskAssigneedUpdated, listCreated,
listUpdated, listDeleted, folderCreated, folderUpdated,
folderDeleted, spaceCreated, spaceUpdated, spaceDeleted,
goalCreated, goalUpdated, goalDeleted, goalTargetCreated,
goalTargetUpdated, goalTargetDeleted
"""
payload = {
"endpoint": endpoint_url,
"events": events or ["taskCreated", "taskUpdated", "taskDeleted"]
}
resp = requests.post(
f"https://api.clickup.com/api/v2/team/{workspace_id}/webhook",
headers=HEADERS,
json=payload
)
resp.raise_for_status()
return resp.json()
# Register to receive all task changes
webhook = register_webhook("12345678", "https://my-app.com/clickup-webhook")
print(f"Webhook ID: {webhook['id']} — secret: {webhook.get('secret', 'N/A')}")
ClickUp sends the following JSON body via POST to your endpoint:
{
"webhook_id": "abc-123-def",
"event": "taskUpdated",
"task_id": "9abc1234",
"history_items": [
{
"id": "12345",
"type": 1,
"field": "status",
"before": {"status": "to do"},
"after": {"status": "in progress"}
}
]
}
ClickUp signs webhook payloads with HMAC-SHA256 using the webhook secret returned during registration.
import hmac
import hashlib
def verify_clickup_webhook(payload_body: bytes, signature: str,
secret: str) -> bool:
"""Verify a ClickUp webhook HMAC signature.
The signature is in the X-Signature header of the webhook request.
"""
expected = hmac.new(
secret.encode(), payload_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your web handler (Fastify example):
# const signature = req.headers['x-signature'];
# const verified = verifySignature(JSON.stringify(req.body), secret, signature);
ClickUp does not have native "sprints" as a first-class concept. The pattern is to use a list per sprint or a custom field for sprint name with status transitions.
def setup_sprint_list(workspace_id: str, space_id: str,
sprint_name: str) -> dict:
"""Create a sprint list inside a folder or space."""
# 1. Create folder for the sprint cycle (optional)
folder_resp = requests.post(
f"https://api.clickup.com/api/v2/space/{space_id}/folder",
headers=HEADERS,
json={"name": f"Sprint {sprint_name}"}
)
folder = folder_resp.json()
# 2. Create lists within the folder (Backlog, Current Sprint, Done)
for list_name in ["Backlog", "Current Sprint", "Done"]:
requests.post(
f"https://api.clickup.com/api/v2/folder/{folder['id']}/list",
headers=HEADERS,
json={"name": list_name}
)
return folder
def move_task_to_sprint(task_id: str, sprint_list_id: str):
"""Move an existing task into a sprint list."""
resp = requests.put(
f"https://api.clickup.com/api/v2/task/{task_id}",
headers=HEADERS,
json={"list": {"id": sprint_list_id}}
)
resp.raise_for_status()
Tasks can be linked with "waiting on" / "blocking" relationships.
def add_dependency(task_id: str, depends_on_id: str):
"""Make task_id depend on depends_on_id (task_id blocked by depends_on_id)."""
resp = requests.post(
f"https://api.clickup.com/api/v2/task/{task_id}/link",
headers=HEADERS,
json={
"depends_on": depends_on_id,
"depends_on_links_to": task_id
}
)
resp.raise_for_status()
def get_dependent_tasks(task_id: str) -> dict:
"""Get dependency info for a task (included in the task response)."""
resp = requests.get(
f"https://api.clickup.com/api/v2/task/{task_id}",
headers=HEADERS,
params={"include": ["dependencies"]}
)
return resp.json().get("dependencies", {})
def log_time(task_id: str, duration_minutes: int,
description: str = "", billable: bool = True) -> dict:
"""Log time against a task.
Duration is in milliseconds for the API.
"""
duration_ms = duration_minutes * 60 * 1000
resp = requests.post(
f"https://api.clickup.com/api/v2/task/{task_id}/time",
headers=HEADERS,
json={
"duration": duration_ms,
"description": description,
"billable": billable
}
)
resp.raise_for_status()
return resp.json()
def get_time_for_task(task_id: str) -> int:
"""Get total tracked time for a task in milliseconds."""
resp = requests.get(
f"https://api.clickup.com/api/v2/task/{task_id}/time",
headers=HEADERS
)
entries = resp.json().get("data", [])
return sum(e["duration"] for e in entries)
ClickUp permission levels cascade: Workspace → Space → Folder → List → Task. A user with access to a space automatically has access to all folders, lists, and tasks within it, unless restricted by sharing settings.
def get_space_members(space_id: str) -> list[dict]:
"""Get all members of a space (includes all sub-folders/lists/tasks)."""
resp = requests.get(
f"https://api.clickup.com/api/v2/space/{space_id}",
headers=HEADERS,
params={"include": ["members"]}
)
return resp.json().get("members", [])
import time
def bulk_update_tasks(tasks: list[dict], batch_size: int = 20):
"""Update multiple tasks with rate-limit awareness.
Each update counts toward the 100 req/min per workspace limit.
With n tasks and batch_size items per request (requires 1 req per task
since ClickUp doesn't support batch update natively), space requests
with batch_size × min_delay to stay under the limit.
"""
results = []
for i, task_update in enumerate(tasks):
task_id = task_update.pop("id")
resp = requests.put(
f"https://api.clickup.com/api/v2/task/{task_id}",
headers=HEADERS,
json=task_update
)
resp.raise_for_status()
results.append(resp.json())
# Rate limit: 100 req/min → at most 1 req per 600ms
# Be conservative: wait 700ms between writes
if i < len(tasks) - 1:
time.sleep(0.7)
return results
def find_tasks_by_custom_field(list_id: str, field_id: str,
value) -> list[dict]:
"""Find tasks by a custom field value.
ClickUp doesn't support filtering by custom field directly.
Fetch all tasks and filter client-side.
"""
tasks = []
page = 0
while True:
resp = requests.get(
f"https://api.clickup.com/api/v2/list/{list_id}/task",
headers=HEADERS,
params={"page": page, "order_by": }
)
batch = resp.json()[]
batch:
t batch:
cf = t.get(, [])
f cf:
f[] == field_id f.get() == value:
tasks.append(t)
page +=
time.sleep()
tasks
from datetime import datetime, timedelta
def create_recurring_tasks(list_id: str, base_name: str,
template: dict, weeks: int = 4) -> list[dict]:
"""Create a series of recurring tasks with offset due dates."""
tasks = []
for week in range(weeks):
due = datetime.now() + timedelta(weeks=week)
task_data = {
"name": f"{base_name} — Week {week + 1}",
"due_date": int(due.timestamp() * 1000),
**template
}
resp = requests.post(
f"https://api.clickup.com/api/v2/list/{list_id}/task",
headers=HEADERS,
json=task_data
)
resp.raise_for_status()
tasks.append(resp.json())
time.sleep(0.7)
return tasks
| Risk | Symptom | Mitigation |
|---|---|---|
| Rate limit (HTTP 429) | "Rate limit exceeded" with Retry-After header | Implement exponential backoff. Start with 700ms between write requests, 350ms between reads. Cache list/folder/space IDs. |
| Stale API token | HTTP 401 after token regeneration | Rotate tokens in a shared config (env var, vault) and restart any long-running processes. |
| Wrong workspace ID | HTTP 404 on team endpoints | The team_id is the workspace ID from /team endpoint. Always resolve dynamically rather than hardcoding. |
| Invalid status name | HTTP 400 "Invalid status" | Statuses are case-sensitive and must match exactly what's configured in the ClickUp workspace. Use GET /list/{id} to fetch available statuses. |
| Assignee not in workspace | HTTP 400 on assignee field | Verify user IDs belong to the workspace membership list before assigning. |
| Missing custom field | HTTP 400 "Field not found" | Custom field IDs are list-scoped. Fetch valid fields with GET /list/{list_id}/field before referencing them. |
| Task moved to different list | Task returns with different list_id | Always re-fetch the task before updating, or pass list in the update payload. |
| Webhook secret changes | Signature verification fails | Webhook secret is returned only at creation. Store it securely immediately. If lost, delete and re-create the webhook. |
| Nested subtask depth limit | Can't create subtask of subtask | ClickUp allows only one level of subtasks. Use checklists for deeper nesting within a subtask. |
| ID type confusion | Mixing up task vs. list vs. folder IDs | Prefix or track the type alongside the ID in your code. A task ID and a list ID can look identical (numeric string). |
| Markdown in descriptions | Formatting not rendering | ClickUp accepts markdown in descriptions. Test your markdown rendering — ClickUp's parser may differ from GitHub's. |
GET /team returns workspaces)| Rationalization | Reality |
|---|---|
| "ClickUp's API is just like Jira's" | ClickUp uses a hierarchical model (Space→Folder→List→Task). Jira uses a flat project→issue model. ID scoping, permission inheritance, and custom fields work differently. |
| "We can hardcode the workspace ID" | Workspace IDs change when migrating environments or restructuring. Always resolve /team dynamically. |
| "Rate limits won't affect us at our scale" | 100 req/min per workspace is tight. Two concurrent integrations can exhaust the limit. Every read operation counts. |
| "Status names are the same for all lists" | Each list can have its own set of statuses with different names across lists in the same workspace. Always verify per list. |
| "Custom field IDs are globally unique" | Custom field IDs are scoped to a list. The same field name in different lists has different IDs. Always fetch per list. |
| "OAuth is always better than API token" | For server-side automation, API tokens are simpler. OAuth adds redirect handling, token refresh, and scope management overhead. Use API tokens unless you need per-user authorization. |
| "ClickUp's API supports batch operations" | There is no batch endpoint for tasks. Each task create/update is a separate request. Batch must be implemented client-side with rate-limit pacing. |
| "Webhook guarantees delivery" | Webhooks are at-most-once delivery. If your endpoint is down, the event is lost. Build idempotent handlers and implement periodic reconciliation syncs. |
| "ClickUp vs Asana: Asana has better dependencies" | Asana has superior multi-level dependency tracking. ClickUp dependencies are one-to-one linking. For complex Gantt-style dependency chains, evaluate whether Asana is a better fit. |
| "ClickUp Docs API allows full editing" | The Docs API is read-only for content. You can create a new doc from a markdown template, but inline editing of existing docs requires the UI. |
| "We can move tasks between workspaces via API" | Tasks cannot be moved between workspaces via the API. Export/import is the only option for cross-workspace migration. |
| "The API token has no limits" | The API token inherits the user's role permissions. If the user lacks access to a space, the token can't access it either. |
GET /list/{id}), custom fields (GET /list/{id}/field), and members (GET /space/{id})CLICKUP_API_TOKEN), verify connectivity with a GET /team call