Bolrach Ads API

This is the API behind bolrach.com/ads. We built the console on top of it, so a campaign made here is sitting in the console a second later. Report numbers come from our ad server's own counters after bot and fraud traffic is thrown out. Whatever Tuesday's count is in the console, it is the same count here. No card is ever charged through this API, and a budget is a ceiling the ad server respects.

Base URL https://api.bolrach.com/v1/adsSpec openapi.json

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

PermissionWhat it opens
ads:readAccounts, campaigns, ad groups, creatives
ads:reportsReports, CSV export, A/B results
ads:writeDrafts, uploads, pausing, deleting a draft, tests in a sandbox
ads:publishStarting 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

ConventionHow it works
SandboxPOST accounts/{account}/sandbox returns a test account; its campaigns meet test placements only and never spend
Idempotency-KeySend it on every create; the same key again returns the first result, not a second object
next_cursorLists 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

StepRouteNote
1POST campaignsBorn a draft, which spends nothing and shows nowhere
2POST campaigns/{id}/ad-groupsThe bid and the targeting: countries, devices, categories, regions, caps per hour and week
3POST ad-groups/{id}/creativesHeadline, text, link
4POST creatives/{id}/imagePNG or JPEG in, WebP out, scanned before it can serve
5GET campaigns/{id}/readinessLists what still blocks the campaign
6POST campaigns/{id}/activateNeeds 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 fieldMeaning
impressions, clicks, ctrPer variant, billable only
vs_control.p_valueTwo-sided z-test against the control
bayes.probability_bestChance this variant is the best one
bayes.expected_lossClick-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

OptionValues
days1 to 400
breakdownday, creative, adGroup, country, device, site
formatjson or csv

Webhooks

EventWhen
campaign.created, updated, activated, paused, deletedA campaign changes
creative.created, updatedA creative changes
creative.review.approved, rejectedReview finishes
ab_test.started, stopped, arm_pausedA 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

StatusMeaning
400A field is wrong; error.param names it
401No token, or an expired one
403A permission is missing; error.code is insufficient_scope
404No such object in this account
409The object is in the wrong state for this
429Too fast; Retry-After gives the seconds to wait

Every endpoint with its parameters: the Bolrach API reference. Machine-readable: https://api.bolrach.com/v1/openapi.json.