Feishu CDC
This topic describes how to use the Feishu CDC (feishu-cdc) connector.
Background information
The Feishu CDC connector periodically polls the Feishu Open Platform OpenAPI and continuously captures new and updated online documents in Feishu wiki spaces based on document edit time. The connector outputs document metadata such as the title, URL, and update time, together with the document body in Markdown format. The raw block structure of each document is also available. Typical scenarios include RAG knowledge base building, document search indexing, and lakehouse content synchronization.
The following table lists the capabilities of the Feishu CDC connector.
|
Category |
Details |
|
Connector type |
Source table |
|
API |
Flink SQL, data ingestion YAML |
|
Synchronized objects |
|
|
Full + incremental mode |
|
|
Incremental-only mode |
|
|
Full-only mode |
|
|
Data format |
Fixed schema. Field pruning is not supported. |
|
Watermark |
Not supported |
|
Dimension table and sink table |
Not supported |
How it works
Feishu wikis do not expose a change event stream that can be consumed directly. The Feishu CDC connector implements full and incremental reads by polling the OpenAPI:
-
Each wiki space is an independent read split. The connector discovers newly authorized wiki spaces at the interval specified by
scan.discovery.interval. -
On each poll, the connector lists all nodes in a space, filters the
docanddocxdocument nodes within the configured scope, and generates a cursor position for the poll based on document edit time. -
Documents after the cursor are sorted by edit time. A single split emits at most
scan.prefix-sizechanged documents per poll. For each changed document, the connector reads its block structure and renders it into a Markdown body. -
When no incremental change exists, the connector repeats the check at the interval specified by
scan.poll-interval.
Mapping changes to the changelog
The connector emits a changelog with upsert semantics keyed by the primary key node_id. The changelog contains INSERT and UPDATE_AFTER messages but not UPDATE_BEFORE messages.
|
Scenario |
Emitted message |
|
A document first read during the full phase |
INSERT |
|
A document whose content or metadata changed during the incremental phase |
UPDATE_AFTER |
The current implementation does not detect document deletions. After a document is deleted, it no longer appears in the node list, and the connector does not emit a DELETE message. The row remains in the downstream with its last synchronized state. To clean up deleted documents, handle them in your downstream business logic.
Prerequisites
The Flink job must access the Feishu Open Platform OpenAPI (default domain: open.feishu.cn). If the job is deployed in a VPC, make sure that public network access is enabled.
Regardless of the startup mode, you must first create an app on the Feishu Open Platform and authorize it to access the target wiki spaces.
-
Log on to the Feishu Open Platform and create an enterprise custom app to obtain the App ID and App Secret of the app. The connector uses the App ID and App Secret to obtain and automatically renew the tenant_access_token.
-
Grant the app the API permissions required by this connector and publish an app version. The connector calls the following Feishu OpenAPI operations:
API
Purpose
GET /open-apis/wiki/v2/spacesLists the wiki spaces that the app can access
GET /open-apis/wiki/v2/spaces/{space_id}/nodesLists the document nodes in a wiki space
GET /open-apis/wiki/v2/spaces/get_nodeResolves the metadata of a document node
GET /open-apis/docx/v1/documents/{document_token}/blocksReads the block content of a document
-
Add the app as a member of the target wiki spaces so that the app can read the documents to be synchronized. For more information, see the topic "How to authorize an app to access wiki documents" in Wiki FAQ on the Feishu Open Platform. The connector synchronizes only the
docanddocxnodes that the app is authorized to access.
Permission scopes
The exact permission scope names of the preceding APIs are subject to the Feishu Open Platform documentation. The authorization must cover the following two capabilities:
|
Capability |
Corresponding API |
Required |
|
Read wiki spaces and node lists |
|
Yes |
|
Read document block content |
|
Yes |
Limitations
-
Only source tables are supported. Dimension tables, sink tables, and watermarks are not supported.
-
Only
docanddocxnodes that the app is authorized to read in wiki spaces are synchronized. Other objects such as spreadsheets, bitable tables, and files are skipped. -
The current implementation does not detect document deletions and does not emit DELETE messages.
-
A Flink SQL DDL must declare the six fixed physical fields and
PRIMARY KEY (node_id) NOT ENFORCED. Adding or removing physical columns is not supported.blocks_jsoncan only be declared as aMETADATAcolumn. -
A Flink SQL source table reads only one wiki space.
wiki.space-idaccepts only a single value. A data ingestion YAML job supports multiple comma-separated spaces, or automatic discovery when this parameter is not configured. -
The incremental pipeline is based on OpenAPI polling and generates continuous API calls. If a space contains many documents, tune
scan.poll-interval,scan.prefix-size, and the rate limiting parameters to balance API call cost against the synchronization latency of new content. -
contentis Markdown text rendered from the document block structure. It is not guaranteed to preserve all rich media and structural elements. To access the raw block structure, use theblocks_jsonMETADATA column in Flink SQL or read theblocks_jsonphysical column in a data ingestion YAML job. -
Resource discovery is cumulative. Shrinking
wiki.space-id,wiki.document-url, orwiki.node-tokendoes not remove wiki spaces or documents that have already been discovered. -
Failure recovery may emit duplicate INSERT and UPDATE_AFTER messages. We recommend that the downstream write idempotently by the primary key
node_id. -
The YAML schema differs from the SQL schema: the YAML schema contains an additional
space_idphysical column, andblocks_jsonis a physical column rather than a METADATA column. Choose the corresponding schema definition based on the access method.
Startup modes
Use scan.startup.mode to select the read behavior.
|
Mode |
Full read |
Incremental read |
Behavior on first startup |
|
|
Yes |
Yes |
First reads all documents in scope, then continuously polls for incremental changes |
|
|
Yes |
No |
Reads all documents in scope once and finishes |
|
|
No |
Yes |
Reads documents whose update time is not earlier than |
|
|
No |
Yes |
Processes only document changes that occur after the job starts |
For wiki spaces or documents newly discovered while the job is running, scan.new-split.startup.mode determines the read behavior: initial first reads the current full content and then follows subsequent changes; latest-offset processes only changes that occur after discovery.
Synchronization scope
Flink SQL and data ingestion YAML differ in how the synchronization scope can be configured:
|
Capability |
Flink SQL |
Data ingestion YAML |
|
|
Required. Only a single value is allowed. |
Optional. Multiple comma-separated values are supported. If not configured, all visible spaces are automatically discovered. |
|
|
At most one of them can be configured, and only a single document is allowed. |
Multiple comma-separated values are supported. |
|
Multi-space synchronization |
Create one source table per space. |
Tables are automatically created per space within a single job ( |
If no document selector is configured, all doc and docx documents that the app is authorized to read in a space are synchronized.
To narrow the scope of a Flink SQL source table to a single document in the space, configure one of the following parameters:
-
wiki.document-url: the URL of a Feishu document. The connector resolves the document identifier from the URL. Common URL forms such aswiki,docx, anddocsare supported. -
wiki.node-token: the token of a node in the wiki space.
At most one of the two parameters can be configured, and the selected document must belong to the space configured in wiki.space-id. Once configured, both the full and incremental phases process only this document. The YAML document selector supports multiple documents. For more information, see the Data ingestion section.
Resource discovery is cumulative: once a wiki space or document has been discovered by the job, shrinking the scope configuration does not remove it from the running job. For jobs that synchronize an entire space, documents added to the space later are discovered by normal polling without configuration changes. To remove resources completely, stop the job and restart it without state.
SQL
Syntax
CREATE TEMPORARY TABLE <yourTableName> (
node_id STRING NOT NULL,
title STRING,
url STRING,
`type` STRING,
content STRING,
updated_time TIMESTAMP_LTZ(3),
PRIMARY KEY (node_id) NOT ENFORCED
) WITH (
'connector' = 'feishu-cdc',
'object-type' = 'document',
'app-id' = '<yourAppId>',
'app-secret' = '<yourAppSecret>',
'wiki.space-id' = '<yourWikiSpaceId>'
);
The Flink SQL DDL must declare all six physical fields in the following table. The field order and names must match.
|
DDL field |
Type |
Description |
|
|
STRING NOT NULL |
The identifier of the document node. The primary key of the source table. |
|
|
STRING |
The document title. |
|
|
STRING |
The URL to access the document. |
|
|
STRING |
The Feishu object type, for example, |
|
|
STRING |
The document body, Markdown text rendered from the document block structure. |
|
|
TIMESTAMP_LTZ(3) |
The last update time of the document. |
The source table must declare PRIMARY KEY (node_id) NOT ENFORCED.
In addition, the DDL can declare the following readable metadata by using METADATA. Metadata columns do not change the physical schema above.
|
Metadata key |
Type |
Description |
|
|
STRING |
The JSON text of the raw document block structure. It can be used to preserve block-level structure or for custom parsing. |
WITH parameters
Basic parameters
|
Parameter |
Type |
Default |
Required |
Description |
|
|
STRING |
None |
Yes |
Fixed to |
|
|
STRING |
None |
Yes |
The type of object to synchronize. Only |
|
|
STRING |
None |
Yes |
The App ID of the Feishu app, used to obtain the tenant_access_token. |
|
|
STRING |
None |
Yes |
The App Secret of the Feishu app. |
|
|
STRING |
|
No |
The domain of the Feishu Open Platform OpenAPI. |
Synchronization scope parameters
|
Parameter |
Type |
Default |
Required |
Description |
|
|
STRING |
None |
Yes |
The ID of the single wiki space to read. |
|
|
STRING |
None |
No |
A document URL that narrows the scope to a single document. At most one of |
|
|
STRING |
None |
No |
A document node token that narrows the scope to a single document. At most one of |
Startup and polling parameters
|
Parameter |
Type |
Default |
Description |
|
|
STRING |
|
The startup mode. Valid values: |
|
|
BIGINT |
None |
Required for and only valid in |
|
|
STRING |
|
The read behavior for newly discovered wiki spaces. Valid values: |
|
|
DURATION |
|
The polling interval when no incremental change exists. |
|
|
DURATION |
|
The discovery interval for newly authorized wiki spaces. |
|
|
INTEGER |
|
The maximum number of changed documents that a single split emits per poll. |
|
|
INTEGER |
None |
The parallelism of the source. If not configured, the default parallelism of the job is used. |
Request and rate limiting parameters
|
Parameter |
Type |
Default |
Description |
|
|
INTEGER |
|
The page size of the wiki space OpenAPI. Valid values: 1 to 50. |
|
|
DURATION |
|
The timeout of a single OpenAPI request. |
|
|
INTEGER |
|
The maximum number of retries for transient request failures. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that list wiki spaces and nodes. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that resolve node metadata. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that read document block content. |
Examples
Full + incremental synchronization
The following example declares the blocks_json metadata column.
CREATE TEMPORARY TABLE feishu_wiki_source (
node_id STRING NOT NULL,
title STRING,
url STRING,
`type` STRING,
content STRING,
updated_time TIMESTAMP_LTZ(3),
blocks_json STRING METADATA VIRTUAL,
PRIMARY KEY (node_id) NOT ENFORCED
) WITH (
'connector' = 'feishu-cdc',
'object-type' = 'document',
'app-id' = '<yourAppId>',
'app-secret' = '<yourAppSecret>',
'wiki.space-id' = '<yourWikiSpaceId>',
'scan.startup.mode' = 'initial'
);
CREATE TEMPORARY TABLE print_sink (
node_id STRING,
title STRING,
url STRING,
`type` STRING,
content STRING,
updated_time TIMESTAMP_LTZ(3)
) WITH (
'connector' = 'print'
);
INSERT INTO print_sink
SELECT node_id, title, url, `type`, content, updated_time
FROM feishu_wiki_source;
SNAPSHOT full scan
SNAPSHOT performs a one-time full read. It is suitable for content migration or one-off exports.
CREATE TEMPORARY TABLE feishu_wiki_snapshot (
node_id STRING NOT NULL,
title STRING,
url STRING,
`type` STRING,
content STRING,
updated_time TIMESTAMP_LTZ(3),
PRIMARY KEY (node_id) NOT ENFORCED
) WITH (
'connector' = 'feishu-cdc',
'object-type' = 'document',
'app-id' = '<yourAppId>',
'app-secret' = '<yourAppSecret>',
'wiki.space-id' = '<yourWikiSpaceId>',
'scan.startup.mode' = 'snapshot'
);
To synchronize only one document in the space, add 'wiki.document-url' = '<document URL>' or 'wiki.node-token' = '<node token>' to the WITH parameters.
Data ingestion
The type of the data ingestion connector is fixed to feishu-cdc. Each wiki space corresponds to a table named <wiki space ID>.documents. The connector creates the table automatically and synchronizes the schema when the space is first discovered.
Syntax
source:
type: feishu-cdc
name: <yourSourceName>
app-id: <yourAppId>
app-secret: <yourAppSecret>
wiki.space-id: <yourWikiSpaceId>
scan.startup.mode: initial
Parameters
Basic parameters
|
Parameter |
Type |
Default |
Required |
Description |
|
|
STRING |
None |
Yes |
Fixed to |
|
|
STRING |
None |
Yes |
The App ID of the Feishu app, used to obtain the tenant_access_token. |
|
|
STRING |
None |
Yes |
The App Secret of the Feishu app. |
|
|
STRING |
|
No |
The domain of the Feishu Open Platform OpenAPI. |
|
|
STRING |
|
No |
The type of object to synchronize. Only |
Synchronization scope parameters
|
Parameter |
Type |
Default |
Required |
Description |
|
|
STRING |
None |
No |
A comma-separated list of wiki space IDs. If not configured, all wiki spaces that the app is authorized to read are automatically discovered. |
|
|
STRING |
None |
No |
A comma-separated list of document URLs that narrows the synchronization scope to the specified documents. |
|
|
STRING |
None |
No |
A comma-separated list of node tokens that narrows the synchronization scope to the specified documents. |
|
|
BOOLEAN |
|
No |
Whether to require that explicitly selected documents must belong to the wiki spaces configured in |
Startup and polling parameters
|
Parameter |
Type |
Default |
Description |
|
|
STRING |
|
The startup mode. Valid values: |
|
|
BIGINT |
None |
Required for and only valid in |
|
|
STRING |
|
The read behavior for newly discovered wiki spaces. Valid values: |
|
|
DURATION |
|
The polling interval when no incremental change exists. |
|
|
DURATION |
|
The discovery interval for newly discovered wiki spaces. |
|
|
INTEGER |
|
The maximum number of changed documents that a single split emits per poll. |
Request and rate limiting parameters
|
Parameter |
Type |
Default |
Description |
|
|
INTEGER |
|
The page size of the wiki space OpenAPI. Valid values: 1 to 50. |
|
|
DURATION |
|
The timeout of a single OpenAPI request. |
|
|
INTEGER |
|
The maximum number of retries for transient request failures. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that list wiki spaces and nodes. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that resolve node metadata. |
|
|
DOUBLE |
|
The maximum number of requests per second for the OpenAPI operations that read document block content. |
Fixed schema
|
Field |
Type |
Description |
|
|
STRING NOT NULL |
The identifier of the document node. The primary key. |
|
|
STRING NOT NULL |
The ID of the wiki space that the document belongs to. |
|
|
STRING |
The document title. |
|
|
STRING |
The URL to access the document. |
|
|
STRING |
The Feishu object type, for example, |
|
|
STRING |
The document body, Markdown text rendered from the document block structure. |
|
|
STRING |
The JSON text of the raw document block structure. |
|
|
TIMESTAMP_LTZ(3) |
The last update time of the document. |
The primary key of the fixed schema is node_id. Compared with Flink SQL, the YAML schema adds a space_id physical column and outputs blocks_json directly as a physical column.
Space-to-table routing
If wiki.space-id is not configured, the connector automatically discovers all wiki spaces that the app is authorized to read and generates a <wiki space ID>.documents table for each space. If wiki.space-id is configured (multiple comma-separated values are supported), only the specified spaces are synchronized.
If the document selector wiki.document-url or wiki.node-token is configured (both support multiple comma-separated values), only the documents listed in the selector are synchronized within the corresponding space. wiki.restrict-documents-to-selected-spaces controls whether explicitly selected documents must belong to the spaces configured in wiki.space-id. The default value is false, which means a space that is not configured is also automatically discovered if it contains a selected document.
Examples
The following example performs a full read of the documents in the specified wiki space, continuously synchronizes incremental changes, and writes the result to Paimon.
source:
type: feishu-cdc
name: Feishu Wiki Source
app-id: <yourAppId>
app-secret: <yourAppSecret>
wiki.space-id: <yourWikiSpaceId>
scan.startup.mode: initial
route:
- source-table: <yourWikiSpaceId>.documents
sink-table: feishu_wiki.documents
sink:
type: paimon
catalog.properties.metastore: rest
catalog.properties.uri: <yourDlfCatalogEndpoint>
catalog.properties.warehouse: <yourWarehouse>