{
  "id": "3smw3ch1i7q3sJ2A",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Agent Cost Calculator",
  "tags": [],
  "nodes": [
    {
      "id": "e3bc701e-9a6e-4fb9-849a-c35da44dcf28",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3520,
        1968
      ],
      "parameters": {
        "color": 4,
        "width": 450,
        "height": 716,
        "content": "## AI Agent Cost Calculator\n\nScans your AI agent workflows every hour, extracts token usage per model, calculates the estimated USD cost, and stores a receipt in an n8n Data Table.\n\nSupports **Claude, OpenAI, Gemini, Perplexity** and any future model.\n\n## How to setup\n\n**Step 1**: Create a Data Table called `execution_receipts` with columns:\n`workflowid` (text) \u00b7 `executionid` (text) \u00b7 `receipt` (text) \u00b7 `created_at` (text) \u00b7 `units` (number)\n\n**Step 2**: Configure the 3 marked nodes:\n\n- **1.1** \u2192 Set your n8n API credential\n- **1.2** \u2192 Set your n8n API credential\n- **1.3** \u2192 Select your Data Table\n\n\n**Step 3**: Tag your AI agent workflows with `agent`\n\n\n**Step 4**: Activate the workflow\n\nTo add pricing for a new model, add one line to `MODEL_RATES` in the code node.\n\nSelf-hosted? Set `N8N_ENABLED_MODULES=data-table`."
      },
      "typeVersion": 1
    },
    {
      "id": "0662b756-f02e-4a25-96c3-26830089e3b6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4048,
        2240
      ],
      "parameters": {
        "color": 2,
        "width": 630,
        "height": 240,
        "content": "## Collect Workflows\nFetches all workflows tagged `agent` and loops through them one by one."
      },
      "typeVersion": 1
    },
    {
      "id": "909f1174-32f5-4935-b683-e63ed07393f5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4512,
        2656
      ],
      "parameters": {
        "color": 6,
        "width": 528,
        "height": 196,
        "content": "## Extract Token Usage\nPulls all executions per workflow, parses AI model token data, and calculates USD cost using the `MODEL_RATES` pricing table."
      },
      "typeVersion": 1
    },
    {
      "id": "ff2f390d-9c9a-434a-a09e-26b4fac7d00c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4688,
        2112
      ],
      "parameters": {
        "color": 3,
        "width": 820,
        "height": 218,
        "content": "## Store Receipts\nFilters out empty results and saves each receipt to the `execution_receipts` Data Table."
      },
      "typeVersion": 1
    },
    {
      "id": "7aef630f-a314-4baa-a833-d7d191f68587",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5056,
        2656
      ],
      "parameters": {
        "color": 5,
        "width": 428,
        "height": 198,
        "content": "\ud83e\uddfe **itonomy.nl** \u00b7 Built by Ben van Steenbergen\n\n*This workflow costs less to run than the agents it monitors.*\n\nLike it? Let me know! [Follow me on LinkedIn](https://www.linkedin.com/in/ben-van-steenbergen-9006b37/)"
      },
      "typeVersion": 1
    },
    {
      "id": "83a2f90e-0fb0-4b40-b730-22f982ec7d2f",
      "name": "Hourly Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        4080,
        2368
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtMinute": 10
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "15036d2b-7209-422c-8d6a-a972f102567a",
      "name": "1.1 Get AI Agent Workflows",
      "type": "n8n-nodes-base.n8n",
      "position": [
        4256,
        2368
      ],
      "parameters": {
        "filters": {
          "tags": "agent",
          "activeWorkflows": false,
          "excludePinnedData": true
        },
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9c99328d-3866-436b-b351-423a9070626c",
      "name": "Loop Workflows",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4464,
        2368
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "0e9749fc-c8f3-479b-99d7-e3d0ca8ed395",
      "name": "1.2 Get Workflow Executions",
      "type": "n8n-nodes-base.n8n",
      "onError": "continueErrorOutput",
      "position": [
        4704,
        2480
      ],
      "parameters": {
        "filters": {
          "workflowId": {
            "__rl": true,
            "mode": "id",
            "value": "={{ $json.id }}"
          }
        },
        "options": {
          "activeWorkflows": true
        },
        "resource": "execution",
        "returnAll": true,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "46b55b0e-fbc7-4870-92f4-36f5fad06cac",
      "name": "Extract Token Usage & Cost",
      "type": "n8n-nodes-base.code",
      "position": [
        4912,
        2480
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// AI Agent Cost Calculator \u2014 Token Usage & Cost Extractor\n// Supports: Anthropic Claude, OpenAI, Google Gemini, Perplexity\n// To add a new model, add an entry to MODEL_RATES below.\n\nif (Object.keys($json).length === 0) return {};\n\nconst usage = $json.data?.resultData?.runData;\nconst workflowId = $json.workflowId;\nconst runId = $json.id;\n\nif (!usage) return {};\n\n// \u2500\u2500 Model rates (USD per 1M tokens) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Add new models here as providers release them.\n// Format: \"model-id\": { input: $/1M, output: $/1M }\nconst MODEL_RATES = {\n  // \u2500\u2500 Anthropic Claude \u2500\u2500\n  \"claude-opus-4-7\":            { input: 5.00,  output: 25.00 },\n  \"claude-opus-4-6\":            { input: 5.00,  output: 25.00 },\n  \"claude-sonnet-4-6\":          { input: 3.00,  output: 15.00 },\n  \"claude-sonnet-4-5-20250929\": { input: 3.00,  output: 15.00 },\n  \"claude-sonnet-4-5\":          { input: 3.00,  output: 15.00 },\n  \"claude-haiku-4-5-20251001\":  { input: 1.00,  output: 5.00  },\n  \"claude-haiku-4-5\":           { input: 1.00,  output: 5.00  },\n  \"claude-opus-4-5-20251101\":   { input: 5.00,  output: 25.00 },\n  \"claude-opus-4-5\":            { input: 5.00,  output: 25.00 },\n  \"claude-opus-4-1-20250805\":   { input: 15.00, output: 75.00 },\n  \"claude-opus-4-1\":            { input: 15.00, output: 75.00 },\n  \"claude-opus-4-20250514\":     { input: 15.00, output: 75.00 },\n  \"claude-opus-4-0\":            { input: 15.00, output: 75.00 },\n  \"claude-sonnet-4-20250514\":   { input: 3.00,  output: 15.00 },\n  \"claude-sonnet-4-0\":          { input: 3.00,  output: 15.00 },\n  \"claude-3-7-sonnet-20250219\": { input: 3.00,  output: 15.00 },\n  \"claude-3-5-sonnet-20241022\": { input: 3.00,  output: 15.00 },\n  \"claude-3-5-sonnet-20240620\": { input: 3.00,  output: 15.00 },\n  \"claude-3-5-haiku-20241022\":  { input: 0.80,  output: 4.00  },\n  \"claude-3-opus-20240229\":     { input: 15.00, output: 75.00 },\n  \"claude-3-sonnet-20240229\":   { input: 3.00,  output: 15.00 },\n  \"claude-3-haiku-20240307\":    { input: 0.25,  output: 1.25  },\n\n  // \u2500\u2500 OpenAI \u2500\u2500\n  \"gpt-4.1\":       { input: 2.00,  output: 8.00  },\n  \"gpt-4.1-mini\":  { input: 0.40,  output: 1.60  },\n  \"gpt-4.1-nano\":  { input: 0.10,  output: 0.40  },\n  \"gpt-4o\":        { input: 2.50,  output: 10.00 },\n  \"gpt-4o-mini\":   { input: 0.15,  output: 0.60  },\n  \"gpt-4-turbo\":   { input: 5.00,  output: 15.00 },\n  \"gpt-4\":         { input: 30.00, output: 60.00 },\n  \"gpt-3.5-turbo\": { input: 0.50,  output: 1.50  },\n  \"o4-mini\":       { input: 1.10,  output: 4.40  },\n  \"o3\":            { input: 2.00,  output: 8.00  },\n  \"o3-mini\":       { input: 1.10,  output: 4.40  },\n  \"o3-pro\":        { input: 20.00, output: 80.00 },\n  \"o1\":            { input: 15.00, output: 60.00 },\n  \"o1-mini\":       { input: 1.10,  output: 4.40  },\n\n  // \u2500\u2500 Google Gemini \u2500\u2500\n  \"gemini-2.5-pro\":        { input: 1.25,  output: 10.00 },\n  \"gemini-2.5-flash\":      { input: 0.30,  output: 2.50  },\n  \"gemini-2.5-flash-lite\": { input: 0.10,  output: 0.40  },\n  \"gemini-2.0-flash\":      { input: 0.15,  output: 0.60  },\n  \"gemini-2.0-flash-lite\": { input: 0.075, output: 0.30  },\n  \"gemini-1.5-pro\":        { input: 1.25,  output: 5.00  },\n  \"gemini-1.5-flash\":      { input: 0.075, output: 0.30  },\n  \"gemini-1.5-flash-8b\":   { input: 0.0375,output: 0.15  },\n\n  // \u2500\u2500 Perplexity \u2500\u2500\n  \"sonar\":               { input: 1.00, output: 1.00  },\n  \"sonar-pro\":           { input: 3.00, output: 15.00 },\n  \"sonar-reasoning\":     { input: 1.00, output: 5.00  },\n  \"sonar-reasoning-pro\": { input: 2.00, output: 8.00  },\n};\n\nfunction calculateCost(model, promptTokens, completionTokens) {\n  const rates = MODEL_RATES[model];\n  if (!rates) return null;\n  const inputCost = (promptTokens / 1000000) * rates.input;\n  const outputCost = (completionTokens / 1000000) * rates.output;\n  return Math.round((inputCost + outputCost) * 1000000) / 1000000;\n}\n\nconst receipt = {};\nreceipt[workflowId] = {};\nreceipt[workflowId][runId.toString()] = {};\n\nfor (const key in usage) {\n  if (!Object.prototype.hasOwnProperty.call(usage, key)) continue;\n  const steps = usage[key];\n\n  for (const i in steps) {\n    try {\n      const element = steps[i];\n      const elementData = element.data;\n      const elementDataOverride = element.inputOverride;\n\n      if (!elementData) continue;\n\n      let model = undefined;\n      let tokenData = undefined;\n\n      // LLM nodes: Anthropic, OpenAI, Gemini (ai_languageModel output)\n      if (Object.prototype.hasOwnProperty.call(elementData, 'ai_languageModel')) {\n        const override = elementDataOverride?.['ai_languageModel']?.[0]?.[0]?.json;\n        const output = elementData['ai_languageModel']?.[0]?.[0]?.json;\n\n        model = override?.options?.model\n          ?? override?.options?.model_name\n          ?? override?.model\n          ?? output?.model\n          ?? undefined;\n\n        tokenData = output?.tokenUsageEstimate ?? output?.tokenUsage ?? undefined;\n\n      // HTTP/API nodes: Perplexity and other REST-based LLMs\n      } else if (Object.prototype.hasOwnProperty.call(elementData, 'main')) {\n        const output = elementData['main']?.[0]?.[0]?.json;\n        model = output?.model ?? undefined;\n        tokenData = output?.usage ?? undefined;\n      }\n\n      if (model && tokenData) {\n        const pt = tokenData.promptTokens ?? tokenData.prompt_tokens ?? 0;\n        const ct = tokenData.completionTokens ?? tokenData.completion_tokens ?? 0;\n        const tt = tokenData.totalTokens ?? tokenData.total_tokens ?? (pt + ct);\n        const cost = calculateCost(model, pt, ct);\n\n        const existing = receipt[workflowId][runId.toString()][model];\n        if (existing && typeof existing === 'object' && existing.promptTokens !== undefined) {\n          // Aggregate tokens when same model appears multiple times in one execution\n          existing.promptTokens += pt;\n          existing.completionTokens += ct;\n          existing.totalTokens += tt;\n          if (cost !== null) existing.costUSD = (existing.costUSD ?? 0) + cost;\n        } else {\n          receipt[workflowId][runId.toString()][model] = {\n            promptTokens: pt,\n            completionTokens: ct,\n            totalTokens: tt,\n            ...(cost !== null ? { costUSD: cost } : {})\n          };\n        }\n        receipt[workflowId][runId.toString()]['createdAt'] = $json.startedAt ?? \"\";\n      }\n    } catch (e) {\n      // Skip steps that cannot be parsed\n      continue;\n    }\n  }\n}\n\nif (Object.keys(receipt[workflowId][runId.toString()]).length === 0) return {};\n\nreturn receipt;\n"
      },
      "executeOnce": false,
      "typeVersion": 2
    },
    {
      "id": "2f64c928-1c3f-41ff-af5e-cae9d6324c44",
      "name": "Loop Receipts",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4736,
        2224
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "1cef6528-b49a-4a1d-bfa5-f4fd98cbfae0",
      "name": "Has Usage Data?",
      "type": "n8n-nodes-base.if",
      "position": [
        4944,
        2240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "96b37396-1c38-423d-9d9e-32f1a99fa391",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.keys().length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "79cd6d09-997e-416f-8f3a-951feec99f7d",
      "name": "1.3 Save Receipt to Data Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        5280,
        2224
      ],
      "parameters": {
        "columns": {
          "value": {
            "units": "1",
            "receipt": "={{ $json[$json.keys().first()][$json[$json.keys().first()].keys().first()].toJsonString() }}",
            "created_at": "={{ $json[$json.keys().first()][$json[$json.keys().first()].keys().first()]['createdAt'] ?? DateTime.now().format('yyyy-MM-dd HH:mm:ss') }}",
            "workflowid": "={{ $json.keys().first() }}",
            "executionid": "={{ $json[$json.keys().first()].keys().first() }}"
          },
          "schema": [
            {
              "id": "workflowid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "workflowid",
              "defaultMatch": false
            },
            {
              "id": "executionid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "executionid",
              "defaultMatch": false
            },
            {
              "id": "receipt",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "receipt",
              "defaultMatch": false
            },
            {
              "id": "created_at",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "created_at",
              "defaultMatch": false
            },
            {
              "id": "units",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "units",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "execution_receipts"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "6445f510-a0fa-49a9-9a02-b94dff0d016d",
  "connections": {
    "Loop Receipts": {
      "main": [
        [],
        [
          {
            "node": "Has Usage Data?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hourly Trigger": {
      "main": [
        [
          {
            "node": "1.1 Get AI Agent Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Workflows": {
      "main": [
        [
          {
            "node": "Loop Receipts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "1.2 Get Workflow Executions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Usage Data?": {
      "main": [
        [
          {
            "node": "1.3 Save Receipt to Data Table",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Loop Receipts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1.1 Get AI Agent Workflows": {
      "main": [
        [
          {
            "node": "Loop Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Token Usage & Cost": {
      "main": [
        [
          {
            "node": "Loop Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1.2 Get Workflow Executions": {
      "main": [
        [
          {
            "node": "Extract Token Usage & Cost",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1.3 Save Receipt to Data Table": {
      "main": [
        [
          {
            "node": "Loop Receipts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}