WorkflowsMCP

Topic calendar to WeChat drafts: Feishu → WeChat MP

Feishu due dates open approval, then land as a WeChat official-account draft.

@workflowsmcpVerified contenteditorial-calendarcn-ecosystemapproval-workflow

Feishu Bitable fires once when a row hits its date — use a 送审 (send-for-review) date ahead of publish. That automation POSTs to your HTTPS endpoint; the backend opens a Feishu Approval. Completion arrives on Feishu's HTTP callback (SDK long-connection: one-line alt for this event only), after a one-time subscribe per definition. On APPROVED, cover → permanent WeChat material → draft; a person still sends. No native bridge. Prefer an enterprise-authenticated account — draft-box APIs tightened since mid-2025; verify with a real draft/add.

How it flows

  1. 01

    Calendar row reaches its send-for-review date

    Bitable automation fires once for that record — gated so 状态 (status) is still 待审批 — and POSTs the record id to the backend HTTPS endpoint.

  2. 02

    Backend opens the approval instance

    Reads 选题标题 (title) and 正文 (body), POSTs them into approval/v4/instances (form is a JSON-array string keyed by widget ids; initiator is the owner's user_id), then writes instance_code and 状态 (status)=审批中 back to the row.

  3. 03

    Reviewer decides in Feishu Approval

    The approver acts in Feishu's own approval UI. Nothing touches WeChat until this returns a decision.

  4. 04

    Approval status-change event reaches the backend

    Delivered on the HTTP callback (or the long-connection client, if that is the path you chose for this event). Continue only when status == APPROVED; REJECTED → 已拒绝, CANCELED → 已撤回, and stop without calling WeChat.

  5. 05

    Cover file_token becomes image bytes

    Exchange the attachment file_token for a 24-hour temporary download URL, then pull the image bytes.

  6. 06

    Cover uploads as permanent WeChat material

    POST material/add_material?type=image; the returned media_id becomes thumb_media_id for the draft.

  7. 07

    Draft is created

    POST draft/add with title, digest, content, and thumb_media_id. Write the returned draft media_id and 状态 (status)=草稿已生成 back to the calendar row.

  8. 08

    A person reviews and sends

    Automation stops at the draft box. Someone opens the WeChat backend, reviews the draft, and sends only after confirming — this workflow never calls freepublish/submit.

Set up each app

Work through these in order — later apps usually need a token or an id from an earlier one.

Feishu

Holds the topic calendar, fires on the due date, and runs the approval

  1. 01

    Create the self-built app and wire it to the calendar

    open.feishu.cn → 开发者后台 (Developer Console) → create a 企业自建应用 (custom enterprise app); note the App ID and App Secret. (Menu labels here are the Chinese console; Feishu follows your account language, so switch it if yours is already English.) Under 权限管理 (Permissions) add `bitable:app` (read/write Bitable records), plus one of `approval:approval` or `approval:instance` (create instances) and one of `approval:approval` or `approval:definition` (subscribe to approval events) — `approval:approval` alone covers both if the console offers it. Publish a version, then have a tenant admin approve the app in 管理后台 (Admin Console) → 应用管理 (App Management) → this app → 权限管理 (Permissions): a self-built app's requested scopes sit inactive until an admin approves them there. A new app has zero access to any base until you open the target topic-calendar Bitable's share/more menu and add this app as a collaborator. Add these fields if missing: 选题标题 (title, text), 正文 (body, long text), 封面图 (cover, attachment), 送审日期 (send-for-review date, date), 计划发布日期 (planned publish date, date), 状态 (status, single select: 待审批 (pending review)/审批中 (in review)/已批准 (approved)/已拒绝 (rejected)/已撤回 (canceled)/草稿已生成 (draft created)), 审批实例ID (instance id, text), 草稿media_id (draft media id, text).

  2. 02

    Build the approval definition

    In the approval management console (approval.feishu.cn, or the 审批 (Approval) app's admin entry inside the Feishu client) create a new definition — e.g. '内容选题发布审批' — with at least a title input and a body/summary textarea, and a fixed approver for now (department-hierarchy routing is more setup than this needs). Copy the `approval_code` from the definition's detail-page URL, and read each control's `id` from the form builder — the create-instance call addresses controls by that id, not by the on-screen label. `user_id` on the create-instance call is the initiator's Feishu user_id (readable from 通讯录 (Contacts) / 管理后台 (Admin Console); this flow has no cross-product identity mapping).

    POST /open-apis/approval/v4/instances — body shape (flow time)
    {
      "approval_code": "YOUR_APPROVAL_CODE",
      "user_id": "INITIATOR_USER_ID",
      "form": "[{\"id\":\"widget_title\",\"type\":\"input\",\"value\":\"Next week topic: XXX\"},{\"id\":\"widget_summary\",\"type\":\"textarea\",\"value\":\"Body summary...\"}]"
    }
  3. 03

    Turn on the due-date automation

    On the calendar Bitable, open 自动化 (Automation) → new automation → trigger 到达记录中的时间时 (when a record reaches its time) → point it at 送审日期 (send-for-review date). Add a 逻辑 (Logic) condition that 状态 (status) is still 待审批, so a pulled or already-published row does not empty-fire. Action: 发送 HTTP 请求 (Send HTTP Request) to your backend receiving URL, with the record id in the payload — this hop needs a public HTTPS endpoint. The trigger fires exactly once per record at that field's value; it is not a recurring scan. Feishu's docs do not say whether editing 送审日期 (send-for-review date) afterward reschedules the pending fire or leaves the original time queued — treat a later date edit as unverified until you test it once on a real record.

  4. 04

    Subscribe to approval completion over HTTP

    开发者后台 (Developer Console) → 事件与回调 (Events & Callbacks): subscribe to 审批实例状态变更 (approval instance status changed) and point it at an HTTPS callback URL on your backend — the primary path for approval completion. (One-line alternative for this event only: Feishu's server-side SDK long-connection client pulls the same event over an outbound connection, with no inbound approval callback; the Bitable due-date POST above still needs its HTTPS endpoint either way.) Console subscription alone is not enough: call the endpoint below once per approval definition, or the event type stays enabled while this definition never sends.

    POST approval/v4/approvals/{approval_code}/subscribe (once per definition)
    curl -s -X POST "https://open.feishu.cn/open-apis/approval/v4/approvals/${APPROVAL_CODE}/subscribe" \
      -H "Authorization: Bearer $FEISHU_TENANT_TOKEN"
    # empty body; once per approval_code, not per instance
    # tenant_access_token: POST open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal
    # with {"app_id","app_secret"}
  5. 05

    Exchange the cover file_token for a download URL

    The 封面图 (cover) attachment field only returns a `file_token`, never a URL. Before WeChat can receive the image bytes, exchange it for a 24-hour temporary download link with the call below (same `tenant_access_token` as the other Feishu calls). If `batch_get_tmp_download_url` returns a permission error, add the Drive media-related scopes under 权限管理 (Permissions) and retry (exact scope names PENDING-WEB).

    GET drive/v1/medias/batch_get_tmp_download_url
    curl -s -G "https://open.feishu.cn/open-apis/drive/v1/medias/batch_get_tmp_download_url" \
      --data-urlencode "file_tokens=${FILE_TOKEN}" \
      -H "Authorization: Bearer $FEISHU_TENANT_TOKEN"
    # returned URL is valid for 24 hours; FILE_TOKEN comes from the Bitable attachment field

WeChat MP

Turns the approved item into a draft — outbound only

  1. 01

    Confirm the account can create drafts

    mp.weixin.qq.com → 开发 (Development) → 基本配置 (Basic Configuration): note AppID and AppSecret, and check the account's authentication status. This workflow never calls `freepublish/*` (those publish APIs are documented as restricted to enterprise-authenticated accounts since July 2025). `draft/add` and `add_material` are not named on that same restriction footnote, but community reports treat them as covered in practice. Before production, make one real `draft/add` call from your actual account and confirm it does not return an authorization error — do not assume an unauthenticated account works. No Server URL / Token setup is needed here: this seed only writes outbound to the draft box and never receives WeChat inbound callbacks.

  2. 02

    Draft box is fully available (switch API deprecated)

    The draft box and publish features are fully rolled out — the old `/cgi-bin/draft/switch` enablement API is deprecated, so production does not need to query a switch. If you still call the legacy endpoint, use `checkonly=1` rather than `action=get`.

  3. 03

    Get a stable access token

    Use `stable_token`, not the older `token` endpoint — the old one invalidates the previous token on every refresh, so concurrent callers kick each other out. With `force_refresh: false`, `stable_token` returns the still-valid cached token. Valid for 7200 seconds.

    POST https://api.weixin.qq.com/cgi-bin/stable_token
    {
      "grant_type": "client_credential",
      "appid": "YOUR_APPID",
      "secret": "YOUR_APPSECRET",
      "force_refresh": false
    }
  4. 04

    Upload the cover as permanent material, then create the draft

    Upload first (`type=image`, ≤10MB, bmp/png/jpeg/jpg/gif) — the draft's `thumb_media_id` must be a permanent MediaID from this call, not a temporary upload. Then `draft/add`: `title` ≤32 characters, `digest` ≤120, `content` ≤20,000 characters and <1M with basic HTML. The draft response's `media_id` is a different id from `thumb_media_id` — do not swap them. Stop here; do not call `freepublish/submit`.

    add_material (image) then draft/add
    # 1) permanent cover
    curl -s -X POST "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${ACCESS_TOKEN}&type=image" \
      -F "media=@cover.jpg"
    # {"media_id":"COVER_MEDIA_ID","url":"https://..."}
    
    # 2) draft (thumb_media_id = COVER_MEDIA_ID from step 1)
    curl -s -X POST "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      -d '{
        "articles": [{
          "title": "Next week topic: XXX",
          "author": "Content team",
          "digest": "Body summary, under 120 characters",
          "content": "<p>Body HTML...</p>",
          "thumb_media_id": "COVER_MEDIA_ID",
          "need_open_comment": 1,
          "only_fans_can_comment": 0
        }]
      }'
    # {"media_id":"DRAFT_MEDIA_ID"}