Unlimited Text Ads

Developers

Show ads from your account in your own Android app, video/audio player, or any standards-based ad integration — no WebView, no iframe. Uses the same slot keys as your website embed code. Android SDK below (§1–10); VAST/DAAST/Native for any other player at §11.

1. Overview

The Android SDK talks to a JSON version of the same ad-serving endpoint your website embed uses (serve.php / click.php). It renders the returned ad with native Android views — TextView, ImageView, or a Media3 player for audio/video — styled to match the colors and font you picked when creating the site/placement.

2. Requirements

3. Installation

The SDK currently ships as source — a Gradle module you include directly in your project, not yet a published package.

Download SDK (v1.0.1, .zip)

  1. Unzip it — you'll get an adsdk folder.
  2. Copy that folder into the root of your Android project, next to your app module.
  3. Add it to settings.gradle.kts:
    include(":adsdk")
  4. Add it as a dependency in your app module's build.gradle.kts:
    dependencies { implementation(project(":adsdk")) }

The zip also includes a short README.md with the same steps plus the quick-start code below, in case you're setting this up without this page open.

4. Quick start

Initialize once, e.g. in your Application class:

AdServerSdk.init(context = this, baseUrl = "https://adserver.unlimitedtextads.com/")

Add an AdView to your layout:

<com.adserver.sdk.view.AdView android:id="@+id/adView" android:layout_width="match_parent" android:layout_height="wrap_content" />

Load an ad:

val adView = findViewById<AdView>(R.id.adView) adView.slotKey = "YOUR_SLOT_KEY" adView.adListener = object : AdListener { override fun onAdLoaded() {} override fun onAdFailedToLoad(reason: String) {} override fun onAdClicked() {} } adView.loadAd()

5. Your slots

Log in to see your real slot keys here. Everything above this section works the same whether you're logged in or not — this is just a shortcut so you don't have to copy keys from My Sites by hand.

6. Ad types & renderers

Ad typeRendered as
textStyled headline + body text
imageImage with optional headline/body text
audioMedia3 (ExoPlayer) audio player with title/CTA
videoMedia3 (ExoPlayer) video surface, autoplay muted with tap-to-unmute

Every video ad is server-side transcoded to one consistent resolution/frame-rate/bitrate (admin-configured) before it can air, and capped at 30 seconds — so a player showing several video ads back to back (e.g. a VAST pod) never sees a resolution, frame-rate, or audio jump between them.

A placement's ad format (text/image, video, or audio) is fixed per slot — the same choice you made under My Sites — so a given AdView only ever loads compatible ads.

7. Styling

Background, text, accent, and border colors plus corner radius come from your slot's style settings automatically — no extra code needed. Two things don't map perfectly to native Android and are documented limitations, not bugs:

Teaser display style — if a site's display style is set to "Teaser" under My Sites, AdView continuously animates the whole ad unit the same way the web embed does, picking the animation from the slot's teaser animation setting:

Teaser animationNative behavior
pulseGently scales the ad up and down (1.6s cycle)
glowA pulsing colored border overlay (1.8s cycle) — Android has no direct equivalent of a CSS box-shadow glow, so this is a deliberate visual approximation, same fixed color the web version uses
shakeA small side-to-side shake (2.4s cycle)

A "Standard" display style (the default) has no animation. This applies to every ad type — text, image, audio, and video all animate the same way when teaser is on, matching the web embed's behavior of animating the whole ad container regardless of what's inside it.

8. Click-through behavior

When a user taps an ad, the SDK fires the click-tracking request in the background (never blocking or delaying anything) and immediately opens the destination URL in a Chrome Custom Tab, falling back to a normal browser intent if Custom Tabs isn't available. A slow or dropped tracking ping never affects what the user sees.

9. Auto-refresh

If a slot has auto-refresh enabled, AdView automatically loads a new ad on that interval while it's attached to the window, and stops when it's detached (e.g. the screen is left).

10. Error handling

Any failure — network error, no eligible ad, malformed response — is handled the same way: AdView hides itself (visibility = GONE) and calls onAdListener.onAdFailedToLoad(reason). It never throws or crashes your app.

11. Standards-based delivery: VAST, DAAST & Native

For video and audio you don't need the Android SDK at all — any player that speaks the industry-standard ad templates can pull ads directly. Text/image already is a native ad format, via the same JSON API below.

Video — VAST 4.0

GET https://adserver.unlimitedtextads.com/api/vast.php?slot={key}&pid={optional}&max={optional}

Works with Google IMA SDK (web/Android/iOS), ExoPlayer's IMA extension, Video.js + videojs-contrib-ads, JW Player, and any other VAST 4.0-compliant player. Only slots with ad format "Video" return a real ad — anything else, or nothing eligible, returns a spec-valid empty <VAST/> document, never an error.

Ad pods: a request returns every currently eligible video ad for that slot, one per <Ad> element with an incrementing sequence, up to max (default 4, hard-capped at 10 regardless of what you pass — protects both your player and this server from an oversized response). Fewer eligible ads than the cap is fine, that's just a smaller pod; house ads fill any remaining slots the same way they fill a single-ad request. Each <Ad> is its own real impression with its own credit, exactly as if the player had made that many separate requests — a genuine 3-ad pod shown to one viewer is 3 real impressions, not the same one 3 times. Most players play a pod back-to-back as one ad break; if yours only understands a single ad, pass max=1 to get exactly one <Ad>.

<?xml version="1.0" encoding="UTF-8"?> <VAST version="4.0"> <Ad id="4821" sequence="1"> <InLine> <AdSystem version="1.0">Ad Server</AdSystem> <AdTitle><![CDATA[Ad title]]></AdTitle> <Impression><![CDATA[https://adserver.unlimitedtextads.com/api/vast-pixel.php?imp=...]]></Impression> <Creatives> <Creative id="4821" sequence="1"> <Linear> <Duration>00:00:30</Duration> <VideoClicks> <ClickThrough><![CDATA[https://adserver.unlimitedtextads.com/click.php?imp=...]]></ClickThrough> </VideoClicks> <MediaFiles> <MediaFile delivery="progressive" type="video/mp4" width="640" height="360"><![CDATA[https://adserver.unlimitedtextads.com/uploads/video/....mp4]]></MediaFile> </MediaFiles> </Linear> </Creative> </Creatives> </InLine> </Ad> <Ad id="4822" sequence="2"> ...second ad in the pod, same shape... </Ad> </VAST>

Audio — DAAST 1.0

GET https://adserver.unlimitedtextads.com/api/daast.php?slot={key}&pid={optional}&max={optional}

The IAB's audio-ad counterpart to VAST — same idea, <DAAST> root, <AudioClicks> instead of <VideoClicks>, same pod/max behavior described above. Works with podcast/streaming ad-insertion tools and any DAAST-aware player. Only slots with ad format "Audio" return a real ad.

Text & image — already a native ad format

No separate endpoint needed — the JSON from /api/serve.php (§12 below) already returns exactly what the IAB's Native Ad concept describes: a headline, body text, an image URL, and a destination link, as plain JSON instead of XML. Any native-ad rendering integration can consume it directly.

Scope, honestly

12. Raw JSON API reference

For reference, or if you're integrating from something other than the Android SDK. Requests are unauthenticated aside from the slot key itself, matching the website embed.

GET https://adserver.unlimitedtextads.com/api/serve.php?slot={key}&pid={optional}&app_id={optional}

// ad available { "status": "ok", "impression_token": "5b1e7c2a-1234-4abc-9def-0123456789ab", "ad": { "id": 4821, "type": "image", "is_house": false, "title": "Ad title", "text": { "headline": "Headline copy", "body": "Body copy" }, "media": { "url": "https://adserver.unlimitedtextads.com/uploads/image/ab12cd34ef.jpg" }, "destination_url": "https://advertiser.example.com/landing", "open_in_new_tab": true }, "slot": { "ad_format": "text_image", "display_style": "standard", "teaser_animation": null, "auto_refresh_seconds": 30, "style": { "bg_color": "#ffffff", "transparent_bg": false, "text_color": "#222222", "accent_color": "#3366ff", "border_color": "#dddddd", "border_radius": 8, "font": "default" } } } // nothing to serve right now (unknown/inactive slot, or no eligible ad) { "status": "no_ad" } // malformed request { "status": "error", "message": "missing or invalid 'slot' parameter" }

POST https://adserver.unlimitedtextads.com/api/click.php?imp={impression_token}

Always responds {"status":"ok"} (HTTP 200), even for an unknown or expired token — fire this in the background and open the ad's destination_url immediately, don't wait for the response.

13. Fraud prevention

API traffic goes through the same fraud checks as the website embed (bot filtering, rate limiting, duplicate-impression detection, self-traffic detection). A flagged impression or click still renders/redirects normally — it's simply not credited.