Reports, cost and export
How do I get my figures into Google Sheets?
With a short Apps Script that fetches your daily rows per channel and campaign through the read API every morning and refills a sheet. You keep the read key in the script properties, not in the code and not in a formula. The read API comes with Growth, Scale and the agency plan.
Can I pull my figures into a Google Sheets spreadsheet that updates itself every day?
A formula in Google Sheets cannot fetch your figures on its own: the read API expects the key in the request header, and no formula sets one. A short script in Apps Script does it instead, the scripting environment that comes with every Google spreadsheet. Every morning it fetches the daily rows of your account and writes them to a sheet called “Metrunio”. Pivot tables and charts built on that sheet then show the new figures.
How to set it up
- 01In the portal, create a read key under “Exports & AI connections” in “API access”, ideally one just for this spreadsheet. It is shown in full only once.
- 02Open the spreadsheet and choose “Extensions > Apps Script”. Replace the contents of Code.gs with the template below and save.
- 03Open “Project Settings” on the left and add the property METRUNIO_READ_KEY under “Script Properties”, with your key as its value.
- 04Select the function installDailyTrigger at the top and click “Run”. The first time, Google asks for your consent: the script wants to edit spreadsheets, fetch an external address and run daily, even when you are not signed in. If Google says the app has not been verified: it is your own script, and “Advanced” lets you continue.
- 05The sheet “Metrunio” is then in place, and the trigger refills it every morning between 6 and 7 am.
The template
// Metrunio: daily rows per channel and campaign in the sheet "Metrunio".
// Enter your read key in the project settings under "Script Properties"
// as METRUNIO_READ_KEY, never here in the code.
const API_URL = 'https://api.metrunio.com/v1/data/daily';
const SHEET_NAME = 'Metrunio';
const COLUMNS = [
['day', 'Day'],
['channelName', 'Channel'],
['campaignName', 'Campaign'],
['attributedConversions', 'Attributed conversions'],
['attributedRevenue', 'Attributed revenue'],
['postViewConversions', 'Post-view conversions'],
['unattributedConversions', 'Unattributed conversions'],
['unattributedRevenue', 'Unattributed revenue'],
['clicks', 'Clicks'],
['impressions', 'Impressions'],
['viewableImpressions', 'Viewable impressions'],
['cost', 'Cost'],
];
// Fetches the figures and rewrites the sheet. The trigger calls this every morning.
function updateMetrunio() {
const key = PropertiesService.getScriptProperties().getProperty('METRUNIO_READ_KEY');
if (!key) throw new Error('The script property METRUNIO_READ_KEY is missing.');
const response = UrlFetchApp.fetch(API_URL, {
headers: { Authorization: 'Bearer ' + key, 'Accept-Language': 'en' },
muteHttpExceptions: true,
});
const body = response.getContentText();
if (response.getResponseCode() !== 200) {
throw new Error('Metrunio ' + response.getResponseCode() + ': ' + errorMessage(body));
}
const values = [COLUMNS.map((column) => column[1])];
for (const row of JSON.parse(body).data) {
values.push(COLUMNS.map((column) => cell(row[column[0]])));
}
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName(SHEET_NAME) || spreadsheet.insertSheet(SHEET_NAME);
sheet.clearContents();
sheet.getRange(1, 1, values.length, COLUMNS.length).setValues(values);
}
// Empty cost stays empty: no cost recorded, which is not the same as zero.
// A name starting with =, +, - or @ stays text and does not become a formula.
function cell(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string' && /^[=+@-]/.test(value)) return "'" + value;
return value;
}
// The API's message, in the language from Accept-Language.
function errorMessage(body) {
try {
const error = JSON.parse(body).error;
return [error.message].concat(error.resolution || []).join(' ');
} catch (parseError) {
return body.slice(0, 300);
}
}
// Run once: creates the daily run between 6 and 7 am and fills the sheet right away.
function installDailyTrigger() {
for (const trigger of ScriptApp.getProjectTriggers()) {
if (trigger.getHandlerFunction() === 'updateMetrunio') ScriptApp.deleteTrigger(trigger);
}
ScriptApp.newTrigger('updateMetrunio').timeBased().everyDays(1).atHour(6).create();
updateMetrunio();
}What the sheet contains
One row per day, channel and campaign for the last 30 days up to and including today, counted in your account’s time zone. Today is complete in the sheet only after the next morning’s run. Attributed conversions and attributed revenue follow your account’s default model, like the reports; clicks and impressions are measured.
If a cost cell stays empty, no cost was recorded for that row. That is not the same as zero. Unattributed conversions sit in the row without channel and campaign, and the sums per channel are the same as under “Channels & attribution”.
Adjusting it
- Columns: the COLUMNS list sets the order and the headings. Remove what you do not need. The API also delivers the IDs of channel and campaign (channelId, campaignId) and the attributed acquisition value (attributedSetValue). That is the value you gave a conversion yourself, not revenue.
- Period: append, for example, ?from=2026-01-01 to API_URL. Without an end, the period runs up to today. A query returns at most 50,000 rows; if it needs more, the API refuses instead of dropping rows, and a shorter period helps.
- Model: with ?model=linear the API uses a fixed model instead of your account’s default model. You join two settings with &, for example ?from=2026-01-01&model=linear.
If a run fails, for example because the key was revoked or has expired, Google sends you an email with Metrunio’s message. The sheet then keeps the state of the last successful run.
Why not IMPORTDATA?
IMPORTDATA fetches an address but sends no header. The key would therefore have to be in the address, and so in a formula that anyone you show the spreadsheet to can read. The read API only accepts the key in the request header anyway.
Caution
The key is in no cell and no formula. Anyone who may edit the spreadsheet can, however, open the script and read it in the script properties. So only let people edit the spreadsheet who may know the key, and revoke it under “Exports & AI connections” when someone leaves the team.
Note
The figures go from Metrunio to Google because you want to view them there, and Google processes them under the terms of your Google account. The rows are sums per day, channel and campaign and contain no identifiers of individual people. If you revoke the key, the script fetches nothing more; what is already in the spreadsheet stays there until you delete it.
Last reviewed on 15 Oct 2026
In the customer portal
Also asked
- Connect Google Sheets
- Metrunio figures in a Google spreadsheet
- Is there a Google Sheets template?
- How does a spreadsheet update itself with my figures?
- Does IMPORTDATA work?
- Where do I keep the key in Apps Script?
Related articles
- How do I get my figures into Power BI or Excel?
Can I load my figures into Power BI or Excel and refresh them there?
With a Power Query query that loads your daily rows per channel and campaign through the read API; Power BI and Excel both understand the same query. You create the read key as a parameter, and the query sends it in the request header, never in the address. The read API comes with Growth, Scale and the agency plan.
- How do I export data?
Can I recalculate the numbers myself?
Under “Exports & AI connections” you download CSV files for conversions, customer journeys and attribution by channel, with the same values the reports show, so you can recalculate them yourself. Conversions and journeys appear individually, row by row, and every row states the period, the environment and the time of the data.
- How do I connect my own AI tool?
Is there an MCP server for Metrunio?
Through Metrunio’s MCP server. It exposes the same read tools as the portal chat. Claude and ChatGPT sign in via OAuth: you choose in the portal which account they may read. Other tools use an API key of scope “read”, the same one a script would use for /v1/data.
- How do I rotate or revoke a key?
What do I do if a key was published by accident?
You renew the write key and the server key in “Setup”, the server key on the “Server Events API” tab, where you can also revoke it. Read keys are revoked under “Exports & AI connections”, in the “API access” section, where you also create a new one. A new key is shown in full only once, and the old one stops working immediately.
- When does a day start in the reports?
Which time zone do reports, the chat and the APIs use?
A day runs from midnight to midnight in your account’s time zone; the default is Europe/Berlin, and you can change it under “Settings”. “The last 7 days” are seven whole calendar days including today, and a custom period includes its last day in full. The same rule applies to the overview, reports, costs, the chat, the MCP server and the read API.
- How do the attribution models calculate?
Which models exist and how do they distribute the share?
There are seven models: six distribute a conversion by a fixed, disclosed rule, and the seventh reads the rule from your own paths. If a model finds matching touchpoints, it distributes the conversion’s full value across them; if it finds none, the conversion stays “unattributed” and is shown separately. Attributed and unattributed together add up to all conversions it calculates.