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

  1. 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)
  2. Use Notion search (POST /v1/search) to resolve the correct data_source_id instead of hardcoding IDs
  3. 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
  4. Handle 429/rate-limit on the fallback path — the fallback GetDatabaseInfo call doubles request volume
  5. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/cb4fbe2540f06ca4. Report an issue: GitHub.