Scale / Test / Kill
Which of a client's Meta ads to scale, keep testing or kill against a target CPA, with CPMr x CPA quadrants and pooled hook, angle, offer, format and campaign groups.
Overview
GET /client/v1/creatives/decisions decides every Meta ad of the client that had spend in the window. It needs the Creative Analysis feature (Pro plan) and reads only the client's Airbyte data copies, never the live Meta API.
Each ad gets a zone:
- scale: P(CPA < target) is at least the confidence (default 0.9).
- kill: P(CPA > target) is at least the confidence.
- test: neither yet.
- unclassifiable: no spend, no valid target, or unknown conversions. The
reasonsays which. Unknown conversions are never read as zero.
The probabilities come from a Gamma-Poisson model of conversions per unit of spend with a Jeffreys prior: the conversion rate is Gamma(conversions + 0.5, spend). An ad with no conversions is still decided. At confidence 0.9 it becomes killable once its spend passes about 1.35 times the target, and it is plotted in its own no_conversions band. Fractional conversions (linear attribution) are allowed.
Zones describe the observed cost per conversion. They are not causal: they do not say an ad caused its conversions, what pausing it would save, or that an attribute caused a group's result.
Conversions
conversion is required.
| Value | Counts | Notes |
|---|---|---|
meta:<action_type> | Meta's own count for that action type, e.g. meta:lead | window picks the attribution window, default 7d_click. value (each ad set's own setting) answers 422 when the ads use more than one setting. A window the connection never returns answers 422. available_action_types lists what the window has. |
mythic:event:<event> | Mythic attribution of that event to the ad (identity-resolved, across sessions) | model is last (default), first or linear. Needs the Data API in the plan. Refused with 403 for HIPAA locations, and with a retryable 503 privacy_unknown when the location's privacy configuration cannot be read. |
mythic:deals | Won CRM deals, optionally one pipeline | As above. |
Ad-level Mythic attribution needs the ad id in the landing URL or a click id Mythic can map. Conversions it cannot place on an ad are reported under conversion.attribution and never assigned to one. When the attribution result is cut at 500 rows or sampled, every ad's count is unknown (outcome_missing) rather than partial.
new_customers=true (only a person's first-ever conversion) is not supported yet and answers 422. No substitute count is used.
Targets
The target for an ad is, in order:
target_cpaon the request: a what-if for every ad. It is never saved.- The ad's campaign target, saved with
PUT /client/v1/creatives/decisions/targets. - The client's default target, saved the same way.
Targets are saved per conversion, because a lead target is not a purchase target, and in a currency. Saved targets apply only when their currency is the ads' currency (targets.applied). Saving needs an agency key or an mcp_ key with creative_analysis:write. The PUT replaces every target of that conversion, so client_target_cpa and campaigns are both required: a missing or invalid value answers 400 and changes nothing. Send null to clear the client default and {} to clear every campaign target.
A pooled group uses the spend-weighted target of its ads, or none when one of its ads has none.
Which ads are compared
The population is every ad with spend in the window. Ads are compared only within one currency and one campaign objective: by default the ones with the most spend, or objective= to choose. Every other ad is listed in population.omitted with its spend and reason: another currency or objective, or a currency or objective that is not known yet. An ad whose currency is unknown is never decided, even when no ad's currency is known. Until the ad actions copy has run for a client, no objective is known, every ad is kept and Meta counts are unknown.
Data coverage
Meta counts, reach and CPMr come from a daily copy of the client's Airbyte data that is separate from the spend copy. An ad's numbers from that copy are used only when both copies hold the same days for the ad, days with zero spend included. Otherwise they are unknown and actions_coverage says why:
actions_incomplete: some of the ad's days are not in the actions copy yet (for example during the first backfill).spend_copy_incomplete: the actions copy has days the spend copy does not have yet. Daily copies re-read only recent weeks, so a day that reached the ad data late can stay in this state until Mythic support runs a full re-copy for the client.not_in_actions_copy: no actions data for the ad.
A Meta count is compared with the spend of the same daily rows it came from. If Meta restated an ad's spend between the two copies, spend is that spend and spend_copy is the spend copy's. With mythic: conversions, spend is the spend copy's. A copied day with no conversions counts as zero. population.actions_coverage counts ads by state.
CPMr and quadrants
cpmr is spend per 1,000 reach_summed_daily. That is Meta's daily reach summed over the days of the window, because the Airbyte data has reach per ad per day only. It counts a person reached on several days more than once, so CPMr reads low for long-running ads. It is not unique reach over the range.
Quadrants split each ad account at its median CPMr and median CPA. The medians use the ads with spend, reach and known conversions. An ad with no conversions counts as an infinite CPA. cpa_infinite is true when that is the median.
Groups
group_by pools spend and conversions per value and decides each pool:
hook_type,angle,offer: tags from the creative analysis. An ad can carry several, so pools overlap and their spend does not add up to the total. Ads without tags (not analysed yet) are counted ingroups.without_value.format,campaign: each ad is in exactly one pool.
Charts
Every ad has spend, cpa, cpmr, zone, band and quadrant. curves hold the decision boundaries for each target in use: the CPA below which an ad at that spend is scaled (scale_max_cpa) and above which it is killed (kill_min_cpa). Both approach the target as spend grows.
The math is one pure module in the Mythic repository (packages/shared/src/creative-decisions.mjs). A chart can run it to re-decide the ads and the pools when someone drags the target, using points and group_values from the response, with no new request.
MCP
get_creative_decisions, get_creative_cpa_targets and set_creative_cpa_targets call these routes.