Trillboards

Trillboards CTV Measurement SDK β€” Quickstart

Audience-measurement SDK for Connected TV. Two runtimes share one wire format:

  • @trillboards/ctv-measurement β€” npm package for CTV-web ad units, smart-TV apps (Samsung Tizen, LG webOS, Vizio SmartCast, Fire TV / Google TV / Android TV WebView), and any HTML5 surface running inside an ATSC 3.0 BA or HbbTV runtime.
  • com.github.trillboards.packages:ctv-measurement β€” Android AAR for native Android TV / Fire TV / tablet apps. JitPack-fetched. Built on agent-core-lite, the same proximity stack the Trillboards in-house fleet runs in production today.

Both SDKs POST to the same heartbeat endpoint: POST /v1/partner/device/{deviceId}/heartbeat. Get a tb_ctv_* API key at https://trillboards.com/partners/get-started (self-serve, no email round-trip).

Step 1 β€” Get an API key

curl -X POST https://api.trillboards.com/v1/partner/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Your Company",
    "slug": "your-company",
    "contact_email": "dev@example.com",
    "partner_type": "ctv_publisher",
    "api_agreement_accepted": true
  }'

The response contains a one-time api_key of the form tb_ctv_xxxxx. Store it in your secret manager β€” Trillboards does not keep a plaintext copy.

You can also use the web form at https://trillboards.com/partners/get-started.

Step 2 β€” Install the SDK

npm (CTV web, React Native, smart-TV web apps, ATSC 3.0 / HbbTV runtimes)

npm install @trillboards/ctv-measurement
import { CtvMeasurement } from '@trillboards/ctv-measurement';

const sdk = new CtvMeasurement({
  apiKey: 'tb_ctv_YOUR_KEY',
  deviceId: 'your-device-id',
});

// Consent-first: measurement is inert until this is called.
sdk.setConsentStatus(true);

// Captures fingerprint + viewability + connection + (if the runtime is an
// ATSC 3.0 BA or HbbTV receiver) broadcaster signaling, then POSTs the
// heartbeat. Returns the upload result.
await sdk.measure();

Gradle / JitPack (Android native β€” Android TV, Fire TV, tablet apps)

In your root settings.gradle.kts (or settings.gradle):

dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
  }
}

In your app-module build.gradle.kts:

dependencies {
  implementation("com.github.trillboards.packages:ctv-measurement:0.2.1")
}

In your MainActivity.kt (or Application.onCreate):

import com.trillboards.measurement.TrillboardsMeasurement

TrillboardsMeasurement.initialize(
  context = this,
  apiKey = "tb_ctv_YOUR_KEY",
  deviceId = "your-device-id",
)
TrillboardsMeasurement.setConsentStatus(true)
TrillboardsMeasurement.startScheduledScans()

startScheduledScans() runs the BLE / WiFi / mDNS / SSDP / HTTP-probe scanners on a 30-second cadence and POSTs each snapshot as a heartbeat. The scanners use agent-core-lite, the same code that powers the Trillboards in-house tablet fleet (~658K signal observations per day across 10 active screens).

Required Android permissions

Add to AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

<!-- BLE scanning (Android 12+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                 android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- BLE scanning (Android 11 and below) -->
<uses-permission android:name="android.permission.BLUETOOTH"
                 android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                 android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                 android:maxSdkVersion="30" />

Step 3 β€” Consent gate

Both SDKs are inert until setConsentStatus(true) is called. No network traffic, no scanning, no fingerprinting. This is the GDPR / CCPA gate; surface it through your existing consent flow.

To revoke consent:

sdk.setConsentStatus(false); // npm
TrillboardsMeasurement.setConsentStatus(false) // Android

When consent is revoked, in-flight requests complete but no new measurements are emitted until consent is granted again.

Step 4 β€” Verify your integration

After one heartbeat lands, run this against ClickHouse to confirm rows are arriving for your device:

SELECT source, count() AS rows
FROM trillboards.signal_observations
WHERE screen_mongo_id = 'your-device-id'
  AND observed_at > now() - INTERVAL 5 MINUTE
GROUP BY source
ORDER BY rows DESC;

You should see rows for fingerprint (every runtime) plus any of ble / wifi / mdns / ssdp / http_probe your platform supports (Android native covers all five; CTV web covers fingerprint + connection + viewability + broadcast when running inside an ATSC 3.0 BA or HbbTV runtime).

Endpoints

The SDKs call these endpoints automatically. All four accept the same tb_ctv_* key via the Authorization: Bearer <key> header.

MethodPathPurpose
POST/v1/partner/registerSelf-serve tb_ctv_* key minting (one-time)
POST/v1/partner/device/{deviceId}/heartbeatAudience-measurement heartbeat (every 30 s)
POST/v1/partner/api-key/rotateRotate a tb_ctv_* key without downtime
GET/v1/partner/infoRead the partner record (slug, scoped regions, etc.)

Full OpenAPI: https://api.trillboards.com/docs/openapi/partner-api.yaml.

Platform target matrix

The npm SDK feature-probes the host runtime at boot and selects the right adapter. Every adapter ships fingerprint + viewability + connection; some add broadcaster signaling.

RuntimeUniversal coreBroadcast metadataPath
ATSC 3.0 Broadcaster Applicationyesyes β€” A/344 localhost: WSnpm SDK
HbbTV 2.0+ receiveryesyes β€” oipfObjectFactorynpm SDK
Samsung Tizen TV web appyesno (privileged only)npm SDK
LG webOS TV web appyesno (privileged only)npm SDK
Vizio SmartCast web appyesnonpm SDK
Fire TV / Google TV / Android TV WebViewyesno (bridge to native)npm SDK
Android TV / Fire TV native appyesn/aAndroid AAR
Android tablet / phone (Naki proxy)yesn/aAndroid AAR
Roku BrightScript / SceneGraphn/an/aunsupported
Apple tvOS (TVMLKit / JSC)partialn/aunsupported

For runtimes marked unsupported, contact us at https://trillboards.com/support/contact β€” these need a separate native adapter and are out of scope for v1.

Privacy posture

The SDK is privacy-first by design:

  • No PII collection. BLE scanning counts nearby devices; it does NOT resolve them to a person. Hashed MACs are dropped server-side after k-anon-5 aggregation.
  • No camera, no microphone, no audio. The public ctv-measurement AAR ships zero CV / audio code. The Trillboards in-house fleet SDK (agent-core, distributed via GitHub Packages) is a separate package with a separate consent contract.
  • K-anon-5 enforced server-side. Aggregate exports require at least 5 distinct devices per geohash Γ— hour Γ— venue-type bucket.
  • Consent revocation halts new measurements in flight; no offline queue drains after consent is revoked.

For the canonical privacy & data-handling write-up, see https://api.trillboards.com/docs/integrations/partner-native-bridge.md.

Next steps