diff --git a/packages/backend/src/apps/brave-search/actions/index.js b/packages/backend/src/apps/brave-search/actions/index.js
new file mode 100644
index 00000000..b7bb1459
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/actions/index.js
@@ -0,0 +1,3 @@
+import webSearch from './web-search/index.js';
+
+export default [webSearch];
diff --git a/packages/backend/src/apps/brave-search/actions/web-search/index.js b/packages/backend/src/apps/brave-search/actions/web-search/index.js
new file mode 100644
index 00000000..10f3dfd6
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/actions/web-search/index.js
@@ -0,0 +1,52 @@
+import defineAction from '../../../../helpers/define-action.js';
+
+export default defineAction({
+ name: 'Web search',
+ key: 'webSearch',
+ description: 'Queries Brave Search and get back search results from the web.',
+ arguments: [
+ {
+ label: 'Query',
+ key: 'q',
+ type: 'string',
+ required: true,
+ variables: true,
+ description: 'The search query term.',
+ },
+ {
+ label: 'Safe search',
+ key: 'safesearch',
+ type: 'dropdown',
+ required: true,
+ description: 'Add or remove messages as needed',
+ value: 'moderate',
+ options: [
+ {
+ label: 'Off',
+ value: 'off',
+ },
+ {
+ label: 'Moderate',
+ value: 'moderate',
+ },
+ {
+ label: 'Strict',
+ value: 'strict',
+ },
+ ],
+ },
+ ],
+
+ async run($) {
+ const params = {
+ q: $.step.parameters.q,
+ safesearch: $.step.parameters.safesearch,
+ };
+
+ const { data } = await $.http.get('/v1/web/search', { params });
+
+ $.setActionItem({
+ raw: data,
+ });
+ },
+});
diff --git a/packages/backend/src/apps/brave-search/assets/favicon.svg b/packages/backend/src/apps/brave-search/assets/favicon.svg
new file mode 100644
index 00000000..8f953988
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/assets/favicon.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/brave-search/auth/index.js b/packages/backend/src/apps/brave-search/auth/index.js
new file mode 100644
index 00000000..859a5832
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/auth/index.js
@@ -0,0 +1,34 @@
+import verifyCredentials from './verify-credentials.js';
+import isStillVerified from './is-still-verified.js';
+
+export default {
+ fields: [
+ {
+ key: 'screenName',
+ label: 'Screen Name',
+ type: 'string',
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description:
+ 'Screen name of your connection to be used on Automatisch UI.',
+ clickToCopy: false,
+ },
+ {
+ key: 'apiKey',
+ label: 'API Key',
+ type: 'string',
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Brave Search API key of your account.',
+ docUrl: 'https://automatisch.io/docs/brave-search#api-key',
+ clickToCopy: false,
+ },
+ ],
+
+ verifyCredentials,
+ isStillVerified,
+};
diff --git a/packages/backend/src/apps/brave-search/auth/is-still-verified.js b/packages/backend/src/apps/brave-search/auth/is-still-verified.js
new file mode 100644
index 00000000..3f853952
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/auth/is-still-verified.js
@@ -0,0 +1,5 @@
+const isStillVerified = async () => {
+ return true;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/brave-search/auth/verify-credentials.js b/packages/backend/src/apps/brave-search/auth/verify-credentials.js
new file mode 100644
index 00000000..07e4f027
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/auth/verify-credentials.js
@@ -0,0 +1,5 @@
+const verifyCredentials = async () => {
+ return true;
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/brave-search/common/add-accept-header.js b/packages/backend/src/apps/brave-search/common/add-accept-header.js
new file mode 100644
index 00000000..47c033a4
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/common/add-accept-header.js
@@ -0,0 +1,7 @@
+const addContentTypeHeader = ($, requestConfig) => {
+ requestConfig.headers.accept = 'application/json';
+
+ return requestConfig;
+};
+
+export default addContentTypeHeader;
diff --git a/packages/backend/src/apps/brave-search/common/add-auth-header.js b/packages/backend/src/apps/brave-search/common/add-auth-header.js
new file mode 100644
index 00000000..14b60d88
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/common/add-auth-header.js
@@ -0,0 +1,9 @@
+const addAuthHeader = ($, requestConfig) => {
+ if ($.auth.data?.apiKey) {
+ requestConfig.headers['X-Subscription-Token'] = $.auth.data.apiKey;
+ }
+
+ return requestConfig;
+};
+
+export default addAuthHeader;
diff --git a/packages/backend/src/apps/brave-search/dynamic-data/index.js b/packages/backend/src/apps/brave-search/dynamic-data/index.js
new file mode 100644
index 00000000..6db48046
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/dynamic-data/index.js
@@ -0,0 +1,3 @@
+import listModels from './list-models/index.js';
+
+export default [listModels];
diff --git a/packages/backend/src/apps/brave-search/dynamic-data/list-models/index.js b/packages/backend/src/apps/brave-search/dynamic-data/list-models/index.js
new file mode 100644
index 00000000..f47f5fb9
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/dynamic-data/list-models/index.js
@@ -0,0 +1,31 @@
+export default {
+ name: 'List models',
+ key: 'listModels',
+
+ async run($) {
+ const models = {
+ data: [],
+ };
+
+ const params = {
+ limit: 999,
+ };
+
+ let hasMore = false;
+
+ do {
+ const { data } = await $.http.get('/v1/models', { params });
+ params.after_id = data.last_id;
+ hasMore = data.has_more;
+
+ for (const base of data.data) {
+ models.data.push({
+ value: base.id,
+ name: base.display_name,
+ });
+ }
+ } while (hasMore);
+
+ return models;
+ },
+};
diff --git a/packages/backend/src/apps/brave-search/index.js b/packages/backend/src/apps/brave-search/index.js
new file mode 100644
index 00000000..f85615e8
--- /dev/null
+++ b/packages/backend/src/apps/brave-search/index.js
@@ -0,0 +1,21 @@
+import defineApp from '../../helpers/define-app.js';
+import addAuthHeader from './common/add-auth-header.js';
+import addAcceptHeader from './common/add-accept-header.js';
+import auth from './auth/index.js';
+import actions from './actions/index.js';
+import dynamicData from './dynamic-data/index.js';
+
+export default defineApp({
+ name: 'Brave Search',
+ key: 'brave-search',
+ baseUrl: 'https://search.brave.com',
+ apiBaseUrl: 'https://api.search.brave.com/res',
+ iconUrl: '{BASE_URL}/apps/brave-search/assets/favicon.svg',
+ authDocUrl: '{DOCS_URL}/apps/brave-search/connection',
+ primaryColor: '#181818',
+ supportsConnections: true,
+ beforeRequest: [addAuthHeader, addAcceptHeader],
+ auth,
+ actions,
+ dynamicData,
+});
diff --git a/packages/backend/src/models/__snapshots__/app.test.js.snap b/packages/backend/src/models/__snapshots__/app.test.js.snap
index 18b951e1..624194aa 100644
--- a/packages/backend/src/models/__snapshots__/app.test.js.snap
+++ b/packages/backend/src/models/__snapshots__/app.test.js.snap
@@ -6,6 +6,7 @@ exports[`App model > list should have list of applications keys 1`] = `
"anthropic",
"appwrite",
"azure-openai",
+ "brave-search",
"carbone",
"clickup",
"code",
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 63a996a1..c842386e 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -59,6 +59,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/appwrite/connection' },
],
},
+ {
+ text: 'Brave Search',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Actions', link: '/apps/brave-search/actions' },
+ { text: 'Connection', link: '/apps/brave-search/connection' },
+ ],
+ },
{
text: 'Carbone',
collapsible: true,
diff --git a/packages/docs/pages/apps/brave-search/actions.md b/packages/docs/pages/apps/brave-search/actions.md
new file mode 100644
index 00000000..9c9da7b5
--- /dev/null
+++ b/packages/docs/pages/apps/brave-search/actions.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/brave-search.svg
+items:
+ - name: Web search
+ desc: Queries Brave Search and get back search results from the web.
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/brave-search/connection.md b/packages/docs/pages/apps/brave-search/connection.md
new file mode 100644
index 00000000..eadab30b
--- /dev/null
+++ b/packages/docs/pages/apps/brave-search/connection.md
@@ -0,0 +1,8 @@
+# Brave Search
+
+1. Go to [API Keys page](https://api.search.brave.com/app/keys) on Brave Search.
+2. Create a new API key.
+3. Paste the key into the `API Key` field in Automatisch.
+4. Write any screen name to be displayed in Automatisch.
+5. Click `Save`.
+6. Start using Brave Search integration with Automatisch!
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 0eb08bea..5f0271ad 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -5,6 +5,7 @@ The following integrations are currently supported by Automatisch.
- [Airtable](/apps/airtable/actions)
- [Anthropic](/apps/anthropic/actions)
- [Appwrite](/apps/appwrite/triggers)
+- [Brave Search](/apps/brave-search/actions)
- [Carbone](/apps/carbone/actions)
- [ClickUp](/apps/clickup/triggers)
- [Datastore](/apps/datastore/actions)
diff --git a/packages/docs/pages/public/favicons/brave-search.svg b/packages/docs/pages/public/favicons/brave-search.svg
new file mode 100644
index 00000000..8f953988
--- /dev/null
+++ b/packages/docs/pages/public/favicons/brave-search.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file