Create multiline invoice with the HTTP node on a custom API

Describe the issue/error/question

Hello everyone!

I have a json results that I would like to create an invoice with a custom API via the HTTP node.

The problem is that the number of line items always varies and I would like to input each item on a new row based on the number of items received by the first data.

Each new item is created with this name on the API newitems[0][description] as you can see on the http node on the workflow I shared.

How could I achieve this?

Please share the workflow

{
  "nodes": [
    {
      "parameters": {
        "functionCode": "[\n{\n\"value\": \"1x Fotos impresas - 8 x 10 / Matte\"\n},\n{\n\"value\": \" 1x Fotos impresas - 6 x 8 / Matte\"\n},\n{\n\"value\": \" 1x Fotos impresas - 5 x 7 / Matte\"\n},\n{\n\"value\": \" 1x Foto en formato digital\"\n},\n{\n\"value\": \"\"\n}\n]\n"
      },
      "name": "Sample Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        400,
        300
      ]
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "options": {},
        "queryParametersUi": {
          "parameter": [
            {
              "name": "company"
            },
            {
              "name": "firstname"
            },
            {
              "name": "lastname"
            },
            {
              "name": "email"
            },
            {
              "name": "phonenumber"
            },
            {
              "name": "tags"
            },
            {
              "name": "newitems[0][description]"
            },
            {
              "name": "duedate"
            }
          ]
        }
      },
      "name": "Create invoice",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        550,
        300
      ]
    }
  ],
  "connections": {
    "Sample Data": {
      "main": [
        [
          {
            "node": "Create invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Share the output returned by the last node

[
{
"value": "1x Fotos impresas - 8 x 10 / Matte - _GDP8794"
},
{
"value": " 1x Fotos impresas - 6 x 8 / Matte - _GDP8793"
},
{
"value": " 1x Fotos impresas - 5 x 7 / Matte - _GDP8792"
},
{
"value": " 1x Foto en formato digital - _GDP8790"
},
{
"value": ""
}
]

Information on your n8n setup

  • n8n version: 0.145.0
  • Database you’re using (default: SQLite):
  • Running n8n with the execution process [own(default), main]:
  • Running n8n via [Docker, npm, n8n.cloud, desktop app]: Docker

How is the API that creates the invoice expecting the data exactly?

From the documentation

POST

api/invoices/:id

  • [{Multipart Form} Request-Example:]

(Loading...)

  [
		"clientid"=>1,
		"number"=>"00001",
		"date"=>"2020-09-07",
		"currency"=>1,
		"newitems[0][description]"=>"item 1 description",
		"newitems[0][long_description]"=>"item 1 long description",
		"newitems[0][qty]"=>1,
		"newitems[0][rate]"=>100,
		"newitems[0][order]"=>1,
		"newitems[0][taxname][]"=>CGST|9.00,
		"newitems[0][taxname][]"=>SGST|9.00,
		"newitems[0][unit]"=>"",
		"newitems[1][description]"=>"item 2 description",
		"newitems[1][long_description]"=>"item 2 long description",
		"newitems[1][qty]"=>1,
		"newitems[1][rate]"=>100,
		"newitems[1][order]"=>1,
		"newitems[1][taxname][]"=>CGST|9.00,
		"newitems[1][taxname][]"=>SGST|9.00,
		"newitems[1][unit]"=>"",
		"subtotal"=>236.00,
		"total"=>236.00,
		"billing_street"=>"billing address",
		"allowed_payment_modes[0]"=>1,
		"allowed_payment_modes[1]"=>2,
		....
	]

Do not think you can send multipart/form-data dynamically using the HTTP node. You can do an HTTP request from the function node and that should do it.

const rq = require('request-promise-native');

let values = [];

for (const item of items) {
    values.push({ description: item.json.value })
}

await rq({
    method: 'POST',
    url: 'https://webhook.site/8df892df-f741-431b-98dd-51377fc0d35d',
    form: {
      firstname: 'whatever',
      lastname: 'asasas',
      newitems: values,
      //add all other required parameters
    }
})

return [
  {
    json: {}
  }
]

Make sure you allow the use of the external library using the env variable EXPORT NODE_FUNCTION_ALLOW_EXTERNAL=request-promise-native. I used request-promise-native but you can use Axios as well.

Example workflow
{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        -30,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "return [\n  {\n    json: {\n      value: '1x Fotos impresas - 8 x 10 / Matte'\n    }\n  },\n    {\n    json: {\n      value: '1x Fotos impresas - 8 x 10 / Matte'\n    }\n  }\n]"
      },
      "name": "Sample Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        230,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const rq = require('request-promise-native');\n\nlet values = [];\n\nfor (const item of items) {\n    values.push({ description: item.json.value })\n}\n\nawait rq({\n    method: 'POST',\n    url: 'https://webhook.site/8df892df-f741-431b-98dd-51377fc0d35d',\n    form: {\n      firstname: 'whatever',\n      lastname: 'asasas',\n      newitems: values,\n      //add all other required parameters\n    }\n})\n\nreturn [\n  {\n    json: {}\n  }\n]\n\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        450,
        300
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Sample Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Data": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

I allowed the external library as you said but I’m still getting this error message:

ERROR: Access denied to require ‘request-promise-native’

I also checked that I could send a json payload with the same parameters and it will work.

Ahhh, If you can send the payload as JSON, you do not need the function node. You can do it with the HTTP node. Just use a function node to create the body and then reference it in the HTTP node. It should look similar to the one below.

let values = [];

for (const item of items) {
    values.push({ description: item.json.value })
}

return [
  {
    json: {
      firstname: 'whatever',
      lastname: 'asasas',
      newitems: values,
    }
  }
]
Example workflow
{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "return [\n  {\n    json: {\n      value: '1x Fotos impresas - 8 x 10 / Matte'\n    }\n  },\n    {\n    json: {\n      value: '1x Fotos impresas - 8 x 10 / Matte'\n    }\n  }\n]"
      },
      "name": "Sample Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        510,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "let values = [];\n\nfor (const item of items) {\n    values.push({ description: item.json.value })\n}\n\nreturn [\n  {\n    json: {\n      firstname: 'whatever',\n      lastname: 'asasas',\n      newitems: values,\n    }\n  }\n]\n\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        780,
        300
      ],
      "notesInFlow": true,
      "notes": "Create body"
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "your url",
        "jsonParameters": true,
        "options": {},
        "bodyParametersJson": "={{ $node[\"Function\"].json }}"
      },
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1010,
        300
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Sample Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Data": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Yes, that really worked.

I just have one more request that I really need you help if you can help me to complete this workflow.

I have these results that I would like to modify. What I need is to remove the “1x” and the characters after “/ Matte - _GDP8794.jpg” and then after the first item add a + sign and separate all the next items with a comma.

Like this:

Fotos impresas 8 x 10 + Fotos impresas 6x8, Fotos impresas 5 x 7 and so on. Is it possible?

[
{
"pedido": [
"1x Fotos impresas - 8 x 10 / Matte - _GDP8794.jpg 1x Fotos impresas - 6 x 8 / Matte - _GDP8793.jpg 1x Fotos impresas - 5 x 7 / Matte - _GDP8792.jpg 1x Foto en formato digital - _GDP8790.jpg"
]
}
]

I appreciate all your help. Thanks!

Is it always going to be 1x? Or can this change? Where is this string coming from? Is it standardized somehow?

It is always 1x, it won’t change. The string is coming from a webhook of an email parser.

The function below should do it.

const pedido = items[0].json.pedido[0]

const fotos = pedido.split('1x');

const response = [];

for (let i = 1; i < fotos.length; i++) {
    if (fotos[i].split('/').length > 1) {
        response.push(fotos[i].split('/')[0])
    } else {
        response.push(fotos[i].split('-')[0])
    }
}

return [
  {
    json: {
      pedido: response.join('-')
    }
  }
]
Example workflow
{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        370,
        570
      ]
    },
    {
      "parameters": {
        "functionCode": "return [\n  {\n    json: {\n      pedido: ['1x Fotos impresas - 8 x 10 / Matte - _GDP8794.jpg 1x Fotos impresas - 6 x 8 / Matte - _GDP8793.jpg 1x Fotos impresas - 5 x 7 / Matte - _GDP8792.jpg 1x Foto en formato digital - _GDP8790.jpg']\n    }\n  }\n]"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        590,
        570
      ],
      "notesInFlow": true,
      "notes": "Mockup data"
    },
    {
      "parameters": {
        "functionCode": "\nconst pedido = items[0].json.pedido[0]\n\nconst fotos = pedido.split('1x');\n\nconst response = [];\n\nfor (let i = 1; i < fotos.length; i++) {\n    if (fotos[i].split('/').length > 1) {\n        response.push(fotos[i].split('/')[0])\n    } else {\n        response.push(fotos[i].split('-')[0])\n    }\n}\n\nreturn [\n  {\n    json: {\n      pedido: response.join('-')\n    }\n  }\n]"
      },
      "name": "Function1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        790,
        570
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "Function1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
1 Like

Excuse me if I’m asking too much Ricardo.

The issue I’m facing is that is only showing 1 of the results and I would I like to split all the results

“pedido”: [
“1x Fotos impresas - 8 x 10 / Matte - _GDP8794.jpg 1x Fotos impresas - 6 x 8 / Matte - _GDP8793.jpg 1x Fotos impresas - 5 x 7 / Matte - _GDP8792.jpg 1x Foto en formato digital - _GDP8790.jpg”
]

In this format:

Fotos impresas 8 x 10 + Fotos impresas 6x8, Fotos impresas 5 x 7, Foto en formato digital

Could it be easier using this format?

[
{
“value”: “1x Fotos impresas - 8 x 10 / Matte - _GDP8794”
},
{
“value”: " 1x Fotos impresas - 6 x 8 / Matte - _GDP8793"
},
{
“value”: " 1x Fotos impresas - 5 x 7 / Matte - _GDP8792"
},
{
“value”: " 1x Foto en formato digital - _GDP8790"
},
{
“value”: “”
}
]

I’m always happy to help.

The only thing that changed it’s how the data is returned. Check the example below.

const pedido = items[0].json.pedido[0]

const fotos = pedido.split('1x');

const response = [];

for (let i = 1; i < fotos.length; i++) {
    if (fotos[i].split('/').length > 1) {
        response.push(fotos[i].split('/')[0])
    } else {
        response.push(fotos[i].split('-')[0])
    }
}

  {
    json: {
      pedido: response.join('-')
    }
  }

return [
  ...response.map(pedido => ({ json: { value: pedido } }))
]
Example Workflow
{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        -20,
        380
      ]
    },
    {
      "parameters": {
        "functionCode": "return [\n  {\n    json: {\n      pedido: ['1x Fotos impresas - 8 x 10 / Matte - _GDP8794.jpg 1x Fotos impresas - 6 x 8 / Matte - _GDP8793.jpg 1x Fotos impresas - 5 x 7 / Matte - _GDP8792.jpg 1x Foto en formato digital - _GDP8790.jpg']\n    }\n  }\n]"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        220,
        480
      ],
      "notesInFlow": true,
      "notes": "Mockup data"
    },
    {
      "parameters": {
        "functionCode": "\nconst pedido = items[0].json.pedido[0]\n\nconst fotos = pedido.split('1x');\n\nconst response = [];\n\nfor (let i = 1; i < fotos.length; i++) {\n    if (fotos[i].split('/').length > 1) {\n        response.push(fotos[i].split('/')[0])\n    } else {\n        response.push(fotos[i].split('-')[0])\n    }\n}\n\n  {\n    json: {\n      pedido: response.join('-')\n    }\n  }\n\nreturn [\n  ...response.map(pedido => ({ json: { value: pedido } }))\n]"
      },
      "name": "Function1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        420,
        480
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "Function1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
1 Like

Thanks you very much, this worked perfectly.

2 Likes