Reporting automation

Reporting automation that respects your Sheet

Opera reads your report's structure — tabs, sections, columns, formulas — pulls filtered data from AppsFlyer and every ad platform, previews the exact write, and appends. The report you built, maintained automatically.

The hard part of reporting automation was never pulling the data — connectors have done that for a decade. The hard part is the report itself: a living Google Sheet with monthly sections, weekly blocks, client-specific KPI columns and formulas someone spent months tuning. Opera is built to understand that artifact and maintain it.

The reports it maintains

The recurring reports performance teams actually run:

  • Weekly acquisition reports — spend, installs, new customers, CAC/CPI/ROAS by channel and market, appended under the current month's section
  • Daily pacing tabs — yesterday's spend and conversions by campaign, against budget
  • Monthly client roll-ups — four or five weeks aggregated into the monthly view, per client format
  • Campaign breakdown tabs — per-campaign rows with prefix-scoped pulls (only US_, only US_)
  • Reconciliation tabs — platform-reported vs MMP-attributed, side by side with a variance column
  • Creative performance views — spend and results by creative name, for fatigue calls

Step one: Opera reads the Sheet before it writes

Every run starts with schema detection — never with a write:

What schema detection finds
Tabswhich tab is the weekly report, which is raw data, which is the summary
Sectionsmonthly blocks stacked down the sheet, and which one is current
Headersthe real header row and what each column means — Spend, Purchases, CAC, CPI, ROAS, Installs
Anchorthe exact row where the next period belongs
Formula regionscells and ranges that must never be written into, only extended
Driftanything that changed since the last run — renamed columns, inserted rows, moved sections

If the structure drifted — a colleague added a column, a client renamed a tab — Opera halts and flags it instead of writing into the wrong place. That single behavior is the difference between automation you babysit and automation you trust.

Step two: pulls with the right mechanics, not just the right metrics

Each source has real mechanics that decide whether your numbers are correct:

  • AppsFlyer Master API for aggregated spend, installs and in-app events by media source, campaign, geo and date — the workhorse for CPI and rollups.
  • AppsFlyer Raw Data Pull API for event-level rows, so event-counter KPIs — purchase, purchase, trial_started — are counted per user, per campaign, per day, deduplicated.
  • Google Ads API (GAQL) per customer ID, with cost in micros converted and modeled conversions re-pulled over a trailing window, because Google credits them to the click date and restates recent days.
  • Meta Marketing API Insights with the attribution setting pinned per request (e.g. 7-day click / 1-day view) — and a trailing re-pull, because Meta credits conversions to the impression date and the last week keeps rising.
  • TikTok and Snapchat reporting APIs, whose self-reported conversions are written beside the MMP numbers, never summed into them.

Filters travel with every pull: date range in the app's timezone, geo, campaign prefix, event names, media-source includes and excludes, re-engagement removed.

Step three: your KPIs, computed your way

Opera applies this client's definitions, stored once and reused on every run:

  • CAC = spend ÷ new customers — where "new customer" is the event you define (a purchase, a purchase, a first purchase), never raw installs
  • CPI = spend ÷ MMP-attributed installs — platform self-reported installs stay in their own column
  • ROAS = attributed revenue ÷ spend — cohort or blended, labeled, with young cohorts flagged as still maturing

Blended and per-channel versions are computed in the same pass, so a flattering blended number can't hide a channel that's off.

Step four: the write — previewed, validated, appended

  1. Preview — a diff of the exact target row and values, before anything changes
  2. Duplicate-period check — week 24 cannot be appended twice, even if the run repeats
  3. Overwrite check — the target range must be empty; existing cells are never replaced
  4. Append — the new row lands at the anchor, matching the report's formatting
  5. Formula extension — calculated columns extend to cover the new row; nothing is hard-coded over a formula
  6. Audit entry — what was written, where, from which pulls, when, triggered by whom

What one weekly append looks like (illustrative):

Week Spend Installs Purchases CAC CPI ROAS
Jun 1–7 $22,400 11,480 1,610 $13.91 $1.95 1.8
Jun 8–14 $24,150 12,030 1,742 $13.86 $2.01 1.9

Setup, concretely

Mapping a report is a conversation, not a project. You share the Sheet (view access is enough); Opera reads it and shows you what it found — "Weekly tab, monthly sections starting row 4, current section June 2026, append anchor row 31, formula columns F–H (CAC, CPI, ROAS), summary block A1:D2" — and the definitions it inferred from your headers. You correct anything wrong once. From then on, that mapping is the contract every run re-validates. Most reports map in well under an hour, including the argument about what counts as a customer — which is usually the most valuable hour the report has ever received.

What the audit log records

Every run leaves a reconstructable trail:

Run #214 · Mon Jun 8, 07:00 GST · schedule: weekly Schema: validated (matches mapping v3) · Period: Wk 24 (Jun 1–7) Pulls: AppsFlyer Master ✓ · AppsFlyer Raw Data ✓ (1,610 events → 1,584 unique) · Meta ✓ · Google ✓ Checks: duplicate period ✓ none · overwrite ✓ target empty · formulas ✓ extended F31:H31 Write: appended row 31 · Summary: posted #client-us · Triggered by: schedule (owner: M.)

When a client asks "where did this number come from?", the answer is a log entry, not an archaeology session.

The access it needs (and doesn't)

Mapping a report requires view access to that file; maintaining it requires edit on that file — not your Drive, not your domain. Source platforms connect through their own OAuth with scopes limited to what you've enabled, and read-only reporting runs without any write scope on the ad side at all. Every connection is revocable at the source in one click, independently of Opera. The principle is least privilege made literal: the system can touch exactly the artifacts it maintains, and an auditor can verify that claim in your Google and platform admin consoles, not in our marketing copy.

Multi-currency, handled once

Rosters that span AED, EUR, GBP and USD accounts break manual reports quietly: someone converts at Monday's rate, someone else at Friday's, and the blended CAC moves for currency reasons. Opera applies one FX policy per report — one named source, pull-time conversion, the policy stated in the definitions block — so a number's movement always means the market moved, not the spreadsheet. Original-currency columns can sit beside the converted ones for finance's benefit, clearly labeled.

The failure modes this design removes

Manual reporting fails in predictable ways — each one is specifically engineered out:

  • Wrong-week paste → period resolution + duplicate check
  • Column drift (someone inserted a column) → schema re-validation, halt on drift
  • Overwritten formulas → formula-region protection, append-only writes
  • Double-counted installs (platform + MMP summed) → sources kept in separate, labeled columns
  • Timezone and FX mismatches → pulls resolved in the report's timezone, one FX source
  • Silently stale recent days (Meta/Google restating) → trailing-window re-pulls that converge

Safe enough for production

Opera is built to touch production reports and live ad accounts without breaking anything:

  • No destructive writes. Updates are append-only by default — your existing data and formulas are never overwritten.
  • Preview before execution. You see exactly what Opera will change before a single cell is written.
  • Campaigns paused by default. New campaigns are created paused, with approvals required before any spend.
  • Full audit logs and client-level isolation. Every action is logged, and each client's data and rules stay separate.

See this running on your own reports.A 45-minute workflow audit maps your current process and shows exactly what Opera automates — step by step.

Frequently asked questions

Will Opera overwrite my formulas or formatting?
No. Schema detection marks formula regions as protected; writes are append-only; calculated columns are extended, never replaced. If the structure changed since the last run, Opera halts rather than guessing.
Which report structures can it handle?
Multi-tab workbooks, monthly sections stacked in one tab, weekly blocks, daily breakdowns, campaign-level rows, budget and summary tabs. Opera maps the structure first and shows you what it found before anything runs.
Can it use our exact KPI definitions?
Yes — that's the point. CAC against purchases rather than installs, ROAS on the cohort basis you use, prefixes and country filters per client. Definitions are stored once and applied on every run.
How does it deal with Meta and Google restating recent days?
It re-pulls a trailing window on every run, so restated conversions update in your report instead of freezing at the first low number.
How long does setup take?
Pointing Opera at a report and confirming the detected mapping typically takes minutes per report — there's no migration, because nothing moves.

See exactly what Opera would automate in your workflow.

A 45-minute teardown of how you report today: we map every step, mark what Opera automates, and send you the written spec — useful whether or not you buy.