Skip to content
smartargs
EarnbackDocumentation

Getting started

Install

Requirements: Node.js 22.13 or newer (for node:sqlite) and pnpm to build.

git clone https://github.com/smartargs/earnback.git
cd earnback
pnpm install
pnpm build
npm link            # puts `earnback` on your PATH

earnback demo       # a made-up studio, to look around first

Your first project

cd ~/games/my-game
earnback init       # writes earnback.config.json and ignores .earnback/ in git
  1. Edit earnback.config.json: your app’s ids under apps, then enable the sources you use under connectors. Each source has a setup guide.
  2. Put the API keys where earnback init told you (your user config folder, never the project folder).
  3. Run earnback connect google once if you use AdMob, or Google Ads / GA4 with your own login.
  4. earnback doctor checks the config, the secrets and every connection.
  5. earnback syncs and opens the report.

Run earnback whenever you want fresh numbers. The first sync reads everything since start; later syncs re-read only the last few weeks, because platforms revise recent days.

Configuration

earnback.config.json lives in your project folder and holds no secrets, so you can commit it.

{
  "project": "my-game",
  "start": "2026-09-01",              // first day to sync
  "timeZone": "Europe/Vienna",        // your reporting day
  "currency": "EUR",
  "apps": [
    // every id the platforms use for this app: package name, App Store id, GA4 property, AdMob app ids
    { "id": "my-game", "name": "My Game", "keys": ["com.example.mygame", "123456789", "ca-app-pub-…~…"] }
  ],
  "secrets": {},                      // optional: secret name → file path, when a key lives elsewhere
  "channels": [
    // optional: extra rules mapping GA4 first-user source/medium to a channel, checked before the defaults
    { "source": "partner-network", "channel": "partner" }
  ],
  "funnel": [
    { "event": "first_open", "label": "Opened" },
    { "event": "tutorial_complete", "label": "Finished tutorial" }
  ],
  "connectors": {
    "google-ads": { "enabled": true, "customerId": "123-456-7890" },
    "meta": { "enabled": true, "adAccounts": ["act_1234567890"] },
    "ga4": { "enabled": true, "properties": ["123456789"] },
    "admob": { "enabled": true, "publisherId": "pub-1234567890123456" }
  },
  "plans": []
}

Errors name the field that is wrong (connectors.meta.adAccounts needs at least one entry), and earnback doctor tells you what is missing and where it goes.

Secrets

Each secret has a name (google-service-account.json, meta-token, …). Earnback looks for it in this order:

  1. the environment variable EARNBACK_<NAME> holding the content itself (e.g. EARNBACK_META_TOKEN), for CI;
  2. a path you map in the config’s secrets ("meta-token": "/path/to/meta-token.txt");
  3. a file of that name in the project’s secrets folder: %APPDATA%\earnback\<project>\ on Windows, ~/.config/earnback/<project>/ elsewhere (EARNBACK_HOME moves it).

Files Earnback writes (login tokens) get mode 600. earnback doctor warns when a secret sits inside a git repository that doesn’t ignore it.

Tests with budgets and pass marks

A plan describes a test you are running: which campaigns, what you expect to spend, the hard limit, the marks that decide whether it passed, and the review dates. The Overview shows it on top, the digest reports it every morning, and earnback check exits with code 1 when a budget limit or a stop mark is crossed (for cron or CI).

"plans": [
  {
    "id": "us-video-test",
    "name": "US video test",
    "note": "Which of the three videos brings players who finish the tutorial?",
    "from": "2026-11-06",
    "to": "2026-11-19",
    "campaigns": [
      { "source": "meta", "campaignId": "1202…", "expected": 150, "hardLimit": 150, "approved": 200 }
    ],
    "marks": [
      { "label": "Cost per player who opened", "metric": "costPerPlayer", "channels": ["meta"], "max": 0.5, "stopAbove": 1, "minPlayers": 20 },
      { "label": "Finished the tutorial", "metric": "stepRate", "step": "tutorial_complete", "channels": ["meta"], "min": 0.6, "minPlayers": 20 },
      { "label": "Back the next day", "metric": "retention", "day": 1, "channels": ["meta"], "min": 0.25, "minPlayers": 20 },
      { "label": "Watched 3 seconds", "metric": "hookRate", "channels": ["meta"], "min": 0.25, "minImpressions": 1000 }
    ],
    "reviews": [{ "date": "2026-11-09", "note": "First look: which video gets taps?" }]
  }
]

Marks: costPerPlayer, stepRate, retention, earnedBack, hookRate, holdRate, tapRate. Each waits for minPlayers or minImpressions before it judges (“too early” until then). Definitions are in Metrics.

Several projects

Every folder with an earnback.config.json is a project. earnback init registers it in your workspace (workspace.json in the Earnback config folder); earnback projects add registers an existing one.

earnback projects                  # list (* marks the default)
earnback projects add ~/games/other
earnback projects default my-game
earnback -p other                  # sync and open another project from anywhere
earnback --all                     # sync every project and open the portfolio page

Inside a project folder, commands run on that project; elsewhere they run on the default project. With --all, sync, open, digest and check run on every registered project, and open builds a portfolio report with a project switcher.

Commands

Command Does
earnback sync, then open the report
earnback sync [--full] [--source id] fetch new data; --full re-reads everything from start
earnback open [--no-sync] [--no-open] build the report (and open it)
earnback digest [--send] [--force] build today’s digest; deliver it; deliver again today
earnback check evaluate plans; exit 1 on a budget or stop alert
earnback init [name] create the config here and register the project
earnback projects [add|remove|default] manage registered projects
earnback connect <google|tiktok|telegram|webhook> guided login or setup, then a test call
earnback doctor check the config, secrets and every connection
earnback schedule [--time HH:MM] print how to send the digest every day
earnback demo open a report with made-up data

Global options: -p, --project <name>, --all, -h, -v.

Where things live

What Where
Config (commit it) <project>/earnback.config.json
Data and report (git-ignored) <project>/.earnback/data.sqlite, <project>/.earnback/report.html
Secrets %APPDATA%\earnback\<project>\ or ~/.config/earnback/<project>/
Registered projects, digest settings <earnback home>/workspace.json
Digest secrets (bot token, webhook URL) <earnback home>/_workspace/
Portfolio report <earnback home>/portfolio.html