Coinbase Advanced Trade REST — internal. Nothing here is public API; the facade is
DpExchange.Coinbase.
Credentials choose the endpoint, they do not gate it
Coinbase serves the same market data two ways: /products/{id}/ticker is
account-scoped and needs a Bearer JWT, /market/products/{id}/ticker is public. This
module picks the authenticated path when it is given credentials and the public one
when it is not.
That is the facade's rule made concrete — a caller passes credentials or does not, and
reads the consequence from capabilities/0. It is also a fix for a real incident: the
price-collection task did not pass credentials, hit the authenticated path anyway, and
produced 315 Unauthorized warnings overnight on 2026-04-30.
Historical candles are public
/market/products/{id}/candles, no auth. The authenticated variant 401s on every
backfill call, which is what it did until 2026-07-02.
This module cannot fabricate
There is no fallback path, no test-mode branch and no hardcoded price table. A request
that fails returns an error. The adapter this was ported from had a
generate_fallback_candles/4 that invented OHLC from a table of base prices; its error
path was fixed in May 2026 after fabricated candles were traced to phantom profits in
backtests, but the generator survived behind a node-wide test flag. It is not here in
any form — see docs/reference/coinbase/reconciliation.md.
A caller wanting deterministic candles uses this package's fake, selected per process, which is a real implementation of the facade rather than a branch inside the live one.
Summary
Functions
Historical candles for symbol at timeframe.
The current price for symbol.
Every product Coinbase lists, as canonical symbols.
Every timeframe Coinbase serves, shortest first.
The most candles Coinbase will return for one request. A hard boundary, not a hint.
Functions
@spec get_historical_prices(String.t(), String.t(), keyword(), keyword()) :: {:ok, [DpExchange.Core.Types.Quote.t()]} | {:error, term()} | {:refused, term()}
Historical candles for symbol at timeframe.
A timeframe Coinbase does not serve is an error, never the nearest width.
@spec get_price( String.t(), keyword() ) :: {:ok, DpExchange.Core.Types.Quote.t()} | {:error, term()} | {:refused, term()}
The current price for symbol.
Returns {:refused, reason} when the venue does not carry the symbol — a permanent
answer, distinct from a transient {:error, _}.
Every product Coinbase lists, as canonical symbols.
@spec granularities() :: [String.t()]
Every timeframe Coinbase serves, shortest first.
@spec max_candles() :: pos_integer()
The most candles Coinbase will return for one request. A hard boundary, not a hint.