# Apple TV (tvOS)

Source: https://docs.ezoic.com/docs/ctv/tvos/


This guide covers serving Ezoic video ads from tvOS apps with Google's IMA SDK for tvOS. The tag is player-agnostic, so any VAST-capable player works the same way.

## Tag template

```
https://g.ezoic.net/ezoic/videoad/vast?bundle=<APP_STORE_ID>&app_domain=<YOUR_EZOIC_DOMAIN>&app_name=<APP_NAME>&storeurl=<APP_STORE_URL>&adunit=<YOUR_VIDEO_AD_UNIT>&ifa=<IDFA>&ifa_type=idfa&lmt=<0|1>&devicetype=3&plcmt=1&v=4&w=1920&h=1080&startdelay=0&content_id=<ID>&content_title=<TITLE>&content_genre=<GENRE>&content_cat=<IAB_CAT>&content_len=<SECONDS>&livestream=<0|1>&us_privacy=<USP>&cb=<RANDOM>
```

- `bundle` is your numeric App Store ID, the number in your store URL. It is not the Xcode bundle identifier.
- `ifa_type` is `idfa`.

## App Tracking Transparency

The IDFA is gated behind App Tracking Transparency (ATT). On tvOS, request authorization with `ATTrackingManager`. The prompt appears on the TV screen.

| ATT state | What to send |
|---|---|
| Authorized | `ifa=<real IDFA>` and `lmt=0` |
| Denied, restricted or not determined | `ifa=<zeroed IDFA as returned>` and `lmt=1` |

Never fabricate or persist an IDFA. Expect a meaningful share of tvOS sessions to send `lmt=1`. For those sessions, the content metadata and app identity carry more of the request, so fill them in fully.

## IMA SDK integration (Swift)

```swift
let adsLoader = IMAAdsLoader(settings: nil)
let display = IMAAdDisplayContainer(adContainer: adContainerView, viewController: self)
let request = IMAAdsRequest(
    adTagUrl: buildEzoicTagUrl(for: video),
    adDisplayContainer: display,
    contentPlayhead: contentPlayhead,
    userContext: nil)
adsLoader.requestAds(request)
```

URL-encode every dynamic value when you build the tag URL.

## Open Measurement

IMA for tvOS bundles the OM SDK, and verification runs automatically for ads that carry it. The tag declares OMID support for non-Roku CTV requests, so you do not need `api`. If you run your own OM session outside IMA, also send `omidpn` and `omidpv`.

## Behaviour notes

- An empty VAST document is a no-fill. IMA raises its "VAST response empty" error. Fall through to content.
- Request one ad per break, with `startdelay`. Send `pod` and `slot` for multi-ad breaks.

## Go-live checklist

- [ ] `bundle` is your numeric App Store ID, the store listing is live, and app-ads.txt is on your developer domain
- [ ] The ATT flow is in place, and `ifa` and `lmt` reflect the real authorization state
- [ ] `devicetype=3`, real `w` and `h`, and `startdelay` are sent
- [ ] `adunit` is set to your normal video ad unit name
- [ ] Content metadata is populated
- [ ] The app is registered under Mobile & CTV Apps

