SuperAPI AI Directory

dingtalk-skill-creator

Coding & development

View repository

Updated 10-15 17:29

About this skill

Dingtalk Skill Creator

A standardized process for encapsulating a DingTalk API domain into a reusable skill.Each step must actually be completed and verified before proceeding to the next step; no skipping is allowed.


Process Overview

1. [探索]  SDK 模块探索 → 整理接口清单与字段结构
2. [SDK]   Python SDK 测试 → 全部通过才能继续
3. [HTTP]  纯 requests HTTP 测试 → 全部通过才能继续
4. [创作]  编写 SKILL.md + references/api.md
5. [收尾]  更新 README.md / README_EN.md / AGENTS.md

Key Principles: All tests use Python (no .sh scripts).
Phase 2 and Phase 3's uv run pytest must actually run and all be green before proceeding to Phase 4.

New API Priority + Token Incompatibility Principle: The DingTalk platform has two API systems, old and new. The new version must be used preferentially, and the two types of token must not be mixed:

  • New version (recommended):POST https://api.dingtalk.com/v1.0/oauth2/accessToken → returns accessToken + expireIn, paired with the api.dingtalk.com interface, placed in the Header x-acs-dingtalk-access-token
  • Old version (avoid):GET https://oapi.dingtalk.com/gettoken → returns access_token, paired with the oapi.dingtalk.com interface, placed in URL parameters ?access_token=
  • Mutually incompatible: Using a new-version token for an old-version interface, or an old-version token for a new-version interface, will both result in 401/403, and the error message will not state that it is a token type error, making troubleshooting difficult
  • Sole exception: userId → unionId conversion (oapi.dingtalk.com/topapi/v2/user/get) currently has no v1.0 equivalent interface, so the old-version token is still required; this is a known exception, and OLD_TOKEN is used only in this conversion step and must not be passed to other API calls dt_helper.sh: Each skill's scripts/dt_helper.sh encapsulates basic capabilities such as token acquisition and caching, userId↔unionId conversion, and configuration read/write. Inbash scripts(such as execution scripts), it can be called directly;Python tests (Phase 2/3) still use inline requests logic and do not depend on dt_helper.sh.

Phase 1: SDK Exploration

1.1 Find the SDK module

# 列出与目标领域相关的 SDK 模块
ls /home/breath/project/personal/dingtalk-skills/tests/.venv/lib/python3.13/site-packages/alibabacloud_dingtalk/ \
  | grep -i <关键词>
# 示例:todo / attend / approval / calendar

1.2 Extract the interface list

SDK_BASE=tests/.venv/lib/python3.13/site-packages/alibabacloud_dingtalk/<module>

# 列出所有同步方法(去掉 async)
grep -n "def " $SDK_BASE/client.py | grep -v async

# 提取 HTTP endpoint 路径 + 方法
grep -A2 "pathname=" $SDK_BASE/client.py | grep -E "pathname=|method=" | paste - -

1.3 Understand request/response fields

# 查看核心 Request 类
grep -n "^class " $SDK_BASE/models.py

# 读取关键请求类的 __init__ 和 to_map
sed -n '<start>,<end>p' $SDK_BASE/models.py

1.4 Actually probe the real fields (important!)

SDK documentation is not necessarily accurate;you must use real requests to probe the field names actually returned.

⚠️ The probing phase must use the SDK; it is forbidden to use requests to send HTTP requests directly.
The purpose of probing is to learn the SDK's actual behavior and field structure. Only by using the SDK can you simultaneously verify interface callability and field names.
requests Raw requests are left for Phase 3 (pure HTTP testing) and must not be introduced early in the probing phase.

Get token and unionId (using dt_helper.sh):

# 在 skill 根目录或 tests/ 目录下
TOKEN=$(bash scripts/dt_helper.sh --token)          # 新版 token,带缓存
UNION_ID=$(bash scripts/dt_helper.sh --to-unionid)  # 自动从 DINGTALK_MY_USER_ID 转换

Probe script (called using the SDK):

# 在 tests/ 目录下运行(已有 .env 和 venv)
import os, pathlib, subprocess
from alibabacloud_dingtalk.<module>_1_0 import client as dt_client, models as dt_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models

# 读取 .env
env = pathlib.Path(".env").read_text()
for line in env.splitlines():
    line = line.strip()
    if line and not line.startswith("#") and "=" in line:
        k, _, v = line.partition("="); os.environ.setdefault(k.strip(), v.strip())

# 获取 token(调 dt_helper.sh)
token = subprocess.check_output(
    ["bash", "../scripts/common/dt_helper.sh", "--token"], text=True).strip()
union_id = os.environ["DINGTALK_MY_OPERATOR_ID"]

# SDK 客户端
cfg = open_api_models.Config(protocol="HTTPS", endpoint="api.dingtalk.com")
client = dt_client.Client(cfg)
runtime = util_models.RuntimeOptions()

# CREATE 探测 → 打印实际响应字段
h = dt_models.CreateXxxHeaders()
h.x_acs_dingtalk_access_token = token
req = dt_models.CreateXxxRequest(subject="[probe] 探测")
resp = client.create_xxx_with_options(union_id, req, h, runtime)
print("CREATE body:", vars(resp.body))
# 根据 print 输出确定实际字段名

After organizing into an interface list table, continue.

1.5 Probe and enable the required permissions (important!)

Recommended method (faster than checking documentation): Directly use the SDK to send a "dry run" request to each target interface, and from the 403 response body's requiredScopes field, collect the required permission points in bulk. This is more accurate than checking the documentation one by one.

# tests/probe_<skill>_perms.py(写到 tests/ 目录下,不要写到 /tmp/)
import os, pathlib, subprocess, re, time
from alibabacloud_dingtalk.<module>_1_0 import client as dt_client, models as dt_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models

env_path = pathlib.Path(__file__).parent / ".env"
for line in env_path.read_text().splitlines():
    line = line.strip()
    if line and not line.startswith("#") and "=" in line:
        k, _, v = line.partition("="); os.environ.setdefault(k.strip(), v.strip())

token = subprocess.check_output(
    ["bash", str(pathlib.Path(__file__).parent.parent / "scripts/common/dt_helper.sh"), "--token"],
    text=True).strip()
user_id = os.environ["DINGTALK_MY_USER_ID"]

cfg = open_api_models.Config(protocol="HTTPS", endpoint="api.dingtalk.com")
client = dt_client.Client(cfg)
runtime = util_models.RuntimeOptions()

def h(cls):
    obj = cls(); obj.x_acs_dingtalk_access_token = token; return obj

cases = [
    ("接口名1", lambda: client.create_xxx_with_options(..., h(dt_models.CreateXxxHeaders), runtime)),
    ("接口名2", lambda: client.get_xxx_with_options(..., h(dt_models.GetXxxHeaders), runtime)),
]

for name, fn in cases:
    try:
        fn(); print(f"[OK]  {name}")
    except Exception as e:
        msg = str(e)
        scopes = re.findall(r"'[\w.]+(?:Read|Write)'", msg)
        if "403" in msg:
            print(f"[403] {name}  →  需要权限: {', '.join(scopes) or msg[:120]}")
        else:
            print(f"[ERR] {name}  →  {msg[:120]}")

After collecting all permission points, go to "Developer Console → App Permission Management" to enable them. After publishing, rerun to confirm OK, then continue to Phase 2.
Write the final permission list into the "Required App Permissions" section of references/api.md .

When probing, pass in an "obviously invalid" parameter (such as process_code="PROC-000000"). The purpose is only to trigger a 403 and obtain requiredScopes; it does not matter whether the business logic is correct.


Phase 2: Python SDK Testing (must be all green to continue)

2.1 Directory Structure

tests/
├── .env
├── conftest.py                         # 全局 fixture(token, operator_id, api_headers)
└── dingtalk-<skill-name>/
    ├── __init__.py
    ├── conftest.py                     # 技能专属 fixture(union_id、共享测试资源)
    ├── test_<module>_sdk.py            # 阶段二:SDK 测试
    └── test_<module>.py               # 阶段三:纯 HTTP 测试

2.2 SDK Client Configuration (applicable to all DingTalk v1.0 APIs)

from alibabacloud_dingtalk.<module>_1_0 import client as dt_client, models as dt_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models

@pytest.fixture(scope="session")
def sdk_client():
    config = open_api_models.Config()
    config.protocol = "HTTPS"          # 必须是 HTTPS,HTTP 会 404
    config.endpoint = "api.dingtalk.com"
    return dt_client.Client(config)

@pytest.fixture(scope="session")
def sdk_runtime():
    return util_models.RuntimeOptions()

2.3 SDK Test Template

def test_sdk_create(sdk_client, sdk_runtime, token, union_id):
    h = dt_models.CreateXxxHeaders()
    h.x_acs_dingtalk_access_token = token
    req = dt_models.CreateXxxRequest(subject="[sdk] 测试", operator_id=union_id)
    resp = sdk_client.create_xxx_with_options(union_id, req, h, sdk_runtime)
    item = resp.body
    assert item.id, "响应无 id"           # 字段名先探测,再写断言
    # 清理
    dh = dt_models.DeleteXxxHeaders(); dh.x_acs_dingtalk_access_token = token
    dr = dt_models.DeleteXxxRequest(operator_id=union_id)
    sdk_client.delete_xxx_with_options(union_id, item.id, dr, dh, sdk_runtime)

2.4 Run and Confirm All Green

cd tests
uv run pytest dingtalk-<skill-name>/test_<module>_sdk.py -v
# ✅ 全部 PASSED(允许有权限缺失导致的 SKIPPED)才能继续

Phase 3: Pure HTTP Request Testing (must be all green to continue)

3.1 Description

Do not use the SDK at all; use only the requests library to call the REST API directly, verifying the HTTP behavior of the interface.
This is the second layer of verification independent of the SDK, ensuring that the endpoint path, request fields, HTTP method, and response fields are all correct.

3.2 Test Template

"""
纯 HTTP 测试:不依赖 SDK,验证接口 HTTP 行为
响应字段(从实际探测确认):
  - 创建: id, subject, done, priority
  - 更新: result=True
  - 删除: result=True,删后 GET → 400
"""
import requests

BASE = "https://api.dingtalk.com/v1.0/xxx/users"

def _create(uid, headers, subject, **kwargs):
    r = requests.post(f"{BASE}/{uid}/items", params={"operatorId": uid},
                      headers=headers, json={"subject": subject, **kwargs}, timeout=15)
    assert r.status_code == 200, f"创建失败:{r.text}"
    d = r.json()
    assert "id" in d, f"缺少 id:{d}"   # 用探测到的实际字段名
    return d

def _delete(uid, headers, item_id):
    r = requests.delete(f"{BASE}/{uid}/items/{item_id}", params={"operatorId": uid},
                        headers=headers, timeout=15)
    assert r.status_code == 200, f"删除失败:{r.text}"
    return r.json()

def test_http_create_basic(union_id, api_headers):
    d = _create(union_id, api_headers, "[http] 基础创建")
    assert d["id"]
    _delete(union_id, api_headers, d["id"])

# 权限不足的接口 → 遇到 403 时 pytest.skip
def test_http_list(union_id, api_headers):
    r = requests.post(f"{BASE}/{union_id}/items/list", params={"operatorId": union_id},
                      headers=api_headers, json={"isDone": False}, timeout=15)
    if r.status_code == 403:
        pytest.skip(f"缺少权限 → {r.json().get('message','')[:100]}")
    assert r.status_code == 200
    assert "items" in r.json()  # 使用实际响应字段名

3.3 Run and Confirm All Green

uv run pytest dingtalk-<skill-name>/test_<module>.py -v
# ✅ 全部 PASSED/SKIPPED 才能继续(FAILED = 禁止进入阶段四)

Phase 4: Write SKILL.md + references/api.md

Execute this phase only after both Phase 2 and Phase 3 have fully passed.

4.1 Directory Structure

.agents/skills/dingtalk-<skill-name>/
├── SKILL.md
└── references/
    └── api.md

4.2 SKILL.md Template

Use the SKILL.md of dingtalk-message or dingtalk-todo as a template; it must include:

  • frontmatter:name + description(trigger keywords must be comprehensive)
  • Workflow: read configuration → collect missing items → persist → obtain Token → execute operations
  • Configuration Item Table: a description of the source of each CONFIG key. Prioritize collecting DINGTALK_MY_USER_ID(enterprise employee ID, which can be found in the admin console contacts; it is not a mobile phone number and not a unionId). If the API requires a unionId, the script automatically converts it
  • Identity Identifier Description: the difference between userId vs unionId, explaining which ID the skill's API uses, and the automatic conversion logic
  • Execution Script Template: a complete bash script (create_file /tmp/<task>.sh then bash to execute; heredoc is prohibited), calling scripts/dt_helper.sh to obtain the token and unionId, with no need for inline token caching or id conversion logic:
      HELPER="<THE_SKILLMD_FILE_PATH>/scripts/dt_helper.sh"
    TOKEN=$(bash "$HELPER" --token)                    # 新版 token(带缓存)
    UNION_ID=$(bash "$HELPER" --to-unionid)            # 自动转换,如 API 需要 userId 则用 --get DINGTALK_MY_USER_ID
    USER_ID=$(bash "$HELPER" --get DINGTALK_MY_USER_ID)
    
  • Detailed Reference: points to references/api.md; use grep to look up index patterns

Note: Different APIs use different user ID types. For example, the messaging API only accepts userId, while the todo API only accepts unionId. This must be clearly explained when writing SKILL.md.

Skill Independence Principle: each skill iscompletely independent—when an agent loads a skill, it can only see the SKILL.md of that one skill,and is completely unaware of the existence and content of other skills. Therefore:

  • The identity conversion logic (userId → unionId) must be fully written into each SKILL.md of each skill that needs it; it cannot reference other skills
  • There are only two cross-skill sharing mechanisms:~/.dingtalk-skills/config(configuration file) and scripts/dt_helper.sh(tool script, distributed by common_scripts_load.sh to each skill's scripts/ directory)
  • The bash execution script directly calls scripts/dt_helper.sh, with no need for inline token caching or userId→unionId conversion logic
  • The explanatory text in SKILL.md (workflow, configuration item table, identity description, etc.) must still be complete and self-contained—the agent will only read the SKILL.md of the current skill

4.3 references/api.md Specification

# 钉钉 <功能> API 参考

每个接口包含:
- 完整请求体 JSON 示例(含必填标注)
- 实际响应 JSON 示例(从测试中截取)
- 错误响应示例(含状态码)
- curl 示例

## 错误码表

| HTTP 状态 | 错误码 | 说明 | 处理建议 |

Phase Five: Final Updates

# 1. AGENTS.md 技能目录新增一行
# 2. README.md 技能列表新增一行(含安装命令和能力表)
# 3. README_EN.md 技能列表新增对应英文条目
# 4. tests/.env.example 补充新技能所需配置项(如有)