> ## Documentation Index
> Fetch the complete documentation index at: https://unif.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Coverage and freshness

> Knowing what a field is worth before you depend on it.

Coverage is the measured output of [the waterfall](/docs/concepts/waterfall): what the whole
cascade, taken together, can actually fill in for a given channel and market.

It is also the part of the unified model that keeps it honest. A schema promising the same fields
everywhere is only useful if it also tells you where those fields are real.

## The coverage report

[`GET /v1/coverage`](/api-reference/meta/check-field-coverage) answers one question: for this
entity, on this channel, in this market — what can I actually rely on?

```bash theme={null}
curl "https://api.unif.dev/v1/coverage?entity=creator&channel=tiktok_shop&market=ID" \
  -H "Authorization: Bearer $UNIF_API_KEY"
```

```json theme={null}
{
  "object": "coverage",
  "entity": "creator",
  "fields": [
    {
      "field": "metrics.revenue",
      "channel": "tiktok_shop",
      "market": "ID",
      "availability": "full",
      "filterable": true,
      "sortable": true,
      "typical_lag_hours": 24,
      "note": null
    },
    {
      "field": "metrics.engagement_rate",
      "channel": "tiktok_shop",
      "market": "ID",
      "availability": "partial",
      "filterable": false,
      "sortable": false,
      "typical_lag_hours": 48,
      "note": "Populated for creators above 10k followers."
    },
    {
      "field": "agency",
      "channel": "tiktok_shop",
      "market": "ID",
      "availability": "unavailable",
      "filterable": false,
      "sortable": false,
      "typical_lag_hours": null,
      "note": "Not disclosed by the channel in this market."
    }
  ]
}
```

## Reading the fields

<ResponseField name="availability" type="string">
  `full` — some source in the cascade fills this for essentially every entity.
  `partial` — filled for a defined subset, described in `note`.
  `unavailable` — no source in the cascade reports it here; the field is always `null`, and
  running more sources would not change that.
</ResponseField>

<ResponseField name="filterable / sortable" type="boolean">
  Whether the field can be used in `filters` or as a `sort` key for this channel and market.
  A `partial` field is often readable but not filterable, because filtering on it would silently
  drop every entity where it is missing.
</ResponseField>

<ResponseField name="typical_lag_hours" type="integer | null">
  The usual delay between something happening on the channel and it being reflected here. Pair it
  with `meta.refreshed_at` on a record to know how current a number is.
</ResponseField>

<Warning>
  Filtering on a `partial` field narrows your result set in a way that is invisible in the
  response. Entities missing the field are excluded, not returned with `null`. If `filterable` is
  false, Unif rejects the query with `coverage_error` rather than returning a quietly biased list.
</Warning>

## Per-record completeness

Coverage describes a channel and market. `meta` on each record describes that record.

```json theme={null}
"meta": {
  "refreshed_at": "2026-09-15T04:12:00Z",
  "completeness": 0.85,
  "missing_fields": ["metrics.engagement_rate", "agency"]
}
```

<ResponseField name="completeness" type="number">
  The share of requested fields that came back populated. With `fields` set on an enrich call,
  it is measured against exactly what you asked for.
</ResponseField>

<ResponseField name="missing_fields" type="string[]">
  Which fields are `null` and why they are absent — either unavailable for this channel and
  market, or not populated for this particular entity.
</ResponseField>

An enrich result reports `status: "partial"` when some requested fields could not be filled. That
is a successful call with an incomplete answer, and it is worth branching on:

```python theme={null}
for result in response["data"]:
    if result["status"] == "partial":
        log.warning("row %s missing %s", result["ref"], result["meta"]["missing_fields"])
    elif result["status"] != "enriched":
        queue_for_review(result)
```

## Freshness

Refresh cadence varies by entity, and it follows how fast the underlying thing actually changes.

| Entity     | Typical cadence                      | Typical lag |
| ---------- | ------------------------------------ | ----------- |
| Shop       | Daily                                | 24h         |
| Product    | Daily                                | 24h         |
| Creator    | Daily                                | 24–48h      |
| Video      | Hourly for trending, daily otherwise | 2–24h       |
| Livestream | On session end                       | 1–6h        |
| Category   | Daily                                | 24h         |

<Note>
  `meta.refreshed_at` is when Unif last refreshed the record — not when you called. Two records in
  the same response can legitimately carry different timestamps, because they were refreshed on
  different cycles.
</Note>

## Building against coverage rather than around it

<Steps>
  <Step title="Check coverage when you add a market">
    Not on every request — coverage changes on the order of weeks, usually when the cascade gains
    a source or one is re-tuned. A weekly sync into your own config is the right cadence.
  </Step>

  <Step title="Degrade filters rather than failing">
    If `gpm` is not filterable in a market, fall back to filtering on `revenue` and ranking by
    `gpm` client-side over a wider result set.
  </Step>

  <Step title="Alert on completeness drops">
    A sustained fall in `meta.completeness` for an entity you track is an upstream change worth
    knowing about before your dashboards start looking wrong.
  </Step>
</Steps>
