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 onagent-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.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/partner/register | Self-serve tb_ctv_* key minting (one-time) |
| POST | /v1/partner/device/{deviceId}/heartbeat | Audience-measurement heartbeat (every 30 s) |
| POST | /v1/partner/api-key/rotate | Rotate a tb_ctv_* key without downtime |
| GET | /v1/partner/info | Read 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.
| Runtime | Universal core | Broadcast metadata | Path |
|---|---|---|---|
| ATSC 3.0 Broadcaster Application | yes | yes β A/344 localhost: WS | npm SDK |
| HbbTV 2.0+ receiver | yes | yes β oipfObjectFactory | npm SDK |
| Samsung Tizen TV web app | yes | no (privileged only) | npm SDK |
| LG webOS TV web app | yes | no (privileged only) | npm SDK |
| Vizio SmartCast web app | yes | no | npm SDK |
| Fire TV / Google TV / Android TV WebView | yes | no (bridge to native) | npm SDK |
| Android TV / Fire TV native app | yes | n/a | Android AAR |
| Android tablet / phone (Naki proxy) | yes | n/a | Android AAR |
| Roku BrightScript / SceneGraph | n/a | n/a | unsupported |
| Apple tvOS (TVMLKit / JSC) | partial | n/a | unsupported |
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-measurementAAR 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
- Manage, rotate, and revoke your
tb_ctv_*key in the earner portal at https://trillboards.com/earner under Developer β API Keys. - Read the partner integration deep-dive at https://api.trillboards.com/docs/integrations/ctv-measurement-partner-integration.md.
- Browse the OpenAPI spec at https://api.trillboards.com/docs/openapi/partner-api.yaml.
- Inspect the full machine-readable agent guide at https://api.trillboards.com/llms.txt.