Build a job-listings aggregator
Collect Northwind-relevant postings via public job-board APIs into a sheet for the team.
Published Nov 20, 2025
Northwind keeps half an eye on the hiring market — partly to know what skills are in demand, partly because the studio sometimes refers contractors. Checking a job board by hand is the kind of task that gets skipped for weeks, then done in a frantic catch-up. A sheet that quietly fills itself is a far better habit.
This script queries a public job-board API for postings that match a search term and appends any it has not seen before to a Google Sheet. Because it tracks which job IDs are already logged, it can run on a schedule without ever duplicating a row — the sheet grows by exactly the new listings each time.
What you’ll need
- A Google Sheet for the listings. The first sheet’s column A must hold job IDs — the script reads it to know what it has already seen.
- A search term that matches the roles you care about — the example uses
apps script developer. - Nothing else. The Remotive jobs API used here is public and needs no key.
The script
// The roles to search for on the job board.
const SEARCH = 'apps script developer';
// The spreadsheet that collects the listings.
const JOBS_SHEET_ID = '1abcJobsId';
// How many of the most recent results to consider per run.
const MAX_RESULTS = 30;
/**
* Queries a public job-board API and appends any postings not already
* in the sheet, keyed by the job's unique ID.
*/
function aggregateListings() {
// 1. Ask the job board for postings matching the search term.
const res = UrlFetchApp.fetch(
'https://remotive.com/api/remote-jobs?search=' + encodeURIComponent(SEARCH),
{ muteHttpExceptions: true }
);
const data = JSON.parse(res.getContentText());
if (!data.jobs || !data.jobs.length) {
Logger.log('No jobs returned for search: ' + SEARCH);
return;
}
// 2. Read the job IDs already logged so we never add a duplicate.
const sheet = SpreadsheetApp.openById(JOBS_SHEET_ID).getSheets()[0];
const known = new Set(sheet.getRange('A2:A').getValues().flat());
// 3. Walk the newest results and append only the ones we have not seen.
let added = 0;
for (const j of data.jobs.slice(0, MAX_RESULTS)) {
if (known.has(j.id)) continue;
sheet.appendRow([
j.id,
j.title,
j.company_name,
j.candidate_required_location,
j.url,
new Date(),
]);
added++;
}
Logger.log('Added ' + added + ' new listings.');
}
How it works
aggregateListingscalls the Remotive jobs API with the search term URL-encoded, and parses the JSON response.- If the API returns no jobs it logs the search term and stops — useful when a term is too narrow or the board is having a quiet day.
- It reads column A of the sheet into a
Setof job IDs already logged. ASetgives fasthaslookups, which matters once the sheet is large. - It takes the newest
MAX_RESULTSpostings and, for each one, skips it if the ID is already known and otherwise appends a row with the ID, title, company, location, link, and the date it was found. - The found-date column lets you sort or filter the sheet by “what arrived this week”, and the ID in column A is what keeps the next run from duplicating it.
Example run
The sheet starts with two known listings. After a run, three new postings have been appended:
| id | title | company | location | url | found |
|---|---|---|---|---|---|
| 9001 | Apps Script Developer | Acme | Worldwide | remotive.com/… | (earlier) |
| 9007 | Automation Engineer | Globex | EU only | remotive.com/… | (earlier) |
| 9015 | Google Workspace Dev | Initech | UK | remotive.com/… | 2026-05-25 |
| 9016 | Sheets Automation Lead | Umbrella | Worldwide | remotive.com/… | 2026-05-25 |
| 9018 | Apps Script Contractor | Hooli | US/EU | remotive.com/… | 2026-05-25 |
Run it again an hour later and, if the board has nothing new, the log reads
Added 0 new listings and the sheet is untouched.
Trigger it
Run this on a schedule so the sheet stays current without anyone remembering:
- In the Apps Script editor open Triggers (the clock icon).
- Add Trigger → choose
aggregateListings, event source Time-driven, a Day timer (once a day is plenty for a job board). - Save and approve the authorisation prompt the first time.
Watch out for
- This depends on a third-party public API. If Remotive changes its response shape, rate-limits, or goes down, the script will fail — wrap it in a try/catch if a run failure should not break a daily report.
- The API returns its own ordering.
MAX_RESULTScaps how many results each run considers, so a burst of new postings beyond that cap is missed until the next run. Raise the cap if you search a busy term. - Postings are never removed. Once a job is logged it stays in the sheet even after it is filled or expires — add a cleanup step if staleness matters.
appendRowwrites one row per call. Adding 30 rows means 30 writes, which is fine here but slow for hundreds — batch withsetValuesif you scale up.UrlFetchApphas a daily quota. One search per day uses almost none of it; aggregating many search terms every few minutes can exhaust it.
Related
Sync calendar bookings with Calendly
Bridge Google Calendar and Calendly — Northwind bookings on either side appear on both.
Updated Jan 7, 2026
Connect to an air-quality and weather feed
Build a Northwind environmental dashboard — current London AQI plus 5-day forecast.
Updated Dec 30, 2025
Build a podcast and media stats tracker
Pull Northwind's podcast download numbers across platforms into a single sheet.
Updated Dec 10, 2025
Track real-estate listings for new matches
Monitor property feeds for Northwind office hunts — alert when a match appears.
Updated Nov 28, 2025
Translate columns with a translation API
Localise Northwind text in bulk without manual work — via Google Translate or DeepL.
Updated Nov 24, 2025