{
  "name": "Lumeor | Stripe payouts to QuickBooks journal entries",
  "nodes": [
    {
      "parameters": {
        "content": "## Stripe payouts to QuickBooks Online journal entries\n\n**What it does**\n1. Daily, reads your mapping table and post ledger from Google Sheets.\n2. Lists recent paid Stripe payouts and drops any already in the ledger (duplicate guard first).\n3. Pulls each payout's balance transactions and groups charges by description.\n4. Claude assigns each group to one of YOUR income accounts (enum, so it cannot invent accounts).\n5. Code builds a balanced journal entry: bank debit, fees, refunds, adjustments and income. It blocks unbalanced entries, unmapped types, low-confidence mappings, closed periods and payouts with more than 100 transactions.\n6. Posts to QuickBooks Online and writes the QuickBooks ID to the ledger. Everything else lands in a review tab.\n\n**Setup**\n1. Credentials: Stripe API, QuickBooks Online OAuth2, Google Sheets, Anthropic.\n2. Sheet tabs: \"Mapping\" (source_type, qbo_account_id, qbo_account_name, notes) with rows for bank, stripe_fee, refund, payment_refund, adjustment and one or more income rows; \"Post ledger\" and \"Review\" with the headers in their nodes. Replace REPLACE_WITH_SHEET_ID.\n3. In **Config**, set qboRealmId and closingDate. Test against a sandbox company first.\n\nModel: claude-opus-5-5 at low effort. claude-haiku-5-5 is a cheaper option for this kind of classification.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/automate-quickbooks-data-entry/",
        "height": 720,
        "width": 520
      },
      "id": "ef576745-3808-44aa-ac02-cc8a6c92db2a",
      "name": "Template notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -620,
        -260
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      },
      "id": "ead3c066-f9fc-4fc6-ae28-e595182528c2",
      "name": "Daily 6am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "40bb1b9e-38f0-4cf2-a04b-b3e8e3ee6b3a",
              "name": "qboBaseUrl",
              "value": "https://quickbooks.api.intuit.com",
              "type": "string"
            },
            {
              "id": "6d626d8e-727e-45a8-a119-d2b39deb4ddb",
              "name": "qboRealmId",
              "value": "REPLACE_WITH_QBO_COMPANY_ID",
              "type": "string"
            },
            {
              "id": "c737e576-e105-4904-875b-8d3ba92433d5",
              "name": "homeCurrency",
              "value": "usd",
              "type": "string"
            },
            {
              "id": "7cd40825-9406-45b3-ab7e-f8e34fa16590",
              "name": "closingDate",
              "value": "2026-09-30",
              "type": "string"
            },
            {
              "id": "e15116f5-f6b3-4014-bb31-3edd9d3a79f5",
              "name": "lookbackDays",
              "value": 7,
              "type": "number"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {
          "stripBinary": false
        }
      },
      "id": "6fcba71c-a50c-4db2-b509-58dc5a60df27",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Mapping"
        },
        "options": {}
      },
      "id": "7bd866c0-d4e1-48fe-a440-b7617a025aef",
      "name": "Read mapping",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        440,
        0
      ],
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Post ledger"
        },
        "options": {}
      },
      "id": "a791f96e-1a9b-4825-a3dd-c0c7cbba0152",
      "name": "Read post ledger",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        660,
        0
      ],
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.stripe.com/v1/payouts",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "stripeApi",
        "sendQuery": true,
        "specifyQuery": "keypair",
        "queryParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "paid"
            },
            {
              "name": "limit",
              "value": "50"
            },
            {
              "name": "arrival_date[gte]",
              "value": "={{ Math.floor(Date.now() / 1000) - $('Config').first().json.lookbackDays * 86400 }}"
            }
          ]
        },
        "options": {}
      },
      "id": "e773b907-6bdc-4347-bd32-6328bdbeba0d",
      "name": "Stripe: recent payouts",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Duplicate guard: drop payouts already in the post ledger, before anything else happens.\nconst posted = new Set($('Read post ledger').all().map((i) => String(i.json.source_id || '')).filter(Boolean));\nconst payouts = ($input.first().json.data || []).filter((p) => p.status === 'paid');\nreturn payouts\n  .filter((p) => !posted.has(p.id))\n  .map((p) => ({ json: { payoutId: p.id, amountCents: p.amount, currency: p.currency, arrivalDate: new Date(p.arrival_date * 1000).toISOString().slice(0, 10) }, pairedItem: { item: 0 } }));\n"
      },
      "id": "c13b115c-f9fc-4d33-bdb6-722bc39920e0",
      "name": "Split new payouts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.stripe.com/v1/balance_transactions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "stripeApi",
        "sendQuery": true,
        "specifyQuery": "keypair",
        "queryParameters": {
          "parameters": [
            {
              "name": "payout",
              "value": "={{ $json.payoutId }}"
            },
            {
              "name": "limit",
              "value": "100"
            }
          ]
        },
        "options": {}
      },
      "id": "b67690ee-4808-4a86-a367-ce90b7ab0325",
      "name": "Stripe: payout transactions",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Summarizes each payout's balance transactions and asks Claude to assign charge descriptions to an income account.\n// Claude can only pick account IDs that exist in your mapping table (enum), so it cannot invent accounts.\nconst mapping = $('Read mapping').all().map((i) => i.json).filter((r) => r.source_type && r.qbo_account_id);\nconst income = mapping.filter((r) => String(r.source_type).toLowerCase() === 'income');\nconst out = [];\nconst items = $input.all();\nfor (let i = 0; i < items.length; i++) {\n  const payout = $('Split new payouts').itemMatching(i).json;\n  const resp = items[i].json;\n  const txns = (resp.data || []).filter((t) => t.type !== 'payout');\n  const groups = {};\n  for (const t of txns) {\n    if (!(t.type === 'charge' || t.type === 'payment') || t.amount <= 0) continue;\n    const key = String(t.description || 'No description').replace(/\\d{3,}/g, '#').trim().slice(0, 80);\n    groups[key] = groups[key] || { group_id: 'G' + (Object.keys(groups).length + 1), description: key, count: 0, cents: 0 };\n    groups[key].count++;\n    groups[key].cents += t.amount;\n  }\n  const groupList = Object.values(groups);\n  const incomeIds = income.map((r) => String(r.qbo_account_id));\n  const schema = {\n    type: 'object', additionalProperties: false, required: ['assignments'],\n    properties: {\n      assignments: {\n        type: 'array',\n        items: {\n          type: 'object', additionalProperties: false, required: ['group_id', 'account_id', 'confidence'],\n          properties: {\n            group_id: { type: 'string', enum: groupList.length ? groupList.map((g) => g.group_id) : ['none'] },\n            account_id: { type: 'string', enum: incomeIds.length ? incomeIds : ['none'] },\n            confidence: { type: 'string', enum: ['high', 'medium', 'low'] },\n          },\n        },\n      },\n    },\n  };\n  const prompt = 'Assign each group of Stripe charges to the best income account. Use only the accounts listed. ' +\n    'Use low confidence when the description does not clearly fit one account.\\n\\nIncome accounts:\\n' +\n    income.map((r) => '- ' + r.qbo_account_id + ': ' + (r.qbo_account_name || '') + (r.notes ? ' (' + r.notes + ')' : '')).join('\\n') +\n    '\\n\\nCharge groups:\\n' + groupList.map((g) => '- ' + g.group_id + ': \"' + g.description + '\" x' + g.count).join('\\n');\n  out.push({\n    json: {\n      ...payout,\n      hasMore: Boolean(resp.has_more),\n      txns: txns.map((t) => ({ id: t.id, type: t.type, amount: t.amount, fee: t.fee, net: t.net, currency: t.currency, description: t.description || '' })),\n      groups: groupList,\n      claudeRequest: {\n        model: 'claude-opus-5-5',\n        max_tokens: 2000,\n        messages: [{ role: 'user', content: prompt }],\n        output_config: { effort: 'low', format: { type: 'json_schema', schema } },\n      },\n    },\n    pairedItem: { item: i },\n  });\n}\nreturn out;\n"
      },
      "id": "e69eae3f-95aa-4669-b0b2-25ca7b53bf21",
      "name": "Summarize payout",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "specifyHeaders": "keypair",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.claudeRequest) }}",
        "options": {
          "timeout": 180000
        }
      },
      "id": "37f0086b-a20d-4b91-96a4-800fe42cb22c",
      "name": "Claude: map income accounts",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Builds a balanced journal entry per payout and blocks anything that is not provably right.\n\n// Reads a Messages API response. Thinking blocks may come first, so find the text block.\nfunction readClaude(resp) {\n  if (!resp || resp.error) {\n    const msg = resp && resp.error ? (resp.error.message || JSON.stringify(resp.error)) : 'empty response';\n    return { ok: false, reason: 'api_error: ' + String(msg).slice(0, 300) };\n  }\n  if (resp.stop_reason === 'refusal') {\n    const cat = resp.stop_details && resp.stop_details.category ? ' (' + resp.stop_details.category + ')' : '';\n    return { ok: false, reason: 'model_refusal' + cat };\n  }\n  if (resp.stop_reason === 'max_tokens') return { ok: false, reason: 'max_tokens: output was cut off, raise max_tokens' };\n  const block = (resp.content || []).find((b) => b.type === 'text');\n  if (!block) return { ok: false, reason: 'no_text_block_in_response' };\n  try {\n    return { ok: true, data: JSON.parse(block.text) };\n  } catch (e) {\n    return { ok: false, reason: 'invalid_json: ' + e.message };\n  }\n}\n\nconst cfg = $('Config').first().json;\nconst mapping = $('Read mapping').all().map((i) => i.json).filter((r) => r.source_type && r.qbo_account_id);\nconst byType = {};\nfor (const r of mapping) byType[String(r.source_type).toLowerCase()] = String(r.qbo_account_id);\nconst incomeIds = new Set(mapping.filter((r) => String(r.source_type).toLowerCase() === 'income').map((r) => String(r.qbo_account_id)));\n\nconst out = [];\nconst items = $input.all();\nfor (let i = 0; i < items.length; i++) {\n  const p = $('Summarize payout').itemMatching(i).json;\n  const r = readClaude(items[i].json);\n  const reasons = [];\n  if (p.hasMore) reasons.push('more_than_100_transactions_add_pagination');\n  if (String(p.currency).toLowerCase() !== String(cfg.homeCurrency).toLowerCase()) reasons.push('currency_' + p.currency);\n  if (cfg.closingDate && p.arrivalDate <= cfg.closingDate) reasons.push('in_closed_period_' + cfg.closingDate);\n  if (!byType.bank) reasons.push('mapping_missing_bank');\n  if (!byType.stripe_fee) reasons.push('mapping_missing_stripe_fee');\n\n  const assign = {};\n  if (!r.ok) reasons.push(r.reason);\n  else for (const a of r.data.assignments) {\n    if (a.confidence === 'low') reasons.push('low_confidence_income_mapping_' + a.group_id);\n    if (incomeIds.has(a.account_id)) assign[a.group_id] = a.account_id;\n  }\n\n  // Lines in cents, keyed by account and side.\n  const lines = {};\n  const add = (account, side, cents, desc) => {\n    if (!cents) return;\n    const k = account + '|' + side;\n    lines[k] = lines[k] || { account, side, cents: 0, desc };\n    lines[k].cents += cents;\n  };\n  add(byType.bank, 'Debit', p.amountCents, 'Stripe payout ' + p.payoutId);\n  const groupOf = {};\n  for (const g of p.groups) groupOf[g.description] = g.group_id;\n  let netSum = 0;\n  for (const t of p.txns) {\n    netSum += t.net;\n    if (t.fee) add(byType.stripe_fee, t.fee > 0 ? 'Debit' : 'Credit', Math.abs(t.fee), 'Stripe fees');\n    let account;\n    if ((t.type === 'charge' || t.type === 'payment') && t.amount > 0) {\n      const key = String(t.description || 'No description').replace(/\\d{3,}/g, '#').trim().slice(0, 80);\n      account = assign[groupOf[key]];\n      if (!account) { reasons.push('no_income_account_for_' + t.id); continue; }\n    } else {\n      account = byType[t.type];\n      if (!account) { reasons.push('unmapped_type_' + t.type); continue; }\n    }\n    add(account, t.amount > 0 ? 'Credit' : 'Debit', Math.abs(t.amount), t.type);\n  }\n  if (netSum !== p.amountCents) reasons.push('transactions_net_' + netSum + '_does_not_equal_payout_' + p.amountCents);\n\n  const list = Object.values(lines);\n  const debits = list.filter((l) => l.side === 'Debit').reduce((s, l) => s + l.cents, 0);\n  const credits = list.filter((l) => l.side === 'Credit').reduce((s, l) => s + l.cents, 0);\n  if (debits !== credits) reasons.push('unbalanced_debits_' + debits + '_credits_' + credits);\n  if (list.some((l) => /REPLACE_WITH/.test(l.account || ''))) reasons.push('mapping_has_placeholders');\n\n  const uniq = [...new Set(reasons)];\n  out.push({\n    json: {\n      payoutId: p.payoutId,\n      arrivalDate: p.arrivalDate,\n      amount: (p.amountCents / 100).toFixed(2),\n      route: uniq.length ? 'review' : 'post',\n      reason: uniq.join('; '),\n      journalEntry: {\n        TxnDate: p.arrivalDate,\n        PrivateNote: 'Stripe payout ' + p.payoutId + ' (posted by n8n)',\n        Line: list.map((l) => ({\n          Description: l.desc,\n          Amount: Number((l.cents / 100).toFixed(2)),\n          DetailType: 'JournalEntryLineDetail',\n          JournalEntryLineDetail: { PostingType: l.side, AccountRef: { value: l.account } },\n        })),\n      },\n    },\n    pairedItem: { item: i },\n  });\n}\nreturn out;\n"
      },
      "id": "e4d82276-9aa9-4175-982b-af9869d20796",
      "name": "Build journal entry",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        0
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "1e8519d3-43fb-4058-b04e-873f63cc3e33",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "post",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "post"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "2661ffc6-51ef-4aa0-863f-d9540a2f03a1",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "review",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "review"
            }
          ]
        },
        "options": {}
      },
      "id": "c5fe2c00-1363-4a74-bb94-4b64692a6de4",
      "name": "Post or review",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2200,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Config').first().json.qboBaseUrl }}/v3/company/{{ $('Config').first().json.qboRealmId }}/journalentry",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "quickBooksOAuth2Api",
        "sendQuery": true,
        "specifyQuery": "keypair",
        "queryParameters": {
          "parameters": [
            {
              "name": "minorversion",
              "value": "75"
            }
          ]
        },
        "sendHeaders": true,
        "specifyHeaders": "keypair",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.journalEntry) }}",
        "options": {}
      },
      "id": "733719bc-222c-4fa4-9349-554d5f48da46",
      "name": "QBO: create journal entry",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        -120
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Post ledger"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "source_id": "={{ $('Build journal entry').item.json.payoutId }}",
            "qbo_id": "={{ $json.JournalEntry.Id }}",
            "qbo_type": "JournalEntry",
            "amount": "={{ $('Build journal entry').item.json.amount }}",
            "txn_date": "={{ $('Build journal entry').item.json.arrivalDate }}",
            "posted_at": "={{ $now.toISO() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "source_id",
              "displayName": "source_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "qbo_id",
              "displayName": "qbo_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "qbo_type",
              "displayName": "qbo_type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "amount",
              "displayName": "amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "txn_date",
              "displayName": "txn_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "posted_at",
              "displayName": "posted_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "abfc0370-ab82-42e5-bfc4-2697f436ef1f",
      "name": "Append to post ledger",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2640,
        -120
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Review"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "logged_at": "={{ $now.toISO() }}",
            "source_id": "={{ $json.payoutId || $('Build journal entry').item.json.payoutId }}",
            "amount": "={{ $json.amount || $('Build journal entry').item.json.amount }}",
            "reason": "={{ $json.reason || ($json.error && ($json.error.message || JSON.stringify($json.error))) }}",
            "draft_entry": "={{ JSON.stringify($json.journalEntry || $('Build journal entry').item.json.journalEntry) }}",
            "execution_id": "={{ $execution.id }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "logged_at",
              "displayName": "logged_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "source_id",
              "displayName": "source_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "amount",
              "displayName": "amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "reason",
              "displayName": "reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "draft_entry",
              "displayName": "draft_entry",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "execution_id",
              "displayName": "execution_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "ab758fc3-19e9-4d82-bc17-8afb04dc1398",
      "name": "Append to review tab",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2640,
        140
      ]
    }
  ],
  "connections": {
    "Daily 6am": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Read mapping",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read mapping": {
      "main": [
        [
          {
            "node": "Read post ledger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read post ledger": {
      "main": [
        [
          {
            "node": "Stripe: recent payouts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stripe: recent payouts": {
      "main": [
        [
          {
            "node": "Split new payouts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split new payouts": {
      "main": [
        [
          {
            "node": "Stripe: payout transactions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stripe: payout transactions": {
      "main": [
        [
          {
            "node": "Summarize payout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize payout": {
      "main": [
        [
          {
            "node": "Claude: map income accounts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: map income accounts": {
      "main": [
        [
          {
            "node": "Build journal entry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build journal entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build journal entry": {
      "main": [
        [
          {
            "node": "Post or review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post or review": {
      "main": [
        [
          {
            "node": "QBO: create journal entry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append to review tab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QBO: create journal entry": {
      "main": [
        [
          {
            "node": "Append to post ledger",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append to review tab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
