wechat-official-account
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWe drive the WeChat MP server-side API with . Unlike OAuth-bearer connectors, WeChat MP uses a two-step flow:
curl + jq- Exchange for an
AppID + AppSecret(TTL 7200s, global limit ≈ 2000 calls/day per app).access_token - Pass that as a query string parameter on every other call.
access_token
The user's credentials are in and . Never log or echo — treat it like a password.
$WECHAT_APP_ID$WECHAT_APP_SECRET$WECHAT_APP_SECRETThe WeChat MP API returns standard JSON. Errors are returned with HTTP 200; the body looks like . means success — show the original to the user verbatim on any non-zero code.
{"errcode": 40013, "errmsg": "invalid appid"}errcode == 0errmsg我们使用调用微信公众平台服务端API。与OAuth承载连接器不同,微信公众平台采用两步流程:
curl + jq- 使用换取
AppID + AppSecret(有效期7200秒,每个应用每日全局调用限制约2000次)。access_token - 在其他所有调用中,将该作为查询字符串参数传递。
access_token
用户的凭证存储在和中。切勿记录或回显——请将其视为密码对待。
$WECHAT_APP_ID$WECHAT_APP_SECRET$WECHAT_APP_SECRET微信公众平台API返回标准JSON格式数据。错误信息以HTTP 200状态码返回;响应体格式类似。表示成功——若返回非零错误码,需将原始原封不动展示给用户。
{"errcode": 40013, "errmsg": "invalid appid"}errcode == 0errmsgImportant constraints — surface these to the user before they're surprised
重要限制——在用户遇到问题前提前告知
- IP whitelist: every API call's source IP must be in this app's IP whitelist (公众平台 → 设置与开发 → 基本配置 → IP 白名单). If you see ("invalid ip"), the worker's egress IP isn't whitelisted; tell the user to add the IP shown in
errcode 40164and retry.errmsg - Verified account required for publishing: as of 2025-07, only verified (已认证) corporate-subject accounts can call and
freepublish/*. Personal-subject accounts and unverified corporate accounts get a permission error. Drafts (mass/*) and customer messages (draft/*) usually work without verification.message/custom/* - Group-send quota is harsh: 服务号 = 4 sends/month, 订阅号 = 1 send/day. Treat and
freepublish/submitlike a destructive operation — always confirm with the user before calling them, even if instructions say "publish it". Default to creating a draft and pasting the draft URL.mass/sendall - Customer-service window is 48 hours: only works for a follower whose
message/custom/sendinteracted with the account in the last 48 hours. Outside that window you getopenid.errcode 45015
- IP白名单:所有API调用的源IP必须在该应用的IP白名单中(公众平台 → 设置与开发 → 基本配置 → IP白名单)。若出现("invalid ip"),说明工作节点的出口IP未在白名单中;请告知用户将
errcode 40164中显示的IP添加至白名单后重试。errmsg - 发布需认证账号:截至2025年7月,只有已认证的企业主体账号才能调用和
freepublish/*接口。个人主体账号和未认证企业账号会收到权限错误。草稿接口(mass/*)和客服消息接口(draft/*)通常无需认证即可使用。message/custom/* - 群发配额严格:服务号每月可发送4次,订阅号每日可发送1次。请将和
freepublish/submit视为破坏性操作——即使指令要求“发布”,调用前也务必与用户确认。默认操作应为创建草稿并粘贴草稿链接。mass/sendall - 客服消息窗口为48小时:仅对过去48小时内与账号互动过的粉丝(通过
message/custom/send识别)有效。超出该窗口会返回openid。errcode 45015
Recipes
操作指南
Step 0 — get an access_token (do this first, cache the result)
步骤0——获取access_token(先执行此步骤,缓存结果)
sh
undefinedsh
undefinedCache to /tmp so subsequent calls in the same session reuse it.
缓存至/tmp,以便同一会话中的后续调用复用。
TOKEN_CACHE="/tmp/wx-mp-token-${WECHAT_APP_ID}.json"
TOKEN_CACHE="/tmp/wx-mp-token-${WECHAT_APP_ID}.json"
Reuse cached token if it's still valid (we conservatively refresh
如果缓存的token仍有效(我们提前5分钟刷新,避免窗口边缘的失败),则复用。
5 minutes early to avoid edge-of-window failures).
—
NOW=$(date +%s)
if [ -f "$TOKEN_CACHE" ] && [ "$(jq -r '.exp_at // 0' "$TOKEN_CACHE")" -gt "$((NOW + 300))" ]; then
WECHAT_ACCESS_TOKEN=$(jq -r '.access_token' "$TOKEN_CACHE")
else
RESP=$(curl -sS "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}")
WECHAT_ACCESS_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty')
if [ -z "$WECHAT_ACCESS_TOKEN" ]; then
echo "Failed to fetch access_token: $RESP" >&2
exit 1
fi
EXPIRES=$(echo "$RESP" | jq -r '.expires_in // 7200')
jq -nc --arg t "$WECHAT_ACCESS_TOKEN" --argjson e "$((NOW + EXPIRES))"
'{access_token:$t, exp_at:$e}' > "$TOKEN_CACHE" fi echo "OK token=${WECHAT_ACCESS_TOKEN:0:8}…"
'{access_token:$t, exp_at:$e}' > "$TOKEN_CACHE" fi echo "OK token=${WECHAT_ACCESS_TOKEN:0:8}…"
If the response is `{"errcode": 40164, ...}` (invalid IP) or `{"errcode": 40013, ...}` (invalid appid) — surface that error to the user; it means the connector setup itself is wrong, not the request.NOW=$(date +%s)
if [ -f "$TOKEN_CACHE" ] && [ "$(jq -r '.exp_at // 0' "$TOKEN_CACHE")" -gt "$((NOW + 300))" ]; then
WECHAT_ACCESS_TOKEN=$(jq -r '.access_token' "$TOKEN_CACHE")
else
RESP=$(curl -sS "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}")
WECHAT_ACCESS_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty')
if [ -z "$WECHAT_ACCESS_TOKEN" ]; then
echo "Failed to fetch access_token: $RESP" >&2
exit 1
fi
EXPIRES=$(echo "$RESP" | jq -r '.expires_in // 7200')
jq -nc --arg t "$WECHAT_ACCESS_TOKEN" --argjson e "$((NOW + EXPIRES))"
'{access_token:$t, exp_at:$e}' > "$TOKEN_CACHE" fi echo "OK token=${WECHAT_ACCESS_TOKEN:0:8}…"
'{access_token:$t, exp_at:$e}' > "$TOKEN_CACHE" fi echo "OK token=${WECHAT_ACCESS_TOKEN:0:8}…"
如果响应为`{"errcode": 40164, ...}`(无效IP)或`{"errcode": 40013, ...}`(无效appid)——请将该错误告知用户;这表示连接器本身设置错误,而非请求问题。Verify the connection (always run this once before complex operations)
验证连接(复杂操作前务必先执行一次)
sh
undefinedsh
undefinedPull basic public-account self-info; cheapest call that proves the token works.
获取公众号基本信息;这是验证token有效的最简便调用。
curl -sS "https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo?access_token=${WECHAT_ACCESS_TOKEN}" | jq
undefinedcurl -sS "https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo?access_token=${WECHAT_ACCESS_TOKEN}" | jq
undefinedUpload an image to use inside an article body (returns a public URL)
上传文章正文中使用的图片(返回公共URL)
This is for tags inside the article HTML. The returned is hosted by Tencent and works in published articles.
<img src="...">urlsh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=${WECHAT_ACCESS_TOKEN}" \
-F "media=@/path/to/your-image.jpg"此操作适用于文章HTML中的标签。返回的由腾讯托管,可在已发布的文章中正常使用。
<img src="...">urlsh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=${WECHAT_ACCESS_TOKEN}" \
-F "media=@/path/to/your-image.jpg"→ {"url": "http://mmbiz.qpic.cn/..."}
→ {"url": "http://mmbiz.qpic.cn/..."}
Limits: ≤ 1 MB, JPG/PNG only, no count limit on this endpoint.
限制:≤1MB,仅支持JPG/PNG格式,此接口无调用次数限制。Upload a thumbnail (needed as the article cover)
上传缩略图(文章封面所需)
sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${WECHAT_ACCESS_TOKEN}&type=thumb" \
-F "media=@/path/to/cover.jpg" | jq '{media_id, url}'sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${WECHAT_ACCESS_TOKEN}&type=thumb" \
-F "media=@/path/to/cover.jpg" | jq '{media_id, url}'→ {"media_id": "MEDIA_ID", "url": "http://mmbiz.qpic.cn/..."}
→ {"media_id": "MEDIA_ID", "url": "http://mmbiz.qpic.cn/..."}
The `media_id` you get back is what you pass as `thumb_media_id` when creating a draft.
Recommended cover dimensions: 900×500 px, JPG, < 64 KB ideally.
返回的`media_id`将作为创建草稿时的`thumb_media_id`参数。推荐封面尺寸:900×500像素,JPG格式,理想大小<64KB。Create a draft (the safe default — do not directly publish)
创建草稿(安全默认操作——请勿直接发布)
sh
TITLE="Q1 product update"
AUTHOR="Acme Inc."
THUMB_MEDIA_ID="MEDIA_ID_FROM_PREVIOUS_STEP"
CONTENT_HTML='<p>欢迎关注我们的最新动态。</p><p><img src="http://mmbiz.qpic.cn/..."></p><p>更多内容请见底部「阅读原文」。</p>'
DIGEST="Q1 has been a wild ride — here's what shipped."
SOURCE_URL="https://example.com/q1-recap" # optional; populates 「阅读原文」, leave empty to omit
PAYLOAD=$(jq -nc \
--arg title "$TITLE" \
--arg author "$AUTHOR" \
--arg thumb "$THUMB_MEDIA_ID" \
--arg content "$CONTENT_HTML" \
--arg digest "$DIGEST" \
--arg source_url "$SOURCE_URL" \
'{articles: [{
article_type: "news",
title: $title,
author: $author,
thumb_media_id: $thumb,
content: $content,
digest: $digest,
content_source_url: $source_url,
need_open_comment: 0,
only_fans_can_comment: 0
}]}')
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$PAYLOAD" | jqsh
TITLE="Q1产品更新"
AUTHOR="Acme Inc."
THUMB_MEDIA_ID="MEDIA_ID_FROM_PREVIOUS_STEP"
CONTENT_HTML='<p>欢迎关注我们的最新动态。</p><p><img src="http://mmbiz.qpic.cn/..."></p><p>更多内容请见底部「阅读原文」。</p>'
DIGEST="Q1 has been a wild ride — here's what shipped."
SOURCE_URL="https://example.com/q1-recap" # 可选;填充「阅读原文」,留空则省略
PAYLOAD=$(jq -nc \
--arg title "$TITLE" \
--arg author "$AUTHOR" \
--arg thumb "$THUMB_MEDIA_ID" \
--arg content "$CONTENT_HTML" \
--arg digest "$DIGEST" \
--arg source_url "$SOURCE_URL" \
'{articles: [{
article_type: "news",
title: $title,
author: $author,
thumb_media_id: $thumb,
content: $content,
digest: $digest,
content_source_url: $source_url,
need_open_comment: 0,
only_fans_can_comment: 0
}]}')
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$PAYLOAD" | jq→ {"media_id": "DRAFT_MEDIA_ID"}
→ {"media_id": "DRAFT_MEDIA_ID"}
Tell the user: *"Draft created. Open https://mp.weixin.qq.com → 草稿箱 to review and tap «发表» from there."* — that's the safest path because the user controls the publish click in WeChat's own UI.
告知用户:*"草稿已创建。打开https://mp.weixin.qq.com → 草稿箱进行审核,然后点击「发表」。"*——这是最安全的方式,因为用户可在微信官方UI中控制发布操作。List drafts (newest first)
列出草稿(按最新排序)
sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
| jq '.item[] | {media_id, update_time, title: .content.news_item[0].title}'sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
| jq '.item[] | {media_id, update_time, title: .content.news_item[0].title}'Get the full content of one draft
获取单篇草稿的完整内容
sh
DRAFT_MEDIA_ID="..."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/get?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jqsh
DRAFT_MEDIA_ID="..."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/get?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jqUpdate an existing draft
更新现有草稿
sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/update?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" --arg t "Updated title" --arg c "<p>new body</p>" --arg th "$THUMB_MEDIA_ID" '
{media_id: $m, index: 0, articles: {
title: $t, content: $c, thumb_media_id: $th
}}')" | jqsh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/draft/update?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" --arg t "Updated title" --arg c "<p>new body</p>" --arg th "$THUMB_MEDIA_ID" '
{media_id: $m, index: 0, articles: {
title: $t, content: $c, thumb_media_id: $th
}}')" | jqPublish a draft to «发表记录» — DESTRUCTIVE, confirm before calling
将草稿发布至「发表记录」——破坏性操作,调用前请确认
sh
undefinedsh
undefinedEats one of the monthly publish slots. Always echo back to the user
会消耗一个月度发布配额。调用前务必向用户回显即将发布的内容,并要求用户在对话中明确回复“yes”确认。
what's about to go live and require an explicit "yes" confirmation
—
in conversation BEFORE invoking this.
—
curl -sS -X POST
"https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
"https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
curl -sS -X POST
"https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
"https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
→ {"errcode": 0, "msg_data_id": ..., "publish_id": "..."}
→ {"errcode": 0, "msg_data_id": ..., "publish_id": "..."}
Then poll publish status (publishing is async, takes ~5-30 seconds):
```sh
PUBLISH_ID="..."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg p "$PUBLISH_ID" '{publish_id: $p}')" | jq '{publish_status, fail_idx, article_id, article_url: .article_detail.item[0].article_url}'
然后轮询发布状态(发布为异步操作,耗时约5-30秒):
```sh
PUBLISH_ID="..."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg p "$PUBLISH_ID" '{publish_id: $p}')" | jq '{publish_status, fail_idx, article_id, article_url: .article_detail.item[0].article_url}'publish_status: 0 = success, 1 = publishing, 2 = original-check-failed,
publish_status: 0 = 成功, 1 = 发布中, 2 = 原创校验失败,
3 = failed, 4 = published-but-removed, 5 = unverified-removed
3 = 失败, 4 = 已发布但被删除, 5 = 未认证被删除
When `publish_status == 0`, return `article_detail.item[0].article_url` to the user — that's the canonical https://mp.weixin.qq.com/s/... URL.
当`publish_status == 0`时,将`article_detail.item[0].article_url`返回给用户——这是标准的https://mp.weixin.qq.com/s/...链接。List already-published articles
列出已发布文章
sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
| jq '.item[] | {article_id, update_time, title: .content.news_item[0].title, url: .content.news_item[0].url}'sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
| jq '.item[] | {article_id, update_time, title: .content.news_item[0].title, url: .content.news_item[0].url}'Send a customer-service message (one specific follower, 48h window)
发送客服消息(指定单个粉丝,48小时窗口)
sh
OPENID="oXXXXXXXXXXXXXXXXX" # the recipient follower's openid
TEXT="Hi! Your subscription has been renewed. Thanks for sticking with us."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg u "$OPENID" --arg t "$TEXT" '
{touser: $u, msgtype: "text", text: {content: $t}}')" | jqerrcode 45015To send an article card via customer message, change to with (use to first create a permanent news media_id).
msgtype: "mpnews"mpnews: {media_id: "..."}material/add_newssh
OPENID="oXXXXXXXXXXXXXXXXX" # 接收粉丝的openid
TEXT="Hi! Your subscription has been renewed. Thanks for sticking with us."
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw "$(jq -nc --arg u "$OPENID" --arg t "$TEXT" '
{touser: $u, msgtype: "text", text: {content: $t}}')" | jqerrcode 45015若要通过客服消息发送文章卡片,需将改为,并传入(需先使用创建永久图文素材的media_id)。
msgtype"mpnews"mpnews: {media_id: "..."}material/add_newsPull follower-growth + read stats
获取粉丝增长+阅读统计数据
sh
BEGIN="2026-04-25"
END="2026-05-01" # max 7-day window for getusersummarysh
BEGIN="2026-04-25"
END="2026-05-01" # getusersummary接口最多支持7天时间窗口New / unsubscribed / cumulative followers per day
每日新增/取消关注/累计粉丝数
curl -sS -X POST
"https://api.weixin.qq.com/datacube/getusersummary?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$END" '{begin_date: $b, end_date: $e}')"
| jq '.list[] | {date: .ref_date, new: .new_user, lost: .cancel_user, source: .user_source}'
"https://api.weixin.qq.com/datacube/getusersummary?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$END" '{begin_date: $b, end_date: $e}')"
| jq '.list[] | {date: .ref_date, new: .new_user, lost: .cancel_user, source: .user_source}'
curl -sS -X POST
"https://api.weixin.qq.com/datacube/getusersummary?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$END" '{begin_date: $b, end_date: $e}')"
| jq '.list[] | {date: .ref_date, new: .new_user, lost: .cancel_user, source: .user_source}'
"https://api.weixin.qq.com/datacube/getusersummary?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$END" '{begin_date: $b, end_date: $e}')"
| jq '.list[] | {date: .ref_date, new: .new_user, lost: .cancel_user, source: .user_source}'
Article reads per day (max 3-day window for getuserread)
每日文章阅读量(getuserread接口最多支持3天时间窗口)
curl -sS -X POST
"https://api.weixin.qq.com/datacube/getuserread?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$BEGIN" '{begin_date: $b, end_date: $e}')" | jq
"https://api.weixin.qq.com/datacube/getuserread?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$BEGIN" '{begin_date: $b, end_date: $e}')" | jq
`datacube/*` requires an authenticated (已认证) account with the data-cube permission.curl -sS -X POST
"https://api.weixin.qq.com/datacube/getuserread?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$BEGIN" '{begin_date: $b, end_date: $e}')" | jq
"https://api.weixin.qq.com/datacube/getuserread?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$BEGIN" '{begin_date: $b, end_date: $e}')" | jq
`datacube/*`接口需要已认证且拥有数据魔方权限的账号。Manage the bottom menu
管理底部菜单
sh
undefinedsh
undefinedRead current menu
读取当前菜单
Replace the menu (max 3 top-level buttons; each top-level button can have ≤ 5 sub-buttons)
替换菜单(最多3个一级按钮;每个一级按钮最多可包含5个二级按钮)
MENU=$(jq -nc '{
button: [
{type: "click", name: "今日推荐", key: "DAILY_REC"},
{name: "更多",
sub_button: [
{type: "view", name: "官网", url: "https://example.com"},
{type: "view", name: "联系我们", url: "https://example.com/contact"}
]}
]
}')
curl -sS -X POST
"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$MENU" | jq
"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$MENU" | jq
MENU=$(jq -nc '{
button: [
{type: "click", name: "今日推荐", key: "DAILY_REC"},
{name: "更多",
sub_button: [
{type: "view", name: "官网", url: "https://example.com"},
{type: "view", name: "联系我们", url: "https://example.com/contact"}
]}
]
}')
curl -sS -X POST
"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$MENU" | jq
"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${WECHAT_ACCESS_TOKEN}"
-H "Content-Type: application/json; charset=utf-8"
--data-raw "$MENU" | jq
→ {"errcode": 0, "errmsg": "ok"}
→ {"errcode": 0, "errmsg": "ok"}
undefinedundefinedGet the follower list (paginated by next_openid)
获取粉丝列表(按next_openid分页)
sh
NEXT=""
curl -sS "https://api.weixin.qq.com/cgi-bin/user/get?access_token=${WECHAT_ACCESS_TOKEN}&next_openid=${NEXT}" \
| jq '{total, count, openids: .data.openid, next: .next_openid}'sh
NEXT=""
curl -sS "https://api.weixin.qq.com/cgi-bin/user/get?access_token=${WECHAT_ACCESS_TOKEN}&next_openid=${NEXT}" \
| jq '{total, count, openids: .data.openid, next: .next_openid}'Loop: pass the returned next_openid
as NEXT until count < 10000.
next_openid循环:将返回的next_openid
作为NEXT参数,直到count < 10000。
next_openid
For each openid, batch-fetch profile (≤ 100 per call):
```sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"user_list": [{"openid": "OPENID1", "lang": "zh_CN"}]}' \
| jq '.user_info_list[] | {openid, nickname, subscribe_time, tagid_list}'
针对每个openid,批量获取用户信息(每次调用最多100个):
```sh
curl -sS -X POST \
"https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
--data-raw '{"user_list": [{"openid": "OPENID1", "lang": "zh_CN"}]}' \
| jq '.user_info_list[] | {openid, nickname, subscribe_time, tagid_list}'HTML rules for article content
content文章content
字段的HTML规则
contentThe field in accepts a subset of HTML, not full web HTML:
contentdraft/add- Allowed: ,
<p>,<span>,<strong>,<em>,<a href>,<img src>,<br>–<h1>,<h3>,<ul>/<ol>/<li>,<blockquote>.<section> - Inline styles only () — no
<p style="...">blocks, no<style>to external CSS.<link> - All
<img>URLs must be either previously-uploadedsrcURLs (frommmbiz.qpic.cn) or already-publishedmedia/uploadimgURLs. WeChat strips images hosted on third-party domains.mmbiz - Total content size limit: ~ 20 K characters (HTML included).
draft/addcontent- 允许使用的标签:、
<p>、<span>、<strong>、<em>、<a href>、<img src>、<br>–<h1>、<h3>、<ul>/<ol>/<li>、<blockquote>。<section> - 仅支持内联样式()——不允许使用
<p style="...">块或外部CSS的<style>标签。<link> - 所有的
<img>URL必须是之前上传的src链接(来自mmbiz.qpic.cn接口)或已发布的media/uploadimg链接。微信会移除第三方域名托管的图片。mmbiz - 内容总大小限制:约20000字符(包含HTML)。
Common errcode cheat-sheet
常见错误码速查表
| errcode | meaning | what to tell the user |
|---|---|---|
| 0 | success | — |
| 40001 | invalid access_token | Token expired mid-call; flush |
| 40013 | invalid appid | The AppID in the connector is wrong — re-add the connection |
| 40164 | source IP not in whitelist | Add the IP shown in |
| 41001 | missing access_token | Bug — you forgot to pass |
| 45009 | API daily quota exceeded | Try again tomorrow; the per-app daily quota was hit |
| 45015 | response message out of 48h | Customer-service window has closed for this openid; can't recover via API |
| 48001 | api unauthorized | Account doesn't have permission for this endpoint (e.g. publish without 认证); see the doc URL in errmsg |
| 61450 | system error | Tencent-side flake; retry once after a 1-second backoff |
| errcode | 含义 | 告知用户的内容 |
|---|---|---|
| 0 | 成功 | — |
| 40001 | 无效access_token | 调用过程中token过期;清空 |
| 40013 | 无效appid | 连接器中的AppID错误——重新添加连接 |
| 40164 | 源IP不在白名单中 | 将 |
| 41001 | 缺少access_token | 程序错误——忘记传递 |
| 45009 | API每日配额耗尽 | 请明天重试;该应用的每日配额已用完 |
| 45015 | 回复消息超出48小时窗口 | 该openid的客服消息窗口已关闭;无法通过API恢复 |
| 48001 | API未授权 | 账号无此接口权限(例如未认证账号尝试发布);请查看errmsg中的文档链接 |
| 61450 | 系统错误 | 腾讯侧故障;等待1秒后重试一次 |
Why we use bare curl + jq
(not an SDK)
curl + jq为什么使用原生curl + jq
(而非SDK)
curl + jqSkill bodies must be self-contained shell. Calling is not allowed in the sandbox, and the API surface here is small (15-ish endpoints) and stable since 2018. The python SDKs (wechatpy, werobot) are useful references for parameter shapes, but every recipe above is a direct call to the documented endpoint — the SDKs are just thin wrappers around the same JSON.
pip install wechatpy技能主体必须是独立的Shell脚本。在沙箱环境中不允许调用,且此处涉及的API范围较小(约15个接口),自2018年以来一直稳定。Python SDK(wechatpy、werobot)可作为参数格式的参考,但上述所有操作指南都是直接调用文档化的接口——SDK只是对相同JSON请求的简单封装。
pip install wechatpy