Browse the help center

Native API reference

Field-level reference of the /ads-api-native endpoint — the request URL and its parameters, the count / rank / tmax controls, the two response shapes, every field of the ad and no-fill objects, and which beacons you actually receive.

Reference network manager admin Account → Publishers → site → zone → native placement → Ad Tags & API → API

This is the integration map for anyone calling Epom’s native endpoint directly — a mobile app, a site backend, or a feed service that renders ads with its own components instead of taking markup from a JS tag. It documents the request URL, the parameters the endpoint reads, the two shapes the response can take, every field of the ad object, and what you must fire back so the impression counts.

The endpoint serves native placements only, and it returns structured creative fields (title, images, CTA text, click URL, beacons) rather than HTML. You decide the layout; Epom decides which creative fills it. For everything else — display, video, email — use the ad tags on the placement’s Ad Tags & API card.

Note: the endpoint is unauthenticated by design. The placement key in the URL is what identifies the inventory; it is not a secret API key, and there is no signature. This is the same trust model as a JS ad tag.

Request URL

https://<ad-serving-domain>/ads-api-native?key=<placement key>

/ads-api-native is the only path that reaches this endpoint — there are no aliases.

Take the exact URL from the placement itself — Account → Publishers → site → zone → native placement → Ad Tags & API → API. The field there is built for that placement: it carries the right serving domain and key, and it already includes every default parameter configured on the Settings tab (channel, custom parameters, click chain), so it stays correct as you edit them.

Corner case: the ads segment of the path is fixed for native placements. If your network renames ad-serving paths, that rename applies to the other API placement types but not to this one — a native placement always answers on /ads-api-native.

Parameters

All parameters are query-string parameters on a GET. Only key is always required.

Native-only controls

ParameterWhat it doesExampleRequired
countHow many ads to return. Meaning depends on rank — see How many ads come back.5No
rankSelection mode: full (default), fast, none. See Ranking modes.fastNo
tmaxTime budget for ad selection, in milliseconds. See Capping the response time. Default: no limit.120No

Note: native_bs is a deprecated alias for count, kept so existing integrations keep working. New callers should send count.

Identification

ParameterWhat it doesExampleRequired
keyPlacement unique identifier6d7add73ed34ece3f4e927210fbe471fYes
pNumeric placement id — an alternative to key48213No
chChannel value passed in the ad tagsport-sectionNo
click3rd-party click-tracking URL prepended to the click chainhttp://tracker.example.com/click?u=No
cp.<name>Custom parameter, readable by macros and targetingcp.section=financeNo
sub1 … sub10Partner pass-through values, recognised without the cp. prefix and available as $$SUB1$$ … $$SUB10$$ macros; they travel with the click to the conversionsub1=affiliate42No
excludedBanner ids to keep out of this selection. Repeatable.9912No
pbPassback marker for a chained request1No

Server-to-server

When your server calls the endpoint rather than the end user’s device, Epom sees your server’s IP and user agent, which would break targeting, capping and fraud checks. Pass the real client’s values instead:

ParameterWhat it doesExampleRequired
clientIpIP of the client112.12.23.33Yes, for S2S
clientUaUser agent of the clientAndroid (Linux; U; Android 2.2.1; …)Yes, for S2S
clientOsOperating system of the clientAndroidNo
osvDevice operating system version3.1.2No
requestUrlClient’s request URLhttp://foo.bar/foo/barNo
requestRefClient’s request referer URLhttp://foo.bar/foo/barNo

Note: each of these also accepts a legacy alias (epomIp, epomUa, ua, epomUrl, epomRef, epomOs, epomOsv, and clientIP). Send the canonical name above; the aliases exist only so older integrations keep working.

A caller that sits in front of the viewer, such as a server-side ad insertion stitcher, can send the viewer’s address and agent as headers instead: X-Device-IP is read after an explicit clientIp and ahead of X-Forwarded-For, and X-Device-User-Agent replaces the caller’s own user agent (an explicit clientUa still wins). X-Device-Accept-Language and X-Device-Referer carry the viewer’s language and referer.

Device, geo and layout

ParameterWhat it doesExample
ifaAdvertising ID — Android AAID or iOS IDFA (OpenRTB device.ifa)38400000-8cf0-11bd-b23e-10b96e40000d
bundleApplication bundle / package id of in-app inventory (OpenRTB app.bundle)com.example.game
ifa_typeWhat kind of id ifa is — a short lower-case word such as rida, tifa, aaid or sessionid. Sent on to DSPs as device.ext.ifa_type, and only while an ifa is presentaaid
lmtLimit Ad Tracking: 1 or true when the viewer opted out, 0 or false when not. Sent on to DSPs as device.lmt; Epom does not act on it itself1
tzClient’s UTC offset in hours, as a decimal number5.5
tznClient’s IANA time-zone nameEurope/Kyiv
latitude / longitudeClient’s coordinates3.13192 / 101.684059
sw / shDevice screen width / height, px1080 / 2340
plWidth / plHeightPlacement width / height for dynamic-size placements, px300 / 250

For bundle, a value that is still an unsubstituted macro token ([BUNDLE], {{bundle}}, ${BUNDLE}, %%BUNDLE%%) is read as not sent. For ifa, an all-zero id, a placeholder word (null, undefined, unknown, none, n/a) or a macro token is not used to recognise the viewer, who then falls back to the IP and user-agent fingerprint for capping.

Corner case: tz is a single decimal number, parsed as a float — -1, 2 and 5.5 are three separate examples of valid values, not one value. Sending -1;2;5.5 fails to parse and the time zone is treated as unknown. ifa is not used for targeting on this endpoint directly; it feeds macros, any onward OpenRTB bid request, and the viewer identity that server-side capping counts against (see Frequency capping).

Privacy signals

ParameterWhat it doesExample
gdprGDPR-applies flag: 1 applies, 0 does not1
gdprConsentIabIAB TCF consent stringCPtq…base64
ucUser has given consent: 1 yes, 0 no1
gpcGlobal Privacy Control opt-out flag (also read from the Sec-GPC header)1
imIncognito / private browsing: 1 private, 0 normal, absent unknown1

The consent signals gate ad serving the same way they do for tags — see GDPR and IAB TCF consent.

How many ads come back

count means two different things depending on rank, because the two modes select differently:

ModeWhat count meanscount absent
rank=full, rank=fastExactly how many ads to select (ranked)1 ad
rank=noneA cap on the eligible pool, sampled at randomall eligible creatives

Corner case: rank=none without count returns the whole eligible pool, which on a busy placement can be far more creatives than you intended to fetch. Always send count with rank=none unless you genuinely want everything.

A non-positive count (0 or a negative number) is ignored and the mode’s default applies.

Corner case: a non-numeric count does not fall back to the default — it falls back to native_bs. count=abc&native_bs=5 returns five ads. count only wins over native_bs when it actually parses as an integer.

Response shape

The endpoint always answers JSON — one object or an array of them:

RequestBody
rank=full (default) and count absent or 1A single JSON object
rank=full with count > 1A JSON array
rank=fast or rank=none — always, even for one adA JSON array

Corner case: parse defensively on the mode you send. rank=fast&count=1 returns [ { … } ], not { … } — the array is chosen by the mode, not by how many ads came back.

Corner case: the array promise breaks when the request itself cannot be built — a bad key, a placement that no longer resolves, a request rejected as a bot. In those cases the endpoint has no parsed request to inspect and answers with a bare object even in the array modes. Accept either shape at the top level.

An ad

{
  "type": "native",
  "version": "1.0",
  "beacons": [{ "type": "impression", "url": "http://adserv.com/impression.gif?b=1" }],
  "bannerId": 1042,
  "placementId": 318,
  "customization": { "theme": "dark" },
  "title": "Best offer",
  "subtitle": "app of the day",
  "description": "this is the best application in its category",
  "ctaText": "Click to Install",
  "advertiser": "Northwind Media",
  "rating": 4.9,
  "icons": [{ "width": 128, "height": 128, "url": "http://example.com/icon.png" }],
  "images": [{ "width": 320, "height": 50, "url": "http://example.com/image.png" }],
  "videos": [{ "width": 640, "height": 360, "url": "http://example.com/promo.mp4" }],
  "clickUrl": "http://adserv.com/cr?b=1&u=https%3A%2F%2Fplay.google.com%2F…",
  "refreshInterval": 30
}

Every field:

FieldTypeWhen it appears
typestringAlways. native on an ad, error on a no-fill.
versionstringAlways. Currently always 1.0.
beaconsarrayWhen the ad has any — see Beacons.
bannerIdnumberAlways on an ad. The creative that was selected.
placementIdnumberAlways.
customizationobjectWhen the creative has custom parameters. String→string; macros already resolved.
title / subtitle / description / ctaTextstringWhen the creative carries them. Macros already resolved.
advertiserstringEither the advertiser’s name or a fixed value Epom sets network-wide — a server-side setting chooses which. Dropped if that source is empty.
ratingnumberAlways. 0.0 when the creative has no rating.
icons / images / videosarrayWhen the creative carries that asset.
clickUrlstringWhen the creative has a click URL — see the corner case below.
refreshIntervalnumber or stringAlways — see the corner case below.

Asset entries in icons, images and videos are all the same shape — width and height (numbers, 0 when the size is unset) and url:

{ "width": 320, "height": 50, "url": "http://example.com/image.png" }

Corner case: those three are arrays, but each carries at most one entry. A creative has one icon, one image and one video; the array shape does not mean you can be sent several.

Corner case: refreshInterval is present on every ad object, and it is not always a number. When the placement has no refresh interval set, it serializes as the string "UNDEFINE", not null and not 0. A strictly-typed client that maps it to an integer will fail to deserialize a perfectly normal response. Treat the field as “number or string”, and read "UNDEFINE" as “no refresh”.

Corner case: clickUrl is omitted entirely when the creative has no click URL, rather than being sent empty. Whether such a creative can be selected at all depends on the placement: if URL is one of the placement’s required native fields, the creative is excluded from selection instead. See Native placements.

Which of the optional fields are present depends on what the creative carries and on the required fields configured for the placement — see Native placements and Native creatives. Empty strings, empty arrays and empty objects are dropped rather than sent, and key order is not guaranteed.

No ad

There are two no-fill shapes, and the one you get says where the request stopped.

An ordinary no-fill — the placement is fine, nothing matched — carries the same envelope as an ad, plus a noImpression beacon:

{
  "type": "error",
  "version": "1.0",
  "message": "no ads",
  "beacons": [{ "type": "noImpression", "url": "http://adserv.com/noimp.gif?p=318" }],
  "placementId": 318,
  "bannerId": null,
  "customization": null
}

A failed lookup — an unknown key, a placement missing from the ad-server cache, a request rejected as a bot, or an internal error — carries only the three envelope fields:

{
  "type": "error",
  "version": "1.0",
  "message": "no ads"
}

Corner case: both shapes use "message": "no ads", so the message does not distinguish them and cannot be used to diagnose a bad key. The presence of placementId is what tells you the placement resolved. An internal server error also produces the short shape with a 200 — a genuine no-fill and a crash are indistinguishable from the response alone.

Corner case: “no ads” is a 200 response with an error object, not an HTTP error. In the array modes an unfilled request is a shorter array — down to []. Treat type: "error" and an empty array as the same “nothing to render” outcome. An array can also contain an error object ([{"type":"error",…}]) when the request fails partway, so check type on each element rather than assuming every array member is an ad.

Corner case: a 204 No Content is possible but rare, and it can only happen in the single-object mode — the array modes always emit at least []. It means the placement id resolved to a deleted placement, or the request failed on the server side. It never means “no ad matched”.

Beacons

An ad’s beacons array carries the URLs you must fire. Firing them is the caller’s job — Epom cannot see your rendering, so an ad you display without firing its impression beacon is an ad that was never counted and never billed.

typeWhere you receive itFire it when
impressionOn an ad objectThe ad is actually rendered to the user
noImpressionOn a no-fill object onlyNothing to fire — it is Epom’s own no-fill record

clickUrl already goes through Epom’s click chain (including any click tracker you passed), so sending the user to it registers the click by itself.

Corner case: a successful ad object carries impression beacons only. There is no noImpression beacon on an ad you decide not to show, so there is no way to tell Epom you dropped it. If you fetch several ads and render fewer — with rank=none, or by re-ranking a count>1 response — simply do not fire the impression beacons for the ones you drop. That is the whole mechanism: an unfired impression beacon is an uncounted ad.

Corner case: the response contract defines a third beacon type, click, but the ad server never emits one. Do not build a code path that waits for it. Click accounting happens through clickUrl.

Ranking modes

rankWhat it doesDSP / OpenRTB demand
full (default)Weighted ranking, one full selection pass per returned ad. The most accurate, the most work.Included — each pass can run a DSP auction
fastSame ranking, but eligibility is computed once and count ads are drawn in a single pass.Falls back to full when the placement has OpenRTB banners (they need the auction)
noneNo ranking — returns the eligible direct creatives so you can rank them yourself.Excluded

Choose full when you want Epom’s optimization to decide. Choose fast when you fetch several ads at once and the per-ad filter passes cost more than the ranking precision is worth. Choose none only when you re-rank on your side.

Note: an unrecognized rank value is treated as full rather than rejected. Matching is case-insensitive and trims whitespace, so FAST and fast both work.

Corner case: when rank=fast falls back to full ranking, the response stays an array. The fallback changes how ads are selected, not the shape you parse.

Corner case: with rank=none, the pool is only shuffled when it is larger than count. Ask for as many as exist (or more) and you get them in a stable internal order, not a random one — do not rely on rank=none to randomize a small pool for you.

Capping the response time with tmax

tmax is a budget in milliseconds, counted from the moment the request arrives — so it covers request parsing (geo and device resolution) as well as ad selection. When the budget is gone, the endpoint stops selecting and returns what it already has.

GET /ads-api-native?key=<key>&count=5&tmax=120

Asking for five ads and getting three back is a normal response. Nothing is reported as an error, and no tmax value causes a failure — the previous behaviour (run to completion) is what you get when tmax is absent, 0, or negative.

Corner case: tmax only bites on a multi-ad request. The default single-ad path (rank=full with count absent or 1) never consults the budget, so tmax cannot shorten it and cannot make it return nothing. Send tmax alongside count>1, rank=fast or rank=none, or it does nothing at all.

Corner case: the budget is checked between ad selections, never inside one. If a single selection is slow — typically because it triggers a DSP sub-auction, which runs on that partner’s own timeout — that selection is not cut short, and the request can overrun tmax by roughly the length of it. Size tmax as “how long am I willing to wait for more ads”, not as a hard ceiling on the HTTP call; keep your own client-side timeout above it.

Corner case: with rank=none the eligible pool is built and sampled before the budget is ever consulted, so tmax cannot shorten the expensive part of that mode — only the per-ad response building that follows it.

Corner case: in the multi-ad modes, if the budget is already spent before the first selection, you get []. A very small tmax on a slow path can therefore return nothing at all, where the same request without tmax would have returned an ad.

Corner cases and gotchas

  • format, callback and maxBids do nothing here. They appear in the placement’s shared parameter table because the table serves every API placement type; the native endpoint always answers JSON and never wraps the body in a JSONP callback.
  • The response is not cached. The endpoint sends Cache-Control: no-cache, no-store, must-revalidate. Each call is a fresh selection with its own capping, limits and frequency accounting. Do not fetch once and reuse the payload across users.
  • Capping differs by mode. full and fast both apply advertiser-side request capping per selected ad. rank=none returns a pool that has not been through advertiser-side capping at all — inventory-side capping and the shared eligibility filters still run once for the request. If exact capping matters more than latency, stay on full.
  • A creative appears at most once per response. Each selection excludes what it already picked, so a count larger than the eligible pool returns fewer ads rather than repeats.
  • rank=none is not billed per candidate. The pool is not logged as one ad request per candidate; accounting happens when you fire an impression beacon.

Related pages

Please disable Adblock
Are you using adblock? Disable it and reload the page to proceed to the destination page.

A registration has already been submitted from this page. Please try again in 10 minutes.