Update a campaign’s bidding
Changes the bidding of a campaign. The path names the campaign, and the body holds only the change.
It starts a Mutation of the kind meta_update_bidding, the same change that POST /mutations starts with that kind. The MCP tool of the same name makes the same change.
The change does not happen during this request. The answer is 202, and Location names the Mutation. Poll GET /mutations/{id} until it reports complete or errored. A Mutation is durable, so the id stays valid across retries and restarts.
A refused request starts nothing. AdCrunch refuses a body that does not match, an advertiser that is not yours, an advertiser with no usable connection, and an advertiser connected without write access before a Mutation exists. So a failed call leaves no row in the history.
This operation checks the credential first and the body second. A missing or unusable credential answers 401 even when the body is wrong as well.
/mutations/{advertiserId}/campaigns/{id}/biddingAuthorizationBearer token · headerrequiredSend Authorization: Bearer <credential>.
Use an API key (acr_…), from the AdCrunch console under Settings → API keys.
The credential names the organization, and no operation takes an organization parameter.
See https://docs.adcrunch.dev/api/authentication.
advertiserIdstringrequiredidstringrequiredapplication/jsonbidCapnumberThe highest bid Meta places in an auction. Only with LOWEST_COST_WITH_BID_CAP. A bid is per optimization event, for example per purchase, and per 1,000 impressions when the optimization goal is IMPRESSIONS or REACH. The amount is in whole units of the ad account currency: 2.5 is 2.50. Meta refuses more decimals than the currency has.
costCapnumberThe average cost per result that Meta tries to keep. Only with COST_CAP. A bid is per optimization event, for example per purchase, and per 1,000 impressions when the optimization goal is IMPRESSIONS or REACH. The amount is in whole units of the ad account currency: 2.5 is 2.50. Meta refuses more decimals than the currency has. A cost cap can stop the ad set before it spends its full budget.
roasFloornumberThe lowest return on ad spend that Meta bids for, as a ratio: 1.5 means purchase value of 1.5 times the spend. Only with LOWEST_COST_WITH_MIN_ROAS. From 0.01 to 1000, with at most 4 decimals.
bidStrategystringrequiredHow Meta bids. LOWEST_COST_WITHOUT_CAP ("Highest volume" in Ads Manager) gets the most results for the budget and takes no amount. LOWEST_COST_WITH_BID_CAP ("Bid cap") takes bidCap. COST_CAP ("Cost per result goal") takes costCap. LOWEST_COST_WITH_MIN_ROAS ("ROAS goal") takes roasFloor, and needs the optimization goal VALUE. Give exactly the amount that the strategy takes, and no other amount. A higher bid cap, a higher cost cap or a lower ROAS floor can make an ad set spend its full budget, never more. Send the strategy also to change only an amount. A change of strategy starts the learning phase again, and a large change of amount can too. Meta refuses a change of strategy on a campaign with ad set budget sharing, on a campaign budget with more than 70 ad sets, and on a campaign budget on ROAS goal. Meta advises at most 2 or 3 bid or budget changes a day, and 15 minutes between a budget change and a bid change.
LOWEST_COST_WITHOUT_CAPLOWEST_COST_WITH_BID_CAPCOST_CAPLOWEST_COST_WITH_MIN_ROASThe Mutation started, and it has not ended. The body is the answer of GET /mutations/{id}, with status of running. Poll the path in Location for the outcome.
mutationIdstringrequiredThe id of the Mutation, for example mut_k3x9q2v7m4n8p1r5t6w0y2z4. A start answers it, and also gives the path to poll in Location. A Mutation that started before the mut_ prefix keeps its UUID id.
priorValueobjectThe values that the change replaced, read from the provider just before the change. It uses the field names and the units of action, for example {"dailyBudget": 800}, {"status": "ACTIVE"}, {"bidStrategy": "COST_CAP", "costCap": 4}, {"startTime": "2026-10-01T00:00:00+0200", "endTime": null} or {"name": "Spring sale"}. To undo the change, send the same kind again with these values. A bid change on a campaign also gives the amount of each ad set in adsets: undo it on each ad set. A create and a copy have none.
resultanyHow the change ended. Present only when status is complete. ok of true carries what the provider gave back: a create carries the new object id, a copy carries the id of the copy and the budget that the copy carries, and every other action carries success. The budget is {"dailyBudget": 50} or {"lifetimeBudget": 500} in whole units of the account currency, "campaign" for an ad set copy that spends the budget of its campaign, or "adset" for a campaign copy whose ad sets each carry a budget. A schedule change also carries deliveryStartsAgain of true when it gave an ACTIVE ad set that had ended a new end: the ad set then delivers again. ok of false is a refusal, with a stable error code and a sentence: a provider refusal, or, for a change that an AI agent started through MCP, rejected (a person rejected it in the console) or expired (nobody confirmed it within 24 hours). After rejected or expired, nothing changed at the provider.
statusstringrequiredrunning means poll again. awaiting_confirmation means a person must confirm the change in the AdCrunch console first: only a change that an AI agent started through MCP waits, never one started through this API. complete means the change finished, and result says how it ended. errored means the Mutation itself broke down, and AdCrunch cannot say whether the change happened.
runningawaiting_confirmationcompleteerroredThe request does not match the schema of this operation: a field is missing or has the wrong type, or the body is not valid JSON. error is invalid_request, and issues names each field. Nothing was changed.
issuesobject[]requiredOne entry for each field that does not match.
Show propertiesHide properties
objectinstringrequiredThe part of the request that holds the field.
bodycookieheadersparamsquerymessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
pathstringrequiredA JSON Pointer into that part of the request, such as /filename. An empty string is the whole part.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
invalid_requestmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
No API key, or one that does not resolve. See the security scheme. error is unauthorized.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
unauthorizedmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
The organization has no active subscription. An owner subscribes at paywallUrl. error is organization_unentitled.
paywallUrlstring<uri>requiredThe page where an owner of the organization subscribes. Show it to the person.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
organization_unentitledmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
The caller does not hold mutation:write, or the advertiser is connected without write access. In the second case error is missing_write_access and message says how to fix it. AdCrunch started nothing.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
forbiddenmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
missing_write_accessmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
The advertiser does not exist, it belongs to another organization, or it is not a Meta ad account. error is not_found in each case. An advertiser that does not exist and an advertiser of another organization get the same answer on purpose, so a caller cannot learn which ids exist. AdCrunch started nothing.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
not_foundmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.
The advertiser has no usable connection: a person removed it, it expired, or the provider refused it. A person must connect it again. error is provider_not_connected, the code that a live read of Observe sends for the same failure. AdCrunch started nothing.
providerstringrequiredThe ad platform of the advertiser. It is meta today.
errorstringrequiredA stable code for the failure. This is the field to branch on. It does not change for a given failure.
provider_not_connectedmessagestringrequiredA sentence to show a person. Written to say what to do next. Reworded whenever it can be said better, so never branch on it.