@trillboards/api-client 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -45,6 +45,43 @@ interface ListResponse<T> {
45
45
  has_more: boolean;
46
46
  };
47
47
  }
48
+ type ContentFitMode = 'fit' | 'fill';
49
+ interface ContentFitSettings {
50
+ /** Direct and programmatic advertisements. */
51
+ advertisements?: ContentFitMode;
52
+ /** Default/background streams. */
53
+ default_streams?: ContentFitMode;
54
+ /** Self-promotion and QR-code house ads. */
55
+ self_promo?: ContentFitMode;
56
+ }
57
+ interface PartnerDisplaySettingsInput {
58
+ content_fit?: ContentFitSettings;
59
+ [key: string]: unknown;
60
+ }
61
+ interface PartnerDisplaySettingsResponse {
62
+ object: 'display_settings' | string;
63
+ screen_id: string;
64
+ content_fit: Required<ContentFitSettings>;
65
+ [key: string]: unknown;
66
+ }
67
+ interface PartnerDeviceRegistrationInput {
68
+ /** Stable partner-owned device identifier. */
69
+ device_id?: string;
70
+ /** Legacy SDK alias retained for backwards compatibility. */
71
+ device_name?: string;
72
+ device_type?: string;
73
+ name?: string;
74
+ display?: Record<string, unknown>;
75
+ display_settings?: PartnerDisplaySettingsInput;
76
+ location?: Record<string, unknown>;
77
+ venue?: Record<string, unknown>;
78
+ placement?: Record<string, unknown>;
79
+ [key: string]: unknown;
80
+ }
81
+ interface PartnerDeviceBatchRegistrationInput {
82
+ devices: PartnerDeviceRegistrationInput[];
83
+ defaults?: Omit<PartnerDeviceRegistrationInput, 'device_id' | 'device_name'>;
84
+ }
48
85
  interface Screen {
49
86
  screen_id: string;
50
87
  name: string;
@@ -160,6 +197,128 @@ interface BillingInfo {
160
197
  usage_this_month: Record<string, number>;
161
198
  stripe_connected: boolean;
162
199
  }
200
+ /**
201
+ * Comparison operators accepted for a Route threshold condition — the same set
202
+ * the API validates against (services/fein/clipTriggerSchema VALID_OPERATORS).
203
+ */
204
+ type RouteConditionOp = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'neq';
205
+ /** A threshold condition: fires when `field` crosses `value` via `op`. */
206
+ interface RouteCondition {
207
+ field: string;
208
+ op?: RouteConditionOp;
209
+ value?: number | string | boolean;
210
+ }
211
+ /** Channels a Route action can fire on. */
212
+ type RouteChannel = 'webhook' | 'whatsapp' | 'sms' | 'slack' | 'email' | 'clip';
213
+ /** One channel action fired when a Route matches. */
214
+ interface RouteAction {
215
+ channel: RouteChannel;
216
+ webhookId?: string;
217
+ recipientE164?: string;
218
+ recipientEmail?: string;
219
+ slackChannelId?: string;
220
+ templateName?: string;
221
+ attachClip?: boolean;
222
+ }
223
+ /** Scheduled-digest config (mode = 'digest'). */
224
+ interface RouteDigest {
225
+ metric: 'count' | 'avg' | 'max';
226
+ windowMinutes: number;
227
+ intervalMinutes: number;
228
+ nextRunAt?: string;
229
+ }
230
+ /** A Sense Anything Route — a (condition -> channel actions) alert/automation rule. */
231
+ interface SensingRoute {
232
+ routeId: string;
233
+ ownerKind: 'earner_user' | 'partner';
234
+ ownerId: string;
235
+ partnerId?: string | null;
236
+ name: string;
237
+ enabled: boolean;
238
+ profileName?: string | null;
239
+ mode: 'threshold' | 'digest';
240
+ condition: RouteCondition | null;
241
+ actions: RouteAction[];
242
+ screenMongoIds: string[];
243
+ sustainWindowSeconds?: number;
244
+ sustainMinSamples?: number;
245
+ debounceSeconds?: number;
246
+ activeTz?: string | null;
247
+ activeStartMinute?: number | null;
248
+ activeEndMinute?: number | null;
249
+ activeDaysMask?: number;
250
+ digest: RouteDigest | null;
251
+ createdAt: string;
252
+ updatedAt: string;
253
+ }
254
+ /** Request body to create a Route. */
255
+ interface CreateRouteParams {
256
+ name: string;
257
+ screenMongoIds: string[];
258
+ actions: RouteAction[];
259
+ mode?: 'threshold' | 'digest';
260
+ enabled?: boolean;
261
+ profileName?: string;
262
+ condition?: RouteCondition;
263
+ digest?: {
264
+ metric: 'count' | 'avg' | 'max';
265
+ field?: string;
266
+ windowMinutes: number;
267
+ intervalMinutes: number;
268
+ };
269
+ sustainWindowSeconds?: number;
270
+ sustainMinSamples?: number;
271
+ debounceSeconds?: number;
272
+ activeTz?: string;
273
+ activeStartMinute?: number;
274
+ activeEndMinute?: number;
275
+ activeDaysMask?: number;
276
+ }
277
+ /** Request body to update a Route (partial, non-clobbering). */
278
+ type UpdateRouteParams = Partial<CreateRouteParams>;
279
+ /**
280
+ * The webhook events a Route fire can deliver. The event type is in the
281
+ * `X-Trillboards-Event` request header: a custom sensed field delivers
282
+ * `sensing.threshold_crossed`, a known semantic delivers the matching
283
+ * `audience.*` / `venue.*` event, and a scheduled digest delivers
284
+ * `venue.traffic_summary`.
285
+ */
286
+ type RouteWebhookEvent = 'sensing.threshold_crossed' | 'audience.spike' | 'audience.venue_busy' | 'audience.purchase_intent' | 'audience.demographics_update' | 'venue.traffic_summary';
287
+ /**
288
+ * The aggregate summary delivered as the `observed` value of a digest Route fire
289
+ * (`venue.traffic_summary`). The digest runner builds it over the Route's window.
290
+ */
291
+ interface RouteDigestSummary {
292
+ metric: 'count' | 'avg' | 'max';
293
+ field: string | null;
294
+ /** The headline value for `metric`. null for an avg/max window with no samples. */
295
+ value: number | null;
296
+ count: number;
297
+ avg: number | null;
298
+ max: number | null;
299
+ window_minutes: number;
300
+ window_end: string;
301
+ }
302
+ /**
303
+ * The FLAT webhook request body delivered when a Route fires. Unlike the
304
+ * device/programmatic events there is NO `{event,timestamp,data}` wrapper — the
305
+ * event type rides the `X-Trillboards-Event` header. Verify the
306
+ * `X-Trillboards-Signature` header over the raw body before trusting it.
307
+ *
308
+ * `observed` is a scalar for threshold Routes; for a digest Route
309
+ * (`venue.traffic_summary`) it is a {@link RouteDigestSummary} object.
310
+ */
311
+ interface SensingThresholdCrossedEvent {
312
+ route_id: string;
313
+ route_name: string;
314
+ owner_kind: 'earner_user' | 'partner';
315
+ screen_id: string;
316
+ field: string | null;
317
+ op: RouteConditionOp | null;
318
+ threshold: number | string | boolean | null;
319
+ observed: number | string | boolean | RouteDigestSummary | null;
320
+ fired_at: string;
321
+ }
163
322
 
164
323
  /**
165
324
  * Internal HTTP client for the Trillboards API.
@@ -173,6 +332,32 @@ interface HttpClientConfig {
173
332
  baseUrl: string;
174
333
  version?: string;
175
334
  }
335
+ interface RetryOptions {
336
+ /** Maximum number of retry attempts after the initial call. Defaults to 6. */
337
+ retries?: number;
338
+ /** Base delay in ms for exponential backoff. Defaults to 1000. */
339
+ baseMs?: number;
340
+ /**
341
+ * Optional callback invoked when an attempt fails with a retryable error.
342
+ * `attempt` is 1-indexed (1 = first failure). Useful for observability —
343
+ * the CTV SDK uses this to emit `upload_error` events without coupling
344
+ * the retry loop to event emission.
345
+ */
346
+ onAttemptError?: (attempt: number, error: unknown) => void;
347
+ }
348
+ /**
349
+ * Wrap an async operation in retry-with-exponential-backoff.
350
+ *
351
+ * - Retries up to `retries` times on retryable errors (429, 5xx, TypeError network).
352
+ * - Honors `Retry-After` header on 429 responses (parsed into TrillboardsError.retryAfter).
353
+ * - Throws the last error after the budget is exhausted.
354
+ * - Permanent 4xx (auth, validation, not-found) throw immediately on first attempt.
355
+ *
356
+ * This is the SINGLE retry primitive in the SDK — every POST/PUT/DELETE is
357
+ * wrapped automatically by HttpClient. GET is left to the caller because
358
+ * idempotency assumptions vary by route.
359
+ */
360
+ declare function retryWithBackoff<T>(operation: () => Promise<T>, opts?: RetryOptions): Promise<T>;
176
361
  declare class HttpClient {
177
362
  private readonly config;
178
363
  constructor(config: HttpClientConfig);
@@ -180,10 +365,33 @@ declare class HttpClient {
180
365
  get<T>(path: string, opts?: {
181
366
  params?: Record<string, unknown>;
182
367
  }): Promise<T>;
183
- post<T>(path: string, body?: unknown): Promise<T>;
184
- put<T>(path: string, body?: unknown): Promise<T>;
185
- patch<T>(path: string, body?: unknown): Promise<T>;
186
- delete<T>(path: string): Promise<T>;
368
+ /**
369
+ * POST / PUT / PATCH / DELETE auto-retry on 429 + 5xx + network TypeError
370
+ * via `retryWithBackoff()`. Callers can opt into observability by passing
371
+ * `retry.onAttemptError` to receive a callback per transient failure.
372
+ *
373
+ * The retry behavior is identical across all four methods because all of
374
+ * them are state-changing operations subject to the same transient-failure
375
+ * classes (rate limit, upstream-down, network-flap). The wire idempotency
376
+ * of POST vs DELETE is enforced server-side via Idempotency-Key headers,
377
+ * which the caller can attach via `opts.headers`.
378
+ */
379
+ post<T>(path: string, body?: unknown, opts?: {
380
+ retry?: RetryOptions;
381
+ headers?: Record<string, string>;
382
+ }): Promise<T>;
383
+ put<T>(path: string, body?: unknown, opts?: {
384
+ retry?: RetryOptions;
385
+ headers?: Record<string, string>;
386
+ }): Promise<T>;
387
+ patch<T>(path: string, body?: unknown, opts?: {
388
+ retry?: RetryOptions;
389
+ headers?: Record<string, string>;
390
+ }): Promise<T>;
391
+ delete<T>(path: string, opts?: {
392
+ retry?: RetryOptions;
393
+ headers?: Record<string, string>;
394
+ }): Promise<T>;
187
395
  private buildUrl;
188
396
  }
189
397
 
@@ -207,18 +415,18 @@ declare class ScreensClient {
207
415
  data: Screen;
208
416
  }>;
209
417
  /** Register a new device/screen. */
210
- create(body: {
211
- device_name: string;
212
- device_type?: string;
213
- venue_type?: string;
214
- country?: string;
215
- city?: string;
216
- timezone?: string;
217
- resolution?: string;
218
- }): Promise<{
418
+ create(body: PartnerDeviceRegistrationInput): Promise<{
219
419
  success: boolean;
220
420
  data: Screen;
221
421
  }>;
422
+ /** Get the resolved display settings for one screen. */
423
+ getDisplaySettings(screenId: string): Promise<PartnerDisplaySettingsResponse>;
424
+ /** Partially update display settings; omitted content-fit keys are preserved. */
425
+ patchDisplaySettings(screenId: string, settings: PartnerDisplaySettingsInput): Promise<PartnerDisplaySettingsResponse>;
426
+ /** Update display settings through the PUT endpoint. */
427
+ updateDisplaySettings(screenId: string, settings: PartnerDisplaySettingsInput): Promise<PartnerDisplaySettingsResponse>;
428
+ /** Apply a partial display-settings update to up to 100 screens. */
429
+ batchUpdateDisplaySettings(screenIds: string[], settings: PartnerDisplaySettingsInput): Promise<Record<string, unknown>>;
222
430
  /** Delete a screen/device. */
223
431
  delete(screenId: string): Promise<{
224
432
  success: boolean;
@@ -245,20 +453,32 @@ declare class DevicesClient {
245
453
  data: Device;
246
454
  }>;
247
455
  /** Register a new device. */
248
- create(body: {
249
- device_name: string;
250
- device_type?: string;
251
- platform?: string;
252
- }): Promise<{
456
+ create(body: PartnerDeviceRegistrationInput): Promise<{
253
457
  success: boolean;
254
458
  data: Device;
255
459
  }>;
460
+ /** Register or re-register up to 100 devices with optional fleet defaults. */
461
+ createBatch(body: PartnerDeviceBatchRegistrationInput): Promise<{
462
+ success: boolean;
463
+ summary: Record<string, number>;
464
+ devices: Array<Record<string, unknown>>;
465
+ errors: Array<Record<string, unknown>>;
466
+ }>;
256
467
  /** Delete a device. */
257
468
  delete(deviceId: string): Promise<{
258
469
  success: boolean;
259
470
  }>;
260
- /** Send heartbeat for a device. */
261
- heartbeat(deviceId: string, body?: Record<string, unknown>): Promise<{
471
+ /**
472
+ * Send heartbeat for a device.
473
+ *
474
+ * Auto-retries on 429 + 5xx + network TypeError via the shared
475
+ * retryWithBackoff() helper (see http.ts). Pass `opts.retry.onAttemptError`
476
+ * to observe transient failures (e.g. emit an `upload_error` event from
477
+ * the CTV SDK without coupling the retry loop to its event emitter).
478
+ */
479
+ heartbeat(deviceId: string, body?: Record<string, unknown>, opts?: {
480
+ retry?: RetryOptions;
481
+ }): Promise<{
262
482
  success: boolean;
263
483
  }>;
264
484
  }
@@ -515,6 +735,57 @@ declare class BillingClient {
515
735
  }>;
516
736
  }
517
737
 
738
+ /**
739
+ * RoutesClient — Sense Anything "Routes": alerting + automation on the sensing
740
+ * stream. A Route fires channel actions (webhook / managed WhatsApp / SMS /
741
+ * Slack / email) when a sensed field crosses a threshold on a set of your
742
+ * screens, or on a scheduled digest.
743
+ *
744
+ * Base path: /v1/partner/sensing/routes
745
+ */
746
+
747
+ declare class RoutesClient {
748
+ private readonly http;
749
+ constructor(http: HttpClient);
750
+ /** Create a Route. Every screen in `screenMongoIds` must be one you own. */
751
+ create(body: CreateRouteParams): Promise<{
752
+ success: boolean;
753
+ data: {
754
+ route: SensingRoute;
755
+ };
756
+ }>;
757
+ /** List your Routes. Pass `includeDisabled: false` to return only enabled Routes. */
758
+ list(params?: {
759
+ includeDisabled?: boolean;
760
+ }): Promise<{
761
+ success: boolean;
762
+ data: {
763
+ routes: SensingRoute[];
764
+ };
765
+ }>;
766
+ /** Get one Route by id. */
767
+ get(routeId: string): Promise<{
768
+ success: boolean;
769
+ data: {
770
+ route: SensingRoute;
771
+ };
772
+ }>;
773
+ /** Update a Route (partial, non-clobbering — only the fields you send change). */
774
+ update(routeId: string, body: UpdateRouteParams): Promise<{
775
+ success: boolean;
776
+ data: {
777
+ route: SensingRoute;
778
+ };
779
+ }>;
780
+ /** Soft-delete a Route. */
781
+ delete(routeId: string): Promise<{
782
+ success: boolean;
783
+ data: {
784
+ routeId: string;
785
+ };
786
+ }>;
787
+ }
788
+
518
789
  /**
519
790
  * TrillboardsClient — main entry point for the Trillboards Partner API SDK.
520
791
  *
@@ -537,6 +808,7 @@ declare class TrillboardsClient {
537
808
  readonly data: DataClient;
538
809
  readonly errors: ErrorsClient;
539
810
  readonly billing: BillingClient;
811
+ readonly routes: RoutesClient;
540
812
  /** The underlying HTTP client (exposed for advanced use cases). */
541
813
  readonly _http: HttpClient;
542
814
  constructor(options: ClientOptions);
@@ -600,46 +872,133 @@ declare class TrillboardsError extends Error {
600
872
  }
601
873
 
602
874
  /**
603
- * Webhook signature verification for Trillboards Partner API webhooks.
875
+ * Webhook signature verification for Trillboards webhooks.
876
+ *
877
+ * Header format produced by the server (matches Stripe-style compound shape):
878
+ *
879
+ * X-Trillboards-Signature: t=<unix_seconds>,v0=<hex>,v1=<hex>
880
+ * X-Trillboards-Timestamp: <unix_seconds> // redundant; same value as t=
881
+ * X-Trillboards-Event: <event_name>
882
+ * X-Trillboards-Webhook-Id: <subscription_id>
883
+ *
884
+ * Where v1 = HMAC-SHA256(secret, `${timestamp}.${payload}`)
885
+ * v0 = HMAC-SHA256(secret, payload) ← legacy body-only, emitted for back-compat
604
886
  *
605
- * Uses HMAC-SHA256 with timing-safe comparison to prevent signature
606
- * forgery and timing attacks.
887
+ * `constructEvent` prefers v1 (replay-protected); falls back to v0 when v1 is
888
+ * absent (server in pre-migration state) or when only the legacy
889
+ * `sha256=<hex>` form is present.
890
+ *
891
+ * Replay protection: a configurable tolerance window (default 300s, Stripe-
892
+ * aligned) rejects payloads whose timestamp is too far from the verifier's
893
+ * clock. Adjust via the `tolerance` option.
894
+ *
895
+ * Migration note (since 0.3.0):
896
+ * The 0.2.0-shape `constructEvent(payload, signatureString, secret)` is kept
897
+ * as `constructEventLegacy` for one minor version. It does NOT verify the
898
+ * timestamp (the 0.2.0 verifier had no access to one). Migrate to the new
899
+ * call shape `constructEvent(payload, req.headers, secret)` to gain replay
900
+ * protection and compound-header support.
607
901
  */
608
902
 
903
+ interface ConstructEventOptions {
904
+ /** Maximum allowed age (seconds) of a signed payload. Default 300 (Stripe-aligned). */
905
+ tolerance?: number;
906
+ /** Override the verifier's current time (seconds). Tests only. */
907
+ nowSec?: number;
908
+ }
909
+ /**
910
+ * Headers carried by a Trillboards webhook delivery. Accepts either the
911
+ * canonical Node `IncomingHttpHeaders` shape (lowercase keys) or any
912
+ * Record<string, string|string[]> with case-insensitive lookup.
913
+ */
914
+ type WebhookHeaders = Record<string, string | string[] | undefined> | {
915
+ [key: string]: string | string[] | undefined;
916
+ };
917
+ /**
918
+ * Parsed signature-header components.
919
+ */
920
+ interface ParsedSignatureHeader {
921
+ /** Unix seconds from `t=` if present. */
922
+ timestamp?: number;
923
+ /** Algorithm → hex-digest map. Standard keys: `v0`, `v1`. */
924
+ versions: Map<string, string>;
925
+ /** Set when the raw input matched the legacy `sha256=<hex>` shape. */
926
+ legacy?: string;
927
+ }
928
+ /**
929
+ * Parse a Trillboards `X-Trillboards-Signature` header into its component
930
+ * algorithm-keyed digests.
931
+ *
932
+ * Accepts:
933
+ * "t=<ts>,v1=<hex>" → canonical
934
+ * "t=<ts>,v0=<hex>,v1=<hex>" → canonical with legacy companion
935
+ * "sha256=<hex>" → pre-migration emitter (single value)
936
+ *
937
+ * @public
938
+ */
939
+ declare function parseSignatureHeader(rawSig: string): ParsedSignatureHeader;
609
940
  /**
610
941
  * Verify a webhook payload signature and parse the event.
611
942
  *
612
- * @param payload - Raw request body as string or Buffer.
613
- * @param signature - The signature from the `X-Trillboards-Signature` header.
614
- * @param secret - The webhook secret from your webhook subscription.
615
- * @returns The parsed WebhookEvent if signature is valid.
616
- * @throws TrillboardsError if the signature does not match.
943
+ * @param payload — raw request body (string or Buffer). Must be EXACTLY the
944
+ * bytes the server signed — do NOT re-stringify after JSON.parse.
945
+ * @param headers — IncomingHttpHeaders or any Record<string, string|string[]>
946
+ * produced by your HTTP framework. Case-insensitive lookup.
947
+ * For Express, pass `req.headers`. For Next.js, `req.headers`.
948
+ * Backwards-compat: a raw signature string (the 0.2.0 shape)
949
+ * is also accepted and parsed as if it were the value of
950
+ * `X-Trillboards-Signature`; in that case the verifier looks
951
+ * for the timestamp in the compound header itself, OR throws.
952
+ * @param secret — webhook subscription secret (whsec_...).
953
+ * @param opts.tolerance — replay-window in seconds. Default 300.
954
+ * @returns parsed WebhookEvent if signature valid AND timestamp within tolerance.
955
+ * @throws TrillboardsError(authentication_error / webhook_signature_invalid)
617
956
  *
618
957
  * @example
619
- * ```typescript
620
958
  * import { Webhooks } from '@trillboards/api-client';
621
959
  *
622
- * app.post('/webhooks', (req, res) => {
623
- * const event = Webhooks.constructEvent(
624
- * req.body,
625
- * req.headers['x-trillboards-signature'],
626
- * process.env.WEBHOOK_SECRET
627
- * );
628
- * console.log('Received event:', event.type);
629
- * res.sendStatus(200);
960
+ * app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
961
+ * try {
962
+ * const event = Webhooks.constructEvent(
963
+ * req.body, // raw Buffer or string
964
+ * req.headers, // IncomingHttpHeaders
965
+ * process.env.WEBHOOK_SECRET!, // your subscription secret
966
+ * );
967
+ * console.log('Received', event.type);
968
+ * res.sendStatus(200);
969
+ * } catch (err) {
970
+ * // TrillboardsError with code "webhook_signature_invalid"
971
+ * res.sendStatus(401);
972
+ * }
630
973
  * });
631
- * ```
632
974
  */
633
- declare function constructEvent(payload: string | Buffer, signature: string, secret: string): WebhookEvent;
975
+ declare function constructEvent(payload: string | Buffer, headers: WebhookHeaders | string, secret: string, opts?: ConstructEventOptions): WebhookEvent;
976
+ /**
977
+ * @deprecated since 0.3.0 — kept for one minor version for partners stuck on
978
+ * the 0.2.0 call shape. Use `constructEvent(payload, req.headers, secret)`
979
+ * instead — it gains replay protection and compound-header support.
980
+ *
981
+ * Reproduces the 0.2.0 verifier exactly:
982
+ * HMAC-SHA256(secret, payload) compared (constant-time) against the
983
+ * supplied raw-hex signature string.
984
+ *
985
+ * Does NOT verify any timestamp. The 0.2.0 SDK never had access to one and
986
+ * silently accepted any age of payload — that is a vulnerability this
987
+ * legacy shim preserves only for migration compatibility.
988
+ */
989
+ declare function constructEventLegacy(payload: string | Buffer, signature: string, secret: string): WebhookEvent;
990
+ /** Namespaced export — matches the Stripe-style API. */
634
991
  declare const Webhooks: {
635
992
  constructEvent: typeof constructEvent;
993
+ constructEventLegacy: typeof constructEventLegacy;
994
+ parseSignatureHeader: typeof parseSignatureHeader;
636
995
  };
637
996
 
638
997
  /**
639
998
  * SDK version constant. Sent in User-Agent and X-Trillboards-Client headers.
640
999
  * Keep in sync with package.json version.
641
1000
  */
642
- declare const VERSION = "0.1.3";
1001
+ declare const VERSION = "0.4.1";
643
1002
 
644
1003
  type BleScanResultData = {
645
1004
  appleContinuitySubtype?: number | null;
@@ -861,6 +1220,32 @@ type PartnerHeartbeatRequest = {
861
1220
  last_vast_error_code?: number;
862
1221
  last_vast_request_at?: string;
863
1222
  last_vast_response_at?: string;
1223
+ /** Current GPS fix for a moving screen. Processed only for screens whose `mobility_type` is `mobile` or `semi_mobile` (declare it at registration or via PATCH /device/{deviceId}), OR for any screen still missing coordinates (a one-time self-heal that also kicks off venue classification). Static screens with valid coordinates ignore it. Updates live geo-targeting, geofencing and analytics. Fully optional — omit it on stationary screens. */
1224
+ location?: {
1225
+ /** Horizontal accuracy radius in meters. Fixes above ~500m are clamped. */
1226
+ accuracy_m?: number;
1227
+ /** Up to 200 fixes recorded while the device was offline, replayed as a historical track. Timestamps MUST be in ascending order — a non-chronological batch is rejected with HTTP 400. Only the top-level `location` updates the live serving position. */
1228
+ buffered_locations?: Array<{
1229
+ accuracy_m?: number;
1230
+ heading?: number;
1231
+ lat: number;
1232
+ lng: number;
1233
+ speed_mps?: number;
1234
+ timestamp?: number;
1235
+ }>;
1236
+ /** Bearing in degrees (0–360, 0 = true north). Omit if unavailable. */
1237
+ heading?: number;
1238
+ /** Latitude in decimal degrees (WGS 84). */
1239
+ lat?: number;
1240
+ /** Longitude in decimal degrees (WGS 84). */
1241
+ lng?: number;
1242
+ /** `gps` — a live on-device fix. `gps_buffered` — a fix recorded while offline and replayed on reconnect. */
1243
+ source?: "gps" | "gps_buffered";
1244
+ /** Ground speed in meters per second. Omit if unavailable. */
1245
+ speed_mps?: number;
1246
+ /** Unix epoch milliseconds of the on-device fix. Send the device clock value; the server tolerates up to 10 minutes of skew. A position jump implying > 320 km/h between consecutive fixes is rejected. */
1247
+ timestamp?: number;
1248
+ };
864
1249
  /** Alternate namespace for telemetry keys (legacy support). */
865
1250
  metadata?: Record<string, unknown>;
866
1251
  muted?: boolean;
@@ -1018,12 +1403,12 @@ interface heartbeatOperation {
1018
1403
  response: PartnerHeartbeatResponse;
1019
1404
  }
1020
1405
  declare const generatedTypes: {
1021
- readonly schemaCount: 67;
1022
- readonly operationCount: 132;
1023
- readonly schemas: readonly ["Ad", "AdSettings", "AudienceAnalyticsResponse", "AudienceExportResponse", "AudienceMetricsResponse", "AudienceSegment", "BatchResult", "BleScanResultData", "ChangelogEntry", "ChangelogResponse", "ContentPreferencesInput", "ContentPreferencesResponse", "ContentPreset", "DashboardResponse", "DemographicsResponse", "Device", "DeviceCapabilityPayload", "DeviceSummary", "DisplaySettingsInput", "DisplaySettingsResponse", "EarningsBreakdownResponse", "EarningsResponse", "Error", "HeaderBiddingSettings", "HttpProbeResult", "MdnsNetworkDevice", "Pagination", "Partner", "PartnerAdDeliveryProfile", "PartnerDefaultStream", "PartnerDefaultStreamCreate", "PartnerDefaultStreamUpdate", "PartnerDeviceHeartbeatBody", "PartnerHeartbeatCommand", "PartnerHeartbeatData", "PartnerHeartbeatMeasurementMetadata", "PartnerHeartbeatQueryData", "PartnerHeartbeatQueryResponse", "PartnerHeartbeatRequest", "PartnerHeartbeatResponse", "PartnerHeartbeatTelemetry", "PartnerLBarContent", "PartnerLBarDisplayPreferences", "PartnerLBarSettings", "PartnerSafeHeartbeatTelemetry", "PartnerSelfPromoAd", "PartnerSelfPromoCreate", "PartnerSelfPromoPlacement", "PartnerStats", "PayoutResponse", "PlaygroundResult", "PlaygroundValidationError", "ProgrammaticSettingsInput", "ProgrammaticSettingsResponse", "RequestMetadataResponse", "RolePermissions", "SDKConfig", "ScreenAnalyticsResponse", "SsdpDeviceInfo", "TeamInviteResponse", "TeamMember", "Transaction", "Webhook", "WebhookDelivery", "WebhookEvent", "WifiEnvironmentSnapshot", "WifiScanResult"];
1024
- readonly operations: readonly ["listPartnerSelfPromoAds", "createPartnerSelfPromoAd", "listPartnerSelfPromoPlacements", "schedulePartnerSelfPromo", "createPartnerSelfPromoUpload", "disablePartnerSelfPromo", "registerAgent", "getAnalytics", "rotateApiKey", "getAudienceAnalytics", "exportAudienceData", "getAudienceHeatmap", "getLiveAudience", "getLookalikeScreens", "predictScreenAudience", "listAudienceSegments", "createAudienceSegment", "deleteAudienceSegment", "getAudienceSegment", "updateAudienceSegment", "matchAudienceSegment", "estimateSegmentReach", "generateSegmentVastTag", "getAudienceStats", "purchaseCredits", "createCreditCheckout", "setupBilling", "getUsageSummary", "getConnectDashboard", "getConnectEarningsChart", "getConnectScreenHealth", "listContentPresets", "getContentPreset", "getDashboard", "registerDevice", "deleteDevice", "getDevice", "updateDevice", "getAdsForDevice", "ackDeviceCommand", "getDeviceHeartbeat", "heartbeat", "recordProgrammaticEvent", "listDevices", "registerDevicesBatch", "batchUpdateDeviceOrientation", "getEarnings", "getEarningsBreakdown", "requestPayout", "getTransactions", "getActiveSSEConnections", "subscribeToEvents", "getFleetAnalytics", "pushFleetCommand", "getFleetOverview", "updateFleetSettings", "recordImpressionGet", "recordImpressionPost", "recordImpressionsBatch", "getPartnerInfo", "listIntentCategories", "getIntentCategory", "getIntentPricing", "getNetworkConfig", "patchNetworkConfig", "updateNetworkConfig", "recordAdComplete", "getPricing", "quickStart", "registerPartner", "generateSandboxEvent", "resetSandbox", "createSandboxScreen", "getSandboxStatus", "batchUpdateContentPreferences", "batchUpdateDisplaySettings", "batchUpdateProgrammaticSettings", "resolvePartnerScreen", "getScreenAnalytics", "applyContentPreset", "getAudienceMetrics", "getContentPreferences", "patchContentPreferences", "updateContentPreferences", "listPartnerDefaultStreams", "createPartnerDefaultStream", "createPartnerDefaultStreamImages", "reorderPartnerDefaultStreams", "createPartnerDefaultStreamUpload", "deletePartnerDefaultStream", "updatePartnerDefaultStream", "getScreenDemographics", "getDisplaySettings", "patchDisplaySettings", "updateDisplaySettings", "getPartnerLBarSettings", "updatePartnerLBarSettings", "updatePartnerLBarLiveUpdates", "getProgrammaticSettings", "patchProgrammaticSettings", "updateProgrammaticSettings", "createStripeConnectAccount", "getStripeLoginLink", "getStripeOnboardingLink", "requestStripePayout", "getStripePayoutHistory", "getStripeAccountStatus", "handleStripeWebhook", "listTeamMembers", "acceptInvitation", "inviteTeamMember", "getInvitationByToken", "resendInvitation", "removeTeamMember", "changeTeamMemberRole", "getTrackingPublicKey", "recordTrackingBatch", "upgradeToPartner", "getUsageSummary__get__usage", "getUsageDashboard", "getRateLimitStatus", "queryChangelog", "playgroundDryRun", "getRequestMetadata", "getVastConfig", "getVenueIntelligence", "listWebhooks", "createWebhook", "deleteWebhook", "updateWebhook", "getWebhookDeliveries", "testWebhook"];
1406
+ readonly schemaCount: 99;
1407
+ readonly operationCount: 210;
1408
+ readonly schemas: readonly ["Ad", "AdSettings", "AudienceAnalyticsResponse", "AudienceAttentionPrediction", "AudienceExportResponse", "AudienceLoyaltyFeaturesRow", "AudienceMetricsResponse", "AudienceSegment", "AudienceSocialAttention", "BatchResult", "BleScanResultData", "ChangelogEntry", "ChangelogResponse", "ContentFitSettings", "ContentPreferencesInput", "ContentPreferencesResponse", "ContentPreset", "CreatePartnerSensingRouteRequest", "CreativeClassification", "DashboardResponse", "DataExportSummaryResponse", "DemographicsResponse", "Device", "DeviceCapabilityPayload", "DeviceSummary", "DisplaySettingsInput", "DisplaySettingsResponse", "EarningsBreakdownResponse", "EarningsResponse", "Error", "FleetCommand", "FleetCommandEvent", "HeaderBiddingSettings", "HttpProbeResult", "MdnsNetworkDevice", "Pagination", "Partner", "PartnerAdDeliveryProfile", "PartnerDefaultStream", "PartnerDefaultStreamCreate", "PartnerDefaultStreamUpdate", "PartnerDeviceHeartbeatBody", "PartnerHeartbeatCommand", "PartnerHeartbeatData", "PartnerHeartbeatMeasurementMetadata", "PartnerHeartbeatQueryData", "PartnerHeartbeatQueryResponse", "PartnerHeartbeatRequest", "PartnerHeartbeatResponse", "PartnerHeartbeatTelemetry", "PartnerLBarContent", "PartnerLBarDisplayPreferences", "PartnerLBarSettings", "PartnerMonetizationRequirements", "PartnerNetworkConfig", "PartnerNetworkConfigUpdate", "PartnerPlay", "PartnerRegistrationDisplaySettings", "PartnerRequirement", "PartnerSafeHeartbeatTelemetry", "PartnerSelfPromoAd", "PartnerSelfPromoCreate", "PartnerSelfPromoPlacement", "PartnerStats", "PayoutResponse", "PlaygroundResult", "PlaygroundValidationError", "ProgrammaticSettingsInput", "ProgrammaticSettingsResponse", "RequestMetadataResponse", "ResolvedContentPolicy", "ResolvedSensingConfig", "RolePermissions", "SDKConfig", "ScreenAnalyticsResponse", "ScreenGroup", "ScreenGroupContentPolicy", "ScreenGroupContentPolicyInput", "ScreenGroupEvent", "ScreenGroupSensingConfig", "ScreenGroupSensingConfigInput", "SensingPerformanceMode", "SensingRoute", "SensingRouteAction", "SensingRouteCondition", "SensingSettingsResponse", "SensingThresholdCrossedEvent", "SsdpDeviceInfo", "TeamInviteResponse", "TeamMember", "Transaction", "UpdatePartnerSensingRouteRequest", "VastWaterfall", "VastWaterfallSource", "Webhook", "WebhookDelivery", "WebhookEvent", "WifiEnvironmentSnapshot", "WifiScanResult"];
1409
+ readonly operations: readonly ["listPartnerSelfPromoAds", "createPartnerSelfPromoAd", "listPartnerSelfPromoPlacements", "schedulePartnerSelfPromo", "createPartnerSelfPromoUpload", "disableSelfPromoAd", "registerAgent", "getAnalytics", "getAudienceContinuity", "getAudienceProfile", "getBleSummary", "getContentPerformance", "searchCreativeCatalog", "getCreativeScenes", "getCrossScreenFlow", "getPartnerAnalyticsDashboard", "getDeviceSuiteSignature", "exportPartnerAnalytics", "exportPartnerAdsByDay", "getFleetHourly", "getHaloFunnel", "getLoyaltyClock", "getNetworkSummary", "getNetworkTrend", "getProximitySummary", "getRevenueAttentionCorrelation", "getSceneFeed", "getSceneIntelligence", "rotateApiKey", "getAudienceAnalytics", "exportAudienceData", "getAudienceHeatmap", "getLiveAudience", "getLookalikeScreens", "predictScreenAudience", "getResolvedHouseholdReach", "listAudienceSegments", "createAudienceSegment", "deleteAudienceSegment", "getAudienceSegment", "updateAudienceSegment", "matchAudienceSegment", "estimateSegmentReach", "generateSegmentVastTag", "getAudienceStats", "cancelBillingSubscription", "purchaseCredits", "createCreditCheckout", "listBillingPlans", "setupBilling", "createBillingSubscription", "getBillingSubscription", "getBillingUsage", "getUsageSummary", "stripeWebhookCallback", "getConnectDashboard", "getConnectEarningsChart", "getConnectScreenHealth", "listContentPresets", "getContentPreset", "submitPartnerCreative", "getPartnerCreative", "getDashboard", "exportProximityGraphEdges", "getDataExportSummary", "exportDataset", "getGeohashAudiencePanel", "getOutcomeAttribution", "registerDevice", "deleteDevice", "getDevice", "updateDevice", "getAdsForDevice", "ackDeviceCommand", "getDeviceHeartbeat", "heartbeat", "recordProgrammaticEvent", "listDevices", "registerDeviceV2", "registerDevicesBatch", "batchUpdateDeviceOrientation", "reconcileDevices", "partnerDocsBrowser", "getEarnings", "getEarningsBreakdown", "requestPayout", "getTransactions", "getActiveSSEConnections", "subscribeToEvents", "getFleetCommand", "getFleetAnalytics", "pushFleetCommand", "getFleetOverview", "updateFleetSettings", "recordImpressionGet", "recordImpressionPost", "recordImpressionsBatch", "getPartnerInfo", "listIntentCategories", "getIntentCategory", "getIntentPricing", "getMeasurementMethodology", "getNetworkConfig", "patchNetworkConfig", "updateNetworkConfig", "getNetworkTrends", "partnerPing", "partnerPingAuth", "recordPartnerPlay", "recordPartnerPlaysBatch", "getPricing", "recordProgrammaticEventLegacyAlias", "quickStart", "registerPartner", "generateSandboxEvent", "resetSandbox", "createSandboxScreen", "getSandboxStatus", "listScreenGroups", "createScreenGroup", "deleteScreenGroup", "getScreenGroup", "updateScreenGroup", "getScreenGroupAnalytics", "createScreenGroupCommand", "getScreenGroupContentPolicy", "updateScreenGroupContentPolicy", "removeScreenGroupMembers", "listScreenGroupMembers", "addScreenGroupMembers", "getScreenGroupSensingConfig", "updateScreenGroupSensingConfig", "getScreenAudienceSegments", "batchUpdateContentPreferences", "batchUpdateDisplaySettings", "batchUpdateProgrammaticSettings", "resolvePartnerScreen", "getScreenAnalytics", "getPartnerScreenAtmosphere", "getPartnerScreenIntelligence", "getPartnerScreenTasteProfile", "getPartnerScreenTraffic", "getPartnerScreenVAS", "applyContentPreset", "getAudienceMetrics", "getContentPreferences", "patchContentPreferences", "updateContentPreferences", "listPartnerDefaultStreams", "createPartnerDefaultStream", "createPartnerDefaultStreamImages", "reorderPartnerDefaultStreams", "createPartnerDefaultStreamUpload", "deletePartnerDefaultStream", "updatePartnerDefaultStream", "getScreenDemographics", "getDisplaySettings", "patchDisplaySettings", "updateDisplaySettings", "getPartnerLBarSettings", "updatePartnerLBarSettings", "updatePartnerLBarLiveUpdates", "getProgrammaticSettings", "patchProgrammaticSettings", "updateProgrammaticSettings", "getResolvedContentPolicy", "getResolvedSensingConfig", "getScreenSensingChips", "interpretScreenSensingIntent", "deployScreenSensingProfile", "getScreenSensingSettings", "updateScreenSensingSettings", "listPartnerSensingRoutes", "createPartnerSensingRoute", "deletePartnerSensingRoute", "getPartnerSensingRoute", "updatePartnerSensingRoute", "createStripeConnectAccount", "getStripeLoginLink", "getStripeOnboardingLink", "requestStripePayout", "getStripePayoutHistory", "getStripeAccountStatus", "handleStripeWebhook", "listTeamMembers", "acceptInvitation", "inviteTeamMember", "getInvitationByToken", "resendInvitation", "removeTeamMember", "changeTeamMemberRole", "getTrackingPublicKey", "recordTrackingBatch", "getUsageSummary__get__usage", "getUsageDashboard", "getRateLimitStatus", "queryChangelog", "playgroundDryRun", "getRequestMetadata", "getVastConfig", "getVenuesNearby", "getVenueAtmosphere", "getVenueTraffic", "getVenueIntelligence", "listWebhooks", "createWebhook", "deleteWebhook", "updateWebhook", "getWebhookDeliveries", "testWebhook"];
1025
1410
  };
1026
1411
  type GeneratedSchemaName = (typeof generatedTypes.schemas)[number];
1027
1412
  type GeneratedOperationName = (typeof generatedTypes.operations)[number];
1028
1413
 
1029
- export { AnalyticsClient, type AnalyticsData, type ApiErrorType, AudienceClient, type AudienceData, BillingClient, type BillingInfo, type BleScanResultData, type ClientOptions, DataClient, type Device, type DeviceCapabilityPayload, DevicesClient, EarningsClient, type EarningsData, type ErrorDetail, type ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, type FleetCommand, type GeneratedOperationName, type GeneratedSchemaName, type HttpProbeResult, type Impression, ImpressionsClient, type ListResponse, type MdnsNetworkDevice, type PartnerAdDeliveryProfile, type PartnerDeviceHeartbeatBody, type PartnerHeartbeatMeasurementMetadata, type PartnerHeartbeatQueryData, type PartnerHeartbeatQueryResponse, type PartnerHeartbeatRequest, type PartnerHeartbeatResponse, type PartnerSafeHeartbeatTelemetry, type RequestOptions, type Screen, ScreensClient, type SsdpDeviceInfo, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, VERSION, type Webhook, type WebhookEvent, Webhooks, WebhooksClient, type WifiEnvironmentSnapshot, type WifiScanResult, constructEvent, generatedTypes, type getDeviceHeartbeatOperation, type heartbeatOperation };
1414
+ export { AnalyticsClient, type AnalyticsData, type ApiErrorType, AudienceClient, type AudienceData, BillingClient, type BillingInfo, type BleScanResultData, type ClientOptions, type ConstructEventOptions, type ContentFitMode, type ContentFitSettings, type CreateRouteParams, DataClient, type Device, type DeviceCapabilityPayload, DevicesClient, EarningsClient, type EarningsData, type ErrorDetail, type ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, type FleetCommand, type GeneratedOperationName, type GeneratedSchemaName, type HttpProbeResult, type Impression, ImpressionsClient, type ListResponse, type MdnsNetworkDevice, type ParsedSignatureHeader, type PartnerAdDeliveryProfile, type PartnerDeviceBatchRegistrationInput, type PartnerDeviceHeartbeatBody, type PartnerDeviceRegistrationInput, type PartnerDisplaySettingsInput, type PartnerDisplaySettingsResponse, type PartnerHeartbeatMeasurementMetadata, type PartnerHeartbeatQueryData, type PartnerHeartbeatQueryResponse, type PartnerHeartbeatRequest, type PartnerHeartbeatResponse, type PartnerSafeHeartbeatTelemetry, type RequestOptions, type RetryOptions, type RouteAction, type RouteChannel, type RouteCondition, type RouteConditionOp, type RouteDigest, type RouteDigestSummary, type RouteWebhookEvent, RoutesClient, type Screen, ScreensClient, type SensingRoute, type SensingThresholdCrossedEvent, type SsdpDeviceInfo, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, type UpdateRouteParams, VERSION, type Webhook, type WebhookEvent, type WebhookHeaders, Webhooks, WebhooksClient, type WifiEnvironmentSnapshot, type WifiScanResult, constructEvent, constructEventLegacy, generatedTypes, type getDeviceHeartbeatOperation, type heartbeatOperation, parseSignatureHeader, retryWithBackoff };