wecom
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWe drive the WeCom server-side API
() with as a self-built app (自建应用).
https://qyapi.weixin.qq.comcurl + jqSetup: see WeCom authentication for how to create the self-built app, collect CorpID / Secret / AgentId, and grant it the contacts / docs / calendar / meeting permissions. The skill reads,WECOM_CORP_IDandWECOM_CORP_SECRETfrom the environment; on AceDataCloud they are injected automatically by the 企业微信 connector.WECOM_AGENT_ID
WeCom uses a two-step token flow (identical in shape to the WeChat MP API):
- Exchange for an
CorpID + Secret(TTL 7200s).access_token - Pass that as a query string parameter on every other call.
access_token
Never log or echo — treat it like a password.
$WECOM_CORP_SECRETResponses are JSON returned with HTTP 200; means success. On any non-zero
, show the original to the user verbatim (see the error table in
the setup doc).
errcode == 0errcodeerrmsg配置说明: 请查看企业微信认证了解如何创建自建应用、收集CorpID / Secret / AgentId,并为其授予通讯录/文档/日历/会议权限。本功能从环境变量中读取、WECOM_CORP_ID和WECOM_CORP_SECRET;在AceDataCloud平台上,这些变量会由企业微信连接器自动注入。WECOM_AGENT_ID
企业微信采用两步式令牌流程(与微信公众号API流程一致):
- 使用换取
CorpID + Secret(有效期7200秒)。access_token - 在后续所有调用中,将该作为查询字符串参数传递。
access_token
切勿记录或输出——请将其视为密码妥善保管。
$WECOM_CORP_SECRET接口响应为JSON格式,返回HTTP 200状态码;表示调用成功。若不为0,请将原始直接展示给用户(详见配置文档中的错误表)。
errcode == 0errcodeerrmsgRecipes
操作示例
Step 0 — get an access_token (do this first, cache the result)
步骤0 — 获取access_token(需先执行此步骤并缓存结果)
Every recipe below assumes holds a valid token from this step.
$ATsh
undefined以下所有操作示例均假设存储了通过此步骤获取的有效令牌。
$ATsh
undefinedFail loudly if credentials are missing/blank. WECOM_AGENT_ID must be a plain
Fail loudly if credentials are missing/blank. WECOM_AGENT_ID must be a plain
integer because message/meeting recipes pass it to jq via --argjson (numeric JSON).
integer because message/meeting recipes pass it to jq via --argjson (numeric JSON).
: "${WECOM_CORP_ID:?WECOM_CORP_ID not set}" "${WECOM_CORP_SECRET:?WECOM_CORP_SECRET not set}"
case "${WECOM_AGENT_ID:?WECOM_AGENT_ID not set}" in [!0-9]|"") echo "WECOM_AGENT_ID must be an integer" >&2; exit 1;; esac
: "${WECOM_CORP_ID:?WECOM_CORP_ID not set}" "${WECOM_CORP_SECRET:?WECOM_CORP_SECRET not set}"
case "${WECOM_AGENT_ID:?WECOM_AGENT_ID not set}" in [!0-9]|"") echo "WECOM_AGENT_ID must be an integer" >&2; exit 1;; esac
Cache to $TMPDIR so subsequent calls in the same session reuse it (WeCom
Cache to $TMPDIR so subsequent calls in the same session reuse it (WeCom
rate-limits gettoken). Refresh 5 minutes early to avoid edge-of-window failures.
rate-limits gettoken). Refresh 5 minutes early to avoid edge-of-window failures.
TOKEN_CACHE="${TMPDIR:-/tmp}/wecom-token-${WECOM_CORP_ID}-${WECOM_AGENT_ID}.json"
NOW=$(date +%s)
if [ -f "$TOKEN_CACHE" ] && [ "$(jq -r '.exp_at // 0' "$TOKEN_CACHE")" -gt "$((NOW + 300))" ]; then
AT=$(jq -r '.access_token' "$TOKEN_CACHE")
else
RESP=$(curl -sS "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${WECOM_CORP_ID}&corpsecret=${WECOM_CORP_SECRET}")
AT=$(echo "$RESP" | jq -r 'if .errcode == 0 then .access_token else empty end')
if [ -z "$AT" ]; then
echo "Failed to fetch access_token: $RESP" >&2
exit 1
fi
EXPIRES=$(echo "$RESP" | jq -r '.expires_in // 7200')
echo "$RESP" | jq --argjson now "$NOW" --argjson ttl "$EXPIRES"
'{access_token, exp_at: ($now + $ttl)}' > "$TOKEN_CACHE" chmod 600 "$TOKEN_CACHE" fi
'{access_token, exp_at: ($now + $ttl)}' > "$TOKEN_CACHE" chmod 600 "$TOKEN_CACHE" fi
A tiny helper keeps the recipes short — GET with the token appended, erroring on non-zero `errcode`:
```sh
wc_get() { curl -sS "https://qyapi.weixin.qq.com/cgi-bin/$1&access_token=${AT}" \
| jq 'if .errcode == 0 then . else error("WeCom \(.errcode): \(.errmsg)") end'; }
wc_post() { curl -sS -X POST "https://qyapi.weixin.qq.com/cgi-bin/$1?access_token=${AT}" \
-H 'Content-Type: application/json' -d "$2" \
| jq 'if .errcode == 0 then . else error("WeCom \(.errcode): \(.errmsg)") end'; }TOKEN_CACHE="${TMPDIR:-/tmp}/wecom-token-${WECOM_CORP_ID}-${WECOM_AGENT_ID}.json"
NOW=$(date +%s)
if [ -f "$TOKEN_CACHE" ] && [ "$(jq -r '.exp_at // 0' "$TOKEN_CACHE")" -gt "$((NOW + 300))" ]; then
AT=$(jq -r '.access_token' "$TOKEN_CACHE")
else
RESP=$(curl -sS "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${WECOM_CORP_ID}&corpsecret=${WECOM_CORP_SECRET}")
AT=$(echo "$RESP" | jq -r 'if .errcode == 0 then .access_token else empty end')
if [ -z "$AT" ]; then
echo "Failed to fetch access_token: $RESP" >&2
exit 1
fi
EXPIRES=$(echo "$RESP" | jq -r '.expires_in // 7200')
echo "$RESP" | jq --argjson now "$NOW" --argjson ttl "$EXPIRES"
'{access_token, exp_at: ($now + $ttl)}' > "$TOKEN_CACHE" chmod 600 "$TOKEN_CACHE" fi
'{access_token, exp_at: ($now + $ttl)}' > "$TOKEN_CACHE" chmod 600 "$TOKEN_CACHE" fi
我们编写了一个小型辅助函数来简化操作示例——自动附加令牌的GET请求,当`errcode`不为0时抛出错误:
```sh
wc_get() { curl -sS "https://qyapi.weixin.qq.com/cgi-bin/$1&access_token=${AT}" \
| jq 'if .errcode == 0 then . else error("WeCom \(.errcode): \(.errmsg)") end'; }
wc_post() { curl -sS -X POST "https://qyapi.weixin.qq.com/cgi-bin/$1?access_token=${AT}" \
-H 'Content-Type: application/json' -d "$2" \
| jq 'if .errcode == 0 then . else error("WeCom \(.errcode): \(.errmsg)") end'; }通讯录 Contacts (read)
通讯录(读取)
Reading real names / mobiles requires the app to have 通讯录读取 privilege; otherwise names come back masked. Members outside the app's 可见范围 return/errcode 60011.81013
List the department tree (root department id is ):
1sh
wc_get "department/list?id=1" | jq '[.department[] | {id, name, parentid}]'Preferred enumeration — (cursor). This is the robust way to list members:
it works for a self-built app with only its own Secret. / still
work within the app's 可见范围, but WeCom has been tightening those (the 2022‑08‑15 change blocks
newly‑added 通讯录同步 IPs from calling them), so lead with and fall back to
only if you need names in one shot.
user/list_iduser/simplelistuser/listuser/list_idsimplelistsh
wc_post "user/list_id" '{"cursor":"","limit":10000}' \
| jq '{next_cursor, userids: [.dept_user[] | .userid] | unique}'Get one member's full profile — this is how you resolve a name → userid for messaging:
sh
wc_get "user/get?userid=USERID" \
| jq '{userid, name, department, position, mobile, email, status}'List members of a department in one call (convenience; needs 通讯录 view on that department —
if it returns , use + above instead):
60011user/list_iduser/getsh
wc_get "user/simplelist?department_id=1&fetch_child=1" \
| jq '[.userlist[] | {userid, name, department}]'Search by name — robust path (enumerate ids, then read each profile and filter):
sh
for uid in $(wc_post "user/list_id" '{"cursor":"","limit":10000}' | jq -r '.dept_user[].userid' | sort -u); do
wc_get "user/get?userid=${uid}" | jq -c '{userid, name}'
done | jq -s --arg q "张三" '[.[] | select(.name | contains($q))]'Shortcut whenis available to the app:simplelist.wc_get "user/simplelist?department_id=1&fetch_child=1" | jq --arg q "张三" '[.userlist[] | select(.name | contains($q)) | {userid, name}]'
读取真实姓名/手机号需要应用拥有通讯录读取权限;否则返回的姓名会被掩码处理。应用可见范围外的成员会返回/errcode 60011。81013
获取部门树(根部门ID为):
1sh
wc_get "department/list?id=1" | jq '[.department[] | {id, name, parentid}]'推荐枚举方式 — (游标分页)。这是列举成员的可靠方式:适用于仅拥有自身Secret的自建应用。 / 仍可在应用可见范围内使用,但企业微信已收紧相关限制(2022-08-15的更新阻止新增的通讯录同步IP调用这些接口),因此优先使用,仅当需要一次性获取姓名时才回退到。
user/list_iduser/simplelistuser/listuser/list_idsimplelistsh
wc_post "user/list_id" '{"cursor":"","limit":10000}' \
| jq '{next_cursor, userids: [.dept_user[] | .userid] | unique}'获取单个成员的完整信息——这是将姓名转换为userid以发送消息的方法:
sh
wc_get "user/get?userid=USERID" \
| jq '{userid, name, department, position, mobile, email, status}'一次性获取部门成员列表(便捷方式;需要应用拥有该部门的通讯录查看权限——若返回,请改用上述 + 组合):
60011user/list_iduser/getsh
wc_get "user/simplelist?department_id=1&fetch_child=1" \
| jq '[.userlist[] | {userid, name, department}]'按姓名搜索(可靠方式:枚举ID,然后读取每个成员信息并过滤):
sh
for uid in $(wc_post "user/list_id" '{"cursor":"","limit":10000}' | jq -r '.dept_user[].userid' | sort -u); do
wc_get "user/get?userid=${uid}" | jq -c '{userid, name}'
done | jq -s --arg q "张三" '[.[] | select(.name | contains($q))]'当应用可使用时的快捷方式:simplelist。wc_get "user/simplelist?department_id=1&fetch_child=1" | jq --arg q "张三" '[.userlist[] | select(.name | contains($q)) | {userid, name}]'
应用消息 App messages (send) — GATED
应用消息(发送)——需确认
message/sendagentid$WECOM_AGENT_IDtouser|@alltopartytotagSending fans out to real people. Always show the exact recipients + content and get explicit user confirmation before running/message/send, even if the instruction says "just send it".appchat/send
Send a text message to one or more members:
sh
wc_post "message/send" "$(jq -nc --arg to "USERID1|USERID2" --argjson agent "${WECOM_AGENT_ID}" \
--arg content "构建已通过,请查看。" \
'{touser:$to, msgtype:"text", agentid:$agent, text:{content:$content}, safe:0}')" \
| jq '{msgid, invaliduser}'Send a Markdown card (richer formatting, members only):
sh
wc_post "message/send" "$(jq -nc --arg to "@all" --argjson agent "${WECOM_AGENT_ID}" \
--arg md "**发布通知**\n>版本:v1.2.0\n>状态:<font color=\"info\">成功</font>" \
'{touser:$to, msgtype:"markdown", agentid:$agent, markdown:{content:$md}}')" \
| jq '{msgid}'Send a clickable text-card:
sh
wc_post "message/send" "$(jq -nc --arg to "USERID1" --argjson agent "${WECOM_AGENT_ID}" \
'{touser:$to, msgtype:"textcard", agentid:$agent,
textcard:{title:"周报已生成", description:"点击查看本周汇总", url:"https://example.com/report", btntxt:"详情"}}')" \
| jq '{msgid}'message/sendagentid$WECOM_AGENT_IDtouser|@alltopartytotag消息会发送给真实用户。在执行/message/send前,务必展示确切的收件人+内容并获得用户明确确认,即使用户指令为“直接发送”。appchat/send
向一位或多位成员发送文本消息:
sh
wc_post "message/send" "$(jq -nc --arg to "USERID1|USERID2" --argjson agent "${WECOM_AGENT_ID}" \
--arg content "构建已通过,请查看。" \
'{touser:$to, msgtype:"text", agentid:$agent, text:{content:$content}, safe:0}')" \
| jq '{msgid, invaliduser}'发送Markdown卡片(格式更丰富,仅支持成员接收):
sh
wc_post "message/send" "$(jq -nc --arg to "@all" --argjson agent "${WECOM_AGENT_ID}" \
--arg md "**发布通知**\n>版本:v1.2.0\n>状态:<font color=\"info\">成功</font>" \
'{touser:$to, msgtype:"markdown", agentid:$agent, markdown:{content:$md}}')" \
| jq '{msgid}'发送可点击的文本卡片:
sh
wc_post "message/send" "$(jq -nc --arg to "USERID1" --argjson agent "${WECOM_AGENT_ID}" \
'{touser:$to, msgtype:"textcard", agentid:$agent,
textcard:{title:"周报已生成", description:"点击查看本周汇总", url:"https://example.com/report", btntxt:"详情"}}')" \
| jq '{msgid}'应用群聊 App group chat
应用群聊
Create a group the app owns, then push messages into it. is a custom id you choose (reuse it later).
chatidsh
undefined创建应用所属的群聊,然后向群内推送消息。为自定义ID(后续可重复使用)。
chatidsh
undefinedCreate (owner + members are userids). Omit chatid to let WeCom assign one.
创建群聊(群主和成员均为userid)。省略chatid则由企业微信自动分配。
wc_post "appchat/create" '{"name":"项目组","owner":"USERID1","userlist":["USERID1","USERID2","USERID3"],"chatid":"proj_alpha"}'
| jq '{chatid}'
| jq '{chatid}'
wc_post "appchat/create" '{"name":"项目组","owner":"USERID1","userlist":["USERID1","USERID2","USERID3"],"chatid":"proj_alpha"}'
| jq '{chatid}'
| jq '{chatid}'
Send into it (same GATED confirmation rule as app messages).
向群内发送消息(与应用消息遵循相同的确认规则)。
wc_post "appchat/send" '{"chatid":"proj_alpha","msgtype":"text","text":{"content":"今晚 8 点线上同步。"}}'
| jq '{errcode, errmsg}'
| jq '{errcode, errmsg}'
wc_post "appchat/send" '{"chatid":"proj_alpha","msgtype":"text","text":{"content":"今晚 8 点线上同步。"}}'
| jq '{errcode, errmsg}'
| jq '{errcode, errmsg}'
Inspect a group the app created.
查看应用创建的群聊信息。
wc_get "appchat/get?chatid=proj_alpha" | jq '.chat_info | {name, owner, userlist}'
undefinedwc_get "appchat/get?chatid=proj_alpha" | jq '.chat_info | {name, owner, userlist}'
undefined企业微信文档 WeDoc (docs & smart sheets)
企业微信文档(文档与智能表格)
Requires the app's 文档 (WeDoc) permission. : = 文档 (document), = 表格 (spreadsheet),
= 智能表格 (smart sheet).
doc_type3410Create a document and get its edit URL:
sh
wc_post "wedoc/create_doc" "$(jq -nc --arg name "项目周报 2026-07" \
'{doc_type:3, doc_name:$name}')" \
| jq '{docid, url}'Get a shareable link for an existing doc:
sh
wc_post "wedoc/doc_share" '{"docid":"DOCID"}' | jq '{share_url: .share_url}'Read a document's structured content. WeCom returns a object (keyed by ,
not ) whose body is a tree of typed blocks, not Markdown — inspect the real shape before
relying on inner field names:
documentdocument_iddoc_idsh
wc_post "wedoc/document/get" '{"docid":"DOCID"}' \
| jq '{document_id: .document.document_id, top_level_keys: (.document | keys)}'Smart-sheet (智能表格) sub-tables, fields and records use theendpoints (wedoc/smartsheet/*,get_sheet,get_fields,get_records, …) — sameadd_recordspattern, keyed bywc_post+docid. Reach for them when the user asks to read/write rows of a 智能表格.sheet_id
需要应用拥有**文档(WeDoc)**权限。取值: = 文档, = 表格, = 智能表格。
doc_type3410创建文档并获取编辑链接:
sh
wc_post "wedoc/create_doc" "$(jq -nc --arg name "项目周报 2026-07" \
'{doc_type:3, doc_name:$name}')" \
| jq '{docid, url}'获取现有文档的可分享链接:
sh
wc_post "wedoc/doc_share" '{"docid":"DOCID"}' | jq '{share_url: .share_url}'读取文档的结构化内容。企业微信返回一个以(而非)为键的对象,其主体为类型化块组成的树结构,并非Markdown格式——在依赖内部字段名称前,请先查看实际返回结构:
document_iddoc_iddocumentsh
wc_post "wedoc/document/get" '{"docid":"DOCID"}' \
| jq '{document_id: .document.document_id, top_level_keys: (.document | keys)}'智能表格的子表格、字段和记录需使用系列接口(wedoc/smartsheet/*、get_sheet、get_fields、get_records等)——遵循相同的add_records调用模式,以wc_post+docid为标识。当用户需要读取/写入智能表格行数据时,请使用这些接口。sheet_id
日程 Schedule (日程)
日程
Requires the app's 日历/日程 permission. Times are epoch seconds; convert with GNU :
datesh
START=$(date -d "2026-07-10 15:00" +%s); END=$(date -d "2026-07-10 16:00" +%s)Create a schedule (organizer + attendees are userids):
sh
wc_post "oa/schedule/add" "$(jq -nc --arg org "USERID1" --argjson s "$START" --argjson e "$END" \
'{schedule:{organizer:$org, start_time:$s, end_time:$e, summary:"项目评审",
description:"评审 v1.2 需求", location:"会议室 A",
attendees:[{userid:"USERID2"},{userid:"USERID3"}],
reminders:{is_remind:1, remind_before_event_secs:900}}}')" \
| jq '{schedule_id}'Read schedules by id, and cancel one:
sh
wc_post "oa/schedule/get" '{"schedule_id_list":["SCHEDULE_ID"]}' \
| jq '[.schedule_list[] | {schedule_id, summary, start_time, end_time, organizer}]'
wc_post "oa/schedule/del" '{"schedule_id":"SCHEDULE_ID"}' | jq '{errcode, errmsg}'To list a member's upcoming schedules you need their calendar id () andcal_id— create/query calendars via theoa/schedule/get_by_calendarendpoints first.oa/calendar/*
需要应用拥有日历/日程权限。时间采用**时间戳(秒)**格式;可使用GNU 工具转换:
datesh
START=$(date -d "2026-07-10 15:00" +%s); END=$(date -d "2026-07-10 16:00" +%s)创建日程(组织者和参会者均为userid):
sh
wc_post "oa/schedule/add" "$(jq -nc --arg org "USERID1" --argjson s "$START" --argjson e "$END" \
'{schedule:{organizer:$org, start_time:$s, end_time:$e, summary:"项目评审",
description:"评审 v1.2 需求", location:"会议室 A",
attendees:[{userid:"USERID2"},{userid:"USERID3"}],
reminders:{is_remind:1, remind_before_event_secs:900}}}')" \
| jq '{schedule_id}'按ID读取日程,以及取消日程:
sh
wc_post "oa/schedule/get" '{"schedule_id_list":["SCHEDULE_ID"]}' \
| jq '[.schedule_list[] | {schedule_id, summary, start_time, end_time, organizer}]'
wc_post "oa/schedule/del" '{"schedule_id":"SCHEDULE_ID"}' | jq '{errcode, errmsg}'若要列举成员的即将到来的日程,需先获取其日历ID()并调用cal_id接口——需先通过oa/schedule/get_by_calendar系列接口创建/查询日历。oa/calendar/*
会议 Meeting (会议)
会议
Requires the app's 会议 permission. is epoch seconds, is seconds.
meeting_startmeeting_durationCreate a scheduled meeting:
sh
wc_post "meeting/create" "$(jq -nc --arg admin "USERID1" --argjson start "$(date -d '2026-07-11 10:00' +%s)" \
'{admin_userid:$admin, title:"周例会", meeting_start:$start, meeting_duration:3600,
description:"同步本周进展", invitees:{userid:["USERID2","USERID3"]}}')" \
| jq '{meetingid}'List a member's meetings, read details, and cancel ( is a POST with a
cursor body, mirroring WeCom's other list endpoints):
get_user_meetingidget_user_*_idsh
wc_post "meeting/get_user_meetingid" '{"userid":"USERID1","cursor":"","limit":100}' \
| jq '{meetingid_list, next_cursor}'
wc_post "meeting/get_info" '{"meetingid":"MEETINGID"}' \
| jq '.meeting_info | {title, meeting_start, meeting_duration, state}'
wc_post "meeting/cancel" '{"meetingid":"MEETINGID"}' | jq '{errcode, errmsg}'需要应用拥有会议权限。为时间戳(秒),为会议时长(秒)。
meeting_startmeeting_duration创建预约会议:
sh
wc_post "meeting/create" "$(jq -nc --arg admin "USERID1" --argjson start "$(date -d '2026-07-11 10:00' +%s)" \
'{admin_userid:$admin, title:"周例会", meeting_start:$start, meeting_duration:3600,
description:"同步本周进展", invitees:{userid:["USERID2","USERID3"]}}')" \
| jq '{meetingid}'列举成员的会议、读取会议详情,以及取消会议(为POST接口,需传入游标参数,与企业微信其他列举接口逻辑一致):
get_user_meetingidget_user_*_idsh
wc_post "meeting/get_user_meetingid" '{"userid":"USERID1","cursor":"","limit":100}' \
| jq '{meetingid_list, next_cursor}'
wc_post "meeting/get_info" '{"meetingid":"MEETINGID"}' \
| jq '.meeting_info | {title, meeting_start, meeting_duration, state}'
wc_post "meeting/cancel" '{"meetingid":"MEETINGID"}' | jq '{errcode, errmsg}'Safety rules
安全规则
- Never print . It is equivalent to full app access.
$WECOM_CORP_SECRET - Confirm before anything outward-facing — ,
message/send,appchat/sendandoa/schedule/addall notify real colleagues. Show the exact recipients / attendees and the content, get explicit approval, then run the call. Never infer consent from vague wording.meeting/create - Messaging / schedules / meetings act on userids, not display names — resolve a name to a userid with the contact recipes first, and confirm the person if the name is ambiguous.
- On (API forbidden) or
errcode 48002/60011(no privilege), the app is missing a permission or the target is outside its 可见范围 — tell the user which capability to grant rather than retrying blindly.301002
- 切勿输出。它等同于应用的完整访问权限。
$WECOM_CORP_SECRET - 所有对外操作前需确认——、
message/send、appchat/send和oa/schedule/add都会通知真实同事。请展示确切的收件人/参会者及内容,获得明确批准后再执行调用。切勿从模糊表述中推断用户同意。meeting/create - 消息/日程/会议操作基于userid而非显示名称——请先通过通讯录操作将姓名转换为userid,若姓名存在歧义,请确认目标人员。
- 若出现(API被禁止)或
errcode 48002/60011(无权限),说明应用缺少对应权限或目标对象在可见范围外——请告知用户需要授予的权限,而非盲目重试。301002
##</think_never_used_51bce0c785ca2f68081bfa7d91973934>暂不支持的功能
- 读取 inbound chat history(会话内容存档 / msgaudit)——这是一项独立的付费功能,需要专用Secret、RSA密钥和原生企业微信金融SDK;无法在此沙箱环境中运行。本功能仅能发送消息,无法读取成员的私人会话。
- 通用待办(todo)增删改查——企业微信开放API未向自建应用提供此功能;若需时间绑定的提醒,请使用日程功能替代。
Not supported here
—
- Reading inbound chat history (会话内容存档 / msgaudit) — a separate paid capability needing a dedicated Secret, an RSA key and the native WeCom Finance SDK; it can't run in this sandbox. The skill sends messages but can't read members' private conversations.
- A generic todo (待办) CRUD — WeCom's open API exposes none for self-built apps; use schedules (日程) for time-bound reminders instead.
—