Compare against the client's own target
When
- Purchase ROAS Last 7 days < custom metric target_roas
- AND Spend Last 7 days > Rp 3,000,000
Then
- Pause campaign
- Add to name Append mode · [BELOW-TARGET {date}]
Use case
AdOps lets an agency run one rule shape across every client ad account while each client keeps its own numbers. A condition can compare a live Meta metric against a custom metric that AdOps resolves from a Google Sheet at evaluation time, so changing a client's ROAS target is a spreadsheet edit rather than a rule edit.
One rule shape applied across client ad accounts, with each client's ROAS target and monthly cap read from a Google Sheet at evaluation time.
Situation
The problem this playbook was written against, and what the account looks like once the rules are live.
Every client has a different ROAS target and a different monthly cap, so the same rule has to be rewritten and re-checked per account, and one edited threshold on a Friday is discovered on a Monday.
One rule shape is reused across accounts, per-client numbers live in a Google Sheet the account manager owns, and each ad account gets its own queued job with its own log.
Playbook
Each card is one rule, printed the way the AdOps rule builder prints it: conditions on top, actions below the hairline.
Every threshold below is an example. Change the numbers to fit your account before you set a rule live.
When
Then
Why this rule
The target lives in a Google Sheet row the account manager maintains, so changing a client's target is a spreadsheet edit rather than a rule edit.
When
Then
Why this rule
A retainer overspend is the one mistake an agency pays for out of its own margin, and the cap is different for every client.
When
Then
Why this rule
The engine re-reads the live Daily Budget from Meta before writing, so a budget raised by hand or by another tool is pulled back to the agreed ceiling.
When
Then
Why this rule
Campaign Name is one of the three pre-filter fields, so a naming convention already in use becomes the client boundary without a list of campaign IDs.
When
Then
Why this rule
A client who opens Ads Manager sees the same 7-day number the agency is reporting, written onto the campaign itself.
Sample data Key facts
AdOps lets an agency run one rule shape across many Meta ad accounts while each client keeps its own numbers. A condition can compare a live Meta metric against a custom metric that AdOps resolves from a Google Sheet at evaluation time, so a client’s ROAS target or monthly cap is edited in a spreadsheet rather than inside a dozen separate rules.
The rules are not the hard part. The thresholds are. Ten clients means ten ROAS targets, ten monthly caps and ten different opinions about when to pause, and every one of them changes at some point in the quarter.
Copying a rule per client works until the day a target moves. Then the change has to be found in the right account, applied to the right rule, and verified, and the account manager who agreed the new number is usually not the person who edits rules.
A custom metric in AdOps names a Google Sheets spreadsheet id, a sheet name, a lookup column and a value column. At evaluation time the engine resolves the metric to a number, and a condition set to compare against a metric uses that number instead of a hardcoded threshold.
The practical shape is one sheet with a row per client: client key, target ROAS, monthly cap, floor budget. Purchase ROAS over the last 7 days is less than target_roas then means something different in every account, and updating a client’s target is a cell edit that takes effect on the next evaluation.
Two limits are worth stating. Rows are cached in memory on a 2-minute last-access TTL with the last stored rows in MongoDB as a fallback, so a continuously read sheet can serve a cached snapshot. And the lookup reads the first matching row for each formula line, so the sheet should hold one row per key.
Each rule belongs to an ad account, which is the first boundary. Inside an account, three pre-filters narrow a rule further: Campaign ID with equals or not equals, Campaign Name with contains or does not contain, and Campaign Status with is or is not, over the values ACTIVE and PAUSED.
Campaign Name is the one that scales. If a shared account uses a client or brand prefix, a rule pre-filtered to campaigns whose name contains that prefix picks up new campaigns automatically the day they are created.
Ad accounts also start switched off. Every account discovered from Meta is stored inactive, and nothing is evaluated until someone activates it, which is a useful property when a new client’s account is connected mid-week.
The dispatcher sweeps for rules whose next execution time has passed, on a 10-second cron with overlap prevention, and enqueues one job per ad account onto a Redis-backed queue. The worker consumes the job, issues a batched Meta Insights read for the metrics the conditions need, evaluates the conditions, and executes the actions.
That split has a property worth knowing when you are handing an agency’s whole book to an automation: the dispatcher only ever issues reads to the Meta Graph API. Every budget change, pause and rename happens in the downstream worker.
Entity fetches from Meta are retried up to 3 total attempts, waiting 1 second and then 2 seconds, so a single transient Graph API error does not silently skip an account.
Every run writes a batch record and one result per campaign per task. The Rule Log Detail screen shows an Executed, Not Executed or Skipped badge per campaign, each condition marked Pass or Fail beside the value it read, the parameters the action used, the execution time in seconds, and the next scheduled execution.
For client-facing reporting, the Add to name action carries live shortcodes such as {metric|purchase_roas.omni_purchase|last_7d} and {date}, so a campaign name can carry its own 7-day ROAS. A client who opens Ads Manager reads the same number the agency reports.
There is no cross-account rule: a rule belongs to one ad account, and running the same policy across a book means creating it per account. Actions are campaign-shaped, so plan around campaign budgets. AdOps connects to Meta Ads only today, with TikTok Ads, Google Ads and Snapchat Ads listed as coming soon, and billing runs in Rupiah through Duitku.
Questions
Yes, through a custom metric. A custom metric names a Google Sheets spreadsheet id, a sheet name, a lookup column and a value column, and a condition can compare a live Meta metric against the resolved value instead of a fixed number.
Every rule belongs to an ad account and can be narrowed further by three pre-filters: Campaign ID, Campaign Name and Campaign Status. A Campaign Name contains filter turns an existing naming convention into the client boundary.
AdOps fans out one queued job per ad account, so accounts are processed as separate jobs rather than one long pass. The dispatcher itself only ever reads from the Meta Graph API; every write happens in the downstream worker.
Sheet rows are cached in memory on a 2-minute last-access TTL, with the last stored rows in MongoDB used as a fallback when the live fetch fails. A row that is read continuously can therefore serve a cached snapshot, so treat the sheet as a target list rather than a live feed.
Yes. A condition can be set to compare against a metric rather than a value, and the compared metric carries its own reporting period, so a rule can test today's Purchase ROAS against the last 7 days of Purchase ROAS on the same campaign.
A suspended account keeps its data but shows a Feature Lock overlay across Automation Rules, Ad Accounts, Activity Logs and Custom Metrics until the invoice is paid. Account statuses are TRIAL, ACTIVE and SUSPEND.
Related
Every rule on this page is built from conditions and actions that ship in AdOps. Point them at one ad account and watch the log before you widen the scope.
Inside the product
Nine screens from the working dashboard — the rule builder, the metric picker, the dayparting grid and the log that records what happened. Scroll the strip.
Every figure is rebuilt from the product’s own interface and filled with invented data — no customer name, ad account or spend figure appears anywhere on this site. See how a run works