Linking, status, tracking codes and ad tags via API
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.
Once the advertisers and campaigns, banners and sites, zones and placements 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, the hash-authentication parameters (an X-Api-Key header works the same), and send parameters form-encoded unless noted — see the API overview for authentication and Entities via API 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 |
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), theidsyou send replace the banner’s entire link set — any placement not listed is unlinked. Passappend=trueto add links without touching existing ones. Unlinking needs the same reach as theDELETEcall: if a link you would drop sits on a site your grant cannot manage links on, the call fails withplacements_cannot_be_linkedlisting it and nothing changes.
Corner case: the two
DELETEverbs takeremoveIds, notids. Reusing thePUTparameter name is a400(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
activeboolean 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 inignoredFieldsasnot permitted(see 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 | |
| Site | See the corner cases under 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 |
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_PLACEMENTonbanners.doreturns the creatives built for video placements;types=VIDEO_PLACEMENT&active=trueonplacements/summary.doreturns the switched-on video placements. Alabelsfilter 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 ahas<Relation>flag for every setting the entity keeps in rows of its own:hasLimit,hasPricing,hasFrequencyCapping,hasTrackingCode,hasPriceAdjustmentRules,hasLabels,hasBanners,hasZonesand 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,hasLimitsandhasTargetingRulesgroup the relations of their kind; note thathasTargetingRules(rules exist) andenableTargeting(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, sooffset/limitpage the filtered result.fieldstrims each entity to the named fields (idis always kept) —fields=id,name,activeturns a 1,000-campaign list from megabytes into a few dozen kilobytes. A requested field that no returned row carries is named in theX-Ignored-Fieldsresponse header rather than failing the call.
# 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 aszone.name,size.adUnit.width,categories[0].nameorlimit.totalImpressionsLimitchanges 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). - 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 fromzoneId. Sending an existing zone’sidwith a differentsiteIdmoves the zone, which needs update rights on both the site it leaves and the site it joins; otherwise the call returnspermission_deniedand nothing changes. Sending an existing placement’sidwith a differentzoneIdmoves 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.doremoves the uploaded video file (400 when the banner has none). The image is removed through the update instead: senddeleteImage=trueto/rest-api/banner/{bannerId}/update.do. A Local File banner cannot be left without an image, so it must upload the replacement inimageFilein the same call (400 otherwise); the old file is then deleted from storage. Naming the replacement inimageFileUrldoes both in one step, withoutdeleteImage. 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.
# 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 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.doand the deprecatedplacement-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.dois 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.
var container = document.getElementById('ad-slot-leaderboard');
var opts = { channel: 'homepage' };
// paste the JS_MOUNT snippet here
Warning: leaving
containerundeclared rather than unassigned throws aReferenceErrorbefore 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 for what each format does.