# `Bourse.CoinbaseCandlePagination`
[🔗](https://github.com/ZenHive/bourse/blob/main/lib/bourse/coinbase_exchange.ex#L1)

Coinbase Exchange candle-window mechanics.

Coinbase's Exchange API accepts granularities `60`, `300`, `900`, `3600`,
`21600`, and `86400` seconds. Its candle endpoint returns newest-first rows
and documents a 300-candle maximum. Because `start` and `end` are inclusive,
Bourse requests at most 299 intervals per page, then merges non-overlapping
pages back into Coinbase's newest-first wire order.

Unified callers use `ETH/USD`; the authored dash symbol pattern routes it to
Coinbase's `ETH-USD` product id. Native product ids remain available on the
generated raw endpoint functions.

Live observation on 2026-08-11 established that the current forming bucket
appears once it contains a trade. Intervals without ticks are omitted, as the
provider documentation states; callers must not assume a dense time series.

# `metadata`

```elixir
@type metadata() :: %{
  start_ms: non_neg_integer(),
  end_ms: non_neg_integer(),
  limit: pos_integer()
}
```

# `page`

```elixir
@type page() :: %{
  params: map(),
  start_ms: non_neg_integer(),
  end_ms: non_neg_integer()
}
```

# `merge_responses!`

```elixir
@spec merge_responses!([map()], metadata()) :: map()
```

Merges paged raw responses, deduplicating and retaining the requested chronological range.

# `pagination`

```elixir
@spec pagination(map(), map(), non_neg_integer()) ::
  {:single, map()} | {:paginate, [page()], metadata()}
```

Builds inclusive, non-overlapping request pages when `limit` exceeds 300.

Single-page requests come back as `{:single, params}` with the window pair
completed: Coinbase ignores BOTH `start` and `end` whenever either one is
missing and silently answers with the most recent page, so a half-open
window must never reach the wire.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
