# Trillboards CTV Measurement SDK Quickstart

Use the Android SDK for native Android TV, Fire TV, and tablet players. It is
built from `agent-core-lite`, the same heartbeat, identity, and proximity code
used by Trillboards-managed players.

The Android heartbeat is deliberately simple:

```text
POST /openrtb/v1/heartbeat
```

Register the device once, then initialize the SDK with that same stable device
identifier. Heartbeat liveness, MAID, HEM, and UID2 ingestion do not require a
Partner API key. The registered device/screen binding is the trust boundary.

The separate `@trillboards/ctv-measurement` web package still accepts a Partner
API key because it also calls authenticated `/v2/sdk/*` enrichment and
attribution endpoints.

## 1. Register each device

Registration is an authenticated provisioning action:

```bash
curl -X POST https://api.trillboards.com/v1/partner/device \
  -H "Authorization: Bearer trb_partner_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "store-123-player-1",
    "name": "Store 123 front screen"
  }'
```

Keep the registration `device_id` stable for the physical player; the stored
binding remains `external_id`. The app bundle and screen
binding belong to that registered device; do not substitute a screen UUID for a
platform advertising identifier.

## 2. Install the Android SDK

```kotlin
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven { url = uri("https://maven.trillboards.com") }
  }
}
```

```kotlin
dependencies {
  implementation("com.trillboards:ctv-measurement:1.3.2")
}
```

Requires Android API 24+ and JDK 17 for the build.

## 3. Initialize with the registered device ID

```kotlin
import com.trillboards.measurement.MeasurementConfig
import com.trillboards.measurement.TrillboardsMeasurement

val config = MeasurementConfig.Builder(
  deviceId = "store-123-player-1",
)
  .scanIntervalMs(30_000)
  .build()

TrillboardsMeasurement.initialize(applicationContext, config)
TrillboardsMeasurement.setConsentStatus(true)
TrillboardsMeasurement.startScheduledScans()
```

No API key is needed for this heartbeat flow. The legacy
`MeasurementConfig.Builder(apiKey, deviceId)` overload remains available for
apps that also call authenticated Partner API operations.

The AAR declares its own networking, Bluetooth, Wi-Fi, and Android advertising
ID permissions. Request the runtime Bluetooth/Wi-Fi permissions appropriate to
the device OS.

## 4. Send targeting identities

The built-in collector sends the actual platform advertising identifier
(GAID/AAID) when the platform exposes one. It never fabricates a screen ID as
`device.ifa`.

For audience IDs owned by the host, provide typed extended identities:

```kotlin
import com.trillboards.ctv.core.identity.ExtendedIdentity
import com.trillboards.ctv.core.identity.ExtendedIdentityProvider

val identityProvider = ExtendedIdentityProvider {
  listOf(
    ExtendedIdentity.hemSha256(
      digest = existingSha256EmailDigest,
      source = "your-identity-domain.example",
      observedAt = observedAtIso8601,
    ),
    ExtendedIdentity.uid2Token(
      token = currentUid2AdvertisingToken,
      source = "uidapi.com",
      observedAt = observedAtIso8601,
      expiresAt = uid2ExpiryIso8601,
    ),
  )
}

val config = MeasurementConfig.Builder("store-123-player-1")
  .extendedIdentityProvider(identityProvider)
  .build()
```

Value semantics are exact:

- MAID/GAID is persisted raw and emitted as OpenRTB `device.ifa`.
- `hem_sha256` must already be one SHA-256 digest. The server lowercases the
  hexadecimal representation once and does not hash it again.
- `uid2_token` is opaque and case-sensitive. Send it exactly as issued, with
  its expiration time so the host can refresh it.
- HEM and UID2 are emitted under OpenRTB 2.6 `user.eids`.

## 5. Verify ingress and egress

Heartbeat success proves only that the registered-device contract accepted the
request. For revenue readiness, verify the full chain:

1. PostgreSQL `earner_screen_devices` has the raw MAID state.
2. PostgreSQL `device_extended_identities` has the exact active HEM/UID2 rows.
3. Redis `dtwin:{screenId}` contains the same active identity projection.
4. A real outbound OpenRTB request contains `device.ifa` and/or `user.eids`.
5. A buyer response produces a VAST render and a canonical
   `completed_impressions` row.

Never use request volume or a VAST tracker alone as revenue proof. Canonical
revenue is a completed impression tied to the actual demand source and creative.

## Endpoint authentication

| Endpoint | Authentication |
|---|---|
| `POST /v1/partner/device` | Partner API key; provisioning |
| `POST /v1/partner/device/{deviceId}/heartbeat` | None after device registration |
| `POST /openrtb/v1/heartbeat` | None after device/screen registration |
| `GET /v1/partner/device/{deviceId}/heartbeat` | Partner API key; readback |
| `/v2/sdk/*` enrichment and attribution | Partner API key |

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

Deep integration guide:
<https://api.trillboards.com/docs/integrations/ctv-measurement-partner-integration.md>.
