DingTalk Card API#
Overview#
LangBot's DingTalk integration uses a custom HTTP-based card delivery layer rather than the SDK's built-in AICardReplier. All card operations go through DingTalkClient in libs/dingtalk_api/api.py. The platform adapter lives in pkg/platform/sources/dingtalk.py; its config schema is in pkg/platform/sources/dingtalk.yaml.
Core API Methods#
Three methods on DingTalkClient cover the full card lifecycle:
| Method | Endpoint | Purpose |
|---|---|---|
create_and_deliver_card | POST /v1.0/card/instances/createAndDeliver | Create + deliver a card in one call |
streaming_update_card | PUT /v1.0/card/streaming | Stream a content chunk into an existing card |
update_card_data | PUT /v1.0/card/instances | Non-streaming full card content update |
create_and_deliver_card replaces the old dingtalk_stream.AICardReplier.async_create_and_deliver_card path . streaming_update_card replaces AICardReplier.async_streaming — the request body shape (outTrackId, guid, key, content, isFull, isFinalize, isError) mirrors the SDK's own implementation .
The higher-level wrapper create_and_card is what DingTalkAdapter.create_message_card calls for the legacy (non-HITL) path. It generates an out_track_id via uuid.uuid4().hex, constructs the openSpaceId based on whether the chat is a group or 1-on-1, then delegates to create_and_deliver_card .
cardData.config Field#
The cardData body passed to /v1.0/card/instances/createAndDeliver contains cardParamMap, which holds all template variables — including the config variable if card layout configuration is provided :
cardData = {
"cardParamMap": {
"content": "hello",
"config": "{\"autoLayout\": true}" # optional card-level settings
}
}
When card_data_config is passed to create_and_deliver_card, it is embedded inside cardParamMap under the config key, JSON-stringified like other template variables . This allows DingTalk templates to resolve the config variable from the template parameter map. The only field currently set in config is autoLayout (boolean), controlled by the card_auto_layout adapter config option .
cardParamMap values must all be strings; non-string values (e.g. button option lists, or the config object itself) are automatically json.dumps-encoded by _stringify_card_param_map. The original caller-provided card_param_map is not mutated — a copy is made before adding the config entry.
This structure was corrected in PR #2491 to fix a bug where card layout configuration was not being properly resolved by DingTalk templates.
Card Template Modes#
The adapter supports two card modes, selected by which template fields are configured :
1. Legacy Two-Card Mode (card_template_id)#
- One card streams the LLM reply; a separate card delivers the human-input form.
card_template_idmust be set (required field, default"ĺˇ«ĺ†™ä˝ çš„ĺŤˇç‰‡template_id") .- Streaming uses
streaming_update_card(PUT/v1.0/card/streaming) .
2. Unified Single-Card Mode (human_input_card_template_id)#
- One card per conversation turn handles both streamed content and form buttons. Content is updated via
update_card_data(PUT/v1.0/card/instances) rather than the streaming endpoint, because DingTalk clients treat those two update paths differently onceupdate_card_datahas been called . - Set up by importing the built-in template JSON (
dingtalk_human_input_card.json) into the DingTalk Open Platform Card Template Management, then pasting the resulting template ID intohuman_input_card_template_id. - A download-link field (
human_input_card_template_download) exposes the JSON at/api/v1/platform/adapters/dingtalk/human-input-card-template. - Leaving
human_input_card_template_idempty falls back to legacy two-card behavior .
This unified mode was introduced in PR #2226 — feat: add supports for dify hitl.
Card Template Variables (cardParamMap)#
For the unified template, the card param map includes :
| Key | Description |
|---|---|
content | Markdown body rendered in a MarkdownBlock |
btns | JSON-serialized list of button objects for the ButtonGroup |
flowStatus | '1' = loading/streaming, '3' = complete |
bot_avatar | DingTalk @xxx media_id for the bot avatar, re-sent on every update to prevent DingTalk from wiping it |
input_visible, input_title, input_placeholder, input_value | Controls for the native Input component |
select_visible, select_placeholder, select_options, index_o, select_index | Controls for the native SelectBlock component |
_dingtalk_card_markdown converts \n newlines to <br> for proper rendering inside DingTalk card template markdown slots .
Card Action Callbacks#
Button clicks arrive over the DingTalk WebSocket stream on topic /v1.0/card/instances/callback . DingTalkCardActionHandler in libs/dingtalk_api/card_callback.py subclasses dingtalk_stream.CallbackHandler, extracts action_id and params from the callback payload (probing multiple template-binding paths: params, cardPrivateData.params, userPrivateData.params, actionData.cardPrivateData.params) , and forwards a normalized dict to the adapter via DingTalkAdapter._on_card_action .
Auth: Two Token Domains#
DingTalk uses two separate OAuth domains :
- v1.0 token (
api.dingtalk.com) — used by all card endpoints (createAndDeliver,streaming,instances). - Legacy token (
oapi.dingtalk.com) — required only forPOST /media/upload, which is the only documented endpoint for obtaining a@xxxmedia_id consumable by cardAvatar.imageUrl. The v1.0 token is rejected there.
Key Source Files#
| File | Purpose |
|---|---|
libs/dingtalk_api/api.py | DingTalkClient: token management, card create/deliver/stream/update, media upload |
libs/dingtalk_api/card_callback.py | DingTalkCardActionHandler: WebSocket card-action callback parsing |
pkg/platform/sources/dingtalk.py | DingTalkAdapter: message/card orchestration, HITL form painting |
pkg/platform/sources/dingtalk.yaml | Config schema for all card-related fields |
src/langbot/templates/dingtalk_human_input_card.json | Built-in unified card template (added in PR #2226) |