---
title: "Release 154"
description: "Publish sellers.json and tell every site which ads.txt lines to carry, checked nightly; pass an upstream supply chain from a CTV stitcher; hand a stitcher's privacy, content and player signals to your buyers, with stitcher presets managed under Admin; serve a sequence of VMAP ad breaks through the content; fill a video requirement preset from a publisher's spec document; name what each stale location is called now with Fix with AI; a read-only Billing Tab; report grouping by campaign without creative and by zone without placement; a network can run on its own time zone; and crawler and carrier traffic judged properly, which changes impression counts."
type: "reference"
audience: [network-manager, admin, publisher, advertiser]
keywords: [release 154, whats new, upstream macro presets, pass privacy signals to dsps, us privacy, gpp, coppa, dnt, content genre, content targeting, vast pricing, vast advertiser, vast category, sellers.json, ads.txt, app-ads.txt, supply chain, schain, seller id, seller type, intermediary, reseller, ctv stitcher, ssai, springserve, mediatailor, vmap, ad breaks, break sequence, mid-roll, post-roll, video requirement preset, spec document, fill from document, stale locations, fix with ai, renamed city, billing tab read-only, link permission, can view share, report grouping, campaign without creative, zone without placement, crawler impressions, bot detection, carrier signature, impression counts, tracking code no ad, fire without ad, estimated ctr, i2c, masked names api, categories api, delete user api, custom parameter filter, hour range, win notice timeout, external storage export, auto-linking badge, network time zone, time zone, daylight saving, dayparting, weekly cap, monthly cap, flight dates, last 30 days]
---

Fetch the complete documentation index at: https://epom.com/ad-server/docs/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Release 154

Release 154 is the **supply-chain release**. Your network can publish a `sellers.json` file, every site can
be told the exact `ads.txt` or `app-ads.txt` lines its publisher must carry — checked for you every night —
and a channel sold through a CTV stitcher can pass the sellers above you into the chain buyers receive. So a
buyer can verify who is selling the inventory instead of discounting the bid for not knowing.

Alongside it: a stitcher can hand your buyers the viewer's privacy signals and the show the ad plays in, a
VMAP placement can serve a whole sequence of ad breaks through the content instead of a single pre-roll, a video requirement preset can be filled from a publisher's own spec document, **Fix with AI** names
what each stale location is called now, the Billing Tab permission gains a read-only value, and report grouping
by campaign no longer forces you to grant creative as well. A network can now run on its own time zone instead
of UTC. Crawler traffic and mobile-carrier beacons are now judged properly, which changes impression counts on
networks that have them.

> **Things to act on.**
>
> **The supply-chain fields are invisible until an administrator grants the permission.** **Site Supply
> Chain** and **Advertiser ads.txt Lines** are off for every role on upgrade, by design — until one is
> granted, the console, the legacy pages and the REST API neither show nor accept those fields, and the
> nightly ads.txt crawl does not start. Grant them in **Admin → Users & Access → User Roles &
> Permissions**. See [Supply chain and sellers.json](https://epom.com/ad-server/docs/guides/inventory/supply-chain-and-sellers-json.md).
>
> **Listing a site in sellers.json is opt-in per site, because it makes the publisher list public.** Test and
> internal sites stay out, and the file is valid with an empty list. The sites grid has a sellers.json column
> and a bulk list/unlist action.
>
> **Impression counts change on two kinds of traffic.** Crawlers that carry a rolling browser version were
> being counted as real viewers and are now refused by the crawler list — on from day one, because stopping
> those impressions is the point. Behind the list, the device-detection service now names the crawler it sees,
> and the ones that are never a reader — search engines, AI crawlers, SEO and link-preview bots, uptime monitors,
> security scanners — get no ad either. Mail image proxies, the HTTP libraries of real apps, Prebid Server and
> ad-verification crawlers are only counted and keep receiving ads. And a beacon from a viewer on a mobile carrier used
> to skip signature verification entirely; it is now judged like any other. On one network, 16.8 million of 27.8 million
> impressions in a day were carrier traffic whose signature was never checked. Expect counts on affected
> placements to fall to what was really delivered. See
> [Moderation and quality](https://epom.com/ad-server/docs/guides/admin/moderation-and-quality.md).
>
> **A share for viewing only no longer carries the right to link.** A user given **Can View** on a site could
> link banners onto its placements, and one given **Can View** on an advertiser could link that advertiser's
> banners — and could switch a placement's linking off altogether. Linking changes what the shared entity
> serves, so it now needs a share that may be changed. Nothing changes on upgrade: the new permission **Link
> on entities shared for viewing only** is granted to every role that had the old reach, so turn it off when
> you want the stricter behaviour. See [Hierarchy and linking](https://epom.com/ad-server/docs/concepts/hierarchy-and-linking.md#1-manual-linking)
> and [Sharing and permission model](https://epom.com/ad-server/docs/concepts/sharing-permission-model.md#layer-3--entity-shares).
>
> **Estimated CTR and I2C above 1 are now refused.** Both are fractions — 0.05 means 5% — and the console
> always said so, but the server used to accept anything up to the column width. A new value above 1 is
> refused on its field. A value above 1 saved before this release is kept and does not block saving the
> campaign; over the REST API, leave `estimatedCtr` / `estimatedConversionRate` out of a pricing update to
> keep it, because a value above 1 that is sent is refused even when unchanged. See
> [Campaigns](https://epom.com/ad-server/docs/guides/advertising/campaigns.md).
>
> **If you read entity names over the REST API on a role with Mask Campaigns, they are now masked there
> too.** The console has always shown `ADVERTISER_{id}`, `CAMPAIGN_{id}` and `BANNER_{id}` to those roles;
> the API answered the real names. It no longer does. Ids and every other field are unchanged.
>
> **Privacy signals reach DSPs only once you turn it on.** The ad server now reads `usPrivacy`, `gpp`,
> `gppSid` and `coppa` from the request, but the bid request carries them — and `gdpr` and the consent string
> with them — only after an administrator ticks **Pass privacy signals to DSPs** on the GDPR card of System
> preferences. It is off on upgrade, so nothing your DSPs receive changes until you decide. See
> [GDPR / TCF consent](https://epom.com/ad-server/docs/guides/admin/gdpr-tcf-consent.md#pass-privacy-signals-to-dsps).
>
> **DSP integrators: `device.dpidsha1` is no longer sent.** The plain `device.ifa` goes out as before. The
> four creative macros for hashed advertising ids are retired with it. See the
> [OpenRTB integration reference](https://epom.com/ad-server/docs/reference/openrtb-integration-reference.md) and
> [Macros](https://epom.com/ad-server/docs/reference/macros.md).

## Your supply chain, published and verified

A site now has a **Supply chain** section of its own: its seller id, its seller type (Publisher, Intermediary
or Both), whether to list it in `sellers.json`, and whether the listing is confidential. The seller domain and
seller id come from one place, so the chain sent to DSPs and the chain your header-bidding tags carry agree
with the file you publish. A site's section says in one line what buyers receive and from how many DSP
partners.

The same section carries the site's **ads.txt block** — the exact lines the publisher must publish, grouped by
source, with Copy, Download, last night's check result and **Re-check now** — plus a row per header-bidding
partner in the site's active tags showing whether the file has its line, and **Additional ads.txt lines** for a
partner that sells the site but is not an advertiser in your account. An advertiser that is really a demand
partner carries the lines it asks publishers for.

The block is built from what buyers actually verify. A site with an active header-bidding tag is asked for the
pair those tags send, not only the OpenRTB pair — following the old block authorised buyers who never check it
while the nightly check marked the site "Missing lines" for a pair those buyers ignore. The network's own line
follows the site's seller type, so an Intermediary site asks for `RESELLER`, which is what buyers pair with
the `INTERMEDIARY` entry in your `sellers.json`. And `CONTACT`, `SUBDOMAIN` and `OWNERDOMAIN` — the
publisher's own variables — are kept out of a publisher's block, where the file had been naming the network as
owner of sites it does not own.

A host that resolves to a private address is reported as "could not check" rather than naming the address.
An app site gets a **Developer website** field, so the app-ads.txt check and the sellers.json domain have a
website to read.

See [Supply chain and sellers.json](https://epom.com/ad-server/docs/guides/inventory/supply-chain-and-sellers-json.md) and
[Setting up ads.txt](https://epom.com/ad-server/docs/guides/inventory/supply-chain-ads-txt-setup.md).

## A CTV channel can pass the sellers above you

A channel sold through a stitcher has a seller before you that a one-node chain cannot name, and many buyers
bid less on a chain that does not reach the owner of the inventory. A VAST tag URL now takes a `schain`
parameter in the IAB form `1.0,1!asi,sid,hp`, which the player or the stitcher fills in — SpringServe's
`{{SCHAIN}}`, MediaTailor's `[player_params.schain]`, Brightcove's `{{url.schain}}` — and DSPs receive that
chain with your own node appended.

It is taken only on sites whose seller type is Intermediary or Both, so nobody can append a seller that
contradicts your `sellers.json`. And where there is no upstream chain to carry, an Intermediary site now says
so honestly: `complete=0` on both the bid request and the header-bidding tag, which had been declaring
`complete=1` by construction and telling a buyer two different things about the same site.

## A stitcher's signals reach your buyers

A stitcher that calls a VAST tag knows things about the viewer and the show that a buyer prices on, and until
now the tag had nowhere to put them. It now takes `dnt`, `appName`, `maxDuration`, the show's `contentGenre`,
`contentRating`, `contentLivestream` and `contentLanguage`, and the privacy strings `usPrivacy`, `gpp`, `gppSid`
and `coppa`. DSPs get them where OpenRTB expects them — `device.dnt`, `app.name`, `content.*`, and a shorter
`video.maxduration` when the player can take only so long an ad. The privacy strings go to `regs` and
`user.consent` on OpenRTB 2.6 partners and to `regs.ext` and `user.ext` on earlier ones, once **Pass privacy
signals to DSPs** is on. A value that is not well-formed, or a stitcher's own macro sent back unfilled, counts
as none. Four macros carry the same privacy strings into banner code and partner tag URLs: `$$US_PRIVACY$$`,
`$$GPP$$`, `$$GPP_SID$$` and `$$COPPA$$`.

You do not have to look up any stitcher's macros. A video placement picks its stitcher under **Server-side ad
insertion (SSAI)**, and its tags come out already carrying that stitcher's macro for every parameter — the
placement's **SSAI macros** table shows each one, and which only arrive when the publisher's player passes
them on. The stitchers are now a list you manage under **Admin → Creatives & tags → Upstream macro
presets**: seven come built in — the IAB standard macros, Google Ad Manager DAI, AWS Elemental MediaTailor,
SpringServe, Brightcove, FreeWheel and Publica — and you can correct one when a vendor changes its macros, or
add your own. **Fill from document** reads a stitcher's macro list for you — its documentation page, a PDF, a
screenshot of the table, or just a VAST tag URL it already uses — and fills the preset's fields with its macros,
quoting where each one came from.

The show is targetable as well: Custom Parameter targeting reads `contentGenre`, `contentRating`,
`contentLivestream` and `contentLanguage` like any other key, so a campaign can keep off live sport or stay on
`TV-G` content.

And the VAST response tells a stitcher that resells the slot what it is selling. From VAST 3.0 it names the
`<Advertiser>`, and VAST 4.x adds the winning bid's IAB categories as `<Category>`. Where the stitcher needs
the price, a video placement can also send the publisher's net CPM as `<Pricing>`: it is off until you turn on
**Send the publisher's price in VAST** in the placement's server-side ad insertion section, which needs the
Manage Pricing permission — the tag is fetched in the viewer's browser, so a price in it is public. A
`<Pricing>` a DSP left in its own markup is removed either way, so what the buyer paid never leaks downstream.

See [Video placements](https://epom.com/ad-server/docs/guides/inventory/placement-video.md#serving-behind-server-side-ad-insertion-ssai),
[Other admin tools](https://epom.com/ad-server/docs/guides/admin/admin-tools.md#upstream-macro-presets),
[Custom Parameter targeting](https://epom.com/ad-server/docs/guides/targeting/custom-parameter-targeting.md#target-the-show-a-stitcher-describes)
and [Video banners](https://epom.com/ad-server/docs/guides/advertising/banner-video.md#what-a-server-side-ad-insertion-stitcher-receives).

## A VMAP placement serves a sequence of ad breaks

A VMAP placement used to serve one pre-roll break and nothing else. It can now carry a **break sequence** —
one digit per break, the digit being how many ads that break asks for, so `1-1-1-1` is four breaks of one ad
and `2-2-2` three breaks of two, and the sizes need not match. The first break plays before the content, the
last is the post-roll where you ask for one, and the rest are spread evenly through it.

The card shows it as one **Ad breaks** control: a mode for pre-rolls only or a sequence, a stepper per break
labelled with the offset it will be served at, the post-roll as a toggle on the last break, and a timeline that
draws a tick per ad. **Edit as text** is still there if you would rather type `3-1-1-2`. A sequence is capped
at ten breaks of five ads, and a sequence the ad server cannot read is now refused when you save instead of
being stored and quietly served as a single pre-roll.

Each break asks the VAST tag for its own ads rather than being handed a creative picked now, because the breaks
play minutes apart and a creative chosen now may be capped, out of budget or beaten by then. Leaving the field
blank keeps the pre-roll flow every placement has today. See
[Video placements](https://epom.com/ad-server/docs/guides/inventory/placement-video.md).

## A creative with nothing to render no longer takes the slot

A creative whose own record has nothing to serve — a Local File with no file, an External File with no link,
an HTML Code creative with an empty code box, a third-party VAST creative with no tag URL, a video with no
video file — is now refused before the auction, so the next creative serves instead of an empty slot or a VAST
wrapper pointing nowhere. The troubleshooting log names it `CREATIVE_HAS_NO_CONTENT`. The new console says
which field is empty: a red **This creative has nothing to render** block on the creative's page, and **Not
served: no …** on its row in a placement's linked creatives, grid and tree. Templates are not judged this way.
See [Ad not serving](https://epom.com/ad-server/docs/troubleshooting/ad-not-serving.md#check-5--do-the-types-and-sizes-fit).

## Video requirement presets are filled from a publisher's spec

**Fill from document** in the preset editor reads a publisher's delivery spec — a PDF, an image of one, a link
to its page, or its text — and fills the form for review; nothing is saved until you save. Every value is held
to the preset's own constraints field by field, and what fails them is listed as not applied rather than
passed on, with a quote from the document per field.

A spec carrying several delivery profiles comes back one preset each: **Add from document** on the presets list
lets you tick any number of them, and the new-preset form opens filled with the first and refills with the
next on each Save, counting down. Deleting and applying presets also say what they are doing now: the
confirmation lists the presets in use with their site counts instead of stacking the same refusal once per
preset, and **Apply to N sites**, which used to fail on every site, now works whoever owns them. See
[Admin tools](https://epom.com/ad-server/docs/guides/admin/admin-tools.md).

## Targeting takes a pasted list

Your list of countries, carriers or domains lives in a spreadsheet, so the targeting editor now takes it as
pasted text on the ten dimensions that hold one value per rule: Country, Browser Country, Language, Device
Vendor, Mobile Carrier, Domain, Channel, App Package, Operating System and Browser (a pasted operating system or
browser carries no version).

Everything the paste produces is shown **before** anything is saved: how many values will be targeted, how many
entries repeated and were counted once, what is already targeted this way, what is already targeted the other
way — with a warning, because one value cannot usefully be both — and what could not be read, listed exactly as
you pasted it so you fix those lines in the text still in front of you. The Include and Exclude buttons carry
the count, so the click is unambiguous. Reading the list is forgiving: newline, comma, semicolon, tab and pipe
all separate values, but a single space does not, so "United States" survives.

Lines that are not in this account's words — a local spelling, a brand, a typo — can go through **Match the
rest with AI**. It is offered on every dimension with a list of values (not Domain, Channel or App Package) and
only when your network has an AI key; it **sends those lines and the dimension's list of values to the AI
provider**, writes the answer back into the text box for you to review, and saves nothing. The operator
between rule types is now a **Between rule types** strip above the rules — **Match all** or **Match any**, the
latter after a confirmation. See [Add targeting](https://epom.com/ad-server/docs/guides/targeting/add-targeting.md).

## Stale location targeting: Fix with AI

**Fix with AI** on the Stale locations page asks for the current name of every stale place in the list and lets
you review and apply the replacements. It is deliberately cautious: only exact name matches are pre-ticked,
every AI suggestion is ranked at most medium, and nothing it matched is ever applied for you.

Three other things on that page improved. Replacing a place now offers to replace the same place in every other
rule that names it, in one request — a renamed town is usually targeted by more than one rule. **Replace** is
offered on safe rows too, not only risky ones, because removing a merged town still loses the audience you
meant to keep. And the red **Stale locations** button on the tree rail now appears only while you have rules
that actually block delivery, with a count badge; it also reaches the Grids view, which has no rail. See
[Stale locations](https://epom.com/ad-server/docs/guides/targeting/stale-locations.md).

## Permissions and reports

**The Billing Tab permission gains a read-only value.** It used to be on or off, so a role that could see a
balance could also book against it. With READONLY the tab, the cards, the trend chart, the payment rows and the
balances are readable while Add payment, Remove and Recalculate are gone and the REST writes refuse. Nothing
changes until an administrator picks the new value. See
[Finances and billing](https://epom.com/ad-server/docs/guides/admin/finances-and-billing.md).

**Report grouping by campaign no longer forces creative, and zone no longer forces placement.** One permission
used to cover two levels on each side. They are split, and the migration copies every existing grant down, so
the creative and placement levels keep their reach and the pair is separable from your next edit onwards. A
report filter now asks for the levels above it, as grouping always did: a role that may filter by campaign but
not by advertiser, or by zone but not by site, loses that filter — saved and scheduled reports included —
until the parent is granted. A refusal now names
the permission that is actually missing instead of a bare "Permission denied", and the Grids layout no longer
offers filters the report would refuse.

**Targeting warns when a value is included and excluded at once.** Such a rule never matches, and a dimension
whose includes are all contradicted that way matches nothing — so the entity quietly stops serving while every
rule on its own looks right. The chips turn amber with a note, and the lists also carry the levels above, so an
advertiser's include against a banner's exclude is visible too. The pair is still saved, because an operator
who means it keeps it. See [Add targeting](https://epom.com/ad-server/docs/guides/targeting/add-targeting.md).

## A network can run on its own time zone

Until now every day and hour was a UTC one: on a network selling New York inventory, "today" started at 19:00 or
20:00 local time, daily caps reopened in the evening and a local day was split across two report rows. A network
can now be set to one time zone, daylight saving included, and then everything that turns at midnight or on the
hour follows it: General reports and dashboards, the Today/Yesterday/Last 30 days presets, daily, weekly and
monthly caps, pacing, dayparting on server time, scheduled reports and the daily e-mails. The Advanced report and
the raw event logs gain a **Network time zone** entry, their new default. Flight start and end are shown and
entered in network time, with a **Network time** label in place of the *Local time* switch; the REST API keeps
UTC for flight dates, while the hour a scheduled report is e-mailed at is network time.

**Nothing changes on upgrade** — every network stays on UTC. The zone is not a console setting, because moving it
moves the day boundary of every report row and cap: ask Epom support or your account manager. Days before the
change keep their UTC dates. The zone your network runs on is shown beside the date range of a General report and on
**System preferences**, and is in `GET /rest-api/network/settings.do` (`reportingTimeZone`). The Advanced
report's zone selector names it, *Network time zone (America/New_York)*, and can be searched by offset, region or
city. See [Limits](https://epom.com/ad-server/docs/guides/delivery/limits.md) and [Run a report](https://epom.com/ad-server/docs/guides/analytics/run-report.md).

## Analytics

**The Advanced report filters custom parameters a row at a time** — a name and the values it may hold, values
within a row OR-ed, two rows narrowing each other, and a switch asking for any of them instead. A row left
without a value counts every request carrying that parameter whatever its value, which the old single pair
could not express. Saved reports, scheduled reports and API callers written before this keep working
unchanged.

**The hour window sits with the dates**, in the row that is already about time, instead of among the twenty
filters, and a window that ends before it starts is refused instead of running an empty report. **The Impressions, Clicks, Conversions and Actions Log take the same hour window**, read in the log's time zone on every day of the range, so a night or a lunchtime of events no longer means exporting whole days. **The placement-size filter takes several sizes**, and a report filtered to them counts the
placements of any of them and names every size in its header and exports. And **the DSP analytics money column is called Revenue**, not Spend: the page belongs to
the network, which is the selling side, so an operator was reading their income filed under expenses. The API
field and the sort value stay `spend`, so integrations keep working. See
[Run a report](https://epom.com/ad-server/docs/guides/analytics/run-report.md).

**The raw event logs show event time with its UTC offset**, `2026-11-01T01:30:00-04:00` instead of
`2026-11-01 01:30:00`, which happens twice on the night the clocks go back. Anything that parses the Date column of
an exported log needs the new shape.

**The raw event export** has a new reference page listing every folder and column, and two options Epom support can
switch on: **daily lookup files** with the names and ISCI or Ad-ID codes the event rows do not carry, and
**empty-hour files**, so that a missing file means a delivery problem rather than no traffic. See
[Raw event export files](https://epom.com/ad-server/docs/reference/raw-event-export-files.md).

**The *Ask for a report* box above the Filter form is gone.** It filled only part of the form and often misread
the request; the [AI assistant](https://epom.com/ad-server/docs/guides/collaboration/ai-assistant.md) answers a report question by running the
report itself.

## Publishers

**Whether a placement's third-party tracking code fires without an ad is now your setting.** The code used to
be emitted on an unfilled response only if the placement also had a default code, so a placement with no
passback lost its no-fill pixel hits entirely. Both are fixed: the pixel no longer depends on the passback, and
a checkbox on the Third-Party tracking codes card decides whether it answers every request or only one filled
with a creative. Nothing changes on upgrade — the flag is backfilled so the pixels that fire today keep firing.
A passback is not a fill.

**Auto-linking drift shows in the edit page header.** Site, placement, campaign and banner pages check in the
background and show an amber "Auto-linking: N to add, M to remove" badge with **Apply auto-linking** only when
the links have actually drifted. A site with more than a thousand placements used to be skipped; those are now
checked in the background and the header serves the stored result like any other site's.

**A large site's edit page opens in 40 ms instead of 3 seconds.** The page was loading every zone and every
zone's placements and needed none of them.

## Also in this release

- **The network's internal notes about an account are no longer readable by that account.** What your
  operators write about a user — and the contact-form messages appended to it — was returned by the user's own
  profile read over the API, and the user could overwrite it. The field is gone from both; your own user edit
  page still shows and edits it.
- **A visitor without consent is anonymised in the bid request too.** Under *Require IAB consent* with *Do not
  use any cookies or server-side processing*, such a request still handed DSPs the advertising ID, the synced
  buyer ID, the DSP's own cookie data, coordinates passed by the tag and, in some cases, the full IP. All of
  them are withheld now, and the bid request carries an anonymised IPv6 address with only its first three
  groups. See
  [GDPR / TCF consent](https://epom.com/ad-server/docs/guides/admin/gdpr-tcf-consent.md#what-no-consent-actually-costs).
- **A user locked out of sign-in can be helped at once.** Epom support now sees which address, username and
  channel the failed sign-ins come from, and can lift a lock or switch the lockout off for your network. See
  [Platform limits](https://epom.com/ad-server/docs/reference/platform-limits.md#sign-in-and-password-throttling).
- **Traffic Quality scores on what the ad server already refused.** A placement whose impressions were mostly
  refused for a forged signature, or whose clicks were flagged as fraud, used to score like a clean one unless
  other signals happened to fire. Those reasons now feed the score, with the signature reason weighing
  heaviest.
- **Your click and conversion logs carry the advertising ID.** The ad server received the IDFA / GAID on every
  in-app request and had nowhere to keep it. Every alias an SDK or a tracking URL may send is recognised, so a
  conversion whose URL carries `androidAdId=` records it with nothing to configure. Where your GDPR settings
  keep the user ID off a record, the advertising ID is kept off with it.
- **A masked campaign can be found by the name you are shown.** On the Campaigns grid the name filter matched
  the advertiser codename, so typing `CAMPAIGN_412` — the only name a masked viewer can see — returned nothing.
- **Crawlers whose token carries no obvious word are detected again**, the Facebook link preview among them.
- **Flights open and close at the time the console shows.** The start and end were compared with the database's
  local clock, which ran three hours ahead of UTC, so campaigns and banners started, stopped and were switched off
  three hours early. Expect them to start and end later than before, at the time you set.
- **A filled weekly or monthly cap reopens when the week or month turns.** It used to stay closed into the next
  period, and a filled daily cap stayed shut past midnight until the next hourly flush. The Finished badge and the
  limit-reached note follow the new period too.
- **Weekly and monthly caps count every hour and hold across all ad servers.** They missed the last hour of each
  day and were enforced per server, so an entity could run past them; expect delivery to stop at the cap.
- **Ad opportunities are counted on the day they happened**, not the next one, for the last hour of each day.
- **"Last 30 days" covers the same days in the Advanced report as in the General report** — Advanced took 31.
- **A retired macro left in a creative no longer prints its own name** onto the page or into a tracking URL.
- **A Native placement with no native preferences answers no-fill once** on the native ad endpoint instead of
  re-selecting creatives until it gives up. Header-bidding and API requests, which never read those preferences,
  still fill it.
- **Cookie beacons honour the request's cookie policy.** The set-cookie pixel and the multi-cookie beacon were
  storing every parameter as data whatever the policy allowed, mirroring the same values into localStorage, and
  storing the privacy signal itself as a cookie. Under a policy that forbids the user id, no identity is
  resolved at all and the cookie-sync hop is skipped.
- **A DSP's raw VAST markup is no longer URL-decoded on the way in**, which had been rewriting every `%XX`
  inside a `VASTAdTagURI` or tracker URL and losing the ad outright on an unescaped `%`.
- **The third-party click macro in VAST no longer counts a click twice**, and now resolves only where the ad
  server does not already wrap the link.
- **Server-side no-click capping works on cookieless and incognito traffic** — it never worked before, and a
  click used to raise the counter instead of releasing the cap. No-Click caps with subject *IP*, *L.E.S. IP* or
  *User (server-side storage)* — and a *mixed* one on cookieless traffic — did nothing until now and **start
  limiting delivery on upgrade**; review any you left in place. An *IP* or *L.E.S. IP* No-Click cap is shared
  by everyone behind the address. See [Frequency capping](https://epom.com/ad-server/docs/guides/delivery/frequency-capping.md).
- **Read-only pricing and limit cards on sites and placements are read-only.** A role with read-only access
  to revenue share, the payment model, limits, the CPM threshold, content replacement or server-side trackers
  saw Add, Delete and the edit pencil on those cards, and every click was refused.
- **Switching a banner's or a placement's template version no longer loses its uploaded file.**
- **Saving your own settings no longer reverts your API key.** Rotating a key and then changing any own setting
  silently restored the old one.
- **An account whose role was deleted is back in the Users grid** and can be given a new role — the one thing
  it needs. Only a role that may manage roles can repair it.
- **A DSP partner gets a win-notice timeout of its own**, so a partner whose win-notice endpoint is slower than
  its bid endpoint stops losing notices. See [DSP partners](https://epom.com/ad-server/docs/guides/admin/dsp-partners.md).
- **One user can no longer run unlimited heavy reports at once** — the default cap is 100 per node.
- **External Storage Export shows every node at once**, and the root re-sends whatever is still missing on a
  node that cannot upload for itself.
- **The troubleshooting log names the capped counter that stopped a creative**, lists how close the others
  were, and no longer lists the creative that was actually served as excluded. The bid request reads as
  indented JSON.
- **Long section descriptions moved behind the ⓘ beside the heading**, the way field help already had.

## For API and assistant users

- **Upstream macro presets** are read by anyone signed in and changed under the admin page's permission:
  `GET /rest-api/upstream-macro-presets` lists them with the parameters each can fill, and `POST` adds or edits
  one. A placement names its preset by code on `ssaiStitcher`, which now accepts any active preset; `whoami`
  reports `may.upstreamMacroPresets`. See [Other admin tools](https://epom.com/ad-server/docs/guides/admin/admin-tools.md#over-the-api).
- **Advertising and publishing categories** can be created and renamed over REST — six new operations, each
  gated on the admin permission of its own page. Deleting a category is deliberately left out.
- **A user can be deleted** over the API, enforcing the Users grid's own rule: the DELETE permission on the
  user's role — at All level for any user of it, at Shared level for the users you own.
  The in-product assistant still refuses it; an external MCP client may run it.
- **Which custom parameters and cookie names the raw logs keep** is read and written over REST instead of being
  admin-UI only.
- **The banner read answers the creative's preview link** (`previewUrl` and `previewPath`), the signed
  no-login link the console's Preview button opens.
- **The Action API sets and reads back the detailed conversion log**, which had no parameter at all.
- **`searchCampaigns` takes the sort field its reference documents** — `sortBy` was declared as an integer, so
  `{"sortBy":"name"}` was refused.
- **Piggybacks of a deleted campaign answer 404** instead of still listing the campaign's name, codes and
  actions.
- **A placement's traffic-quality dataset can be fetched without the AI findings and the PDF.** The MCP tool
  is now `traffic_quality_analyze`, data only by default, `report=true` for the full report. See
  [MCP](https://epom.com/ad-server/docs/api/mcp.md).
- **A VAST companion can wrap its own link in the counted click URL** with the new
  `$$VAST_CLICK_THROUGH_URL$$`, which a hand-written HTML or iframe companion previously had no way to reach.
- **Three dead fields are gone from the API.** Advertiser reads no longer return `selfServeCpmFloorAdjustment`,
  campaign reads (and campaign summary details) no longer return `selfServeCampaignType`, and banner reads no
  longer return `applicationPreCheckPackages`; a create or update that still sends one is not applied and
  names it under `ignoredFields`. The MCP `whoami` loses `cpmFloorAdjustment`.
- **Placements linked to a creative with nothing to render say which field is empty**: `contentIssue` on
  `GET /rest-api/placements/{placementId}/banners.do` and in the MCP `placement_overview`, and a blocker in
  the serving check.
- **Whether a placement's tracking code fires without an ad** is read and set with `GET` and `POST`
  on `/rest-api/tracking-code/placement/fire-without-ad.do`. See
  [Linking, status, tracking codes and ad tags via API](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#third-party-tracking-codes).
- **New reference pages**: the ad-request parameters an integrator should send, and every cookie an
  ad-serving domain sets.