A client's headline promotion changed. Same seven banner sizes, new creative, new landing page, and it had to replace the old set everywhere it was running: 76 line items across four campaigns in Adform. Done in the UI, that is a day of clicking with a real chance of missing one. Done by script it was an afternoon, and the script knows exactly what it touched.

What the job actually is

  1. Deactivate every tag serving the old creative. That was 454 tags.
  2. Upload the seven new HTML5 banners to each of the four campaigns.
  3. Recover the list of line items the old tags were attached to.
  4. Create a new RTB tag for every banner on every line item: 7 sizes times 76 line items, 532 tags.
  5. Set the destination URL on all 532, then read every one back and check it.

Steps 1, 3 and 5 are the ones people underestimate. The creation is easy. Knowing where the old thing ran, and proving the new thing is wired, is the work.

Things the API documentation does not tell you

  • Creating an RTB tag and setting its destination URL are two calls, and the order is fixed. The destination URL contains the tag's own ID, so the tag has to exist before you can set it. Create, then PUT the URL. There is no way to do it in one.
  • The tag list endpoint does not return destination URLs, even when you ask for settings. To verify a URL you have to GET each tag individually. We do, for all 532, because the verification is the point.
  • A paused flag on create is silently ignored. The tag comes back live. If you want it paused, that is a second call too.
  • Updates can fail with a 409 saying the tag was just modified. It is transient. Retry it.
  • The direct tag endpoints want a flat ad UUID in the POST body, but the GET response nests it two levels deep. Posting the shape you just read back fails with an unhelpful error.

The spend that hid

Three days after the swap, the old creative was still spending about 375 dollars a day. It was not running through tags. It was in optimised rotators: creative rotation groups the DSP picks from, which do not show up in a tag sweep. The old banners sat in those groups under a different creative code, so a search for the old promotion's code did not find them either.

The lesson went straight into the checklist: a creative teardown sweeps tags and rotator members, and it searches by every code the creative was ever uploaded under, not just the current one. That is the kind of rule that only exists because it cost money once.

Who actually did the clicking

Nobody. I described the changeover in the terminal to a Claude agent. It loaded our tagging playbook, read the memory notes for the platform (the two-step, the ignored paused flag, the 409 retry), worked out the line items, and showed me the plan: 454 deactivations, 28 uploads, 532 creations. I approved it. It ran, waited on the rate limits, read back every tag, and wrote the log. The rotator lesson at the end went into the same memory the moment we found it.

Why script it at all

  • It is repeatable. The next changeover for that client took about an hour, most of it waiting on the API's rate limit.
  • It leaves a record. Every tag ID created, every URL set, every verification result, in a log. When someone asks in November what changed in August, the answer is a file, not a memory.
  • It verifies itself. The script reads back what it wrote. A human doing 532 of anything in a browser does not.
  • Rate limits make it polite by design. Adform allows a handful of stats calls a minute; the script waits. A person clicking fast does not, and gets locked out.