Automation

The automation language

The complete reference: entities, fields, time ranges, operators, functions, and actions.

Rules are written in a small, readable language built for Amazon Ads: loop over an entity, check conditions on its metrics, call actions. The editor has syntax highlighting, autocomplete, inline error checking, and a built-in reference; every run is recorded and revertible.

The rule editor with a highlighted recipe, line numbers, autocomplete suggestion popup, a schedule and mode selector, and Preview (dry-run) and Create rule buttons.
The rule editor, with live autocomplete and a dry-run preview.

The basic shape

FOR EACH keyword IN LAST 30 DAYS: IF keyword.clicks > 30 AND keyword.orders = 0: keyword.pause() keyword.note("{clicks} clicks and 0 orders") END
StatementMeaning
FOR EACH entity [AS name] [IN range]:Loop over an entity. Default window: LAST 30 DAYS. OVER/USING are accepted for IN.
IF condition: / WHEN condition:Run the indented body when true; bodies may nest.
LET name = expressionName a value or related entity. Top-level LETs are visible everywhere; a LET in a body is visible in that body and below.
ENDClose a FOR EACH block.
# commentIgnored to the end of the line (except inside quotes).

Bodies are indented two spaces. Keywords, entities, fields, and functions are case-insensitive.

Entities you can loop over

campaignadGroupkeywordtargetsearchTermproductportfolio

Hop to related entities with a dot: keyword.campaign, searchTerm.adGroup, campaign.portfolio, campaign.placement. Related objects can be named: LET campaign = keyword.campaign.

Metrics & fields

Read any metric off an item over the loop's window (or an inline one):

impressionsclicksspendsalesordersunitsacosroasctrcvrcpcaov

Plus settings and identity per entity: bid, bidInherited, budget, state, name, matchType, keywordText, searchTerm, asin, adType, targetingType, biddingStrategy, portfolioName, adGroupCount, placement percentages (top_of_search, product_pages, rest_of_search), and recency fields days_since_bid_change / days_since_budget_change. Friendly aliases work everywhere (daily_budget, purchases, conversion_rate, tos, …) — the editor autocompletes them all.

Time ranges

The window after FOR EACH is optional and defaults to the last 30 settled days. Any metric read can carry its own inline window: keyword.clicks IN LAST 7 DAYS.

RangeMeans
LAST 7 DAYS / LAST 30 DAYS / LAST 90 DAYSThe last N settled days (excludes the two newest, still-settling days — see attribution).
YESTERDAYThe latest settled attribution day (currently two UTC days behind today).
TODAYThe current UTC day from the latest sync — partial and unlagged.
N DAYS AGOOne exact unlagged UTC day.
LIFETIMEAll stored history.
FROM 8 DAYS AGO TO 35 DAYS AGOAn offset baseline window, unlagged.
BEFORE 8 DAYS AGOAll history older than 8 days ago (new-vs-historical checks).
FROM 2026-01-01 TO 2026-01-31Explicit start/end dates.

Conditions & operators

KindSyntax
LogicAND, OR, NOT, parentheses (precedence: NOT, AND, OR)
Comparison=/==/IS, !=/<>/IS NOT, <, <=, >, >= (text equality is case-insensitive)
ListsIN, NOT IN — e.g. keyword.matchType IN ["EXACT", "PHRASE"]
TextCONTAINS, NOT CONTAINS, CONTAINS ANY [list], STARTS WITH, ENDS WITH (case-insensitive)
Arithmetic+ - * / % (+ concatenates text; % is modulo), unary -
Unitsmoney $0.85, percent 45% (= 0.45), plain numbers, TRUE/FALSE, NONE, ["lists"]
Inline windowexpression IN range — e.g. keyword.acos IN LAST 7 DAYS > keyword.acos IN LAST 90 DAYS

Historical setting reads: value-returning methods give you a setting as it was N days ago — keyword.historicalBid(30 DAYS) / target.historicalBid(…), campaign.historicalBudget(…), campaign.historicalBiddingStrategy(…), and campaign.historicalPlacementBid("TOP OF SEARCH", 30 DAYS). Each accepts 30 DAYS, 30 DAYS AGO, YESTERDAY, or a plain number of days, and is a value for conditions and LET, not a step — e.g. a drift check: IF keyword.bid > keyword.historicalBid(30 DAYS) * 1.5. Bids come from the daily targeting report, campaign settings from the change log; if history doesn't reach that far, the value is unknown and the comparison simply never matches.

Actions

ActionOnWhat it does
.pause() / .enable() / .setState("PAUSED")campaign, adGroup, keyword, target, productPause or enable the item.
.setBid(amount)keyword, targetSet the bid — compute it, e.g. keyword.bid * 0.9.
.setBudget(amount)campaignSet the daily budget.
.setBiddingStrategy("FIXED BIDS")campaignAlso "DYNAMIC BIDS - DOWN ONLY", "DYNAMIC BIDS - UP AND DOWN".
.setPlacementBid("TOP OF SEARCH", 50)campaignPlacement adjustment 0–900%; also "PRODUCT PAGES", "REST OF SEARCH".
.setName(text) / .rename(text)campaignRename the campaign.
.moveToPortfolio(PORTFOLIO("Brand A"))campaignMove to a portfolio (setPortfolio is an alias).
.addNegative(text[, "exact"|"phrase"])campaign, adGroup, keyword, target, searchTerm, productAdd a negative keyword; match type defaults to exact. See negation.
.createKeyword(text[, match[, bid]])adGroup, keyword, target, searchTerm, productCreate a positive keyword in the entity's ad group — the harvesting primitive. Match: exact/phrase/broad (default exact).
.note("…")anyThe reason logged with every action in the same body. Placeholders: {clicks}, {acos:percent}, {spend:money}, {field:number}, {campaign_name}, {ad_group_name}, and any scalar LET variable.

Functions

FunctionWhat it does
MIN(…) / MAX(…) / CLAMP(v, lo, hi)Smallest / largest / keep a number inside limits — the usual bid-guard tools.
ROUND(v[, places]), FLOOR(v), CEIL(v), ABS(v)Numeric shaping.
IF(cond, then, else)Choose a value; chain IF(c1, v1, c2, v2, …, default) for multi-branch logic.
METRIC("clicks", "last7")Read a metric for the current item in another window ("last7", "today", "8..35", "8..").
LAST_CHANGE("bid") / DAYS_SINCE_CHANGE("budget")When the latest applied bid/budget change happened — for cool-downs between adjustments.
PORTFOLIO("name")Find a portfolio by exact (case-insensitive) name.
LOWER, UPPER, LENGTH, REPLACE, CONCAT, TODAY()Text and date helpers.

A fuller example

FOR EACH keyword IN LAST 30 DAYS: LET campaign = keyword.campaign # cut losers, with a floor and a 7-day cool-down IF keyword.clicks >= 15 AND keyword.orders = 0 AND DAYS_SINCE_CHANGE("bid") > 7: keyword.setBid(MAX($0.05, keyword.bid * 0.85)) keyword.note("{clicks} clicks, no orders") IF campaign.spend > $50 AND campaign.orders = 0: campaign.pause() campaign.note("Parent campaign wasted {spend:money}") END
Built-in guardrails. Settled windows exclude the two newest attribution days so a rule never acts on incomplete sales data; a single run stops at your change cap (default 25,000 changes, adjustable in Settings); and the language has no file, network, or system access. Details in Safety & limits.