<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julian Reiter</title>
    <description>The latest articles on DEV Community by Julian Reiter (@julianreiter).</description>
    <link>https://dev.to/julianreiter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3889006%2F87d326cd-4523-4b1b-9ca4-253d725ba580.png</url>
      <title>DEV Community: Julian Reiter</title>
      <link>https://dev.to/julianreiter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julianreiter"/>
    <language>en</language>
    <item>
      <title>How to pull TikTok Ads reports straight into Google Sheets</title>
      <dc:creator>Julian Reiter</dc:creator>
      <pubDate>Mon, 20 Apr 2026 12:54:23 +0000</pubDate>
      <link>https://dev.to/julianreiter/how-to-pull-tiktok-ads-reports-straight-into-google-sheets-4opi</link>
      <guid>https://dev.to/julianreiter/how-to-pull-tiktok-ads-reports-straight-into-google-sheets-4opi</guid>
      <description>&lt;p&gt;The TikTok Ads Manager UI is fine for browsing, but if you run more&lt;br&gt;
than two campaigns you eventually need yesterday's spend in a Google&lt;br&gt;
Sheet &lt;em&gt;before&lt;/em&gt; you've made coffee. The platform's CSV export button&lt;br&gt;
is the only path the UI offers — and it's the same number of clicks&lt;br&gt;
every morning.&lt;/p&gt;

&lt;p&gt;This post walks through replacing that chore with a 30-line cron job,&lt;br&gt;
using the official TikTok Marketing API and a small open-source CLI&lt;br&gt;
called &lt;code&gt;aigen-reports&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you'll end up with
&lt;/h2&gt;

&lt;p&gt;Every morning at 02:00 your server pulls yesterday's campaign metrics&lt;br&gt;
for each of your TikTok ad accounts and appends them to a single Google&lt;br&gt;
Sheets tab. Total operator effort: zero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mon  03:00 → 12 rows (4 campaigns × 3 metrics) → Sheets:Daily!A2
Tue  03:00 → 12 rows                            → Sheets:Daily!A14
Wed  03:00 → 12 rows                            → Sheets:Daily!A26
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+ on the machine that will run cron.&lt;/li&gt;
&lt;li&gt;A TikTok for Business account with at least one advertiser (sandbox
works for testing — you can switch to a real account once approved).&lt;/li&gt;
&lt;li&gt;A Google Cloud project with the Sheets API enabled and a service
account JSON key file. (Optional — if you only want CSV output you can
skip this.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Create the Marketing API app
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://business-api.tiktok.com/portal/" rel="noopener noreferrer"&gt;https://business-api.tiktok.com/portal/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Become a developer&lt;/strong&gt; → fill in the basic profile.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create an App&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;App name: anything (this is what reviewers see — make it specific).&lt;/li&gt;
&lt;li&gt;App description: 2–3 sentences saying it's a read-only reporting
utility. Don't oversell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission category&lt;/strong&gt;: tick &lt;strong&gt;Reporting&lt;/strong&gt; only. No write
categories, no Ads Management, no Creative Management. The fewer
categories you ask for the faster the review.&lt;/li&gt;
&lt;li&gt;Set the redirect URI to &lt;code&gt;http://localhost:8765/callback&lt;/code&gt; for local
testing.&lt;/li&gt;
&lt;li&gt;Submit for review. While you wait, you can develop against the
sandbox.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 2 — Install the CLI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aigen-reports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 — Authorize once
&lt;/h2&gt;

&lt;p&gt;Set your app credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIKTOK_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIKTOK_APP_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TIKTOK_REDIRECT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8765/callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the auth flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aigen-reports auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser opens, you click &lt;strong&gt;Authorize&lt;/strong&gt;, the CLI exchanges the auth&lt;br&gt;
code for an access token and stores it encrypted at&lt;br&gt;
&lt;code&gt;~/.aigen-reports/credentials.json&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Token lifecycle.&lt;/strong&gt; TikTok access tokens expire (typically 24 h);&lt;br&gt;
the refresh token is good for ~1 year. The CLI refreshes&lt;br&gt;
automatically.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Step 4 — Pull a report
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aigen-reports pull &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertiser&lt;/span&gt; 7xxxxxxxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric&lt;/span&gt; spend,clicks,impressions,conversions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--breakdown&lt;/span&gt; campaign &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--range&lt;/span&gt; last-1d &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt; yesterday.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the entire reporting flow. The output is a plain CSV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;campaign&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;campaign&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;stat&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;spend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;impressions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;conversions&lt;/span&gt;
&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="k"&gt;xxxxxxxxx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;Evergreen&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;EN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ld"&gt;2026-04-16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;148.21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;18342&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;
&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="k"&gt;xxxxxxxxx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;Evergreen&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;DE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ld"&gt;2026-04-16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;212.04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;743&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;24011&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 — Pipe it to Google Sheets
&lt;/h2&gt;

&lt;p&gt;If you want the output in Sheets instead of CSV, install the optional&lt;br&gt;
extra and point &lt;code&gt;--out&lt;/code&gt; at a sheet ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'aigen-reports[gsheet]'&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/service-account.json

aigen-reports pull &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--advertiser&lt;/span&gt; 7xxxxxxxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric&lt;/span&gt; spend,clicks,conversions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--range&lt;/span&gt; last-1d &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt; gsheet:1AbCdEfGhIjKlMnOpQrStUvWxYz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Share the Sheet with the service account's email so it can append.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Schedule it
&lt;/h2&gt;

&lt;p&gt;Add the same command to cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# /etc/cron.d/aigen-reports
0 2 * * * www-data /usr/local/bin/aigen-reports pull \
  --advertiser 7xxxxxxxxxx \
  --metric spend,clicks,conversions \
  --range last-1d \
  --out gsheet:1AbCdEfGhIjKlMnOpQrStUvWxYz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. From here on you don't open the TikTok Ads Manager UI just to&lt;br&gt;
look at numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes I hit (and how to recover)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;code: 40105 access_token has expired&lt;/code&gt;&lt;/strong&gt; — the refresh failed, run
&lt;code&gt;aigen-reports auth&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;code: 40002 advertiser_id not in your business&lt;/code&gt;&lt;/strong&gt; — the operator
who authorized the app no longer has access to that advertiser. Re-
authorize with someone who does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit response&lt;/strong&gt; (&lt;code&gt;code: 50002&lt;/code&gt;) — TikTok caps requests per
app per minute. The CLI retries with exponential backoff; if you're
pulling 50+ accounts in parallel, stagger them across the hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is &lt;strong&gt;not&lt;/strong&gt; a dashboard. It writes a CSV, that's it.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;not&lt;/strong&gt; a campaign manager. No write scopes are requested.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;not&lt;/strong&gt; a hosted SaaS. It runs on your machine, your data
stays on your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need a dashboard, paid hosting, or campaign management, this&lt;br&gt;
isn't the tool you want. If you need yesterday's TikTok Ads numbers in&lt;br&gt;
a CSV at 02:01 every morning, it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CLI: &lt;a href="https://github.com/ArtificialIntelligentGeneration/aigen-reports" rel="noopener noreferrer"&gt;https://github.com/ArtificialIntelligentGeneration/aigen-reports&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Curated docs / SDK list: &lt;a href="https://github.com/ArtificialIntelligentGeneration/awesome-tiktok-ads-api" rel="noopener noreferrer"&gt;https://github.com/ArtificialIntelligentGeneration/awesome-tiktok-ads-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://aigen-agency.com" rel="noopener noreferrer"&gt;https://aigen-agency.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bug reports on GitHub Issues, everything else by email.&lt;/p&gt;

&lt;p&gt;— Julian&lt;/p&gt;

</description>
      <category>tiktok</category>
      <category>python</category>
      <category>googlesheets</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
