{
  "id": "zlC4TinZxwU0uRNb",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "TPL",
  "tags": [],
  "nodes": [
    {
      "id": "babf989a-7fd3-4dd1-b451-f3c849722a07",
      "name": "OpenAI Enhancement",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2144,
        1440
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "chatgpt-4o-latest"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are an AI assistant that categorizes invoice items.\n\nTask:\n- Look at the Invoice-Items from the following TOON data:\n{{ $json.toon }}\n\n- Review all invoices and consider the appropriate categories. Assign exactly one category to each invoice item:\n\n- First, structure the result as a JSON array like this:\n[\n  { \"category\": \"web_development\" },\n  { \"category\": \"system_administration\" }\n]\n\n- Then, **convert this JSON array into TOON notation**.\n- Return **only the TOON notation**, do not include quotes, JSON braces, escaped characters, or explanations.\n"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7f537c67-d5b8-40e5-8212-844ea91c13a7",
      "name": "JSON to TOON",
      "type": "@custom-js/n8n-nodes-pdf-toolkit.jsonToToon",
      "position": [
        1984,
        1440
      ],
      "parameters": {
        "jsonData": "={{ $json }}"
      },
      "credentials": {
        "customJsApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "baf16dab-4b75-4c13-bae6-bb9bd59d802c",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        384,
        1456
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "4dd1d8ee-d620-4095-9d3b-5b962f7a17d1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -912,
        848
      ],
      "parameters": {
        "width": 1120,
        "height": 1776,
        "content": "# Categorizing invoices with TOON optimization\n\nHere is an example of how you can categorize all \nyour invoices in Airtable, from our [invoice creation example](https://n8n.io/workflows/9772-automatic-invoice-generation-and-email-with-airtable-and-customjs-pdf-generator/):\n\nTo save OpenAI tokens, we convert them into [TOON](https://github.com/toon-format/toon) and back again.\n\n### Setup \n-  This workflow uses the CustomJS JSON to TOON node from [CustomJS](https://www.customjs.space), which requires a self-hosted n8n instance and a CustomJS API key.\n- You will also need an API key for an Airtable table, which you can clone if you like. [Public Airtable Example](https://airtable.com/apphyDa3uYAq0VOMW/shrSe39NZYrqm4gtE)\n![Airtable Screenshot](https://www.beta.customjs.space/images/integration/n8n/InvoiceGeneratorWorkflow.png)\n\n### How it works \n1. **Manual Trigger**  \n   Run the workflow on demand\n\n2. **Load Data from Airtable**  \n   - Fetch ready invoices  \n   - Resolve related clients  \n   - Fetch and aggregate invoice items  \n\n3. **Prepare Structured Context**  \n   Group client, invoice, and invoice items into a single object\n\n4. **JSON \u2192 TOON**  \n   Convert structured data into token-efficient TOON\n\n5. **AI Categorization**  \n   - OpenAI receives TOON data  \n   - Assigns exactly one category per invoice  \n   - Returns only TOON (no JSON, no prose)\n\n6. **TOON \u2192 JSON**  \n   Convert AI output back into valid JSON\n\n7. **Persist Result**  \n   Update the invoice category field in Airtable\n\n@[youtube](QX6ffaf-uvA)"
      },
      "typeVersion": 1
    },
    {
      "id": "db143ee8-cab9-49af-97d3-a1b839499058",
      "name": "Get Ready Invoices",
      "type": "n8n-nodes-base.airtable",
      "position": [
        592,
        1456
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "apphyDa3uYAq0VOMW",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW",
          "cachedResultName": "Custom JS - Invoicing Template"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblW46vfkwOFQJLMs",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW/tblW46vfkwOFQJLMs",
          "cachedResultName": "Invoices"
        },
        "options": {},
        "operation": "search"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "cc0fabf3-3634-4799-acab-b50a9c0f1459",
      "name": "Get Clients",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1472,
        1440
      ],
      "parameters": {
        "id": "={{ $('Get Ready Invoices').item.json['Client ID'][0] }}",
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "apphyDa3uYAq0VOMW",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW",
          "cachedResultName": "Custom JS - Invoicing Template"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblQdiFVsZ9w3sahJ",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW/tblQdiFVsZ9w3sahJ",
          "cachedResultName": "Clients"
        },
        "options": {}
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "fc2f2761-8bc4-4151-b508-f0be45e40eae",
      "name": "Get Invoice Items",
      "type": "n8n-nodes-base.airtable",
      "position": [
        976,
        1568
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "apphyDa3uYAq0VOMW",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW",
          "cachedResultName": "Custom JS - Invoicing Template"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblASYLVpsnrUoKt5",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW/tblASYLVpsnrUoKt5",
          "cachedResultName": "Invoice-Items"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "=FIND(\"{{ $json.ID }}\", ARRAYJOIN({Invoice}))"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c4a6c493-dbee-4ef3-8c02-74a5609f2f4f",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1296,
        1568
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "items"
      },
      "typeVersion": 1
    },
    {
      "id": "9a8696bd-459d-49a9-b848-e68a0c0a6e9c",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        800,
        1456
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "3bbef89f-1f29-4cc9-9bbf-a2cb64022b37",
      "name": "Update record",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2720,
        1440
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "apphyDa3uYAq0VOMW",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW",
          "cachedResultName": "Custom JS - Invoicing Template"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblW46vfkwOFQJLMs",
          "cachedResultUrl": "https://airtable.com/apphyDa3uYAq0VOMW/tblW46vfkwOFQJLMs",
          "cachedResultName": "Invoices"
        },
        "columns": {
          "value": {
            "id": "={{ $('Get Ready Invoices').item.json.id }}",
            "Category": "={{ $json.json.category }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "ID",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Client ID",
              "type": "array",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Client ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Name",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Client Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Description (from Invoice-Items)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Description (from Invoice-Items)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Total",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoicedate",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Invoicedate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "Delayed",
                  "value": "Delayed"
                },
                {
                  "name": "Sent",
                  "value": "Sent"
                },
                {
                  "name": "Paid",
                  "value": "Paid"
                },
                {
                  "name": "Ready",
                  "value": "Ready"
                }
              ],
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Create Invoice (Airtable API)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Create Invoice (Airtable API)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Create Invoice (Get Parameters)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Create Invoice (Get Parameters)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Invoice-Items",
              "type": "array",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Invoice-Items",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Prices (from Invoice-Items)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Prices (from Invoice-Items)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Default Hourly Rate (from Client ID)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Default Hourly Rate (from Client ID)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total (from Invoice-Items)",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "Total (from Invoice-Items)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ClientAddressField1",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "ClientAddressField1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ClientAddressField2",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "ClientAddressField2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ClientTax",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "ClientTax",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "70a430e2-932f-41e3-a431-ce3b0e70ef0e",
      "name": "Map Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1136,
        1568
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a95b61df-c10d-43f1-b005-d6d84c6fec47",
              "name": "description",
              "type": "string",
              "value": "={{ $json.Description }}"
            },
            {
              "id": "f290e440-dd5b-46f9-a23b-be419443685b",
              "name": "quantity",
              "type": "string",
              "value": "={{ $json.Hours }}"
            },
            {
              "id": "09688f5f-0461-4c04-988a-2b92da3e595e",
              "name": "unitPrice",
              "type": "string",
              "value": "={{ $json['Custom Hourly Rate'] || $json['Default Hourly Rate'][0]}}"
            },
            {
              "id": "bcdadef7-0f5b-48a3-851b-f7fe5f401fa7",
              "name": "invoiceId",
              "type": "string",
              "value": "={{ $json.ID }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7689029a-47eb-4aa3-b296-cfe4ae9e0d7b",
      "name": "GroupClientAndInvoice",
      "type": "n8n-nodes-base.set",
      "position": [
        1776,
        1440
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "44c69f01-7e87-4fb9-8ab6-31fc966073a6",
              "name": "clientAndInvoiceData",
              "type": "object",
              "value": "={   \n  \"client\": {{ JSON.stringify($json) }},   \n  \"invoice\": {{ JSON.stringify($('Get Ready Invoices').item.json) }} \n}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "68528c43-c25e-4941-9e47-e12c65550214",
      "name": "TOON to JSON",
      "type": "@custom-js/n8n-nodes-pdf-toolkit.toonToJson",
      "position": [
        2432,
        1440
      ],
      "parameters": {
        "toonData": "={{ $json.output[0].content[0].text }}"
      },
      "credentials": {
        "customJsApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0a0c52fb-2716-4a13-a2a7-bf5457744edc",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        320,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 1296,
        "height": 416,
        "content": "## Collect invoice data from Airtable"
      },
      "typeVersion": 1
    },
    {
      "id": "f1346b90-3f73-4b36-a52a-06139c7ee7d0",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 416,
        "content": "## Categorizing Invoices"
      },
      "typeVersion": 1
    },
    {
      "id": "e3650253-bfd2-4307-af75-66015c69e265",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2672,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 416,
        "content": "## Save categories back to Airtable"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b0be609b-1d3d-4916-a554-d6c1827c73bd",
  "connections": {
    "Aggregate": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Fields": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Clients": {
      "main": [
        [
          {
            "node": "GroupClientAndInvoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JSON to TOON": {
      "main": [
        [
          {
            "node": "OpenAI Enhancement",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "TOON to JSON": {
      "main": [
        [
          {
            "node": "Update record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Get Clients",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Invoice Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Invoice Items": {
      "main": [
        [
          {
            "node": "Map Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Ready Invoices": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Enhancement": {
      "main": [
        [
          {
            "node": "TOON to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GroupClientAndInvoice": {
      "main": [
        [
          {
            "node": "JSON to TOON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Get Ready Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}