> For the complete documentation index, see [llms.txt](https://docs.contextsdk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.contextsdk.com/adtech/golden-signals.md).

# Golden Signals

Golden Signals are a snapshot of the device's current context - motion, device state, and the outputs of ContextSDK's on-device models - returned as a plain dictionary you can attach to an ad request. Everything is computed on device.

{% hint style="info" %}
Golden Signals are only available in an AdTech build of ContextSDK, distributed per ad network. If `goldenSignals` doesn't exist on `ContextManager` in your integration, you're on a standard build - talk to us and we'll get you the right one.
{% endhint %}

## Choosing between the two methods

There are two ways to read the signals, and the difference is only in how they treat the motion sensor:

<table><thead><tr><th width="230">Method</th><th>Behaviour</th></tr></thead><tbody><tr><td><code>goldenSignals()</code></td><td>Returns immediately, reading whatever the accelerometer has buffered at that instant.</td></tr><tr><td><code>fetchGoldenSignals()</code></td><td>Waits until a full sensor window has been collected, then calls you back.</td></tr></tbody></table>

The distinction matters because the accelerometer buffer starts out empty **at every app launch and every return to the foreground**, and takes a few seconds to fill. Ad requests tend to cluster in exactly that window - interstitial and rewarded preloads usually fire moments after launch or resume - so `goldenSignals()` called there returns a thinner dictionary, with motion-derived signals and any model outputs that depend on them missing or based on a partial window.

Use `goldenSignals()` when you cannot afford to wait at all. Use `fetchGoldenSignals()` when you would rather wait a moment for complete data - it is the better default for a bid request that isn't already at its deadline.

## Reading the signals instantly

{% tabs %}
{% tab title="Swift" %}

```swift
let signals = ContextManager.goldenSignals(
    trigger: "before_bid_request",
    bidID: "your-bid-id"
)
// Attach `signals` to your ad request
```

{% endtab %}
{% endtabs %}

Every parameter is optional - see [Tagging the request](#tagging-the-request).

## Waiting for a full sensor window

`fetchGoldenSignals()` calls your closure once, on the main thread, with a status and the signals:

{% tabs %}
{% tab title="Swift" %}

```swift
ContextManager.fetchGoldenSignals(
    trigger: "before_bid_request",
    bidID: "your-bid-id"
) { status, signals in
    // A dictionary always comes back - attach it to your ad request
    if status != .ok {
        // Optional: note that this request went out on incomplete sensor data
    }
}
```

{% endtab %}
{% endtabs %}

The status tells you how complete the sensor data behind the dictionary is:

<table><thead><tr><th width="200">Status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>.ok</code></td><td>A full sensor window was collected. This is the case the method exists to guarantee.</td></tr><tr><td><code>.timeout</code></td><td>The call ran out of time before the sensor delivered a full window. The dictionary holds the instantly-available data instead - the same thing <code>goldenSignals()</code> would have returned.</td></tr><tr><td><code>.sensorUnavailable</code></td><td>The device has no usable motion sensor, or you're running in the simulator. Motion signals are flagged invalid and the model-driven results that depend on them are absent.</td></tr></tbody></table>

{% hint style="info" %}
A dictionary always comes back - the call never returns empty-handed, and never blocks indefinitely. If you have no use for the distinction, you can ignore the status entirely and treat the method as a drop-in for `goldenSignals()`.
{% endhint %}

What a non-`.ok` status does mean is that the dictionary is **less complete**. Anything derived from motion needs a usable window to be computed from, so on `.timeout` and `.sensorUnavailable` the motion signals and the model-driven results that consume them (`u_`, `m_` and `mv_` keys) may be based on a partial window or missing altogether. Read keys defensively rather than assuming a fixed set - which is good practice anyway, since the exposed set is configured remotely.

The call is bounded: it gives up after **at most 10 seconds** and reports `.timeout`, so it will never block an ad request indefinitely.

{% hint style="warning" %}
A call made while your app is in the background cannot collect fresh motion samples and will report `.timeout` once that budget elapses. Read Golden Signals from the foreground.
{% endhint %}

## Tagging the request

Both methods take the same optional parameters, and all of them are worth setting:

* **`trigger`** - where in your flow you're reading the signals, for example `"before_bid_request"` or `"before_ad_show"`. Use a distinct value per call site.
* **`bidID`** - your identifier for the bid. Setting it lets us connect the several contexts captured across one bid's lifecycle, which is what makes stage-by-stage analysis possible.
* **`customSignals`** - anything you know about this request that we can't measure on device. See [Attaching custom signals](#attaching-custom-signals).

The dictionary also comes back with a `ctxId` key: a short identifier for this specific context. Keep it alongside your own records if you want to join your reporting back to a single Golden Signals read.

## Attaching custom signals

Both methods accept custom signals for that one call - the ad format you're about to request, how many impressions this session has already shown, whatever else describes the request:

{% tabs %}
{% tab title="Swift" %}

```swift
let customSignals: [CustomSignal] = [
    CustomSignalString(id: "ad_format", value: "rewarded"),
    CustomSignalInt(id: "session_impressions", value: 4),
]
let signals = ContextManager.goldenSignals(
    trigger: "before_bid_request",
    bidID: "your-bid-id",
    customSignals: customSignals
)
```

{% endtab %}
{% endtabs %}

These are layered on top of any global signals you've set with `setGlobalCustomSignal(id:value:)`, and a per-call signal wins over a global one with the same id. See [Custom Signals](/context-decision/advanced/custom-signals.md) for the rules an id and value must follow, and for how to set globals.

They are used in two places: they're recorded on the context event we log for this call, so your bid data and ours can be joined on `ctxId`, and they're available to the on-device models as features - so a model trained on one of your custom signals can act on it.

{% hint style="warning" %}
Custom signals must not contain personally identifiable information - no user ids, email addresses, phone numbers, IP addresses or exact locations. The same rules apply here as everywhere else in ContextSDK.
{% endhint %}

## What's in the dictionary

Keys are stable strings and values are heterogeneous, so read each key as the type that signal carries:

<table><thead><tr><th width="190">Key pattern</th><th>Contents</th></tr></thead><tbody><tr><td><code>ctxId</code></td><td>A <code>String</code> identifying this context.</td></tr><tr><td><code>contextDuration</code></td><td>An <code>Int</code>: the sensor window length in seconds, when enabled for your app.</td></tr><tr><td><code>c&#x3C;number></code></td><td>An individual signal. The type follows the signal - <code>Bool</code>, <code>Int</code>, <code>Float</code>, <code>Double</code>, <code>Decimal</code>, <code>String</code>, <code>[String]</code> or <code>[Float]</code>.</td></tr><tr><td><code>u_&#x3C;name></code></td><td>A <code>Float</code> probability from an on-device activity model.</td></tr><tr><td><code>m_&#x3C;name></code></td><td>An on-device model output: a <code>Double</code> score, or a <code>[Float]</code> vector for a moment embedding.</td></tr><tr><td><code>mv_&#x3C;name></code></td><td>A <code>String</code> version of the model that produced the matching <code>m_</code> embedding.</td></tr></tbody></table>

Keys that aren't available for a given call are omitted rather than returned empty, so check for a key's presence rather than assuming a fixed set. Which signals and model outputs are exposed is configured remotely per app, so the set can change without an app update.

{% hint style="warning" %}
A moment embedding is a model's unmodified output, so individual elements may be `NaN` or infinite. Filter them if your pipeline can't represent them - in particular, `JSONSerialization` rejects non-finite values.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.contextsdk.com/adtech/golden-signals.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
