---
title: "Sites, zones and placements via API"
description: "API endpoints, curl examples and corner cases for the supply side: sites, zones and placements of every type, including copy and move, serving domains, daisy chaining and content replacement."
type: "reference"
audience: [network-manager, admin]
keywords: [api, crud, site, zone, placement, inventoryType, bundle, createDefaultZone, move, copy, serving domain, daisy chaining, content replacement, auto-linking, 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.

# Sites, zones and placements via API

The supply side of the account: a site holds zones and a zone holds placements, the ad slots that banners are [linked to](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#linking-banners-and-placements) and whose [ad tag](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#getting-the-ad-tag) you publish. This page covers the site, zone and placement endpoints, the fields that change how they behave, and their corner cases. 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 and publishers who build out or maintain inventory from a script, and admins. Several site and placement settings apply only when your role holds the matching permission — see the corner cases below.

## Sites

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/sites.do` | List all accessible sites, ordered by id (filters: `publishingCategories`, `filter[…]`; `offset` / `limit`; `fields`; `basicInfo` for extra detail) |
| GET | `/rest-api/sites/{siteId}.do` | Get one site — every field `sites/update.do` writes, under the same name; a field whose console control needs a permission you lack is left out and named in `fieldsHiddenByPermission`, mapped to that permission's identifier (`categoryId` needs *Inventory Categorization*, enabled or read-only). `approvalStatus` is the network's approval of the site (`APPROVED`, `PENDING`, `REJECTED`) — a site that is not approved serves no ads |
| GET | `/rest-api/sites/ids.do` | Fetch several sites by ID (`siteIds`, repeated; required — `400` without it) |
| GET | `/rest-api/sites-tree.do` | Full site → zone → placement tree. On a large network read it in pages with `start` (entries to skip, 0 or more) and `limit` (1 or more; anything else is a `400`) — sites ordered by id, a page shorter than `limit` is the last. `basicInfo` and `publishingCategories` also apply |
| POST | `/rest-api/sites/update.do` | Create (no `id`) or update (with `id`) |
| POST | `/rest-api/sites/{siteId}/delete.do` | Delete site |
| GET | `/rest-api/sites/{siteId}/zones.do` | Zones of one site, ordered by id (`filter[…]`, `fields`) |

Ad-serving and tag domains:

| Method | Path | Purpose |
|---|---|---|
| POST | `/rest-api/sites/{siteId}/changeAdServingDomain.do` | Change ad-serving domain (`host`; omit to reset to the default) |
| POST | `/rest-api/sites/{siteId}/changeInvocationCodeDomain.do` | Change tag domain |
| GET | `/rest-api/adsHosts.do`, `/rest-api/invocationCodeHosts.do` | Valid domain choices |

```bash
curl -X POST "https://<your-network-domain>/rest-api/sites/update.do?$AUTH" \
  -d "name=Harbour Gazette" \
  -d "inventoryType=WEBSITE" \
  -d "url=https://harbour-gazette.example" \
  -d "email=publisher@harbour-gazette.example" \
  -d "createDefaultZone=true" \
  -d "active=true"
```

`inventoryType` decides what the site is: `WEBSITE`, identified by its `url`, or `APP`,
identified by its `bundle` (the app bundle ID) — send the one that matches (see the first corner
case below). `createDefaultZone=true` gives a new site its first zone in the same call. Several
site settings apply only with a role permission; they are listed in the corner cases below.

All fields, with descriptions and an example request:
[`POST /rest-api/sites/update.do`](https://epom.com/ad-server/docs/api/sites.md#post-rest-apisitesupdatedo).

> **Corner case:** `url` belongs to `WEBSITE` inventory and `bundle` (the app bundle ID)
> to `APP`, but that pairing is a **form** rule, not a server rule. The API validates and
> stores whichever you send, for either inventory type, and nothing clears the other one.
> It matters downstream: an outgoing OpenRTB bid request reads `bundle` for any `APP`
> site **and** for any application placement — so a `bundle` left on a `WEBSITE` site can
> reach a DSP. Send only the field that matches your inventory.

> **Corner case:** `revenueShare` is a percentage from 0 to 100; outside it the update is a
> `400` naming it and nothing is saved, as is any other field error of `sites/update.do`.

> **Corner case:** `createDefaultZone=true` creates one zone named **exactly** after the
> site — no suffix — and no placement inside it. It is not create-only: sending it again
> on an update creates a second zone with the same name.

> **Corner case:** a new site that ends up approved — its creator holds *Sites Approval*, or the
> network's default status for new sites is approved — and whose creator holds *Automatic
> placements creation for approved sites* also gets the console's auto-placements: a zone named
> `<site name> Placements` with one placement for every active auto-placement of the network. The
> answer carries only the site `id`, so read the site's zones back instead of assuming the site
> is empty.

> **Corner case:** `GET /rest-api/sites/ids.do` requires `siteIds` — omitting it, or sending
> it empty, is a `400` naming the missing parameter rather than an empty list. Always send
> at least one id.

> **Corner case:** the site settings that have a console control of their own are applied
> only with that control's permission enabled — `adsHost` (*Set Ad Serving Domain
> (Inventory)*), `invocationCodeHost` (*Set Ad-Serving Domain (Placement)*),
> `piggybackStatus` (*Piggyback Pixel Tracking*), `privateField` (*Set Private*),
> `maskInventory` (*Manage Mask Inventory*) and `cookieCollectingEnabled` (*Site Cookie
> Collecting*). Without it the stored value stays and the field is listed in
> `ignoredFields` as `not permitted`. With it, `adsHost` and `invocationCodeHost` take the
> same values `changeAdServingDomain.do` / `changeInvocationCodeDomain.do` do — one of the
> network's domains (`adsHosts.do`, `invocationCodeHosts.do`) or blank for the system
> default; anything else fails with `errors.adsHost` / `errors.invocationCodeHost` and
> nothing is saved. Sending back the domain the site already has is always accepted.

## Zones

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/zones/{zoneId}.do` | Get one zone — `id`, `siteId` / `siteName`, `enableRules`, `enableTargeting`, `operationBetweenRuleTypes` and the rest of what `zones/update.do` writes |
| GET | `/rest-api/zones/{zoneId}/placements.do` | Placements of a zone (needs view access to the zone's site: `403` otherwise, `404` for a missing or deleted zone) |
| POST | `/rest-api/zones/update.do` | Create (needs `siteId`, no `id`) or update (with `id`) |
| POST | `/rest-api/zones/{zoneId}/delete.do` | Delete zone |
| POST | `/rest-api/zones/{zoneId}/copy.do` | Copy a zone with its placements into a site, as the console's clipboard copy: `siteId`, optional `name` (later copies numbered ` 2`, ` 3`…), `count`, `keepBannersLinked` (default `true`). Answers `newItemIds` and `copies` (`id`, `name`, `active`, and `placements` with the new placements' `id`, `name`, `active`) |

```bash
curl -X POST "https://<your-network-domain>/rest-api/zones/update.do?$AUTH" \
  -d "siteId=3001" \
  -d "name=Main Content Zone" \
  -d "active=true"
```

A create needs `siteId`. A zone carries little more than its name, its on/off switch and its
pre-targeting and rules switches — everything else about serving lives on the placements beneath
it. All fields, with descriptions and an example request:
[`POST /rest-api/zones/update.do`](https://epom.com/ad-server/docs/api/zones.md#post-rest-apizonesupdatedo).

> **Corner case:** `siteId` together with `id` moves the zone to that site. The move
> needs update rights on both sites; without them the call returns `403` with
> `{"success":false,"error":"permission_denied"}` and a message naming the site, and the zone stays where it was.
> Every placement in a moved zone keeps its placement key, so the ad tags already published for them keep working.

> **Corner case:** toggling a zone's `active` flag through this endpoint only takes
> effect if your role has the zone activation permission — without it the rest of the
> update is applied but the status is left unchanged and `active` is listed in `ignoredFields`
> (see [Activation, approval, status](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#activation-approval-status)).

## Placements

Each placement type has its own create-or-update endpoint (send `id` to update):

| Placement type | Endpoint |
|---|---|
| Standard (display) | `POST /rest-api/placements/update/standard.do` |
| Non-standard (pop, interstitial…) | `POST /rest-api/placements/update/non-standard.do` |
| Native | `POST /rest-api/placements/update/native.do` |
| Video | `POST /rest-api/placements/update/video.do` |
| Mobile web | `POST /rest-api/placements/update/mobile.do` |
| In-app | `POST /rest-api/placements/update/app.do` |
| Click-through | `POST /rest-api/placements/update/click_through.do` |
| E-mail | `POST /rest-api/placements/update/email.do` |

> **Warning:** `click_through.do` is the only **snake_case** path in the whole API — its
> seven neighbours are kebab-case (`non-standard.do`). Copy it exactly.

Reading, copying and removing:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placements/all.do` | List all placements (id + name) |
| GET | `/rest-api/placements/summary.do` | Placement summary, every placement type (filters: `placementIds`, `siteId`, `zoneId`, `types`, `active`, `publishingCategories`, `labels`, `filter[…]`, `offset`, `limit`; `fields`) |
| GET | `/rest-api/placements/{placementId}.do` | One placement: the summary fields plus every setting `update/*` accepts; `404` if missing, `403` without view access to its site |
| GET / POST | `/rest-api/placements/{placementId}/openrtb.do` | The placement's OpenRTB card: `iabCategories`, `blockedIabCategories` (ids or IAB codes), `blockedBannerAttributes` (names or OpenRTB ids) and `customOpenRtbTagId`; only the fields sent change, an empty one clears. Needs *Open RTB* |
| POST | `/rest-api/placements/{placementId}/delete.do` | Delete placement |
| POST | `/rest-api/placements/copy-placement.do` | Copy a placement: `placementId`, `zoneId`, `count`, `keepBannersLinked`, optional `name` (later copies numbered ` 2`, ` 3`…). Answers `newItemIds` and `copies` (`id`, `name`, `active`) |
| POST | `/rest-api/placements/labels.do` | Add, replace or remove labels on many placements — see [Bulk labels](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#bulk-labels) |
| POST | `/rest-api/placements/labels/add.do` | Add labels to one placement (`placementId`, `labels`) |

In both reads `size` is the ad unit's title, `WIDTHxHEIGHT` for a placement sized without an
ad unit, or `-` when it has none; `dimensions` carries the same as `{width, height, adUnitId,
adUnitName}` (or `null`). A placement on inventory you see masked comes back masked
(`PLACEMENT_<id>`), judged by your share on that placement's own site.

What a placement answers for delivery and tags:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placements/{placementId}/matchedAds.do` | Ads currently matching this placement |
| GET | `/rest-api/placements/{placementId}/banner-probability.do` | The *Banners Probability* card: each eligible banner with the chance it is served to a simulated visitor (`countryCode`, `browser`, `operatingSystem`, `mobileCarrier`; `ignore` drops a dimension from the match; a banner that matches but gets no share of the rotation reads `-1`) — see [Optimize ad rotation](https://epom.com/ad-server/docs/guides/delivery/optimization.md) |
| GET | `/rest-api/placements/{placementId}/tracking-urls.do` | A click-through placement has no ad tag: one set of click-through, click-pixel and impression-pixel URLs per linked image or video banner — see [Application, email and click-through placements](https://epom.com/ad-server/docs/guides/inventory/placement-mobile-email-other.md) |
| GET | `/rest-api/placements/{placementId}/video-compliance.do` | For a video placement on a site whose video requirements are on: every linked uploaded video and whether it meets them (`notServed` counts the ones that stay linked but are not served) — see [Check video creatives against a publisher's spec](https://epom.com/ad-server/docs/guides/inventory/video-creative-requirements.md) |
| GET | `/rest-api/placements/{placementId}/serving-domains.do` | The ad-serving domains `servingDomain` accepts (`domains`), the one in use (`servingDomain`) and whether you may change it (`editable`); same `404` / `403` as the single-placement read |
| GET | `/rest-api/placements/{placementId}/default-invocation-params.do` | The parameters every tag of the placement carries by default (`type` / `value` pairs). `POST` adds one (`type`, `value`) and `DELETE` removes one; `GET /rest-api/placements/default-invocation-param-types.do` lists the types. Update access to the site is needed to change them — see [Ad tags, invocation codes and the API](https://epom.com/ad-server/docs/guides/inventory/ad-tags-and-api.md) |

Daisy chaining:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placements/{placementId}/chaining.do` | Read the daisy-chain (waterfall) configuration |
| POST | `/rest-api/placements/{placementId}/chaining.do` | Set up daisy chaining: `enabled`, `type`, `lookupDepth`, `rotatingEnabled` / `rotatingCount`, `viewPerEntityEnabled` / `viewPerEntityCount`, `groupByDeliveryPriority`. `POST …/chaining/delivery-priority-filter.do` sets the countries one delivery priority of a grouped chain serves (`deliveryPriority`, `enabled`, `countries`) — see [Set up daisy chaining and passbacks](https://epom.com/ad-server/docs/guides/delivery/daisy-chaining-passback.md) |
| POST | `/rest-api/placements/chaining/update-priority.do` | Set a banner's or campaign's waterfall priority |

Content replacement:

| Method | Path | Purpose |
|---|---|---|
| GET | `/rest-api/placements/{placementId}/content-replacement.do` | The placement's [content replacement](https://epom.com/ad-server/docs/guides/inventory/content-replacement.md) rules (`id`, `target`, `replacement`), `total` and `contentReplacementEnabled` |
| POST | `/rest-api/placements/{placementId}/content-replacement/create.do` | Add a content replacement rule: `target` (required, up to 255 characters, unique within the placement), `replacement` (optional, up to 65,535 bytes) |
| POST | `/rest-api/placements/{placementId}/content-replacement/{ruleId}/update.do` | Change a rule's `target` and/or `replacement`; a field not sent keeps its value |
| POST | `/rest-api/placements/{placementId}/content-replacement/{ruleId}/delete.do` | Remove a content replacement rule |

Create a standard display placement:

```bash
curl -X POST "https://<your-network-domain>/rest-api/placements/update/standard.do?$AUTH" \
  -d "zoneId=4001" \
  -d "type=SITE_PLACEMENT" \
  -d "name=Homepage Leaderboard 728x90" \
  -d "adUnitId=3" \
  -d "adPosition=ABOVE_THE_FOLD" \
  -d "active=true"
```

> **Corner case:** on create, `zoneId` **and** `type` are both required, and `type` must match
> the endpoint (`SITE_PLACEMENT` for `update/standard.do`). Leave one out and the call is a `400`
> with `error: invalid_placement_type` and the message *Pass id to update a placement, or zoneId
> and type to create one.* A `type` that belongs to another endpoint is the same code with the
> message *This operation creates SITE_PLACEMENT, not VIDEO_PLACEMENT.*; sending the `id` of a
> placement of another kind, or a `type` that differs from the stored one, says *Placement 4001
> is VIDEO_PLACEMENT; this operation takes SITE_PLACEMENT.* An unknown `id` is a `404` (`placement_not_found`) and an unknown `zoneId` on create a `404`
> (`zone_not_found`), each naming the id. The ad slot size comes
> primarily from `adUnitId` (look up IDs via `GET /rest-api/ad-units.do`); if you omit it,
> a new standard placement silently defaults to Leaderboard 728×90. Sizes can also be set
> directly with `size.width` / `size.height`. An `adUnitId` that does not exist, or is not
> an ad unit of the placement's type, is a `400` and nothing is changed. On `update/app.do`,
> `adCategory` / `operatingSystem` left out keep their stored values.

Beyond `zoneId`, `type` and the size, the settings that most often change what a placement does:

- `defaultCode` — the passback HTML served when no ad matches; needs *Placement Default Code*.
- `refreshInterval` / `rotateInterval` — in seconds, `0` = off.
- `disableAutoLinking` — switches create-time auto-linking off (see the corner case below).
  `enableLinking` is the separate hard-linkage flag — **not** its inverse.
- `labels` + `labelUpdateType` — the same names and values banners and campaigns use; needs
  *Placement Labels*.
- App placements: `operatingSystem` (`IOS` / `ANDROID`) and `adCategory` (`BANNER`,
  `INTERSTITIAL`, `NATIVE_AD`, `REWARDED_VIDEO`); `applicationOperatingSystem` /
  `applicationAdCategory` set the same values.
- Video placements: the lists `videoPlaybackMethod` and `videoApiFramework` take OpenRTB names
  or ids, comma-separated; an empty value clears a list and leaving it out keeps it.
- Native placements: the `nativePreferences.*` sizes are all optional, and on an update a size
  you do not send keeps its current value.

Many settings apply only with a role permission — see
[Activation, approval, status](https://epom.com/ad-server/docs/api/entities-linking-status-tags.md#activation-approval-status) for
the full list.

All fields, with descriptions and an example request, one entry per placement type:
[`update/standard.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdatestandarddo),
[`update/non-standard.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdatenon-standarddo),
[`update/mobile.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdatemobiledo),
[`update/video.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdatevideodo),
[`update/native.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdatenativedo),
[`update/app.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdateappdo),
[`update/click_through.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdateclick_throughdo),
[`update/email.do`](https://epom.com/ad-server/docs/api/placements.md#post-rest-apiplacementsupdateemaildo).

> **Corner case:** because the whole placement model is expanded onto every endpoint, the
> generated reference lists the video and app fields on all eight. They bind everywhere,
> but only take effect on a placement of the matching type.

> **Corner case — labels.** A label name that does not exist yet is created only when your
> role holds *Create Labels*; without it the call is a `400` naming the missing labels
> (`Labels X, Y not found and you don't have permission to create new labels`) and nothing
> is saved. This applies everywhere a label is given by name: a placement's, a campaign's and a
> banner's `labels`, a banner's `autoLinkLabels`, `placements/labels/add.do` and the bulk
> `*/labels.do` endpoints. `placements/labels/add.do` also needs *Placement Labels* and update
> rights on the placement's site. On `update/*` a caller without *Placement Labels* has
> `labels` and `labelUpdateType` left unapplied and listed in `ignoredFields` as
> `not permitted`; so does `defaultCode` without *Placement Default Code*, while
> the rest of the placement is saved.

> **Corner case:** sending an existing placement's `id` with a different `zoneId` **moves**
> the placement to that zone, on the same or another site. The move needs update rights on
> both sites; without them the call returns `403` with `{"success":false,"permission_denied":<id>}`
> and a message naming the site, and the placement stays where it was. An unknown `zoneId` is a `404`. The move is written
> to the action log, and the placement's banner links stay as they were. So does its placement key (`key`): it is set once,
> when the placement is created, and a move never changes it — every ad tag already published for the placement keeps working.

> **Corner case:** `POST /rest-api/placements/chaining/update-priority.do` (`id`,
> `bannerId` or `campaignId`, `priority`) reorders the placement's daisy chain and changes
> nothing else on it; the answer's `priority` is the one stored (a value past the end of
> the chain moves the entry last). Neither `bannerId` nor `campaignId` is a `400`, an entry
> that is not in the chain a `404`. It needs the daisy-chaining permission and update rights
> on the placement's site.

> **Corner case — serving domain.** `servingDomain` must be one of the domains
> `GET /rest-api/placements/{placementId}/serving-domains.do` lists — the ones the console's
> **Domain** picker offers — on every `update/*` endpoint. Anything else is a `400` naming the
> placement's domains, and nothing is saved. Without *Set Ad-Serving Domain (Placement)* the
> field is not checked or applied; it is listed in `ignoredFields` as `not permitted`.

> **Corner case:** creating an e-mail placement through `update/email.do` needs the *Email
> Placements* permission, and an e-mail placement must keep an ad unit (`adUnitId`).

> **Corner case:** the four `content-replacement` operations need the *Placement Content
> Replacement* permission (a `403` naming it otherwise) plus view (list) or update (changes)
> rights on the placement's site. The rules only apply while the placement's
> `contentReplacementEnabled` is `true`, and reach live traffic on the ad server's next cache
> reload.

> **Corner case:** auto-linking runs **on create only**, never on update, and
> `disableAutoLinking=true` switches it off. Leaving it off is not a promise of links,
> though: the creative must be size- and type-compatible, and the banner (or its campaign)
> must have new-placement auto-linking enabled. Its **categories** and its **labels** are
> independent filters (see [Auto-linking](https://epom.com/ad-server/docs/concepts/hierarchy-and-linking.md)): with
> categories set, the parent site must carry one of them; with labels set, the placement
> must carry one of them; with both set, both must hold; and a banner or campaign with
> labels but no categories links to every new placement that carries one of its labels,
> whatever the site's categories (banners of private advertisers are skipped on that route).
> With neither set nothing links, and on a network that does not use publisher categories
> only the label-only banners ever auto-link. Read the links back rather than assuming.

> **Corner case:** `POST /rest-api/placements/copy-placement.do` with a `placementId` or `zoneId`
> that does not exist, or that was deleted, is refused with a `4xx` that names the missing entity
> and copies nothing. `count` outside
> 1–20 is a `400` naming the limit.

## 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)
- [Linking, status, tracking codes and ad tags via API](https://epom.com/ad-server/docs/api/entities-linking-status-tags.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)
- [Placement types](https://epom.com/ad-server/docs/reference/placement-types.md)
- [Set up daisy chaining and passbacks](https://epom.com/ad-server/docs/guides/delivery/daisy-chaining-passback.md)
- [Rewrite served markup with Content replacement](https://epom.com/ad-server/docs/guides/inventory/content-replacement.md)