Four guarantees
Stable identity
An entity keeps its Unif ID across renames, handle changes and re-listings. Store the ID,
not the URL.
One name per concept
revenue is revenue on every entity and every channel. There is no gmv on one and
sales_amount on another.One unit per field
Money is in the currency you requested. Rates are ratios, never percentages. Timestamps are
RFC 3339 in UTC.
Declared coverage
When a field is not available for a channel and market, it is
null and named in
meta.missing_fields. It is never quietly zero.The shape of every record
Each entity response has the same four layers, so code you write against one entity transfers to the others.1
Identity
id, object, channel, market. Stable, and safe to store.2
Attributes
Facts about the entity that do not depend on a period — name, category, price, follower count.
3
Metrics
Everything measured over a window, always under
metrics. If a number moves with the period
you asked for, it lives here.4
Context
period, currency and meta. The record tells you what it is, rather than making you
remember what you asked for.What normalization does not do
Being explicit about the limits is what keeps the guarantees credible.It does not invent data a channel never reports
It does not invent data a channel never reports
If a channel does not publish a metric for a market, Unif returns
null and lists the field
in meta.missing_fields. Modelling a plausible value would make the number look like the
ones next to it, which is worse than an absent value.It does not make every channel comparable on every metric
It does not make every channel comparable on every metric
Attribution windows and return handling genuinely differ between channels. Unif documents each
definition in the metrics reference and reports the channel on every
record. Cross-channel totals are your call to make, with that context in hand.
It does not smooth over period granularity
It does not smooth over period granularity
If a channel only reports weekly buckets for a market, a request for three arbitrary days
comes back with
period.adjusted: true and the window that was actually measured. Silent
interpolation would be a lie with a plausible shape.It does not hide latency
It does not hide latency
meta.refreshed_at is when Unif last refreshed the record, not when you asked. Combine it
with typical_lag_hours from GET /coverage to know how current a
number really is.Adding a channel changes nothing you wrote
This is the property the model exists to deliver. When a channel goes live:- Your stored Unif IDs stay valid, because IDs are namespaced per entity and never recycled.
- Your filters keep working, because filter names are model-level, not channel-level.
- Your queries do not automatically widen. A search without
channelspans the channels your workspace has enabled, so turning one on is a deliberate act. - New channel-specific nuance shows up in coverage, not in new field names.
The one thing you should re-read after enabling a channel is the coverage report. A filter that
is fully supported on one channel may be
partial on another, which changes what your result
set means.