When I downloaded my species list from GBIF, I received a gobbledygook list of scientific names. I might have been able to spot one familiar name, carreta carreta. The rest was Greek. Or Latin. Or Biologican.
Enter iNaturalist, the Facebook of the nature world. Each species has a profile, followed by lots of people posting pictures and sightings of the species. I wanted iNaturalist to give me a common name for each of my species so I can understand that scientific language.
Luckily, iNaturalist is generous and offers its data through APIs. I wasn’t able to find a way to access the API through Excel, so I ported my spreadsheet over to Google Sheets. And by ported, I mean pasted. If you simply open an Excel file in Google Sheets, you don’t get the magic menu option.
So for API’s sake, I created a new Google Sheet and copy+pasted my whole Excel spreadsheet into it. Voila! the magic menu option appeared: Extensions.
Extensions holds a few fun things. Ahh. I remember AppSheet and Timesheet. Thanks, Google. Such good free tools. I’ll have to write posts about those. But this time, I’m using Apps Script.

Although Apps Script opens in a new tab, you can run code that interacts with the spreadsheet in another browser tab. It’s Chrome. They know each other by tab.
Now about that code. You’ve probably heard that ChatGPT can’t be trusted with facts (yet). I’ll up the ante. ChatGPT can’t be trusted with facts (ever). Artificial intelligence is artificial by definition. You may have also heard that ChatGPT is quite handy with code. I believe ChatGPT has gotten a big head about this. It frequently delivers non-working code with promises like “complete and working” or “final tested working script”. It’s disheartening to think AI gets its verbiage from human conversations which throw around empty claims like flower petals.
That being said, I am eternally grateful to ChatGPT for this code. Yes, it took quite a bit of trial and error, during which I’m proud to say I refrained from typing, “I JUST TOLD YOU IT WAS THIS NOT THAT!” I suspect I have to communicate nicely so I don’t send it down some dark troll hole. I’d say I spent an hour getting it to work, but that was way faster than the 2-3 days I would have spent piecing this script together on my own.

Here’s a quick overview of the script:
- Line 4 tells Chrome to go to the URL where iNaturalist shares free data and offer it the scientificName of my species.
- Line 11 says to collect the taxonID associated with the scientificName that was offered, and append it to iNaturalist’s generic URL for every species.
- Line 28 says to write that engineered URL into the cell next to the scientificName that started this request.
- Line 25 says to repeat this process for as long as there are “names”, which was defined in Line 23 as the first column of my spreadsheet beginning at the second row.
To run the script, we click the Run button at the top. However, there are two functions in this script (Line 1 and Line 20). Apps Script defaults to the first function. I might have lost two full days trying to figure out why the script claimed to run correctly and nothing showed up in my spreadsheet. ChatGPT did me a huge favor by pointing out that I must run the second function fillINatURLs(), not the first one. You have to click the dropdown arrow next to Run to choose a function.
Okay, we’re in. The script went knocking on iNaturalist’s API door and concocted a profile page URL for all 500 of my species. Not only that, but the script WROTE those URLs into the column I prepared for them on my spreadsheet. Aren’t they beautiful? I feel like a recognizable species name is a blink away.

Now I will use those URL’s to get common names for these species. After not much ado, here’s the script that worked:
function getCommonNameFromINatURL(url) {
if (!url || !url.includes("/taxa/")) return "";
const taxonId = url.split("/taxa/")[1];
const apiUrl = "https://api.inaturalist.org/v1/taxa/" + taxonId;
try {
const response = UrlFetchApp.fetch(apiUrl);
const data = JSON.parse(response.getContentText());
if (data.results && data.results.length > 0) {
const taxon = data.results[0];
if (taxon.preferred_common_name) {
return taxon.preferred_common_name;
} else {
return "(no common name)";
}
} else {
return "Not found";
}
} catch (e) {
return "Error";
}
}
function fillCommonNames() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
const urls = sheet.getRange(2, 2, lastRow - 1).getValues(); // Column B
for (let i = 0; i < urls.length; i++) {
const url = urls[i][0];
const commonName = getCommonNameFromINatURL(url);
sheet.getRange(i + 2, 3).setValue(commonName); // Column C
Utilities.sleep(300); // Throttle to avoid rate limiting
}
}
Excellent! At my script’s command, Chrome went knocking on iNaturalist’s API door again and collected a bag of recognizable species names. Hi friends!

Now that we can understand the language, notice how my top 20 species are all birds. This is a list of 527 species of plants and animals, sorted by number of reported sightings. Do you think we have more ospreys than lizards or pine trees? What can you deduce from this bird sighting frequency?
Now that I can see their names, I can also see that I won’t be able to match most of my existing photos to the proper species. I can spot an osprey a mile away (with a telescope), but I have no idea what a Carolina chickadee looks like. Since it’s in the top 20, chance are I’ve taken its picture. If not, I’ll ask my birder friends. Meanwhile, to launch my Nature page and know what I’m looking for, I’m going to use copyright-shared pictures of the 500 species as placeholders. Will I search for shared pictures one species at a time? Not in this day and age. Let’s send Apps Script out for photo URLs.