On start, every SDK calls GET /api/config with its SDK key and gets back the
settings for that app. This is how plan entitlements and per-app policy reach a
client you have already shipped — changing a setting in the dashboard takes
effect on the next app start, with no release.
GET /api/config
Authorization: Bearer <your-sdk-key>
{
"replayEnabled": true,
"replayDurationSec": 30,
"samplingRate": 1.0
}
The client-veto rule
Every gated feature resolves the same way:
effective = serverEnabled && !localConfig.disabled
A client can always turn a feature off. A client can never turn one on.
That asymmetry is the entire point. A customer with a privacy requirement can disable body capture in their own build and be certain it is off. Nobody can enable a feature their plan does not include by editing a config object, so the gate is worth something commercially as well as legally.
Blocks
The response is a set of independent blocks. The replay fields and
breadcrumbs are always sent. Every newer block is negotiated: an SDK
receives it only if it names the block in the X-TX-SDK-Features request
header — a comma-separated, case-insensitive list such as
replies, networkBodies, branding — so an older SDK never has to parse a
field it was not built for. A missing or garbage header simply means “no
newer blocks”, never an error.
replayEnabled · replayDurationSec · samplingRate
Session replay. Duration is the rolling buffer length in seconds; sampling rate is the fraction of sessions that record at all. See your SDK’s Session replay page.
resources
{
"enabled": true,
"windowSec": 60
}
Report Resource Window — CPU/memory sampling. Off by default; when enabled,
the SDK keeps a rolling in-memory ring of samples and stamps the last
windowSec seconds onto payload.resources on every bug report and crash.
windowSec is one of 30, 60 or 120. As with replies, this block is
sent only when the SDK declares resources in X-TX-SDK-Features; an older
SDK that never asks for it never receives it. cpu is absent from every
sample on web — no browser API exposes per-process CPU usage — so a web
report’s payload.resources entries carry mem (and t) only.
breadcrumbs
{
"enabled": true,
"kinds": [
"navigation",
"tap",
"console",
"network",
"lifecycle",
"error",
"custom"
],
"maxCount": 100,
"byteBudget": 65536,
"consoleEntryCap": 250
}
maxCount is capped at 128 by the protocol, byteBudget at 64 KB and
consoleEntryCap at 2 048. kinds lets an app record navigation but not taps,
for instance. When the block is absent the SDK defaults to on, all kinds, 100
entries, a 16 KB budget and 1 024 console entries.
networkBodies
{
"captureBodies": false,
"bodyByteCap": 8192,
"bodyContentTypes": ["application/json"],
"bodyTotalBudget": 262144
}
Off by default — an absent block means no body capture. bodyContentTypes is
an allowlist of up to 16 entries — a body whose content type is not listed is
never read, which is what keeps binary uploads out of reports. bodyByteCap is
capped at 64 KB and bodyTotalBudget at 1 MiB.
replies
{ "enabled": true }
Sent only when replies are enabled for the app and the SDK asked for the block; its absence tells the SDK not to poll at all. See your SDK’s Two-way replies page.
identity
{ "enabled": true }
Whether the app’s project has an identity signing secret configured, so
verified identity tokens can be checked. When false the SDK never asks your
host for a token.
companionBadge
{
"enabled": true,
"position": "bottom-right"
}
The pairing badge shown on TV apps. position is one of bottom-right,
bottom-left, top-right, top-left. An absent block
leaves the SDK default (enabled, bottom-right) in force; the dashboard disables
it by storing enabled: false.
branding
{
"watermark": false,
"theme": {
"accent": "#3b82f6"
}
}
watermark is derived from the org’s plan at request time and never stored.
When it is true the theme is suppressed entirely — that is the free-plan
posture, and it returns 200, not an error. theme keys are background,
surface, border, text, textMuted, accent, accentForeground and
destructive, each a 6-digit hex colour; every key is optional.
Failure behaviour
The SDK holds a cached config that starts at the conservative default — replay
off, bodies off, breadcrumbs on — and is overwritten only by a fully
validated 200. If the call fails for any reason (network, non-200, malformed
body, timeout) the cache keeps its current value, nothing throws, and the SDK
refetches once the 5-minute TTL expires. A report captured before config
resolves is still valid; it simply reflects whatever the cache held at the
time. An invalid or revoked key gets 401 { "error": "invalid_sdk_key" }; an
app with no stored config gets the off defaults at 200.
GET /api/config keeps answering while an organisation’s ingestion is
suspended for exceeding its install limit — that is what keeps
its install meter running so the suspension can lift automatically.