Access Brazil CVM Filings with Go

Need a Go CVM filings API client for Brazilian public companies? apicvm is plain HTTP — use net/http, Bearer auth, and JSON. There is no official Go SDK; you call /v1/* directly.

The problem

Go services that ingest Brazil filings usually hit one of:

  • Portal ZIP downloads that break CI when HTML changes
  • Local CSV dumps that do not map to ticker + type + year
  • Missing stable document IDs for caching and retries

Setup

export APICVM_URL='https://apicvm.dev'
export APICVM_KEY='apicvm_...'   # from /signup

Minimal client

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

func apicvmGET(path string, q url.Values) (map[string]any, error) {
	base := os.Getenv("APICVM_URL")
	u, _ := url.Parse(base + path)
	u.RawQuery = q.Encode()
	req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("APICVM_KEY"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf("status %d: %s", res.StatusCode, body)
	}
	var out map[string]any
	return out, json.Unmarshal(body, &out)
}

func main() {
	company, err := apicvmGET("/v1/companies/resolve", url.Values{
		"query": {"PETR4"},
		"by":    {"ticker"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(company)

	docs, err := apicvmGET("/v1/documents", url.Values{
		"ticker":  {"PETR4"},
		"type":    {"DFP"},
		"year":    {"2024"},
		"perPage": {"20"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(docs["data"])
}

Download a PDF

func downloadFile(docID, outPath string) error {
	req, _ := http.NewRequest(
		http.MethodGet,
		os.Getenv("APICVM_URL")+"/v1/documents/"+docID+"/file",
		nil,
	)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("APICVM_KEY"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	f, err := os.Create(outPath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, res.Body)
	return err
}

File download does not consume extraction credits.

curl cross-check

curl -H "Authorization: Bearer $APICVM_KEY" \
  "$APICVM_URL/v1/companies/resolve?query=PETR4&by=ticker"

See Access Brazil CVM filings with curl.

Paginate documents

perPage max is 50. Walk pages until meta.lastPage:

func listAll(ticker, docType, year string) ([]any, error) {
	var all []any
	for page := 1; ; page++ {
		payload, err := apicvmGET("/v1/documents", url.Values{
			"ticker":  {ticker},
			"type":    {docType},
			"year":    {year},
			"page":    {fmt.Sprintf("%d", page)},
			"perPage": {"50"},
		})
		if err != nil {
			return nil, err
		}
		data, _ := payload["data"].([]any)
		all = append(all, data...)
		meta, _ := payload["meta"].(map[string]any)
		last := 1
		if meta != nil {
			if v, ok := meta["lastPage"].(float64); ok {
				last = int(v)
			}
		}
		if page >= last {
			break
		}
	}
	return all, nil
}

See Paginate and filter CVM documents.

Errors and rate limits

Map HTTP status codes explicitly: 401 auth, 404 missing document, 429 rate limit. Read X-RateLimit-Remaining when batching tickers. Details: Handle errors and rate limits.

Current limitations

  • Text extraction (POST /v1/document-text-extractions) is async via callback — not a sync Go return value.
  • perPage max is 50; paginate with page.
  • Corpus coverage depends on ingestion lag.

Next steps

Ready to integrate?

Get an API key and start querying Brazilian CVM filings programmatically.