Use the API from Google Sheets
Import one line per book — royalties, ad spend, net profit — into a Google Sheet with a short Apps Script, with your API key kept out of the sheet.
This script fills a sheet named Books with one row per book for the last 30 days: title, ASIN, format, marketplace, units, royalties, ad spend, net profit and TACOS.
1. Create a key
Settings → API → Create a key. Name it "Google Sheets" and copy it. How keys work: Authentication and API keys.
2. Store the key in the script's properties
- In your Google Sheet, open Extensions → Apps Script.
- Click Project Settings (the gear), then Add script property.
- Property:
TRUEROYALTIES_API_KEY. Value: your key. Save.
Never paste the key into a cell: anyone the sheet is shared with could read it. Never put it in the URL either — the API refuses the request.
3. Paste the script
In the Editor, replace the content of Code.gs with:
const API_URL = "https://author.trueroyalties.com/api/v1/reports/books";
function importBooks() {
const key = PropertiesService.getScriptProperties().getProperty(
"TRUEROYALTIES_API_KEY",
);
if (!key) {
throw new Error("Add TRUEROYALTIES_API_KEY in Project Settings.");
}
const rows = [];
let cursor = null;
let currency = "";
while (true) {
let url = API_URL + "?range=last_30_days&limit=100";
if (cursor) url += "&cursor=" + encodeURIComponent(cursor);
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: "Bearer " + key },
muteHttpExceptions: true,
});
const status = response.getResponseCode();
// Limit reached: wait as long as the API asks, then retry the same page.
if (status === 429) {
const headers = response.getHeaders();
const wait = Number(headers["Retry-After"] || headers["retry-after"] || 1);
Utilities.sleep(wait * 1000);
continue;
}
const body = JSON.parse(response.getContentText());
if (status !== 200) {
throw new Error(body.code + ": " + body.detail + " (" + body.request_id + ")");
}
currency = body.currency;
for (const line of body.data) {
rows.push([
line.book.title,
line.book.asin,
line.book.format,
line.book.marketplace,
line.units,
line.revenue,
line.ad_spend,
line.net_profit,
line.tacos === null ? "" : line.tacos,
]);
}
if (!body.has_more) break;
cursor = body.next_cursor;
}
const spreadsheet = SpreadsheetApp.getActive();
const sheet =
spreadsheet.getSheetByName("Books") || spreadsheet.insertSheet("Books");
sheet.clearContents();
sheet.getRange(1, 1, 1, 9).setValues([[
"Title", "ASIN", "Format", "Marketplace", "Units",
"Royalties (" + currency + ")", "Ad spend", "Net profit", "TACOS",
]]);
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, 9).setValues(rows);
}
}4. Run it
- Select
importBooksin the toolbar and click Run. - The first time, Google asks you to allow the script to reach an external service and to edit your sheet. Accept.
- Go back to the sheet: the Books tab is filled.
To refresh it on its own, open Triggers (the clock), Add Trigger, choose
importBooks and a Time-driven day timer.
Change what you import
Edit the query in the url line:
| You want | Add or change |
|---|---|
| Another period | range=last_month, or start_date=2026-01-01&end_date=2026-06-30 |
| Every sale since the start | range=all_time |
| Another currency | ¤cy=EUR |
| Another order | &sort=revenue (also ad_spend, units, title; net_profit by default) |
Good to know:
- Books with no activity in the period are listed too, with zeros, like the Books page.
- Each page of 100 books is one request. The script waits by itself when it meets the 60-requests-per-minute limit, so a large catalogue simply takes a little longer.
- The script does not check
meta.truncated. If it istrue, some rows could not be read: import a shorter period.