---
title: "Linking, status, tracking codes and ad tags via API"
description: "API reference for linking banners to placements, serving checks, activation and bulk on/off, list filters, bulk labels, banner approval, third-party tracking codes and retrieving ad tags."
type: "reference"
audience: [network-manager, admin]
keywords: [api, linking, serving check, activation, active, bulk status, approval, labels, bulk labels, filters, fields, tracking code, ad tag, invocation code, JS_MOUNT, curl]
---

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.

# Linking, status, tracking codes and ad tags via API

Once the [advertisers and campaigns](https://epom.com/ad-server/docs/api/entities-advertisers-campaigns.md), [banners](https://epom.com/ad-server/docs/api/entities-banners.md) and [sites, zones and placements](https://epom.com/ad-server/docs/api/entities-sites-zones-placements.md) exist, these calls tie them together and put them live: linking banners to placements, switching entities on and off, which fields need a permission, approval, list filters and labels, third-party tracking codes and the ad tag. Examples append [`$AUTH`](https://epom.com/ad-server/docs/api.md#the-auth-shorthand), the hash-authentication parameters (an `X-Api-Key` header works the same), and send parameters form-encoded unless noted — see the [API overview](https://epom.com/ad-server/docs/api.md) for authentication and [Entities via API](https://epom.com/ad-server/docs/api/entities-crud.md) for the corner cases every entity shares.

## Who this is for

Network managers, publishers and advertisers who link, activate and tag entities from a script, and admins. Linking, activation, approval, labels and each ad-tag type are gated by their own role permissions, named in each section.

## Linking banners and placements

Links can be managed from either side:

From the banner side:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/banner/{bannerId}/placements.do` | Placements linked to a banner |
| GET | `/rest-api/banners/{bannerId}/placements.do` | The banner's linked site placements as `id` / `name` (view access to the advertiser needed) |
| GET | `/rest-api/banner/{bannerId}/matchplacements.do` | Compatible placements **not yet linked** (filters: `publisherCategories`, `labels`), each with `type`, `zoneId`, `siteId` and, when it has a size, `width` / `height` |
| GET | `/rest-api/banner/{bannerId}/mismatched-placements.do` | Placements of exactly the banner's size, on sites you may link to, that it **cannot** be linked to because their kind differs (a mobile placement for a desktop banner) — the ones a "link it to every 300x250" request would otherwise skip silently. Same row shape as `matchplacements`; `404` for a missing or deleted banner, `403` without view access to its advertiser |
| PUT | `/rest-api/banners/{bannerId}/placements.do` | Set/add links from the banner side (`ids` array, `append` flag) |
| DELETE | `/rest-api/banners/{bannerId}/placements.do` | Remove links listed in `removeIds` |

From the placement side:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placements/{placementId}/banners.do` | Banners linked to a placement (needs view access to its site; banners of a private advertiser you have no share on are left out) |
| PUT | `/rest-api/placements/{placementId}/banners.do` | Set/add links from the placement side (`ids`, `append`) |
| DELETE | `/rest-api/placements/{placementId}/banners.do` | Remove links listed in `removeIds` |

And the question both sides end up asking — why a banner is not serving on a placement:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/serving-check.do` | Why a banner does not serve on a placement: `bannerId`, `placementId`, optional `country` (two letters). Answers `blockers` — any of the six levels deleted or switched off (and, when the ad server switched it off itself because a total impressions, clicks or conversions limit was reached or the end date passed, which one: raising the limit or moving the date does not switch it back on), a delivery limit that is full on any level but the zone, moderation that switched the banner off, a missing link or links switched off on either side, a kind or size mismatch, a video file off the site's requirements, the banner's or campaign's run dates, a location rule naming a place the geo database no longer knows that keeps every visitor out (an exclude, or includes that are all such places) and, with `country`, a country rule that keeps that visitor out — `warnings` — what does not stop serving: an approval pending on a banner that is on, a site the network has not approved, stale location includes next to ones that still exist — and `targeting`, each level's rules as text — the site, zone and placement rules only with the *Manage Supply Pre-targeting* permission and the advertiser, campaign and banner rules only with *Manage Demand Targeting* (either one enabled or read-only), as the console shows them; a hidden level whose country rule keeps the visitor out is still named as a blocker, without its rules. `404` for a missing banner or placement (a deleted one is answered as a blocker), `403` without view access to either side |

```bash
curl -X PUT "https://<your-network-domain>/rest-api/banners/5001/placements.do?$AUTH" \
  -d "ids=4001" -d "ids=4002" \
  -d "append=true"
```

> **Warning:** with `append=false` (the **default**), the `ids` you send **replace the
> banner's entire link set** — any placement not listed is unlinked. Pass `append=true`
> to add links without touching existing ones. Unlinking needs the same reach as the
> `DELETE` call: if a link you would drop sits on a site your grant cannot manage links on,
> the call fails with `placements_cannot_be_linked` listing it and nothing changes.

> **Corner case:** the two `DELETE` verbs take **`removeIds`**, not `ids`. Reusing the `PUT`
> parameter name is a `400` (`Parameter missing: removeIds`) on both sides, and nothing is
> unlinked.

An id of no placement, or of a deleted one, is skipped: the rest are linked and the answer
lists the skipped ones in `skipped` as `{id, reason}` — *no entity with this id* or *it is
deleted*. Every placement that is linked must be type-compatible with the banner, or the call
fails validation; when no id is left to link, the call fails too. The placement-side call skips
unknown and deleted banner ids the same way, in the same `skipped` list that already names the
banners that do not fit the placement.

Ids your *Link Placements to Banners* grant does not reach are rejected rather than skipped:
the call fails with `403` and `{"success": false, "placements_cannot_be_linked": [4002]}` and **nothing
is written**. With the grant at *Shared* every id must sit on inventory shared with you; at
*All* any placement in the network is accepted; at *Linked* / *Shared linked* only ids already
linked to the banner are accepted, since those grants never create a link. The placement-side
`PUT /rest-api/placements/{placementId}/banners.do` answers the same way with
`banners_cannot_be_linked`, and additionally needs the right to update the placement's site:
without it the call is a `403` and nothing is written, whatever the link grant.

## Activation, approval, status

- **Activation** is the `active` boolean on each entity's create/update endpoint — there
  is no separate activate endpoint. Deactivating an advertiser stops everything beneath
  it. `active`, the feature switches and several other fields take effect only with the
  permission the console asks for before it changes them. Without it the rest of the create or
  update is applied, the field is left as stored (on create: at its default), and it is listed
  in `ignoredFields` as `not permitted` (see
  [Parameters that were not applied](https://epom.com/ad-server/docs/api.md#parameters-that-were-not-applied)):

| Entity | Field | Permission it needs |
|---|---|---|
| Advertiser, campaign, banner, site, zone, placement | `active` | *Manage Advertisers' Active Status*, *Manage Campaigns' Active Status*, *Manage Banners' Active Status*, *Manage Sites' Active Status*, *Manage Zone's Active Status*, *Manage Placement's Active Status* — one per entity type |
| Advertiser | `enableTargeting` | *Manage Demand Targeting* |
| Site, zone, placement | `enableTargeting` | *Manage Supply Pre-targeting* |
| Any | `operationBetweenRuleTypes` | The side's targeting permission plus *Manage Operation Between TargetingRule Types* |
| Any | `enablePriceAdjusting` | *Manage Price Adjusting* |
| Any | `actionLimitsEnabled` | *Manage Capping & Limitations* plus the campaign or inventory per-action limit permission |
| Any | `enableRules` | *Manage Rules* |
| Advertiser | `adomain` | *Set Advertiser Domain (OpenRTB adomain)* |
| Advertiser | `privateField` | *Set Private* |
| Advertiser | `confirmationDelay`, `preliminaryConfirmationDays`, `lastConfirmedDate` | *Manage Completeness Delay for Advertisers*. The date is `yyyy-MM-dd` and an empty value clears it; `GET /rest-api/advertisers/{id}.do` returns the three under the same permission |
| Advertiser | `adsHost` | *Set Ad Serving Domain (Campaigns)* |
| Advertiser | `selfServeCpmFloorAdjustment` | *Self-serve CPM Floor Adjustment* (0–10000, where 100 means no adjustment) |
| Campaign | See [Campaign settings that need a permission](https://epom.com/ad-server/docs/api/entities-advertisers-campaigns.md#campaign-settings-that-need-a-permission) | |
| Site | See the corner cases under [Sites](https://epom.com/ad-server/docs/api/entities-sites-zones-placements.md#sites) | |
| Placement | `customOpenRtbTagId` | *Open RTB* |
| Placement | `videoAdMinDuration`, `videoAdMaxDuration`, `videoAdSkipMin`, `videoPlaybackMethod`, `videoApiFramework` | *Video RTB Configuration* |
| Placement | `serverSideTrackersEnabled` | *Placement Server-Side Trackers* |
| Placement | `contentReplacementEnabled` | *Placement Content Replacement* |
| Placement | `piggybackStatus` | *Piggyback Pixel Tracking* |
| Placement | `alwaysRedirect` | *Always Redirect* |
| Placement | `rotateInterval` | *Auto Refresh* |
| Placement | `allowVariableBannerSizes` | *Flexible Placement Size* |
| Placement | `disableAutoLinking` | *Auto-linking settings* |
| Placement | `enablePricingOverride` | *Manage Pricing* |
| Placement | `servingDomain` | *Set Ad-Serving Domain (Placement)* |
| Placement | `defaultCode` | *Placement Default Code* |
| Placement | `labels`, `labelUpdateType` | *Placement Labels* |
| Placement | `nativePreferences.*Size.*` | *Native Placement Sizes* |
| Placement | `nativePreferences.requiredFields` | *Native Placement Required Fields* |
| Placement | `nativePreferences.contextType` | *Native Placement OpenRTB Context Type* |
| Placement | `nativePreferences.contextSubType` | *Native Placement OpenRTB Context Sub Type* |
| Placement | `nativePreferences.placementType` | *Native Placement OpenRTB Placement Type* |
| Placement | `enableLinking` | Any *Link Placements to Banners* scope but none |
| Banner | `weight` | *Manage Banner Weight* |
| Banner | `labels`, `labelUpdateType` | *Banner Labels* |
| Banner | `allowUseForTemplate`, `targetTemplateName` | *Templates* |
| Banner | `dspPartnerId`, `usePlacementSize`, `always2ndPrice`, `advertisingNetworkParameters[…]`, `mimeTypes` | *Open RTB* |
| Banner | `skipButtonShowAfterSeconds` | *Banner Video Skip Button* |
| Banner | `applicationPreCheckPackages` | *Application pre-check* |
| Banner | `hardLinkage` | *Link Placements to Banners* |
| Banner | `thirdTrackingCode[…]` | *Banner Third-Party Tracking Code* |
| Banner | `universalAdIdValue`, `universalAdIdRegistry` (a video creative's VAST UniversalAdId) | *Banner ISCI / Ad-ID Code* |
| Banner | `enableViewTracking` | *Banner View Tracking* |
| Banner | `allowNewPlacementsAutoLinking`, `autolinkCategories` | *Auto-linking settings* (`autoLinkLabels` also needs *Placement Labels*) |
| Banner | `status` | *Banners Approval* — see **Banner approval** below |

A read-only grant does not count: it shows the control in the console but does not let
it be saved. This applies whether you authenticate with hash parameters or call from a
logged-in session; a `username` sent without `hash` and `timestamp` authenticates nothing
and lends the call none of that user's permissions.

- **Bulk on/off** switches up to 500 advertisers, campaigns or banners in one call, with the
  same rules as the console grids' on/off toggle:

| Method | Path | Purpose |
|---|---|---|
| POST | `/rest-api/advertisers/status.do` | `ids` (repeat the parameter) and `active` = `true`/`false` |
| POST | `/rest-api/campaigns/status.do` | Same for campaigns |
| POST | `/rest-api/banners/status.do` | Same for banners |

```bash
curl -X POST "https://<your-network-domain>/rest-api/banners/status.do?$AUTH" \
  -d "ids=3001" -d "ids=3002" -d "ids=3999" -d "active=false"
# → {"success":true,"changed":[3001],"skipped":[{"id":3002,"reason":"unchanged"},{"id":3999,"reason":"not-found"}]}
```

  Unlike the single update, a missing grant is **not** silent here: without *Manage
  Advertisers' / Campaigns' / Banners' Active Status*, or when any id belongs to an advertiser
  outside your update share, the call returns **403 and changes nothing**. Ids already in the
  requested state are skipped as `unchanged`, deleted or unknown ids as `deleted` /
  `not-found`, and a template creative whose template is inactive is skipped as
  `inactive-template` when switched on without *Update properties and status, based on inactive template*. A
  call where none of the ids exists returns 404; no ids or more than 500 return 400. Each
  switched entity raises the same activated / deactivated notification as a single update.
- **List filters narrow, and a list filter matches any of its values.** `placementTypes=VIDEO_PLACEMENT`
  on `banners.do` returns the creatives built for video placements; `types=VIDEO_PLACEMENT&active=true`
  on `placements/summary.do` returns the switched-on video placements. A `labels` filter naming no
  label that exists returns an empty list rather than every entity, so a typo in a label name never
  looks like "everything carries it".
- **`filter[…]` narrows the advertiser, campaign, banner, site, zone and placement lists** by
  any on/off setting of the entity — `filter[cpaOptimization]=true`, `filter[enableTargeting]=true`,
  `filter[alwaysRedirect]=false` — and by a `has<Relation>` flag for every setting the entity keeps
  in rows of its own: `hasLimit`, `hasPricing`, `hasFrequencyCapping`, `hasTrackingCode`,
  `hasPriceAdjustmentRules`, `hasLabels`, `hasBanners`, `hasZones` and so on. A flag is true when the
  relation holds at least one row — a limit or pricing row exists only once it is switched on in
  the console. `hasCapping`, `hasLimits` and `hasTargetingRules` group the relations of their kind;
  note that `hasTargetingRules` (rules exist) and `enableTargeting` (rules are switched on) are
  different questions. Every key must match. The keys come from each entity's data model, so the
  list differs per entity and grows with it: a key the list does not know is a 400 whose message
  lists every valid key for that list. On banners and placements the filter runs in the database,
  so `offset` / `limit` page the filtered result.
- **`fields` trims each entity** to the named fields (`id` is always kept) — `fields=id,name,active`
  turns a 1,000-campaign list from megabytes into a few dozen kilobytes. A requested field that no
  returned row carries is named in the `X-Ignored-Fields` response header rather than failing the call.

```bash
# Campaigns with CPA optimisation on that also carry a frequency or click cap, 100 at a time
curl "https://<your-network-domain>/rest-api/campaigns.do?$AUTH&filter%5BcpaOptimization%5D=true&filter%5BhasCapping%5D=true&fields=id,name&offset=0&limit=100"
```

- **Only the documented fields are applied.** Each create/update endpoint binds the
  parameters listed for it and ignores every other one — naming it in `ignoredFields` — so a nested path such as
  `zone.name`, `size.adUnit.width`, `categories[0].name` or `limit.totalImpressionsLimit`
  changes nothing. Related entities are named by id only — `siteId`, `zoneId`,
  `advertiserId`, `categoryId`, `adUnitId`, `autolinkCategories` — and are linked, never
  edited. Limits have their own endpoints (see
  [Targeting, capping and limits](https://epom.com/ad-server/docs/api/targeting-capping-limits.md)).
- **Site approval** is never taken from the request. A site created by a caller whose role
  holds the site-approval permission is approved as part of that call; everyone else's new
  sites start at the network's default status. An update never changes the approval
  status, so renaming a site a moderator rejected leaves it rejected.
- **Parents.** A zone's site comes from `siteId`, a new placement's zone from `zoneId`.
  Sending an existing zone's `id` with a different `siteId` **moves** the zone, which needs
  update rights on both the site it leaves and the site it joins; otherwise the call
  returns `permission_denied` and nothing changes. Sending an existing placement's `id`
  with a different `zoneId` moves the placement to that zone the same way, on the same or
  another site; its banner links stay.
- **Banner approval** (when your network moderates creatives):

| Method | Path | Purpose |
|---|---|---|
| POST | `/rest-api/banner/{bannerId}/approvalstatus.do` | Set `status` = `NEW`, `PENDING`, `APPROVED`, `REJECTED` |
| POST | `/rest-api/banners/sendToApprove.do` | Submit banners for approval |
| POST | `/rest-api/banners/reaprove.do` | Re-approve banners (note the spelling — one `p`) |

Those four are the whole `status` enum. Setting a status the banner already has is a no-op
that returns a message. Moving to `APPROVED` or `REJECTED` notifies every user who has a
share on the banner's advertiser.

The call needs the *Banners Approval* permission (`BANNERS_APPROVAL`); without it the answer is
a `403` naming that permission, whatever the banner and its current status.

The banner create and update calls also take a `status`, under the same rule as
`approvalstatus.do`: changing it needs the *Banners Approval* permission. Without it the
status is not changed and `status` is listed in `ignoredFields`, with `ignoredFieldReasons`
naming the permission, while the rest of the create or update is saved — a new banner gets the
status the network's moderation gives it (`NEW` when your role's creatives need approval,
`APPROVED` otherwise), and an edited one keeps the status moderation leaves it. Sending the
status the banner already has changes nothing and is not reported. `approvalstatus.do` itself
still answers `403` without the permission.

- **Removing a banner's media.** `DELETE /rest-api/banner/{bannerId}/video.do` removes the
  uploaded video file (400 when the banner has none). The image is removed through the
  update instead: send `deleteImage=true` to `/rest-api/banner/{bannerId}/update.do`. A
  *Local File* banner cannot be left without an image, so it must upload the replacement in
  `imageFile` in the same call (400 otherwise); the old file is then deleted from storage.
  Naming the replacement in `imageFileUrl` does both in one step, without `deleteImage`.
  Both need update access to the banner's advertiser.

### Bulk labels

`POST /rest-api/banners/labels.do`, `/rest-api/placements/labels.do` and
`/rest-api/campaigns/labels.do` apply one label change to up to 500 entities, the same action as
the console grids' **Labels** button. `labelUpdateType` picks the change: `ADD` (default) appends,
`OVERWRITE` replaces the entity's labels, `REMOVE` takes the named labels off, `CLEAR` removes every
label and ignores `labels`.

```bash
# Every switched-on video placement of site 1001 gets the label "video-q4"
curl "https://<your-network-domain>/rest-api/placements/summary.do?$AUTH&siteId=1001&types=VIDEO_PLACEMENT&active=true"
curl -X POST "https://<your-network-domain>/rest-api/placements/labels.do?$AUTH" \
  -d "ids=4001" -d "ids=4002" -d "ids=4999" -d "labels=video-q4"
# → {"success":true,"changed":[4001],"skipped":[{"id":4002,"reason":"unchanged"},{"id":4999,"reason":"not-found"}]}
```

The rules follow bulk on/off: without the entity's labels permission (*Banner Labels*,
*Placement Labels*, *Campaign Labels*), or when any id is outside your update share, the call
returns **403 and changes nothing**. A label name that does not exist yet is created when you hold
*Create Labels*; otherwise the call returns 400 naming it. Entities whose labels would not change
are skipped as `unchanged`, deleted or unknown ids as `deleted` / `not-found`; a call where none of
the ids exists returns 404, and no ids, more than 500, or no `labels` for `ADD` / `OVERWRITE` /
`REMOVE` return 400.

## Third-party tracking codes

A banner or a placement can carry HTML tracking code that fires when it serves, for every country
(`country=*`) or for one. The two families have the same eight operations — under
`/rest-api/tracking-code/banner/` and `/rest-api/tracking-code/placement/` — and take
**`domainId`, the banner or placement id**, as server-side trackers do:

| Method | Path (after `/rest-api/tracking-code/banner/` or `…/placement/`) | Purpose |
|---|---|---|
| GET | `list.do` | The entity's codes: `id`, `code`, `country` (`*` is global) and `countryStr` (`domainId`) |
| POST | `add.do` | Add or **replace** the code for that entity and country (`domainId`, `country`, `code`) |
| POST | `add/batch.do` | The same code on many entities (`domainIds`, `country`, `code`); answers a map keyed by id, each `{success}` |
| POST | `edit.do` | Change one code by `id`, moving it to another `country` if that differs; the global code is edited with `country=*` and `id=0` |
| POST | `delete.do` | Remove codes: `countryTrackingCodeIds`, and/or `clearGlobalTrackingCode=true` |
| POST | `global-clear/batch.do` | Clear the global code on many entities (`domainIds`); country codes stay |
| POST | `enable-tracking-code.do` | Switch the feature on with an empty set (`id`); on already, nothing changes and the codes are kept |
| POST | `disable-tracking-code.do` | Switch it off (`id`). **This deletes every code of the entity** — there is no dormant state |

An unknown `country` on `add.do` or `edit.do` is a `400` under `errors.country`. Switching the
feature on or off needs *Banner Third-Party Tracking Code* or *Placement Third-Party Tracking Code*
and update rights on the entity, otherwise a `403` naming the permission; an entity or code that
does not exist is a `404`. A banner's `thirdTrackingCode[…]` on `banner/create.do` / `update.do` is
the same setting from the other side.

## Getting the ad tag

Once a placement exists, retrieve its serving tag by placement **key**, not by its numeric
ID. The key is 32 lowercase hex characters, generated by the server when the placement is
created, shown on the placement, and never rewritten. Moving the placement or its zone keeps
it, so a published tag keeps working; do not try to derive it from the ids. **`GET /rest-api/placements/summary.do` and the single read
`GET /rest-api/placements/{placementId}.do` return it**, as the field `key`;
`placements/all.do`, the site tree and the zone placement lists do not carry it.

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placement/{key}/code.do?t=AJAX` | Invocation code; `t` is **required** — `AJAX`, `JS_MOUNT`, `IFRAME_SIMPLE`, `DIRECT_URL`, `API`, `EMAIL`, `VIDEO_PLAYER`. `tpId` is an internal test-page id — leave it out |
| GET | `/rest-api/placement/{key}/vast-tag.do?v=3.0` | VAST tag URL for video placements (`v` = `2.0`, `3.0`, `4.0`; default 3.0) |
| GET | `/rest-api/placement/{key}/vmap-tag.do` | VMAP tag |
| GET | `/rest-api/placements/{placementId}/invocation-code-types.do` | The tag types your role may generate for this placement (`placementType`, `types`) — the values `invocation-code.do` takes; `types` is empty when the role holds none of the invocation-code permissions for that placement type |
| GET | `/rest-api/placements/{placementId}/invocation-code.do?type=AJAX` | The tag by placement **id**, checking your view access to its site and the per-type permission |
| GET | `/rest-api/placements/{placementId}/invocation-code/export.do?type=AJAX` | The same tag as a text-file attachment, as the console's download button hands it out |
| GET | `/rest-api/placements/{placementId}/preview-link.do` | A shareable preview link (`url`, `path`) that renders the placement as a visitor sees it, needs no login and works for 30 days; optional `codeType` (default `AJAX` when your role may generate it). Your role needs the invocation-code permission of the tag it renders, as for the ad-tag card — `403` otherwise, or when it may generate no tag for the placement at all. `servesOnlyWhileActive` is false when the placement, its zone or its site is switched off — the preview then shows no ad |

`t` is bound to an enum, so a value outside that list is a **400**, not an empty code, and
omitting it is `400 {"success":false,"message":"Parameter missing: t"}`. Those seven are
the whole list: `IFRAME_SYNC`, `IFRAME_ASYNC`, `JS_SYNC`, `JS_ASYNC`, `JSP`, `PHP`,
`API_JS_SYNC_CODE` and `LITE` were removed, and a caller still sending one now fails
outright — see [Retired code types](https://epom.com/ad-server/docs/reference/invocation-codes.md#retired-code-types) for what to
migrate each to.

A key that names no placement (or one that is not active) is a **404** on `code.do`, `vast-tag.do`
and `vmap-tag.do` — and on the deprecated `placement-code/{key}.do` — with `success: false` and
the key in the message.

> **Note:** the four key-based reads (`code.do`, `vast-tag.do`, `vmap-tag.do` and the deprecated
> `placement-code/{key}.do`) run no REST authentication and no role check — the key in the path is
> the only thing they look at, so treat it as a secret. `placements/{placementId}/invocation-code.do`
> is the variant that checks your access to the site and the permission for the tag type.

The names differ from the tab labels in the control panel: `AJAX` is **Universal JS**,
`JS_MOUNT` is **Mount JS**, `EMAIL` is **Mail**.

**`JS_MOUNT`** is the one most integrations miss. It returns a bare anonymous function —
`(function(container, opts) { … })(container, opts)` — with no surrounding `<script>` tags, for
you to inline into your own script context.

Both `container` and `opts` are resolved from **your** scope at the call, so both must be
declared there. `container` is the element the ad mounts into; declare it and leave it
unassigned and the snippet falls back to the running script's next sibling element. If
neither resolves, it returns silently and nothing renders.

```js
var container = document.getElementById('ad-slot-leaderboard');
var opts = { channel: 'homepage' };
// paste the JS_MOUNT snippet here
```

> **Warning:** leaving `container` **undeclared** rather than unassigned throws a
> `ReferenceError` before the snippet body ever runs — the fallback never gets a chance.

`opts` overrides the placement's baked-in defaults per call, for exactly five keys:
`channel`, `click`, `customParams`, `bundle` and `ifa`. `customParams` merges key by key
rather than replacing, so a parameter baked into the tag survives unless you override that
key by name. It shares the Universal JS loader rather than running a parallel one, so
mounting several placements on a page is safe. Use it for single-page apps and late
injection — anywhere the ad slot is created by your own code rather than sitting in the
HTML where a pasted `AJAX` tag would go.

There is also a deprecated `GET /rest-api/placement-code/{key}.do`, which returns only the
Simple Iframe and Direct URL forms. Use `placement/{key}/code.do` instead.

See [Invocation codes](https://epom.com/ad-server/docs/reference/invocation-codes.md) for what each format does.

## Related

- [Entities via API (overview)](https://epom.com/ad-server/docs/api/entities-crud.md)
- [Advertisers and campaigns via API](https://epom.com/ad-server/docs/api/entities-advertisers-campaigns.md)
- [Banners (creatives) via API](https://epom.com/ad-server/docs/api/entities-banners.md)
- [Sites, zones and placements via API](https://epom.com/ad-server/docs/api/entities-sites-zones-placements.md)
- [API overview](https://epom.com/ad-server/docs/api.md)
- [Hierarchy and linking](https://epom.com/ad-server/docs/concepts/hierarchy-and-linking.md)
- [Invocation codes](https://epom.com/ad-server/docs/reference/invocation-codes.md)
- [Ad tags, invocation codes and the API](https://epom.com/ad-server/docs/guides/inventory/ad-tags-and-api.md)