> ## Documentation Index
> Fetch the complete documentation index at: https://octo-98895abd.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quotes

> B3 cash-market quotes — the unadjusted api.quotes view plus quote_history (with adjusted and total-return closes) and quote_latest.

<Note>
  **Parameters, response columns and types are generated** from the SQL catalog and
  render under **Endpoints → Quotes** in the sidebar. This page carries what a
  schema cannot: the conventions, the nulls that are deliberate, and the traps.
</Note>

### Overview

B3 COTAHIST cash-market prints (`tpmerc = 010`), exactly as published. Nothing is split- or dividend-adjusted, and every row carries `adjusted: false`.

The grain is **`(ticker, trade_date, board, term_days)`**. A ticker can print on more than one board on the same day, so a query that does not pin `board` can return several rows per session. `codbdi` varies by instrument type (`02` common equities, `12` FIIs, `14` ETFs, `34` BDRs). `quote_history` follows the instrument (its ISIN) across boards unless you pass one; `quote_latest` reads every board.

`asset_class` is derived conservatively from B3's published `tpmerc` and `especi`: `equity`, `unit`, `bdr`, `fund_quota`, `index`, `right`, `bonus`, or `cash_security`. To split `fund_quota` into ETF vs FII vs FIAGRO, use [`fund_quotas`](/api-docs/instruments)' `fund_type` column, which comes from B3's published CODBDI board code.

<Warning>
  **`close` is the price of `quotation_factor` units, not always one.** `close_unit` = `close / quotation_factor` is served for you. The factor is 1 for almost everything (13 tickers on the whole 2019–2026 tape carry another value), but a paper quoted per lot reads 1000× its unit price in `close`.

  **Neither is corporate-action adjusted.** `close_unit` divides by a published field; it does not account for splits, groupings or bonuses. A split still appears as a jump in the price. `panel`'s `close_return` is null across a factor change and, across a split, grouping or bonus between the two prints, divides the previous close by the event's share ratio.
</Warning>

### `GET /quotes`

The view — full column set, arbitrary filters and sort. Use it for cross-sectional slices (a whole session, a set of tickers) rather than one instrument's series.

<Warning>
  **This view has no built-in cap — always send `limit`.** An unbounded read is cut at 1,000 rows server-side, and `Content-Range` is the only thing that tells you.
</Warning>

| Field | What to know |
| - | - |
| `ticker` | Stored upper-case, and the view does **not** normalize your input — send it upper-case, unlike the functions, which upper-case for you. |
| `board` | COTAHIST `codbdi`. Do not assume `02` outside ordinary equities. Part of the grain. |
| `term_days` | COTAHIST `prazot`. A **text** field despite the name, usually empty for cash. Part of the grain, and only on the view — the functions do not return it. |
| `close_unit` | `close / quotation_factor`, both published. A division by a published field, not an adjustment. |
| `adjusted` | Always `false`. Constant, so nobody mistakes these for adjusted prices. |
| `fetched_at` | When the pipeline ingested this row. View only. |
| `asset_class` | `index` is an index line (IBOV11 — not an ETF, whatever the ticker suggests); `right` and `bonus` are claims, not the security itself; `cash_security` is the residual. |

<CodeGroup>
  ```bash curl theme={null}
  curl -s -G "$SILO_URL/quotes" \
    -H "apikey: $SILO_ANON_KEY" \
    --data-urlencode "select=ticker,trade_date,board,close,volume,adjusted,asset_class" \
    --data-urlencode "ticker=in.(PETR4,VALE3)" \
    --data-urlencode "board=eq.02" \
    --data-urlencode "trade_date=eq.2026-08-26"
  ```

  ```python Python theme={null}
  params = {
      "select": "ticker,trade_date,board,close,volume,adjusted,asset_class",
      "ticker": "in.(PETR4,VALE3)",
      "board": "eq.02",
      "trade_date": "eq.2026-08-26",
  }
  rows = requests.get(f"{URL}/quotes", headers=H, params=params).json()
  ```

  ```javascript JavaScript theme={null}
  const { data } = await supabase
    .from('quotes')
    .select('ticker, trade_date, board, close, volume, adjusted, asset_class')
    .in('ticker', ['PETR4', 'VALE3'])
    .eq('board', '02')
    .eq('trade_date', '2026-08-26')
  ```
</CodeGroup>

### `POST /rpc/quote_history`

One ticker's daily series over a window, ordered by `trade_date` ascending, one JSON object per session holding **only the fields you select**.

```sql theme={null}
api.quote_history(p_ticker TEXT,
                  p_from   DATE   DEFAULT CURRENT_DATE - 365,  -- NULL = from the first session
                  p_to     DATE   DEFAULT CURRENT_DATE,
                  p_board  TEXT   DEFAULT NULL,                -- NULL = every board
                  p_after  TEXT   DEFAULT NULL,                -- cursor, see below
                  p_fields TEXT[] DEFAULT NULL)                -- NULL = ticker, trade_date, close_adj
```

**Default: `ticker`, `trade_date`, `close_adj`.** `close_adj` is the close per single share, backward-adjusted for splits (DESDOBRAMENTO), groupings (GRUPAMENTO) and bonus shares (BONIFICACAO) by B3's rule (`1 + factor/100` for splits and bonuses, `factor` for groupings, events on one date multiplied), anchored to the instrument's latest session and rounded to 6 decimal places. Past levels change when an event lands; returns do not. Dividends, JCP and subscription rights are not adjusted: they move value to holders and change no share count, so they belong to a total-return series, not this one. Shares (ISIN code `ACN`) and units (`CDA`/`UNT`, ticker ending `11`) only.

**The raw close is an explicit selection:** `p_fields: ["close"]`, or any of `open`, `high`, `low`, `average`, `bid`, `ask`, `close_unit`, `trades`, `quantity`, `volume`, `quotation_factor`, `board`, `isin`, `short_name`, `spec`, `currency`, `asset_class`, `source`, `coverage_start`, `coverage_end`, `prior_no_trade_sessions`, `events_proven_at`, `data_revision` (types in `catalog().defaults.quote_history.available`). Keep the same `p_fields` on every page.

Additional preserved fields are opt-in: `market`, `term_days`, `contract_price`,
`contract_expiry`, `contract_correction`, `contract_points`, `contract_points_raw`,
`distribution_number` and `fetched_at`. The last is warehouse time, not original
publication time. See `catalog().cotahist` for the source aliases and dated code
reference. No change to the default adjusted-price selection.

```bash theme={null}
curl -s -X POST "$SILO_URL/rpc/quote_history" \
  -H "apikey: $SILO_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"p_ticker":"PETR4","p_from":"2026-01-02","p_to":"2026-08-26"}'
# [{"ticker": "PETR4", "trade_date": "2026-01-02", "close_adj": 30.123400}, ...]

curl -s -X POST "$SILO_URL/rpc/quote_history" \
  -H "apikey: $SILO_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"p_ticker":"PETR4","p_from":"2026-01-02","p_to":"2026-08-26","p_fields":["close","volume"]}'
```

**A ticker's history follows the company across an ISIN change.** When a company changes its trading code or B3 issues a new ISIN (VIIA3 `BRVIIAACNOR7` until 2023-09-19, then BHIA3 `BRBHIAACNOR1` from 2023-09-20), a window that reaches back runs through the older instrument. Each row keeps its own `ticker` and `isin`, `close_adj` stays continuous across the seam, and `close_total_return` is null before it. The splice needs the same company in CVM's FCA, the same share class, adjacent sessions with no overlap, no stock event at the seam and exactly one candidate (`docs/adr/0002-ticker-activity-and-lineage.md`); otherwise the window is refused.

**What it refuses** (`22023`, with `details` starting `reason=`) rather than serve a partial or substituted series:

| reason | when |
| - | - |
| `unknown_ticker` | the code never printed on the cash tape (or not on `p_board`) |
| `outside_coverage` | no session in the window, or the window starts before the instrument's first session (the tape starts 2019-01-02, see `coverage()`) |
| `isin_change` | the ticker printed under two ISINs in the window that its lineage does not splice; a reused code is a new instrument and is never joined |
| `ambiguous_session` | two rows on one session; pass `p_board` |
| `invalid_field` | a name `p_fields` does not know |
| `adjustment_unavailable` | `close_adj` cannot cover the window: not a share or unit, issuer events not proven swept (or proven before the last session), or a spin-off, merger, capital restitution in shares or any other stock event this version does not adjust; the message names ticker, period and cause |

Inside the coverage a missing session is a session with **no trade** (COTAHIST lists only papers that traded; `prior_no_trade_sessions` counts them) and holidays are not sessions. A field with no value is a JSON `null`.

**Data revision.** Every response carries the header `X-Silo-Data-Revision` (and the `data_revision` field when selected): the newest load of the tape, the corporate events or their proof. `close_adj` levels move when it changes, so never combine pages or calls with different revisions; the SDK restarts instead.

<Warning>
  **One 1,000-row page, and it refuses above it** (`22023`) — you never receive a truncated series that looks complete. Roughly four years of one ticker fits in a page.

  Page it with `p_after: ""` then the last row's `trade_date`, or narrow the window:

  ```json theme={null}
  { "p_ticker": "PETR4", "p_from": "2019-01-02", "p_after": "" }
  { "p_ticker": "PETR4", "p_from": "2019-01-02", "p_after": "2022-12-29" }
  ```

  The SDK walks it: `silo.iter_quote_history("PETR4", start="2019-01-02")`.
</Warning>

### `POST /rpc/quote_latest`

The most recent stored session for one ticker. At most one row, same columns as `quote_history`.

```sql theme={null}
api.quote_latest(p_ticker TEXT,
                 p_board  TEXT DEFAULT NULL)
```

`p_board` null means "latest across all boards".

```python theme={null}
rows = requests.post(
    f"{URL}/rpc/quote_latest", headers=H, json={"p_ticker": "PETR4"}
).json()
latest = rows[0] if rows else None      # empty list = we have no print
```

**Note**: "latest" means the latest session stored, not today — whatever the last successful COTAHIST ingest loaded. Check `trade_date` on the row and [`coverage`](/api-docs/coverage) for the dataset. A stale close is never advanced to today's date to make it look current.

### Empty and error behavior

* **Unknown ticker → `200 []`.** Not a 404, and never a fabricated last close.
* **Known ticker, no sessions in the window** (a holiday range, or a window before the paper listed) → also `200 []`. The two cases are indistinguishable from the response; use [`lookup`](/api-docs/lookup) to confirm the ticker exists.
* **Wrong board** is a common cause of a surprising empty result. Some papers do not print on `02` — retry with `"p_board": null`.
* A malformed date or filter returns `400`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.