Getting a token
Requests carry a bearer token for api.bolrach.com. When Claude or ChatGPT is connected to the Bolrach MCP and someone presses Allow on the Authvio screen, the assistant receives one that acts as that person. Our own services hold workload tokens tied to one ads account. Keys for customers' own servers are coming, but they are not switched on yet.
curl -s https://api.bolrach.com/v1/ads/accounts \
-H "Authorization: Bearer $BOLRACH_TOKEN"Permissions
| Permission | What it opens |
|---|---|
ads:read | Accounts, campaigns, ad groups, creatives |
ads:reports | Reports, CSV export, A/B results |
ads:write | Drafts, uploads, pausing, deleting a draft, tests in a sandbox |
ads:publish | Starting or resuming a campaign, a test on a live account, a bigger budget on a running campaign |
Only ads:publish can cost money. A script holding the other three can make a mess of drafts and nothing worse. A token sees what its person sees in the console: members read an organisation's account, and owners and admins can also change it.
Conventions
| Convention | How it works |
|---|---|
Sandbox | POST accounts/{account}/sandbox returns a test account; its campaigns meet test placements only and never spend |
Idempotency-Key | Send it on every create; the same key again returns the first result, not a second object |
next_cursor | Lists come a page at a time; pass it back for the next page |
Build against the sandbox first. It is invisible to the public, yet campaigns, ad groups and tests behave there exactly as they do on the live account.
Building a campaign
| Step | Route | Note |
|---|---|---|
| 1 | POST campaigns | Born a draft, which spends nothing and shows nowhere |
| 2 | POST campaigns/{id}/ad-groups | The bid and the targeting: countries, devices, categories, regions, caps per hour and week |
| 3 | POST ad-groups/{id}/creatives | Headline, text, link |
| 4 | POST creatives/{id}/image | PNG or JPEG in, WebP out, scanned before it can serve |
| 5 | GET campaigns/{id}/readiness | Lists what still blocks the campaign |
| 6 | POST campaigns/{id}/activate | Needs ads:publish on a live account |
curl -s -X POST https://api.bolrach.com/v1/ads/accounts/$ACCOUNT/campaigns \
-H "Authorization: Bearer $BOLRACH_TOKEN" \
-H "Idempotency-Key: spring-sale-1" \
-H "Content-Type: application/json" \
-d '{ "name": "Spring sale", "objective": "traffic", "dailyBudget": 20, "totalBudget": 300 }'Skipping step 5 is tempting, and it is the call that saves an hour of guessing later. If activation refuses, readiness has already said why: perhaps the creative is still in review, perhaps nobody set a budget.
A/B tests
A test splits one campaign's traffic between two to ten of its ad groups, and the first one listed is the control. Give two groups different bids and the API turns the test down. With unequal bids the higher one takes most auctions, so what looked like a better headline was only a bigger bid.
curl -s -X POST https://api.bolrach.com/v1/ads/accounts/$ACCOUNT/campaigns/$CAMPAIGN/ab-tests \
-H "Authorization: Bearer $BOLRACH_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Headline test", "ad_group_ids": ["$GROUP_A", "$GROUP_B"], "labels": ["A", "B"], "auto_pause": true }'| Result field | Meaning |
|---|---|
impressions, clicks, ctr | Per variant, billable only |
vs_control.p_value | Two-sided z-test against the control |
bayes.probability_best | Chance this variant is the best one |
bayes.expected_loss | Click-through given up by choosing it if it is not |
Below 1,000 impressions per variant and 20 clicks for the whole test, we say nothing, because a verdict from forty impressions is noise dressed up as evidence.
With auto_pause on, a variant under a 1% chance of being the best is paused once its expected loss passes the threshold you set (zero by default). The last running variant is always left alone. Each pause lands in the account's audit log with the figures behind it, and switching auto_pause off stops us at once.
Reports
| Option | Values |
|---|---|
days | 1 to 400 |
breakdown | day, creative, adGroup, country, device, site |
format | json or csv |
Webhooks
| Event | When |
|---|---|
campaign.created, updated, activated, paused, deleted | A campaign changes |
creative.created, updated | A creative changes |
creative.review.approved, rejected | Review finishes |
ab_test.started, stopped, arm_paused | A test starts, stops or pauses a variant |
Copy the signing secret straight away. We keep it sealed after that first screen and cannot read it back to anyone. Check bolrach-ads-signature on each delivery before trusting what it says. A delivery that times out is sent again. Keep the event ids already handled and a repeat costs nothing.
Errors
| Status | Meaning |
|---|---|
400 | A field is wrong; error.param names it |
401 | No token, or an expired one |
403 | A permission is missing; error.code is insufficient_scope |
404 | No such object in this account |
409 | The object is in the wrong state for this |
429 | Too fast; Retry-After gives the seconds to wait |