> ## 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.

# portfolio_fee_peers

> Administration fee comparison (catalog v63; ETF peers v66), one row per distinct input CNPJ. Latest CVM Extrato only, not a historical backtest: p_as_of (default today) dates the comparison, excludes future documents and fees older than 36 months. Fund peers have a FI quota in the three reference months ending in p_as_of month, the SAME filed ANBIMA class, FUNDO_COTAS S/N and TP_FUNDO_CLASSE FI/CLASSES - FIF. ETF peers (v66, #609): CVM files no ANBIMA class for an ETF, so an active ETF (cvm_etf_registry.is_active) joins a class only when its underlying_index is mapped to that class in public.portfolio_class_index (the owner-reviewed src/portfolio/rules/equivalents/class_index.yaml, never inferred from a name); it is a peer of every FUNDO_COTAS and scope cell of the class, once per CNPJ. Its fee is the third-party etfsbrasil.com.br value (etf_market_snapshot.taxa_adm_pct, the newest snapshot with a fee dated no later than p_as_of, as portfolio_fees serves etf_site_taxa_adm), never a CVM-disclosed fee; the same 0 < fee <= 5 and 36-month rules apply. n_peers = n_fund_peers + n_etf_peers; at least 30 usable FUND fees (the target included when eligible), and ETFs never make a group qualify (owner, #609 Q18): they join a group that already has 30 funds; no broader fallback. n_excluded counts unusable fund fees, n_etf_excluded mapped ETFs with no usable fee; peer_fee_oldest/newest date the usable fund documents, etf_peer_fee_oldest/newest the ETF snapshots; etf_peer_tickers and etf_peer_fee_source name the ETFs and say their fee is third-party. p25/median/p75 are annual administration fees over the combined group, percentile is midrank (half weight for ties), difference_pp is target minus median. No performance fee, expense ratio, alternative or saving estimate. not_compared carries a reason_code and NULL comparison statistics. Source values are never rescaled. 1 to 200 CNPJs per call, punctuation stripped and padded to 14 digits; invalid input or more than 200 raises 22023; one page, never trimmed.

**Row cap.** A window producing more than 1000 rows raises SQLSTATE `22023` naming this function. Nothing is trimmed to fit — PostgREST would silently cut the response at 1000 and a cut-short series is indistinguishable from one that simply ends. This function does not page — narrow the window instead.



## OpenAPI

````yaml /openapi.json post /rpc/portfolio_fee_peers
openapi: 3.1.0
info:
  title: SILO Data API
  version: '75'
  summary: >-
    Read-only Brazilian public financial data: CVM filings, BACEN series, B3
    quotes and the B3 BDI group.
  description: >
    Schema `api` over PostgREST. Views are `GET /<view>`; functions are

    `POST /rpc/<function>` with a JSON body.


    **This document is generated** from the live Postgres catalog by

    `scripts/gen_openapi.py` — `pg_get_function_arguments` for the RPC
    signatures,

    `information_schema.columns` for the view columns, and the granted-to-`anon`

    privilege set for which objects exist at all. It is not hand-maintained, and

    `tests/test_openapi_spec.py` fails the build when it drifts from the SQL.


    ### Two different ceilings


    Reading one for the other is the most expensive mistake on this API.


    * **The row cap refuses.** Fifty-nine set-returning functions raise SQLSTATE
      `22023` when the window they were handed would produce more than 1000 rows.
      Nothing is trimmed, and the error says why and how to fix it (the message,
      plus PostgREST's `details` and `hint`). Five of them (`panel`,
      `quote_history`, `fund_nav`, `index_history`, `trade_consolidated_history`)
      take a `p_after` cursor so you can walk the series; the other fifty-four ask you to narrow the window. `fund_nav` paging
      additionally REQUIRES `p_entity_type` — its cursor is a bare period, which
      is unique only within one family, and CNPJs that file under both `fi` and
      `fidc` in the same month would otherwise be ambiguous.
    * **The tier ceiling clamps.** Three other functions (`search_funds`,
      `option_chain`, `option_exercises`) silently lower `p_limit` to a per-tier maximum. No error is raised, so the
      only way to know the result was cut is to know the ceiling. Signing in
      raises it.

    Separately from both, PostgREST's server-wide `db-max-rows = 1000` cuts
    every

    response, on every endpoint, for every tier. On the GET views
    `Content-Range`

    is the signal (`0-999/*`); `Prefer: count=exact` turns the `*` into a total.


    ### Retention


    Most of this warehouse is append-only history. The **B3 BDI group** —

    `short_interest`, `short_interest_by_sector`, `investor_flow`,

    `lending_trades`, `lending_participants` — is not: B3 keeps roughly 21
    business

    days and publishes no archive. A missed session cannot be bought back. See

    Market Coverage.
  license:
    name: See repository
    url: https://github.com/PedroDnT/SILO-BZ
servers:
  - url: https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1
    description: Supabase PostgREST, schema `api`
security:
  - apikey: []
tags:
  - name: ANBIMA
  - name: Company events
  - name: Coverage & catalog
  - name: Derivatives
  - name: FIDC
  - name: FNET documents
  - name: Financials
  - name: Funds
  - name: Holdings
  - name: Inflation
  - name: Lookup
  - name: Macro
  - name: Macro expectations
  - name: Other
  - name: Panel
  - name: Portfolio
  - name: Quotes
  - name: Rates
  - name: Research
  - name: Screens
  - name: Securities lending
  - name: Short interest & flows
paths:
  /rpc/portfolio_fee_peers:
    post:
      tags:
        - Portfolio
      summary: portfolio_fee_peers
      description: >-
        Administration fee comparison (catalog v63; ETF peers v66), one row per
        distinct input CNPJ. Latest CVM Extrato only, not a historical backtest:
        p_as_of (default today) dates the comparison, excludes future documents
        and fees older than 36 months. Fund peers have a FI quota in the three
        reference months ending in p_as_of month, the SAME filed ANBIMA class,
        FUNDO_COTAS S/N and TP_FUNDO_CLASSE FI/CLASSES - FIF. ETF peers (v66,
        #609): CVM files no ANBIMA class for an ETF, so an active ETF
        (cvm_etf_registry.is_active) joins a class only when its
        underlying_index is mapped to that class in public.portfolio_class_index
        (the owner-reviewed src/portfolio/rules/equivalents/class_index.yaml,
        never inferred from a name); it is a peer of every FUNDO_COTAS and scope
        cell of the class, once per CNPJ. Its fee is the third-party
        etfsbrasil.com.br value (etf_market_snapshot.taxa_adm_pct, the newest
        snapshot with a fee dated no later than p_as_of, as portfolio_fees
        serves etf_site_taxa_adm), never a CVM-disclosed fee; the same 0 < fee
        <= 5 and 36-month rules apply. n_peers = n_fund_peers + n_etf_peers; at
        least 30 usable FUND fees (the target included when eligible), and ETFs
        never make a group qualify (owner, #609 Q18): they join a group that
        already has 30 funds; no broader fallback. n_excluded counts unusable
        fund fees, n_etf_excluded mapped ETFs with no usable fee;
        peer_fee_oldest/newest date the usable fund documents,
        etf_peer_fee_oldest/newest the ETF snapshots; etf_peer_tickers and
        etf_peer_fee_source name the ETFs and say their fee is third-party.
        p25/median/p75 are annual administration fees over the combined group,
        percentile is midrank (half weight for ties), difference_pp is target
        minus median. No performance fee, expense ratio, alternative or saving
        estimate. not_compared carries a reason_code and NULL comparison
        statistics. Source values are never rescaled. 1 to 200 CNPJs per call,
        punctuation stripped and padded to 14 digits; invalid input or more than
        200 raises 22023; one page, never trimmed.


        **Row cap.** A window producing more than 1000 rows raises SQLSTATE
        `22023` naming this function. Nothing is trimmed to fit — PostgREST
        would silently cut the response at 1000 and a cut-short series is
        indistinguishable from one that simply ends. This function does not page
        — narrow the window instead.
      operationId: rpc_portfolio_fee_peers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                p_cnpjs:
                  type:
                    - array
                    - 'null'
                  items:
                    type:
                      - string
                      - 'null'
                p_as_of:
                  type:
                    - string
                    - 'null'
                  format: date
                  description: Defaults to `CURRENT_DATE`.
              required:
                - p_cnpjs
      responses:
        '200':
          description: >-
            The result set, oldest first. An unknown id is an empty array, not a
            404 and never a guessed value.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    cnpj:
                      type:
                        - string
                        - 'null'
                    classe_anbima:
                      type:
                        - string
                        - 'null'
                    fundo_cotas:
                      type:
                        - string
                        - 'null'
                    tp_fundo_classe:
                      type:
                        - string
                        - 'null'
                    taxa_adm:
                      type:
                        - number
                        - 'null'
                    fee_as_of:
                      type:
                        - string
                        - 'null'
                      format: date
                    comparison_as_of:
                      type:
                        - string
                        - 'null'
                      format: date
                    activity_from:
                      type:
                        - string
                        - 'null'
                      format: date
                    activity_to:
                      type:
                        - string
                        - 'null'
                      format: date
                    n_peers:
                      type:
                        - integer
                        - 'null'
                      format: int32
                    n_excluded:
                      type:
                        - integer
                        - 'null'
                      format: int32
                    peer_fee_oldest:
                      type:
                        - string
                        - 'null'
                      format: date
                    peer_fee_newest:
                      type:
                        - string
                        - 'null'
                      format: date
                    p25_pct_year:
                      type:
                        - number
                        - 'null'
                    median_pct_year:
                      type:
                        - number
                        - 'null'
                    p75_pct_year:
                      type:
                        - number
                        - 'null'
                    percentile_pct:
                      type:
                        - number
                        - 'null'
                    difference_pp:
                      type:
                        - number
                        - 'null'
                    status:
                      type:
                        - string
                        - 'null'
                    reason_code:
                      type:
                        - string
                        - 'null'
                    n_fund_peers:
                      type:
                        - integer
                        - 'null'
                      format: int32
                    n_etf_peers:
                      type:
                        - integer
                        - 'null'
                      format: int32
                    n_etf_excluded:
                      type:
                        - integer
                        - 'null'
                      format: int32
                    etf_peer_tickers:
                      type:
                        - array
                        - 'null'
                      items:
                        type:
                          - string
                          - 'null'
                    etf_peer_fee_oldest:
                      type:
                        - string
                        - 'null'
                      format: date
                    etf_peer_fee_newest:
                      type:
                        - string
                        - 'null'
                      format: date
                    etf_peer_fee_source:
                      type:
                        - string
                        - 'null'
        '400':
          description: >-
            Bad request. Contract violations raise SQLSTATE `22023` with a
            message naming the limit or the argument at fault.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: >-
            No usable `apikey`, or a request for a schema outside `api`.
            `Accept-Profile: public` and any `cvm_*` / `b3_cotahist` / `cia_*`
            path answer 401 for every caller, by grant — schema `api` is the
            entire public surface.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such endpoint in schema `api`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: >-
        PostgREST error body. `code` carries the SQLSTATE — `22023` is a
        contract refusal.
      properties:
        code:
          type:
            - string
            - 'null'
          examples:
            - '22023'
        message:
          type:
            - string
            - 'null'
        details:
          type:
            - string
            - 'null'
        hint:
          type:
            - string
            - 'null'
  securitySchemes:
    apikey:
      type: apiKey
      in: header
      name: apikey
      description: >-
        The publishable key (`sb_publishable_…`), on every request. It is NOT a
        JWT — sending it as `Authorization: Bearer` is rejected as an invalid
        JWT. It identifies the project, not you, and maps to the Postgres `anon`
        role.

````

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