Connect Harlo to Google Sheets
A Google Sheet with tabs of your invoices, loads and payments that refreshes itself every hour. No add-on to install.
You'll need
- A Harlo admin login
- A Google account
- Make a Harlo API key. In Harlo, open Settings, API keys, choose Create API key, name it Google Sheets, and copy it. You'll paste it in a moment; Harlo shows it only once.
- Open the script editor.In a new Google Sheet, choose Extensions, Apps Script. Delete what's there and paste this:To pull other data, editApps Script
// Harlo -> Google Sheets. Each tab below is refilled from the Harlo API. const HARLO_API = "https://harlo-api-dev.pages.dev/v1"; const TABS = { Invoices: "invoices", Loads: "loads", Payments: "payments" }; function harloRefreshAll() { for (const [tab, resource] of Object.entries(TABS)) harloImport_(tab, resource); } function harloImport_(tab, resource) { const key = PropertiesService.getScriptProperties().getProperty("HARLO_API_KEY"); if (!key) throw new Error("Add HARLO_API_KEY under Project Settings > Script properties."); const rows = []; let cursor = null, waits = 0; for (let page = 0; page < 200; page++) { let url = HARLO_API + "/" + resource + "?limit=100"; if (cursor) url += "&cursor=" + encodeURIComponent(cursor); const res = UrlFetchApp.fetch(url, { headers: { Authorization: "Bearer " + key }, muteHttpExceptions: true }); const code = res.getResponseCode(); if (code === 429 && waits++ < 5) { const h = res.getHeaders(); Utilities.sleep(Number(h["Retry-After"] || h["retry-after"] || 10) * 1000); page--; continue; } const body = JSON.parse(res.getContentText()); if (code !== 200) throw new Error(body.error ? body.error.message : "HTTP " + code); body.data.forEach((r) => rows.push(flatten_(r, "", {}))); if (!body.has_more) break; cursor = body.next_cursor; } const cols = []; rows.forEach((r) => Object.keys(r).forEach((k) => { if (cols.indexOf(k) < 0) cols.push(k); })); const values = [cols].concat(rows.map((r) => cols.map((c) => cell_(r[c])))); const ss = SpreadsheetApp.getActive(); const sheet = ss.getSheetByName(tab) || ss.insertSheet(tab); sheet.clearContents(); if (cols.length) sheet.getRange(1, 1, values.length, cols.length).setValues(values); sheet.setFrozenRows(1); } // Nested fields become columns like "customer.name"; lists stay as JSON. function flatten_(v, prefix, out) { if (v !== null && typeof v === "object" && !Array.isArray(v)) { Object.keys(v).forEach((k) => flatten_(v[k], prefix ? prefix + "." + k : k, out)); } else { out[prefix] = Array.isArray(v) ? JSON.stringify(v) : v; } return out; } // Text that looks like a formula is kept as text. function cell_(v) { if (v === null || v === undefined) return ""; if (typeof v === "string" && /^[=+\-@]/.test(v) && isNaN(Number(v))) return "'" + v; return v; }TABS: the tab name on the left, any list from the reference on the right (customers,drivers,equipment...). - Store the key. In Apps Script, open Project Settings (the gear), scroll to Script properties, choose Add script property, and enter
HARLO_API_KEYwith your key as the value. Keeping it here, not in the sheet, means people you share the sheet with can't read it. - Run it once. Back in the editor, pick
harloRefreshAllin the function menu and choose Run. Google asks you to allow the script to reach an outside service and edit this sheet; allow it. Your tabs fill in. - Make it refresh. Open Triggers (the clock), Add Trigger, choose
harloRefreshAll, event source Time-driven, Hour timer, Every hour. Save.
Build on top, not inside
Each refresh rewrites its tabs. Put your formulas, pivot tables and charts on other tabs that point at these ones, and they'll stay up to date.
If something's off
- “Add HARLO_API_KEY under Project Settings”
- The script property is missing or misspelled. It must be HARLO_API_KEY exactly.
- A tab stops at a few thousand rows
- The script reads up to 20,000 records per tab per run. For more, narrow it, for example invoices?status=open, or use Excel or Power BI.
- “Exceeded maximum execution time”
- Google stops a script after 6 minutes. Pull fewer tabs per trigger, or filter with created_since.