diff --git a/packages/nodes-base/credentials/BrandfetchApi.credentials.ts b/packages/nodes-base/credentials/BrandfetchApi.credentials.ts
new file mode 100644
index 0000000000..27ebc7f78e
--- /dev/null
+++ b/packages/nodes-base/credentials/BrandfetchApi.credentials.ts
@@ -0,0 +1,18 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class BrandfetchApi implements ICredentialType {
+ name = 'brandfetchApi';
+ displayName = 'Brandfetch API';
+ documentationUrl = 'brandfetch';
+ properties = [
+ {
+ displayName: 'API Key',
+ name: 'apiKey',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts b/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts
new file mode 100644
index 0000000000..6eafcf3c01
--- /dev/null
+++ b/packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts
@@ -0,0 +1,271 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ brandfetchApiRequest,
+} from './GenericFunctions';
+
+export class Brandfetch implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Brandfetch',
+ name: 'Brandfetch',
+ icon: 'file:brandfetch.png',
+ group: ['output'],
+ version: 1,
+ subtitle: '={{$parameter["operation"]}}',
+ description: 'Consume Brandfetch API',
+ defaults: {
+ name: 'Brandfetch',
+ color: '#1f1f1f',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'brandfetchApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ options: [
+
+ {
+ name: 'Color',
+ value: 'color',
+ description: 'Return a company\'s colors',
+ },
+ {
+ name: 'Company',
+ value: 'company',
+ description: 'Return a company\'s data',
+ },
+ {
+ name: 'Font',
+ value: 'font',
+ description: 'Return a company\'s fonts',
+ },
+ {
+ name: 'Industry',
+ value: 'industry',
+ description: 'Return a company\'s industry',
+ },
+ {
+ name: 'Logo',
+ value: 'logo',
+ description: 'Return a company\'s logo & icon',
+ },
+ ],
+ default: 'logo',
+ description: 'The operation to perform',
+ },
+
+ // ----------------------------------
+ // All
+ // ----------------------------------
+ {
+ displayName: 'Domain',
+ name: 'domain',
+ type: 'string',
+ default: '',
+ description: 'The domain name of the company.',
+ required: true,
+ },
+ {
+ displayName: 'Download',
+ name: 'download',
+ type: 'boolean',
+ default: false,
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'logo',
+ ],
+ },
+ },
+ description: 'Name of the binary property to which to
write the data of the read file.',
+ },
+ {
+ displayName: 'Image Type',
+ name: 'imageTypes',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ operation: [
+ 'logo',
+ ],
+ download: [
+ true,
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Icon',
+ value: 'icon',
+ },
+ {
+ name: 'Logo',
+ value: 'logo',
+ },
+ ],
+ default: [
+ 'logo',
+ 'icon',
+ ],
+ required: true,
+ },
+ {
+ displayName: 'Image Format',
+ name: 'imageFormats',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ operation: [
+ 'logo',
+ ],
+ download: [
+ true,
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'PNG',
+ value: 'png',
+ },
+ {
+ name: 'SVG',
+ value: 'svg',
+ },
+ ],
+ default: [
+ 'png',
+ ],
+ description: 'The image format in which the logo should be returned as.',
+ required: true,
+ },
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const length = items.length as unknown as number;
+
+ const operation = this.getNodeParameter('operation', 0) as string;
+ const responseData = [];
+ for (let i = 0; i < length; i++) {
+ if (operation === 'logo') {
+ const domain = this.getNodeParameter('domain', i) as string;
+ const download = this.getNodeParameter('download', i) as boolean;
+
+ const body: IDataObject = {
+ domain,
+ };
+
+ const response = await brandfetchApiRequest.call(this, 'POST', `/logo`, body);
+
+ if (download === true) {
+
+ const imageTypes = this.getNodeParameter('imageTypes', i) as string[];
+
+ const imageFormats = this.getNodeParameter('imageFormats', i) as string[];
+
+ const newItem: INodeExecutionData = {
+ json: {},
+ binary: {},
+ };
+
+ if (items[i].binary !== undefined) {
+ // Create a shallow copy of the binary data so that the old
+ // data references which do not get changed still stay behind
+ // but the incoming data does not get changed.
+ Object.assign(newItem.binary, items[i].binary);
+ }
+
+ newItem.json = response.response;
+
+ for (const imageType of imageTypes) {
+ for (const imageFormat of imageFormats) {
+
+ const url = response.response[imageType][(imageFormat === 'png') ? 'image' : imageFormat] as string;
+
+ if (url !== null) {
+ const data = await brandfetchApiRequest.call(this, 'GET', '', {}, {}, url, { json: false, encoding: null });
+
+ newItem.binary![`${imageType}_${imageFormat}`] = await this.helpers.prepareBinaryData(data, `${imageType}_${domain}.${imageFormat}`);
+
+ items[i] = newItem;
+ }
+ items[i] = newItem;
+ }
+ }
+ if (Object.keys(items[i].binary!).length === 0) {
+ delete items[i].binary;
+ }
+ } else {
+ responseData.push(response.response);
+ }
+ }
+ if (operation === 'color') {
+ const domain = this.getNodeParameter('domain', i) as string;
+
+ const body: IDataObject = {
+ domain,
+ };
+
+ const response = await brandfetchApiRequest.call(this, 'POST', `/color`, body);
+ responseData.push(response.response);
+ }
+ if (operation === 'font') {
+ const domain = this.getNodeParameter('domain', i) as string;
+
+ const body: IDataObject = {
+ domain,
+ };
+
+ const response = await brandfetchApiRequest.call(this, 'POST', `/font`, body);
+ responseData.push(response.response);
+ }
+ if (operation === 'company') {
+ const domain = this.getNodeParameter('domain', i) as string;
+
+ const body: IDataObject = {
+ domain,
+ };
+
+ const response = await brandfetchApiRequest.call(this, 'POST', `/company`, body);
+ responseData.push(response.response);
+ }
+ if (operation === 'industry') {
+ const domain = this.getNodeParameter('domain', i) as string;
+
+ const body: IDataObject = {
+ domain,
+ };
+
+ const response = await brandfetchApiRequest.call(this, 'POST', `/industry`, body);
+ responseData.push.apply(responseData, response.response);
+ }
+ }
+
+ if (operation === 'logo' && this.getNodeParameter('download', 0) === true) {
+ // For file downloads the files get attached to the existing items
+ return this.prepareOutputData(items);
+ } else {
+ return [this.helpers.returnJsonArray(responseData)];
+ }
+ }
+}
diff --git a/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts
new file mode 100644
index 0000000000..f3ae6e48ca
--- /dev/null
+++ b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts
@@ -0,0 +1,65 @@
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+} from 'n8n-workflow';
+
+export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ try {
+ const credentials = this.getCredentials('brandfetchApi');
+ if (credentials === undefined) {
+ throw new Error('No credentials got returned!');
+ }
+ let options: OptionsWithUri = {
+ headers: {
+ 'x-api-key': credentials.apiKey,
+ },
+ method,
+ qs,
+ body,
+ uri: uri || `https://api.brandfetch.io/v1${resource}`,
+ json: true,
+ };
+
+ options = Object.assign({}, options, option);
+
+ if (this.getNodeParameter('operation', 0) === 'logo' && options.json === false) {
+ delete options.headers;
+ }
+
+ if (!Object.keys(body).length) {
+ delete options.body;
+ }
+ if (!Object.keys(qs).length) {
+ delete options.qs;
+ }
+
+ const response = await this.helpers.request!(options);
+
+ if (response.statusCode && response.statusCode !== 200) {
+ throw new Error(`Brandfetch error response [${response.statusCode}]: ${response.response}`);
+ }
+
+ return response;
+
+ } catch (error) {
+
+ if (error.response && error.response.body && error.response.body.message) {
+ // Try to return the error prettier
+ const errorBody = error.response.body;
+ throw new Error(`Brandfetch error response [${error.statusCode}]: ${errorBody.message}`);
+ }
+
+ // Expected error data did not get returned so throw the actual error
+ throw error;
+ }
+}
diff --git a/packages/nodes-base/nodes/Brandfetch/brandfetch.png b/packages/nodes-base/nodes/Brandfetch/brandfetch.png
new file mode 100644
index 0000000000..144390c620
Binary files /dev/null and b/packages/nodes-base/nodes/Brandfetch/brandfetch.png differ
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 56c1922704..766ce972be 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -43,6 +43,7 @@
"dist/credentials/BitlyApi.credentials.js",
"dist/credentials/BitlyOAuth2Api.credentials.js",
"dist/credentials/BoxOAuth2Api.credentials.js",
+ "dist/credentials/BrandfetchApi.credentials.js",
"dist/credentials/ChargebeeApi.credentials.js",
"dist/credentials/CircleCiApi.credentials.js",
"dist/credentials/ClearbitApi.credentials.js",
@@ -259,6 +260,7 @@
"dist/nodes/Bitly/Bitly.node.js",
"dist/nodes/Box/Box.node.js",
"dist/nodes/Box/BoxTrigger.node.js",
+ "dist/nodes/Brandfetch/Brandfetch.node.js",
"dist/nodes/Calendly/CalendlyTrigger.node.js",
"dist/nodes/Chargebee/Chargebee.node.js",
"dist/nodes/Chargebee/ChargebeeTrigger.node.js",