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

# Periods and currency

> How a window is chosen, when it gets adjusted, and how money is converted.

Every metric is measured over a period and denominated in a currency. Both are yours to choose,
and both are reported back on the response so a stored record is self-describing.

## Choosing a period

Supply either a preset or an explicit range.

<CodeGroup>
  ```json Preset theme={null}
  { "period": { "preset": "last_30d" } }
  ```

  ```json Explicit range theme={null}
  { "period": { "start": "2026-08-01", "end": "2026-08-31" } }
  ```
</CodeGroup>

| Preset           | Window                                          |
| ---------------- | ----------------------------------------------- |
| `last_7d`        | The 7 complete days ending yesterday            |
| `last_30d`       | The 30 complete days ending yesterday (default) |
| `last_90d`       | The 90 complete days ending yesterday           |
| `last_180d`      | The 180 complete days ending yesterday          |
| `month_to_date`  | The 1st of the current month through yesterday  |
| `previous_month` | The previous calendar month, complete           |

On `GET` endpoints the same choice is expressed as query parameters — `period=last_30d`, or
`period_start=2026-08-01&period_end=2026-08-31`.

<Note>
  Presets end **yesterday**, not today. A partial day is not comparable to a complete one, and
  including it would make every growth rate wrong in the same direction. To measure today
  explicitly, pass today's date as both `start` and `end`.
</Note>

Boundaries are inclusive on both ends, and days are drawn in the market's local time zone —
`America/Los_Angeles` for `US`, `Asia/Jakarta` for `ID`. Read the zone from
[`GET /channels`](/docs/concepts/channels) rather than assuming UTC.

## When a period gets adjusted

Not every market reports daily buckets. When your request is finer than the market supports, Unif
widens it to the nearest supported boundary and says so.

```json theme={null}
"period": {
  "start": "2026-08-31",
  "end": "2026-09-13",
  "granularity": "week",
  "adjusted": true
}
```

That response is answering a request for `2026-09-01` to `2026-09-12` in a weekly-only market: the
window was widened to whole weeks, and `adjusted: true` marks it.

<Warning>
  Always read `period` off the response before storing a metric. Keying an adjusted result by the
  dates you *asked for* silently corrupts any series you build from it — and the error compounds
  quietly, because each row looks plausible on its own.
</Warning>

```python theme={null}
resp = search_products(period={"start": "2026-09-01", "end": "2026-09-12"}, market="ID")
period = resp["period"]

if period["adjusted"]:
    log.info("window widened to %s–%s (%s buckets)",
             period["start"], period["end"], period["granularity"])

store(rows=resp["data"], start=period["start"], end=period["end"])  # not the requested dates
```

## Growth rates

`revenue_growth_rate` compares the period you requested against the immediately preceding window
of equal length. `last_30d` is compared with the 30 days before it.

The value is a **ratio**, never a percentage: `0.25` is +25%, `-0.4` is −40%. This holds for every
`*_rate` and `*_share` field in the API.

```json theme={null}
"metrics": {
  "revenue": 1284300.25,
  "revenue_growth_rate": 0.34
}
```

Growth is `null`, not `0`, when the preceding window has no data — a shop that opened three weeks
ago has no 30-day comparison to make. Treating that `null` as zero growth is the most common way
to mis-rank a "fastest growing" list.

## Currency

Set `currency` to any ISO 4217 code and every monetary field in the response is converted to it.
The default is `USD`.

```json theme={null}
{ "market": "ID", "currency": "USD" }
```

Conversion uses the daily reference rate for each day in the period, not a single spot rate at
request time. A 30-day revenue figure in `USD` for an `IDR` market is the sum of each day's local
revenue converted at that day's rate — so re-running the same query next week returns the same
historical number.

<Tip>
  For comparisons across markets, request one currency for everything. For reconciling against a
  seller's own statements, request the market's native currency — read it from
  `GET /channels` rather than hard-coding the mapping.
</Tip>

Non-monetary fields are never converted. `units_sold` is a count, `engagement_rate` is a ratio,
and `gpm` is revenue-derived so it follows `currency`.

## What to store

For any metric you persist, store the four things that define it. A number without them cannot be
compared to anything later.

```sql theme={null}
CREATE TABLE shop_metrics (
  shop_id       TEXT    NOT NULL,
  period_start  DATE    NOT NULL,   -- from response.period.start
  period_end    DATE    NOT NULL,   -- from response.period.end
  granularity   TEXT    NOT NULL,   -- from response.period.granularity
  currency      CHAR(3) NOT NULL,   -- from response.currency
  revenue       NUMERIC,
  refreshed_at  TIMESTAMPTZ,        -- from meta.refreshed_at
  PRIMARY KEY (shop_id, period_start, period_end, currency)
);
```
