Access Brazil CVM Filings with Kotlin

Need a Kotlin CVM filings API client for Brazilian public companies? apicvm is plain HTTPS JSON. Use the JDK HttpClient from Kotlin (or Ktor Client), a Bearer token, and the /v1/* routes — there is no official Kotlin SDK.

The problem

Kotlin services on the JVM often inherit the same pain as Java stacks:

  • Scrapers that break when the CVM portal UI changes
  • Local ZIP parsers without stable document UUIDs
  • One-off scripts that never become a shared client module

A small HTTP wrapper against a fixed contract is easier to keep in a Spring Boot, Ktor, or Gradle multi-module repo.

Setup

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

Kotlin on JVM 11+ is enough. The examples below use java.net.http.HttpClient and kotlinx.serialization.json (or you can parse with Jackson the same way as the Java guide).

Resolve a ticker

import java.net.URI
import java.net.URLEncoder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets
import java.time.Duration

fun main() {
  val base = System.getenv("APICVM_URL")
  val key = System.getenv("APICVM_KEY")
  val client = HttpClient.newHttpClient()

  val q = URLEncoder.encode("PETR4", StandardCharsets.UTF_8)
  val url = "$base/v1/companies/resolve?query=$q&by=ticker"

  val req = HttpRequest.newBuilder(URI.create(url))
    .timeout(Duration.ofSeconds(30))
    .header("Authorization", "Bearer $key")
    .GET()
    .build()

  val res = client.send(req, HttpResponse.BodyHandlers.ofString())
  require(res.statusCode() < 400) { "HTTP ${res.statusCode()}: ${res.body()}" }
  println(res.body())
}

List DFP documents

val listUrl = "$base/v1/documents?ticker=PETR4&type=DFP&year=2024&perPage=20"

val listReq = HttpRequest.newBuilder(URI.create(listUrl))
  .header("Authorization", "Bearer $key")
  .GET()
  .build()

val body = client.send(listReq, HttpResponse.BodyHandlers.ofString()).body()
println(body)

perPage max is 50. Paginate with page until meta.lastPage. See Paginate and filter CVM documents.

Download a PDF

import java.nio.file.Files
import java.nio.file.Path

val docId = "..." // UUID from list response
val fileReq = HttpRequest.newBuilder(
  URI.create("$base/v1/documents/$docId/file")
)
  .header("Authorization", "Bearer $key")
  .GET()
  .build()

val pdf = client.send(fileReq, HttpResponse.BodyHandlers.ofByteArray()).body()
Files.write(Path.of("petr4-dfp.pdf"), pdf)

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"

Same contract as Java, Go, and curl.

Errors and rate limits

Map status codes in your sealed hierarchy or Result type: 401 auth, 404 missing document, 429 rate limit. Read X-RateLimit-Remaining when fan-out across tickers. Details: Handle errors and rate limits.

Current limitations

  • Text extraction (POST /v1/document-text-extractions) is async via HTTPS callback — not a sync Kotlin return value.
  • Student plans cannot start markdown extraction (403).
  • Corpus coverage depends on ingestion lag; resolve before hard-coding CNPJ.

Next steps

Ready to integrate?

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