paperroute_transparency

How PaperRoute earnings are computed

This document is the public, authoritative description of the payout math. The constants here are the same constants the code runs (apps/web/src/lib/earnings-constants.ts); if they ever disagree, that's a bug — tell us.

The formula

Every minute your device is actively used (not idle, not locked), part of your wallpaper is actually visible, and a sponsor or house creative is actually composited on it, you earn:

cents = RATE × exposed_megapixels × (1 minute / 60)

where:

Constant Value Meaning
RATE_CENTS_PER_ACTIVE_EXPOSED_MPX_HOUR 2 Cents (your 50% share) per active-exposed-megapixel-hour
DAILY_ACTIVE_MINUTES_CAP 480 Earning stops after 8 active hours per device per UTC day
EXPOSED_MPX_CAP 16.5888 Exposed area is clamped to two 4K monitors (2 × 3840 × 2160 px)
CLICK_MULTIPLIER 50 A sponsor click is worth 50 impression-minutes
CLICK_CREDIT_CENTS 2 Flat click credit: round(50 × 2 / 60) — once per ad per device per day
PAYOUT_MINIMUM_CENTS 1000 $10.00 minimum balance to request a payout
MAX_DEVICES_PER_ACCOUNT 5 Devices per account

Worked example: a 2560×1440 monitor (3.7 mpx) fully exposed for an active hour earns 2 × 3.7 = 7.4 cents… but wallpaper is rarely fully exposed. A typical 30%-exposed desktop earns about 2.2 cents/hour — PaperRoute is beer money, not rent money, and we'd rather you know that up front.

Rounding

Per-minute amounts are accumulated as exact fractions and rounded once per device per day, half-down (1.5 → 1). No per-bucket rounding, no cumulative rounding drift.

What "active" and "exposed" mean

  • Active: keyboard/mouse input within the last 120 seconds, session not locked. Idle and locked minutes earn nothing.
  • Ad displayed: minutes only accrue while a creative (sponsor or operator-funded house ad) is composited into the wallpaper. Running the app with no ad to show — for example when there's no inventory — earns nothing and doesn't consume the daily cap.
  • Exposed: the part of the desktop wallpaper not covered by windows, measured as window rectangles only. Fully see-through overlays (screen dimmers, click-through HUDs) don't count as covering — the wallpaper behind them is visible. We never read window titles, process names, or screen contents — see the data dictionary.

Clicks

Opening the current sponsor from the tray menu (or following an ad's short link from your paired device) credits CLICK_CREDIT_CENTS once per ad per device per UTC day. Anonymous QR scans redirect but credit nothing.

The 50/50 split

Ledger amounts are your half of the ad revenue. In the MVP, the day-one inventory is operator-funded house and affiliate ads — PaperRoute itself is the sponsor, paying out of pocket at the rate above while real sponsor inventory is built. The rate may change; changes are announced and never applied retroactively.

The advertiser side

Sponsors prepay into a wallet (Stripe Checkout) and ads serve only while that wallet is positive — nothing runs on credit:

  • For every cent a publisher is credited from a sponsor's ad, the sponsor's wallet is debited (ADVERTISER_GROSS_MULTIPLIER = 2). That's the 50/50 split from the money-in side: the publisher keeps their half, and the platform keeps the other half — Stripe's processing fees come out of the platform's half, never the publisher's.
  • A click debits CLICK_DEBIT_CENTS = 4 (2× the publisher's CLICK_CREDIT_CENTS), once per ad per device per UTC day.
  • The wallet is an append-only ledger, same as publisher earnings: the balance is always a SUM of deposits, spend, refunds, and adjustments. Nothing is ever overwritten.
  • At $0 the sponsor's ads pause automatically and resume on the next deposit. An ad that reaches its total budget stays paused until the sponsor changes it, even after a top-up.
  • An ad may also set an optional daily spend cap. Once a UTC day's attributed spend (impressions + clicks) reaches the cap, no further debits are attributed to the sponsor for the rest of that day; billing resumes at 00:00 UTC. The ad itself may keep displaying past the cap — publishers are still credited for that exposure, and the platform absorbs the difference, the same way it does for house ads.
  • Unspent budget is refundable on request. Refunds are reviewed by an operator and returned to the original payment method — only the un-spent remainder is ever refundable.
  • House ads (PaperRoute's own inventory) debit no one; publisher credits for them are operator-funded as described above.

The dwell report

Each ad's detail page reframes raw spend as genuinely-visible desktop time. An exposed-screen-hour is one megapixel of the creative kept visible on an active desktop for one hour (exposed-megapixel-minutes ÷ 60) — the same exposed-area × time that drives the split above, so cost per exposed-screen-hour is the true unit price of attention. The report also shows the average exposed megapixels across serving devices and cost per click, over a rolling 30-day window (the telemetry retention horizon). Every figure sits next to a platform-wide inventory average for the same window. That benchmark is aggregate scalars only — the average across all sponsor ads — never another advertiser's identity or itemised numbers.

Payouts

  • Publisher payouts run on Stripe Connect Express: you connect a payout account once from the dashboard, and Stripe handles identity and banking — PaperRoute never sees or stores those details.
  • You can request a payout once your available balance is at least PAYOUT_MINIMUM_CENTS ($10.00).
  • Requests move through a reviewed queue: requested → approved → paid (or rejected). The amount is held — excluded from your available balance — from the moment you request, so the same cent can never be requested twice.
  • On "paid", a Stripe Transfer moves the money to your connected account. The transfer is idempotent per request: a retry after a failure re-sends the same transfer instead of paying twice.

Caps exist to keep the system honest

The daily cap, area clamp, and device limit are enforced server-side so that an inflated or dishonest client cannot mint money. Anomalous devices are flagged and their earnings held for review rather than silently paid.


Data dictionary — everything PaperRoute collects

This is the complete list of fields the desktop client ever transmits. It matches the zod contracts in packages/shared/src/index.ts field-for-field; if the code and this page ever disagree, that's a bug — tell us.

What is never collected, by design: window titles, process names, application identities, screenshots, screen contents, keystrokes, URLs, clipboard, or anything readable on your screen. The client computes geometry locally and transmits only the aggregates below.

Telemetry (POST /api/v1/telemetry, one batch per minute)

Contract: TelemetryBatch

Field Type Purpose
deviceId string Which paired device is reporting
buckets[].bucketStart integer (unix seconds, minute-aligned) Which minute the measurement covers
buckets[].exposedMpx number (megapixels) How much wallpaper was visible that minute, averaged, summed across monitors
buckets[].active boolean Whether you were present (any input in the last 120s, session unlocked)
buckets[].adId string | null Which creative was displayed; null = none composited (those minutes never earn)

Request headers: x-device-id, x-timestamp (unix seconds), x-nonce (random, replay protection), x-signature (HMAC). None carry personal data.

Pairing (POST /api/v1/pair, once per device)

Contract: PairRequest

Field Type Purpose
code string The one-time code you generated in the dashboard
deviceName string A label you see in your dashboard (defaults to the Windows computer name — rename it if that bothers you)
monitors[].widthPx, monitors[].heightPx integers Monitor resolutions — the ceiling for plausible exposure (anti-fraud) and the area clamp

Sponsor interactions

  • GET /api/v1/ads/next — signed headers only, no body.
  • POST /api/v1/click{ adId }: which sponsor you opened from the tray.

Account data (website, via Clerk)

Your authentication identity (e-mail) lives with Clerk, our sign-in provider. PaperRoute's own database stores your Clerk ID, role, devices, telemetry buckets, the earnings ledger, and payout requests.

Receipts (website)

The dashboard can render any earning day — or a completed payout — as a downloadable thermal-till PNG receipt. A receipt is a read-only projection of your own ledger and telemetry: exposure time, active-exposed megapixel-hours, distinct sponsor count, the 50/50 split, and your running balance. Like every other surface it carries geometry-derived aggregates only — never window titles, process names, or screen pixels. The tear-off barcode is a deterministic hash of that day's ledger entry ids, not a scannable code.

Advertiser accounts (website)

Opening an advertiser account stores, in PaperRoute's own database:

Table What it holds
advertisers Your Clerk-linked user id and a Stripe customer id. Card numbers and billing details live with Stripe — PaperRoute never sees or stores them.
advertiser_ledger_entries The append-only wallet ledger: amount, kind (deposit / spend_impression / spend_click / refund / adjustment), the Stripe object id for deposits and refunds, and attribution metadata (which ad, which UTC day). No personal data.
advertiser_refund_requests Amount requested, status, an optional note, and timestamps.
ads The creative image you uploaded, its destination URL, name, serving status, total budget, optional per-UTC-day spend cap (daily_cap_cents), optional schedule, and (if rejected) the moderation reason. When the cap is set, the earnings cron stops attributing spend to the ad once a UTC day's spend reaches it, resuming at the next UTC day.
stripe_events Processed Stripe webhook event ids, their event type, and when they were processed — pure idempotency bookkeeping.

The ad detail dwell report (exposed-screen-hours, cost per screen-hour, CPC, average exposed area) stores nothing new — it is derived on demand from your own ad's telemetry buckets and wallet ledger. Its platform benchmark is an aggregate average across all sponsor ads; no other advertiser is named.

Payout accounts (website)

Setting up payouts stores a payout_accounts row: your Stripe Connect account id, two flags (details submitted, payouts enabled), and the timestamp of the last Stripe account event applied (webhook-ordering bookkeeping). Identity, KYC, and banking details are collected by Stripe Express onboarding and live entirely with Stripe. Payout requests store amount, status, an optional note, and the Stripe transfer id once paid.

Retention

Telemetry buckets exist to compute and audit earnings: retained while your account is active, deletable with your account. Replay nonces expire within the hour. The earnings ledger — publisher and advertiser alike — is append-only and kept for the life of the account (it is the money record). Backups of your original wallpaper never leave your machine — they live in %APPDATA%\PaperRoute.