Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export * from './specs/abuseipdb';
export * from './specs/alienvault_otx';
export * from './specs/greynoise';
export * from './specs/notion';
export * from './specs/shodan';
export * from './specs/urlvoid';
export * from './specs/virustotal';
150 changes: 150 additions & 0 deletions src/platform/packages/shared/kbn-connector-specs/src/specs/notion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { z } from '@kbn/zod/v4';
import type { ConnectorSpec } from '../connector_spec';

export const NotionConnector: ConnectorSpec = {
metadata: {
id: '.notion',
displayName: 'Notion',
description: 'Explore content and databases in Notion',
minimumLicense: 'gold',
supportedFeatureIds: ['workflows'],
},

// TODO: we also need to send another custom header "Notion-Version": "2025-09-03"
// https://developers.notion.com/docs/authorization#making-api-requests-with-an-internal-integration
authTypes: ['bearer'],

// TODO: check if we need `schema` attribute / what for

actions: {
// https://developers.notion.com/reference/post-search
searchPageOrDSByTitle: {
isTool: true,
input: z.object({
query: z.string(),
queryObjectType: z.enum(['page', 'data_source']),
startCursor: z.string().optional(),
pageSize: z.number().optional(),
}),
handler: async (ctx, input) => {
const typedInput = input as {
query: string;
queryObjectType: 'page' | 'data_source';
startCursor?: string;
pageSize?: number;
};

const response = await ctx.client.post(
'https://api.notion.com/v1/search',
{
query: typedInput.query,
filter: {
value: typedInput.queryObjectType,
property: 'object',
},
...(typedInput.startCursor && { start_cursor: typedInput.startCursor }),
...(typedInput.pageSize && { page_size: typedInput.pageSize }),
},
{ headers: { 'Notion-Version': '2025-09-03' } }
);

return response.data;
},
},

// https://developers.notion.com/reference/retrieve-a-page
getPage: {
isTool: true,
input: z.object({ pageId: z.string() }),
handler: async (ctx, input) => {
const typedInput = input as { pageId: string };
const response = await ctx.client.get(
`https://api.notion.com/v1/pages/${typedInput.pageId}`,
{}
// { headers: { 'Notion-Version': '2025-09-03' } }
);
return response.data;
},
},

// https://developers.notion.com/reference/retrieve-a-data-source
getDataSource: {
isTool: true,
input: z.object({ dataSourceId: z.string() }),
handler: async (ctx, input) => {
const typedInput = input as { dataSourceId: string };
const response = await ctx.client.get(
`https://api.notion.com/v1/data_sources/${typedInput.dataSourceId}`,
{}
// { headers: { 'Notion-Version': '2025-09-03' } }
);
return response.data;
},
},

// https://developers.notion.com/reference/query-a-data-source
queryDataSource: {
isTool: true,
input: z.object({
dataSourceId: z.string(),
filter: z.string().optional(),
startCursor: z.string().optional(),
pageSize: z.number().optional(),
}),
handler: async (ctx, input) => {
const typedInput = input as {
dataSourceId: string;
filter?: string;
startCursor?: string;
pageSize?: number;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
let requestData: Record<string, any> = {
page_size: typedInput.pageSize,
start_cursor: typedInput.startCursor,
};
if (typedInput.filter) {
requestData = { ...requestData, filter: JSON.parse(typedInput.filter) };
}

// TODO: check if there's error handling for free from the framework
// or if we still need to add some of our own
const response = await ctx.client.post(
`https://api.notion.com/v1/data_sources/${typedInput.dataSourceId}/query`,
requestData,
{ headers: { 'Notion-Version': '2025-09-03' } }
);
return response.data;
},
},
},

test: {
description: 'Verifies Notion connection by fetching metadata about given data source',
// TODO: might need to accept some input here in order to pass to the API endpoint to test
// if listing all users feels a bit too much
handler: async (ctx) => {
ctx.log.debug('Notion test handler');

try {
const response = await ctx.client.get('https://api.notion.com/v1/users');
const numOfUsers = response.data.results.length;
return {
ok: true,
message: `Successfully connected to Notion API: found ${numOfUsers} users`,
};
} catch (error) {
return { ok: false, message: error.message };
}
},
},
};