diff --git a/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.json b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.json
new file mode 100644
index 0000000000..fdf3169be8
--- /dev/null
+++ b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.json
@@ -0,0 +1,20 @@
+{
+ "node": "n8n-nodes-base.apiTemplateIo",
+ "nodeVersion": "1.0",
+ "codexVersion": "1.0",
+ "categories": [
+ "Marketing & Content"
+ ],
+ "resources": {
+ "credentialDocumentation": [
+ {
+ "url": "https://docs.n8n.io/credentials/apiTemplateIo"
+ }
+ ],
+ "primaryDocumentation": [
+ {
+ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.apiTemplateIo/"
+ }
+ ]
+ }
+}
diff --git a/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts
new file mode 100644
index 0000000000..ee6e63effa
--- /dev/null
+++ b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts
@@ -0,0 +1,560 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ ILoadOptionsFunctions,
+ INodeExecutionData,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ apiTemplateIoApiRequest,
+ downloadImage,
+ loadResource,
+ validateJSON,
+} from './GenericFunctions';
+
+export class ApiTemplateIo implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'APITemplate.io',
+ name: 'ApiTemplateIo',
+ icon: 'file:apiTemplateIo.svg',
+ group: ['transform'],
+ version: 1,
+ description: 'Consume the APITemplate.io API',
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ defaults: {
+ name: 'APITemplate.io',
+ color: '#c0c0c0',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'apiTemplateIoApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ options: [
+ {
+ name: 'Account',
+ value: 'account',
+ },
+ {
+ name: 'Image',
+ value: 'image',
+ },
+ {
+ name: 'PDF',
+ value: 'pdf',
+ },
+ ],
+ default: 'image',
+ description: 'Resource to consume',
+ },
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ default: 'create',
+ required: true,
+ description: 'Operation to perform',
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'image',
+ 'pdf',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ default: 'get',
+ required: true,
+ description: 'Operation to perform',
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'account',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Template ID',
+ name: 'imageTemplateId',
+ type: 'options',
+ required: true,
+ default: '',
+ description: 'ID of the image template to use.',
+ typeOptions: {
+ loadOptionsMethod: 'getImageTemplates',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Template ID',
+ name: 'pdfTemplateId',
+ type: 'options',
+ required: true,
+ default: '',
+ description: 'ID of the PDF template to use.',
+ typeOptions: {
+ loadOptionsMethod: 'getPdfTemplates',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'JSON Parameters',
+ name: 'jsonParameters',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Download',
+ name: 'download',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Name of the binary property to which to
write the data of the read file.',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ required: true,
+ default: 'data',
+ description: 'Name of the binary property to which to write to.',
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ download: [
+ true,
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Overrides (JSON)',
+ name: 'overridesJson',
+ type: 'json',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ jsonParameters: [
+ true,
+ ],
+ },
+ },
+ placeholder: `[ {"name": "text_1", "text": "hello world", "textBackgroundColor": "rgba(246, 243, 243, 0)" } ]`,
+ },
+ {
+ displayName: 'Properties (JSON)',
+ name: 'propertiesJson',
+ type: 'json',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ ],
+ operation: [
+ 'create',
+ ],
+ jsonParameters: [
+ true,
+ ],
+ },
+ },
+ placeholder: `{ "name": "text_1" }`,
+ },
+ {
+ displayName: 'Overrides',
+ name: 'overridesUi',
+ placeholder: 'Add Override',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'image',
+ ],
+ operation: [
+ 'create',
+ ],
+ jsonParameters: [
+ false,
+ ],
+ },
+ },
+ default: {},
+ options: [
+ {
+ name: 'overrideValues',
+ displayName: 'Override',
+ values: [
+ {
+ displayName: 'Properties',
+ name: 'propertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: {},
+ options: [
+ {
+ name: 'propertyValues',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the property',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to the property.',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Properties',
+ name: 'propertiesUi',
+ placeholder: 'Add Property',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'pdf',
+ ],
+ operation: [
+ 'create',
+ ],
+ jsonParameters: [
+ false,
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'propertyValues',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'Name of the property',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to the property.',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'pdf',
+ 'image',
+ ],
+ 'download': [
+ true,
+ ],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'The name of the downloaded image/pdf. It has to include the extension. For example: report.pdf',
+ },
+ ],
+ },
+ ],
+ };
+
+ methods = {
+ loadOptions: {
+ async getImageTemplates(this: ILoadOptionsFunctions): Promise {
+ return await loadResource.call(this, 'image');
+ },
+
+ async getPdfTemplates(this: ILoadOptionsFunctions): Promise {
+ return await loadResource.call(this, 'pdf');
+ },
+ },
+ };
+
+ async execute(this: IExecuteFunctions) {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const length = items.length;
+
+ let responseData;
+
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ if (resource === 'account') {
+
+ // *********************************************************************
+ // account
+ // *********************************************************************
+
+ if (operation === 'get') {
+
+ // ----------------------------------
+ // account: get
+ // ----------------------------------
+
+ for (let i = 0; i < length; i++) {
+
+ responseData = await apiTemplateIoApiRequest.call(this, 'GET', '/account-information');
+
+ returnData.push(responseData);
+ }
+ }
+
+ } else if (resource === 'image') {
+
+ // *********************************************************************
+ // image
+ // *********************************************************************
+
+ if (operation === 'create') {
+ // ----------------------------------
+ // image: create
+ // ----------------------------------
+
+ const download = this.getNodeParameter('download', 0) as boolean;
+
+ // https://docs.apitemplate.io/reference/api-reference.html#create-an-image-jpeg-and-png
+ for (let i = 0; i < length; i++) {
+ const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ const qs = {
+ template_id: this.getNodeParameter('imageTemplateId', i),
+ };
+
+ const body = { overrides: [] } as IDataObject;
+
+ if (jsonParameters === false) {
+ const overrides = (this.getNodeParameter('overridesUi', i) as IDataObject || {}).overrideValues as IDataObject[] || [];
+ if (overrides.length !== 0) {
+ const data: IDataObject[] = [];
+ for (const override of overrides) {
+ const properties = (override.propertiesUi as IDataObject || {}).propertyValues as IDataObject[] || [];
+ data.push(properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {}));
+ }
+ body.overrides = data;
+ }
+ } else {
+ const overrideJson = this.getNodeParameter('overridesJson', i) as string;
+ if (overrideJson !== '') {
+ const data = validateJSON(overrideJson);
+ if (data === undefined) {
+ throw new Error('A valid JSON must be provided.');
+ }
+ body.overrides = data;
+ }
+ }
+
+ responseData = await apiTemplateIoApiRequest.call(this, 'POST', '/create', qs, body);
+
+ if (download === true) {
+ const binaryProperty = this.getNodeParameter('binaryProperty', i) as string;
+ const data = await downloadImage.call(this, responseData.download_url);
+ const fileName = responseData.download_url.split('/').pop();
+ const binaryData = await this.helpers.prepareBinaryData(data, options.fileName || fileName);
+ responseData = {
+ json: responseData,
+ binary: {
+ [binaryProperty]: binaryData,
+ },
+ };
+ }
+ returnData.push(responseData);
+ }
+
+ if (download === true) {
+ return this.prepareOutputData(returnData as unknown as INodeExecutionData[]);
+ }
+ }
+
+ } else if (resource === 'pdf') {
+
+ // *********************************************************************
+ // pdf
+ // *********************************************************************
+
+ if (operation === 'create') {
+
+ // ----------------------------------
+ // pdf: create
+ // ----------------------------------
+
+ // https://docs.apitemplate.io/reference/api-reference.html#create-a-pdf
+ const download = this.getNodeParameter('download', 0) as boolean;
+
+ for (let i = 0; i < length; i++) {
+ const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ const qs = {
+ template_id: this.getNodeParameter('pdfTemplateId', i),
+ };
+
+ let data;
+
+ if (jsonParameters === false) {
+ const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || [];
+ if (properties.length === 0) {
+ throw new Error('The parameter properties cannot be empty');
+ }
+ data = properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {});
+ } else {
+ const propertiesJson = this.getNodeParameter('propertiesJson', i) as string;
+ data = validateJSON(propertiesJson);
+ if (data === undefined) {
+ throw new Error('A valid JSON must be provided.');
+ }
+ }
+
+ responseData = await apiTemplateIoApiRequest.call(this, 'POST', '/create', qs, data);
+
+ if (download === true) {
+ const binaryProperty = this.getNodeParameter('binaryProperty', i) as string;
+ const data = await downloadImage.call(this, responseData.download_url);
+ const fileName = responseData.download_url.split('/').pop();
+ const binaryData = await this.helpers.prepareBinaryData(data, options.fileName || fileName);
+ responseData = {
+ json: responseData,
+ binary: {
+ [binaryProperty]: binaryData,
+ },
+ };
+ }
+ returnData.push(responseData);
+ }
+ if (download === true) {
+ return this.prepareOutputData(returnData as unknown as INodeExecutionData[]);
+ }
+ }
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts b/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts
new file mode 100644
index 0000000000..083683434d
--- /dev/null
+++ b/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts
@@ -0,0 +1,91 @@
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ IExecuteFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+export async function apiTemplateIoApiRequest(
+ this: IExecuteFunctions | ILoadOptionsFunctions,
+ method: string,
+ endpoint: string,
+ qs = {},
+ body = {},
+) {
+ const { apiKey } = this.getCredentials('apiTemplateIoApi') as { apiKey: string };
+
+ const options: OptionsWithUri = {
+ headers: {
+ 'user-agent': 'n8n',
+ Accept: 'application/json',
+ 'X-API-KEY': `${apiKey}`,
+ },
+ uri: `https://api.apitemplate.io/v1${endpoint}`,
+ method,
+ qs,
+ body,
+ followRedirect: true,
+ followAllRedirects: true,
+ json: true,
+ };
+
+ if (!Object.keys(body).length) {
+ delete options.body;
+ }
+
+ if (!Object.keys(qs).length) {
+ delete options.qs;
+ }
+
+ try {
+ const response = await this.helpers.request!(options);
+ if (response.status === 'error') {
+ throw new Error(response.message);
+ }
+ return response;
+ } catch (error) {
+ if (error?.response?.body?.message) {
+ throw new Error(`APITemplate.io error response [${error.statusCode}]: ${error.response.body.message}`);
+ }
+ throw error;
+ }
+}
+
+export async function loadResource(
+ this: ILoadOptionsFunctions,
+ resource: 'image' | 'pdf',
+) {
+ const target = resource === 'image' ? ['JPEG', 'PNG'] : ['PDF'];
+ const templates = await apiTemplateIoApiRequest.call(this, 'GET', '/list-templates');
+ const filtered = templates.filter(({ format }: { format: 'PDF' | 'JPEG' | 'PNG' }) => target.includes(format));
+
+ return filtered.map(({ format, name, id }: { format: string, name: string, id: string }) => ({
+ name: `${name} (${format})`,
+ value: id,
+ }));
+}
+
+export function validateJSON(json: string | object | undefined): any { // tslint:disable-line:no-any
+ let result;
+ if (typeof json === 'object') {
+ return json;
+ }
+ try {
+ result = JSON.parse(json!);
+ } catch (exception) {
+ result = undefined;
+ }
+ return result;
+}
+
+
+export function downloadImage(this: IExecuteFunctions, url: string) {
+ return this.helpers.request({
+ uri: url,
+ method: 'GET',
+ json: false,
+ encoding: null,
+ });
+}
diff --git a/packages/nodes-base/nodes/ApiTemplateIo/apiTemplateIo.svg b/packages/nodes-base/nodes/ApiTemplateIo/apiTemplateIo.svg
new file mode 100755
index 0000000000..5ccaf179a9
--- /dev/null
+++ b/packages/nodes-base/nodes/ApiTemplateIo/apiTemplateIo.svg
@@ -0,0 +1,102 @@
+
+
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 01f2943c56..1fbde4bdda 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -35,6 +35,7 @@
"dist/credentials/Amqp.credentials.js",
"dist/credentials/AsanaApi.credentials.js",
"dist/credentials/AsanaOAuth2Api.credentials.js",
+ "dist/credentials/ApiTemplateIoApi.credentials.js",
"dist/credentials/AutomizyApi.credentials.js",
"dist/credentials/Aws.credentials.js",
"dist/credentials/AffinityApi.credentials.js",
@@ -268,6 +269,7 @@
"dist/nodes/Amqp/AmqpTrigger.node.js",
"dist/nodes/Asana/Asana.node.js",
"dist/nodes/Asana/AsanaTrigger.node.js",
+ "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js",
"dist/nodes/Affinity/Affinity.node.js",
"dist/nodes/Affinity/AffinityTrigger.node.js",
"dist/nodes/Automizy/Automizy.node.js",