This page calls /api/routes?sheet=Stations and /api/routes?sheet=Routes on its own domain to read live data, and /api/suggest to accept public suggestions. Two Netlify Functions (routes.js read-only, suggest.js write-only into the review tabs) talk to the Google Sheets API as a service account — no Apps Script, no public link, credentials never reach the browser.
One-time setup:
client_email as Editor (Viewer isn't enough now — suggestions need to write into the review tabs).Stations, Routes (the live data — keep these as the only ones the site reads from) and Suggested Stations, Suggested Routes (where public suggestions land — never read by the site, only written to).GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_PRIVATE_KEY, and SPREADSHEET_ID. Redeploy.Routes sheet has one row per directed connection (From → To). This network's connections aren't all mirrored, so add both directions as separate rows if a new connection should work both ways.
Reviewing suggestions: open the Suggested Stations / Suggested Routes tabs, check each row, then copy the matching columns (they line up with the live tabs) into Stations / Routes and delete the reviewed row. Nothing a user submits goes live automatically. The form itself already checks both the live tabs and these review tabs before writing — someone submitting a station or route that already exists (or is already pending review) is told so directly and nothing duplicate gets added.