Tencent/WeKnora · error
query database %s: not a data_source (%v) and not a database
Error message
query database %s: not a data_source (%v) and not a database (%v)
What it means
QueryDatabaseAll first tries POST /v1/data_sources/{id}/query, and if that fails, falls back to resolving the ID as a database container via GetDatabaseInfo (GET /v1/databases/{id}). When BOTH attempts fail it wraps the data_source error and the database error together. It is thrown when the ID is neither a data_source_id nor an accessible database_id — wrong ID, missing permissions, or the object is a page/other type.
Source
Thrown at internal/datasource/connector/notion/client.go:354
}
return allBlocks, nil
}
// QueryDatabaseAll retrieves all records from a database via POST /v1/data_sources/{id}/query.
// Accepts either a data_source_id (from search) or a database_id (from child_database blocks).
// For database_ids, resolves to data_source_id via GET /v1/databases/{id}.
func (c *notionClient) QueryDatabaseAll(ctx context.Context, id string) ([]notionPage, error) {
// Try as data_source_id directly
records, err := c.paginatePages(ctx, http.MethodPost, fmt.Sprintf("/v1/data_sources/%s/query", id))
if err == nil {
return records, nil
}
// If 404, id might be a database container ID — resolve to data_source_id
info, dbErr := c.GetDatabaseInfo(ctx, id)
if dbErr != nil {
return nil, fmt.Errorf("query database %s: not a data_source (%v) and not a database (%v)", id, err, dbErr)
}
if info.DataSourceID == "" {
return nil, fmt.Errorf("database %s has no data sources", id)
}
return c.paginatePages(ctx, http.MethodPost, fmt.Sprintf("/v1/data_sources/%s/query", info.DataSourceID))
}
// ResolveBlock re-fetches a single block to resolve file_upload URLs.
// When a block contains a file_upload type, re-fetching it returns the resolved
// download URL (temporary S3 signed URL, 1-hour expiry).
func (c *notionClient) ResolveBlock(ctx context.Context, blockID string) (*notionBlock, error) {
respBody, err := c.doRequest(ctx, http.MethodGet, "/v1/blocks/"+blockID, nil)
if err != nil {
return nil, err
}
var block notionBlock
if err := json.Unmarshal(respBody, &block); err != nil {
return nil, fmt.Errorf("unmarshal block: %w", err)View on GitHub (pinned to 988cbb0330)
Solutions
- Verify the ID is a database or data_source UUID and that the integration is connected to it (share the database in Notion's connection settings)
- Use Notion search (POST /v1/search) to resolve the correct data_source_id instead of hardcoding IDs
- Inspect both wrapped errors in the message: the first (%v err) is the data_source attempt, the second (%v dbErr) the database attempt — fix whichever is not object_notfound
- Handle 429/rate-limit on the fallback path — the fallback GetDatabaseInfo call doubles request volume
- If the database has multiple data sources, check info.DataSourceID is populated rather than empty
Example fix
// before (single hardcoded ID, fails when it's a page or unshared DB)
records, err := client.QueryDatabaseAll(ctx, cfg.DatabaseID)
// after (resolve via search, verify access up front)
dsid, err := resolveDataSourceID(ctx, client, cfg.DatabaseOrPageURL) // uses /v1/search
if err != nil { return fmt.Errorf("cannot resolve data source: %w", err) }
info, err := client.GetDataSourceInfo(ctx, dsid)
if err != nil { return fmt.Errorf("no access to data source %s: %w", dsid, err) }
records, err := client.QueryDatabaseAll(ctx, dsid) Defensive patterns
Strategy: validation
Validate before calling
func ensureQueryableDatabase(ctx context.Context, client *NotionClient, id string) error {
info, err := client.GetDatabaseInfo(ctx, id) // resolve up front
if err != nil { return fmt.Errorf("id %s is not an accessible database: %w", id, err) }
if info.DataSourceID == "" { return fmt.Errorf("database %s has no data sources", id) }
return nil
}
// run before QueryDatabaseAll Type guard
func isObjectNotFound(err error) bool {
return err != nil && strings.Contains(err.Error(), "object_notfound")
}
// if isObjectNotFound(dsErr) && isObjectNotFound(dbErr) -> the ID itself is wrong, not a permission issue Try / catch
records, err := client.QueryDatabaseAll(ctx, id)
if err != nil {
if strings.Contains(err.Error(), "not a data_source") {
// both paths failed: wrong ID or missing integration access
if err := ensureIntegrationShared(ctx, client, id); err != nil {
return fmt.Errorf("check that %s is shared with the integration: %w", id, err)
}
}
return err
} Prevention
- Resolve IDs via POST /v1/search at startup instead of hardcoding database IDs in config
- Share every target database/page with the integration and re-verify after Notion permission changes
- Remember the 2025 API split: query data_source IDs; database container IDs only resolve via GET /v1/databases/{id}
- Sanity-check that the copied URL fragment points at the database itself, not its parent page
When it happens
Trigger: Passing a page ID or block ID instead of a database/data_source ID; the integration lacks access to the database (401/403 on both endpoints); the database was deleted or the ID is malformed (404 on both); GetDatabaseInfo returns a network/rate-limit error during the fallback path.
Common situations: Copying a URL fragment that points at a page containing a database rather than the database itself; 2025 Notion API split of databases into database containers + data_sources (old database IDs no longer query directly); integration not shared with the parent page; typos when hardcoding IDs in config.
Related errors
- get block children for %s: %w
- failed to delete file from KS3: %w
- failed to create directory: %w
- failed to create file: %w
- failed to open source file: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/cb4fbe2540f06ca4.
Report an issue: GitHub.