CTV Measurement SDK
Privacy-first audience measurement for Connected TV. BLE proximity sensing, census enrichment, QR attribution, and MRC-compliant viewability tracking for Android TV and Fire TV apps.
npm install @trillboards/ctv-measurementdependencies {
implementation("com.trillboards:measurement-sdk:0.1.0")
}Capabilities
BLE Proximity
Bluetooth Low Energy device scanning counts nearby mobile devices for audience measurement without any PII collection.
Census Enrichment
IP-based demographic enrichment using US Census ACS data at geohash-6 resolution. IAB income, age, and education segments.
Device Fingerprinting
Canvas hash, WebGL renderer, audio sample rate, and hardware signals for cross-session device identity without cookies.
Viewability Tracking
MRC-compliant viewability with completion ratio, duration thresholds, and IMA impression confirmation for CTV environments.
QR Attribution
Physical-to-digital attribution via QR code scanning. IP co-location matching links the scanning mobile device to the CTV screen.
Connection Intelligence
Network classification (residential, mobile, commercial, VPN) with effective type, downlink speed, and RTT measurements.
Session Management
Persistent session IDs from SDK create() to destroy(). Per-household frequency capping and daily reach deduplication.
GDPR/CCPA Consent
Consent-first architecture. All measurement is inert until the publisher calls setConsentStatus(true). No data leaves the device without consent.
Quick Start
From install to first enriched impression in 5 steps.
Step 1: Install the SDK
npm install @trillboards/ctv-measurementStep 2: Initialize with consent
import { CtvMeasurement } from '@trillboards/ctv-measurement';
const sdk = CtvMeasurement.create({
apiKey: 'tb_ctv_YOUR_API_KEY',
channel: 'ctv',
});
// Consent-first: measurement is inert until this is called
sdk.setConsentStatus(true);Step 3: Enrich with census data
// Server-side IP lookup returns IAB demographic segments
const enrichment = await sdk.enrich();
console.log(enrichment.iabIncomeSegmentName);
// => "Upper-Middle Income ($100-150k)"
console.log(enrichment.iabAgeSegmentName);
// => "Age 35-44"
console.log(enrichment.connectionType);
// => "residential"Step 4: Track an impression
const result = await sdk.trackImpression({
adId: 'ad_summer_sale_15s',
placementId: 'living_room_main',
viewable: true,
viewabilityReason: 'ctv_full_completion',
completionRatio: 1.0,
durationMs: 15000,
bleDeviceCount: 3,
bleProximityConfidence: 0.85,
});
console.log(result.givtPassed); // true (not a duplicate)
console.log(result.frequency); // 2 (household saw this ad twice today)
console.log(result.householdReach); // 1847 (distinct households for this ad)Step 5: QR attribution (optional)
// When a viewer scans the QR code on the CTV screen,
// the mobile browser hits your landing page with the scanId
const scan = await sdk.recordQrScan({
scanId: 'c4a760a8-dbce-443e-a96a-4f2b1e5f0a3b',
screenId: 'screen_lobby_01',
adId: 'ad_summer_sale_15s',
placementId: 'living_room_main',
destinationUrl: 'https://brand.example.com/offer',
});
// The mobile beacon fires automatically from the landing page
// to perform IP co-location matching
console.log(scan.id); // PG row id for the scan eventAndroid Integration
The Kotlin AAR wraps the same measurement pipeline for native Android TV and Fire TV apps. Add the dependency and initialize in your Application class.
dependencies {
implementation("com.trillboards:measurement-sdk:0.1.0")
}import com.trillboards.measurement.CtvMeasurement
class MeasurementActivity : AppCompatActivity() {
private lateinit var sdk: CtvMeasurement
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sdk = CtvMeasurement.create(
apiKey = "tb_ctv_YOUR_API_KEY",
channel = "ctv"
)
// Consent-first: measurement is inert until this is called
sdk.setConsentStatus(true)
// Enrich with census demographics
lifecycleScope.launch {
val enrichment = sdk.enrich()
Log.d("CTV", "Income: ${enrichment.iabIncomeSegmentName}")
Log.d("CTV", "Connection: ${enrichment.connectionType}")
}
// Track an impression
lifecycleScope.launch {
val result = sdk.trackImpression(
adId = "ad_summer_sale_15s",
placementId = "lobby_screen_main",
viewable = true,
completionRatio = 1.0,
durationMs = 15000,
bleDeviceCount = 3
)
Log.d("CTV", "GIVT passed: ${result.givtPassed}")
Log.d("CTV", "Frequency: ${result.frequency}")
}
}
override fun onDestroy() {
super.onDestroy()
sdk.destroy()
}
}API Endpoints
The SDK calls these endpoints automatically. For server-to-server integrations, you can call them directly with your API key.
| Method | Endpoint | Description |
|---|---|---|
| POST | /v2/sdk/enrich | Census demographic enrichment from request IP |
| POST | /v2/sdk/qr/scan | Record a QR scan event |
| POST | /v2/sdk/qr/beacon | Process beacon signals from mobile QR scan |
| POST | /v2/sdk/impression | Ingest enriched CTV impression with GIVT dedup |
Authentication
All endpoints require an API key with the tb_ctv_ prefix. Include it as a header on every request:
# X-API-Key header
curl -X POST https://api.trillboards.com/v2/sdk/enrich \
-H "X-API-Key: tb_ctv_YOUR_KEY" \
-H "Content-Type: application/json"API keys are provisioned with scoped permissions: sdk:enrich, sdk:qr, and sdk:impression. Contact developers@trillboards.com to get your CTV publisher API key.
Rate Limits
| Endpoint | Limit |
|---|---|
| /v2/sdk/enrich | 300/min per API key |
| /v2/sdk/qr/scan | 100/min per API key |
| /v2/sdk/qr/beacon | 200/min per API key |
| /v2/sdk/impression | 1,000/min per API key |
Rate limit headers are included in every response: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset. When rate limited, the response includes a Retry-After header with the number of seconds to wait.
Ready to measure your CTV audience?
Explore the full API reference with interactive request builders and response examples.
Need help integrating? Contact developers@trillboards.com