Parameters, response columns and types are generated from the SQL catalog and
render under Endpoints → Securities lending in the sidebar. This page carries what a
schema cannot: the conventions, the nulls that are deliberate, and the traps.
Overview
Four views over B3’s securities-lending market: what is on loan, at what rate, and who traded it. They are the closest thing the Brazilian market publishes to a short interest report.
These are views:
GET, PostgREST filters (?ticker=eq.PETR4), limit/offset paging. Investor-type flow is on Investor flow.
Sorting on a nullable column: nulls sort last by default in PostgREST. pct_float and days_to_cover are null whenever their denominator is missing, so order=pct_float.desc buries those rows at the end rather than dropping them. Add .nullslast explicitly if you rely on that placement.
GET /short_interest
Short interest per (ticker, trade_date), built from B3’s own Total rows so balances are never double-counted. Filters: ticker, trade_date (sessions only, no weekend rows), float_basis (required for any ranking), instrument_category (SHARES, UNIT, ETF EQUITIES, BDR, FUNDS). An unfiltered days-to-cover screen is dominated by index ETFs, not the crowded single-name shorts the screen is for.
Example, the most shorted index names in a session (the
float_basis filter is not optional):
curl
coverage for the newest session before pinning a trade_date.
GET /short_interest_by_sector
One row per (sector, trade_date). Filters: trade_date, sector. short_value is the sector total; short_value_equities restricts it to SHARES and UNIT; tickers counts what went into the bucket.
Note: Não classificado is a real bucket. Sector comes from the B3 index portfolios, so ETFs, BDRs and every off-index name land there (bucketed, not dropped). For single-name risk read short_value_equities; the unclassified bucket is almost entirely ETFs and BDRs.
GET /lending_trades
The session’s lending trades, aggregated per (ticker, trade_date).
Note: B3’s own published average weights by the number of trades;
rate_pct weights by quantity. Both are right. Measured 2026-09-10 across the 569 tickers both cover: mean absolute difference 0.037 percentage points, 561 of 569 within 0.5pp, largest gap 3.81pp.
GET /lending_participants
One row per (ticker, trade_date, broker_code): what each brokerage lent and borrowed.
Python
Paging and empty results
These are views: the 1,000-row page is PostgREST’sdb-max-rows and it cuts silently with a 200, keeping the oldest rows. It does not raise like the RPC functions.
- Send
Prefer: count=exactand readContent-Range:0-999/*means the response was cut;0-999/4382gives the true total. - Page with
limit/offsetand an explicitorder; without a total order a page boundary can duplicate one row and drop another. - The SDK:
silo.short_interest(...)returns one page and raisesSiloTruncatedon an unbounded cut;silo.view_all("short_interest", order="trade_date.desc")walks every page. - A whole session of
lending_participantsis over one page (one row per(ticker, broker)): filter bytickeror page it.
200 [], never a fabricated balance. With ~21 business days of retention an empty window is common: check coverage first. Landing tables are not readable by client roles; these views are owner-privileged, so the columns they select are the whole boundary.