curl --request POST \
--url https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents \
--header 'Content-Type: application/json' \
--header 'apikey: <api-key>' \
--data '
{
"p_classes": [
"<string>"
],
"p_as_of": "2023-12-25"
}
'import requests
url = "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents"
payload = {
"p_classes": ["<string>"],
"p_as_of": "2023-12-25"
}
headers = {
"apikey": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {apikey: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({p_classes: ['<string>'], p_as_of: '2023-12-25'})
};
fetch('https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'p_classes' => [
'<string>'
],
'p_as_of' => '2023-12-25'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"apikey: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents"
payload := strings.NewReader("{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("apikey", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents")
.header("apikey", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["apikey"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}"
response = http.request(request)
puts response.read_body[
{
"classe_anbima": "<string>",
"class_indices": [
"<string>"
],
"n_etfs": 123,
"ticker": "<string>",
"etf_cnpj": "<string>",
"etf_name": "<string>",
"underlying_index": "<string>",
"segment": "<string>",
"pl_brl": 123,
"pl_as_of": "2023-12-25",
"fee_pct_year": 123,
"fee_as_of": "2023-12-25",
"snapshot_source": "<string>",
"pl_rank": 123,
"is_equivalent": true,
"status": "<string>",
"reason": "<string>"
}
]{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}portfolio_equivalents
The market equivalent of an ANBIMA class (catalog v68, #609): the active ETFs that track an index the owner-reviewed list (src/portfolio/rules/equivalents/class_index.yaml, generated into public.portfolio_class_index; only pairs with status aprovada) maps to the class, read in reverse (class -> index). One row per (class, ETF CNPJ) among cvm_etf_registry.is_active ETFs, the universe portfolio_fee_peers uses; class_indices lists every mapped index and n_etfs counts the ETFs. pl_brl and fee_pct_year are the third-party values etfsbrasil.com.br prints (etf_market_snapshot.nav and taxa_adm_pct, each the newest snapshot that has one dated no later than p_as_of, dated pl_as_of and fee_as_of), the values portfolio_fees serves as etf_site_pl and etf_site_taxa_adm: never a CVM filing, never rescaled. is_equivalent marks the largest by PL across all the class’s indices (pl_rank 1, ties by ticker); an ETF with no PL is never ranked. segment is the registry’s as filed: fixed_income_br trades in B3’s FORWARD segment (prices in trade_consolidated_history), the others on the cash tape (quote_history). A class with no approved pair (status sem_par) or with no active ETF on its indices (sem_etf) comes back as one row with NULL ETF columns; when no ETF has a PL every row is sem_pl and none is the equivalent. The index is never inferred from a fund’s name. reason is Portuguese. It names an ETF with the same objective, not a recommendation. 1 to 50 non-blank classes (outer spaces trimmed, duplicates collapsed); otherwise 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.
curl --request POST \
--url https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents \
--header 'Content-Type: application/json' \
--header 'apikey: <api-key>' \
--data '
{
"p_classes": [
"<string>"
],
"p_as_of": "2023-12-25"
}
'import requests
url = "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents"
payload = {
"p_classes": ["<string>"],
"p_as_of": "2023-12-25"
}
headers = {
"apikey": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {apikey: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({p_classes: ['<string>'], p_as_of: '2023-12-25'})
};
fetch('https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'p_classes' => [
'<string>'
],
'p_as_of' => '2023-12-25'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"apikey: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents"
payload := strings.NewReader("{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("apikey", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents")
.header("apikey", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://zcjbtpxuhdekpwcxmepn.supabase.co/rest/v1/rpc/portfolio_equivalents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["apikey"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"p_classes\": [\n \"<string>\"\n ],\n \"p_as_of\": \"2023-12-25\"\n}"
response = http.request(request)
puts response.read_body[
{
"classe_anbima": "<string>",
"class_indices": [
"<string>"
],
"n_etfs": 123,
"ticker": "<string>",
"etf_cnpj": "<string>",
"etf_name": "<string>",
"underlying_index": "<string>",
"segment": "<string>",
"pl_brl": 123,
"pl_as_of": "2023-12-25",
"fee_pct_year": 123,
"fee_as_of": "2023-12-25",
"snapshot_source": "<string>",
"pl_rank": 123,
"is_equivalent": true,
"status": "<string>",
"reason": "<string>"
}
]{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}{
"code": "22023",
"message": "<string>",
"details": "<string>",
"hint": "<string>"
}Authorizations
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.
Body
Response
The result set, oldest first. An unknown id is an empty array, not a 404 and never a guessed value.
Was this page helpful?