Feishu CDC

Updated at:

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

doc and docx document nodes that the app is authorized to read in wiki spaces

Full + incremental mode

INITIAL, streaming mode

Incremental-only mode

LATEST_OFFSET and TIMESTAMP, streaming mode

Full-only mode

SNAPSHOT, bounded read

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:

  1. Each wiki space is an independent read split. The connector discovers newly authorized wiki spaces at the interval specified by scan.discovery.interval.

  2. On each poll, the connector lists all nodes in a space, filters the doc and docx document nodes within the configured scope, and generates a cursor position for the poll based on document edit time.

  3. Documents after the cursor are sorted by edit time. A single split emits at most scan.prefix-size changed documents per poll. For each changed document, the connector reads its block structure and renders it into a Markdown body.

  4. 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.

  1. 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.

  2. 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/spaces

    Lists the wiki spaces that the app can access

    GET /open-apis/wiki/v2/spaces/{space_id}/nodes

    Lists the document nodes in a wiki space

    GET /open-apis/wiki/v2/spaces/get_node

    Resolves the metadata of a document node

    GET /open-apis/docx/v1/documents/{document_token}/blocks

    Reads the block content of a document

  3. 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 doc and docx nodes 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

wiki/v2/spaces, wiki/v2/spaces/{space_id}/nodes, wiki/v2/spaces/get_node

Yes

Read document block content

docx/v1/documents/{document_token}/blocks

Yes

Limitations

  • Only source tables are supported. Dimension tables, sink tables, and watermarks are not supported.

  • Only doc and docx nodes 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_json can only be declared as a METADATA column.

  • A Flink SQL source table reads only one wiki space. wiki.space-id accepts 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.

  • content is 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 the blocks_json METADATA column in Flink SQL or read the blocks_json physical column in a data ingestion YAML job.

  • Resource discovery is cumulative. Shrinking wiki.space-id, wiki.document-url, or wiki.node-token does 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_id physical column, and blocks_json is 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

INITIAL

Yes

Yes

First reads all documents in scope, then continuously polls for incremental changes

SNAPSHOT

Yes

No

Reads all documents in scope once and finishes

TIMESTAMP

No

Yes

Reads documents whose update time is not earlier than scan.startup.timestamp-millis, then continuously polls for incremental changes

LATEST_OFFSET

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

wiki.space-id

Required. Only a single value is allowed.

Optional. Multiple comma-separated values are supported. If not configured, all visible spaces are automatically discovered.

wiki.document-url / wiki.node-token

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 (<spaceId>.documents).

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 as wiki, docx, and docs are 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

node_id

STRING NOT NULL

The identifier of the document node. The primary key of the source table.

title

STRING

The document title.

url

STRING

The URL to access the document.

type

STRING

The Feishu object type, for example, docx.

content

STRING

The document body, Markdown text rendered from the document block structure.

updated_time

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

blocks_json

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

connector

STRING

None

Yes

Fixed to feishu-cdc.

object-type

STRING

None

Yes

The type of object to synchronize. Only document is supported.

app-id

STRING

None

Yes

The App ID of the Feishu app, used to obtain the tenant_access_token.

app-secret

STRING

None

Yes

The App Secret of the Feishu app.

endpoint

STRING

https://open.feishu.cn

No

The domain of the Feishu Open Platform OpenAPI.

Synchronization scope parameters

Parameter

Type

Default

Required

Description

wiki.space-id

STRING

None

Yes

The ID of the single wiki space to read.

wiki.document-url

STRING

None

No

A document URL that narrows the scope to a single document. At most one of wiki.document-url and wiki.node-token can be configured.

wiki.node-token

STRING

None

No

A document node token that narrows the scope to a single document. At most one of wiki.document-url and wiki.node-token can be configured.

Startup and polling parameters

Parameter

Type

Default

Description

scan.startup.mode

STRING

initial

The startup mode. Valid values: initial, snapshot, timestamp, and latest-offset.

scan.startup.timestamp-millis

BIGINT

None

Required for and only valid in timestamp mode. A Unix timestamp in milliseconds.

scan.new-split.startup.mode

STRING

initial

The read behavior for newly discovered wiki spaces. Valid values: initial and latest-offset.

scan.poll-interval

DURATION

10s

The polling interval when no incremental change exists.

scan.discovery.interval

DURATION

1min

The discovery interval for newly authorized wiki spaces.

scan.prefix-size

INTEGER

100

The maximum number of changed documents that a single split emits per poll.

scan.parallelism

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

api.page-size

INTEGER

20

The page size of the wiki space OpenAPI. Valid values: 1 to 50.

request.timeout

DURATION

30s

The timeout of a single OpenAPI request.

request.max-retries

INTEGER

3

The maximum number of retries for transient request failures.

scan.discovery.rate-limit.requests-per-second

DOUBLE

5

The maximum number of requests per second for the OpenAPI operations that list wiki spaces and nodes.

scan.metadata.rate-limit.requests-per-second

DOUBLE

5

The maximum number of requests per second for the OpenAPI operations that resolve node metadata.

scan.content.rate-limit.requests-per-second

DOUBLE

5

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

type

STRING

None

Yes

Fixed to feishu-cdc.

app-id

STRING

None

Yes

The App ID of the Feishu app, used to obtain the tenant_access_token.

app-secret

STRING

None

Yes

The App Secret of the Feishu app.

endpoint

STRING

https://open.feishu.cn

No

The domain of the Feishu Open Platform OpenAPI.

object-type

STRING

document

No

The type of object to synchronize. Only document is supported.

Synchronization scope parameters

Parameter

Type

Default

Required

Description

wiki.space-id

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.

wiki.document-url

STRING

None

No

A comma-separated list of document URLs that narrows the synchronization scope to the specified documents.

wiki.node-token

STRING

None

No

A comma-separated list of node tokens that narrows the synchronization scope to the specified documents.

wiki.restrict-documents-to-selected-spaces

BOOLEAN

false

No

Whether to require that explicitly selected documents must belong to the wiki spaces configured in wiki.space-id.

Startup and polling parameters

Parameter

Type

Default

Description

scan.startup.mode

STRING

initial

The startup mode. Valid values: initial, snapshot, timestamp, and latest-offset.

scan.startup.timestamp-millis

BIGINT

None

Required for and only valid in timestamp mode. A Unix timestamp in milliseconds.

scan.new-split.startup.mode

STRING

initial

The read behavior for newly discovered wiki spaces. Valid values: initial and latest-offset.

scan.poll-interval

DURATION

10s

The polling interval when no incremental change exists.

scan.discovery.interval

DURATION

1min

The discovery interval for newly discovered wiki spaces.

scan.prefix-size

INTEGER

100

The maximum number of changed documents that a single split emits per poll.

Request and rate limiting parameters

Parameter

Type

Default

Description

api.page-size

INTEGER

20

The page size of the wiki space OpenAPI. Valid values: 1 to 50.

request.timeout

DURATION

30s

The timeout of a single OpenAPI request.

request.max-retries

INTEGER

3

The maximum number of retries for transient request failures.

scan.discovery.rate-limit.requests-per-second

DOUBLE

5

The maximum number of requests per second for the OpenAPI operations that list wiki spaces and nodes.

scan.metadata.rate-limit.requests-per-second

DOUBLE

5

The maximum number of requests per second for the OpenAPI operations that resolve node metadata.

scan.content.rate-limit.requests-per-second

DOUBLE

5

The maximum number of requests per second for the OpenAPI operations that read document block content.

Fixed schema

Field

Type

Description

node_id

STRING NOT NULL

The identifier of the document node. The primary key.

space_id

STRING NOT NULL

The ID of the wiki space that the document belongs to.

title

STRING

The document title.

url

STRING

The URL to access the document.

type

STRING

The Feishu object type, for example, docx.

content

STRING

The document body, Markdown text rendered from the document block structure.

blocks_json

STRING

The JSON text of the raw document block structure.

updated_time

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>