README.md

<div align="center">
<h1><img src="https://raw.githubusercontent.com/BrasilAPI/BrasilAPI/main/public/brasilapi-logo-small.png" /></h1>

</div>

Brasil API lookup library for Elixir with an easy-to-use API for brazilian data including banks, postal codes (CEP), company information (CNPJ), area codes (DDD), and national holidays.

## Features

- [x] **Bancos**
- [x] **Câmbio**
- [x] **CEP**
- [x] **CEP V2**
- [x] **CNPJ**
- [x] **Corretoras**
- [x] **CPTEC**
- [x] **DDD**
- [x] **Feriados Nacionais**
- [x] **FIPE**
- [x] **IBGE**
- [x] **ISBN**
- [x] **NCM**
- [x] **PIX**
- [x] **Registros BR**
- [x] **Rates**

## Installation

Add `brasilapi` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:brasilapi, "~> 1.0"}
  ]
end
```

Then run:

    $ mix deps.get

## Usage

### Bancos (Banks)

```elixir
# Get all banks
{:ok, banks} = Brasilapi.get_banks()
# banks =>
# [%Brasilapi.Banks.Bank{ispb: "00000000", name: "BCO DO BRASIL S.A.", code: 1, full_name: "Banco do Brasil S.A."},
#  %Brasilapi.Banks.Bank{ispb: "00000208", name: "BCO ORIGINAL S.A.", code: 212, full_name: "Banco Original S.A."},
#  ...]

# Find bank by code
{:ok, bank} = Brasilapi.get_bank_by_code(1)
# bank => %Brasilapi.Banks.Bank{ispb: "00000000", name: "BCO DO BRASIL S.A.", code: 1, full_name: "Banco do Brasil S.A."}
```

### Exchange (Currency Exchange Rates)

```elixir
# Get all available currencies
{:ok, currencies} = Brasilapi.get_exchange_currencies()
# currencies =>
# [%Brasilapi.Exchange.Currency{simbolo: "USD", nome: "Dólar dos Estados Unidos", tipo_moeda: "A"},
#  %Brasilapi.Exchange.Currency{simbolo: "EUR", nome: "Euro", tipo_moeda: "A"},
#  ...]

# Get exchange rate for a specific currency and date
{:ok, daily_exchange_rate} = Brasilapi.get_exchange_rate("USD", "2025-02-13")

# Also accepts Date/DateTime/NaiveDateTime structs
{:ok, daily_exchange_rate} = Brasilapi.get_exchange_rate("USD", ~D[2025-02-13])
{:ok, daily_exchange_rate} = Brasilapi.get_exchange_rate("USD", ~U[2025-02-13 14:30:00Z])
{:ok, daily_exchange_rate} = Brasilapi.get_exchange_rate("USD", ~N[2025-02-13 14:30:00])

# daily_exchange_rate =>
# %Brasilapi.Exchange.DailyExchangeRate{
#   moeda: "USD",
#   data: "2025-02-13",
#   cotacoes: [
#     %Brasilapi.Exchange.ExchangeRate{
#       paridade_compra: 1,
#       paridade_venda: 1,
#       cotacao_compra: 5.7624,
#       cotacao_venda: 5.763,
#       data_hora_cotacao: "2025-02-13 13:03:25.722",
#       tipo_boletim: "INTERMEDIÁRIO"
#     }
#   ]
# }

# Available currencies: AUD, CAD, CHF, DKK, EUR, GBP, JPY, SEK, USD
# Data available from November 28, 1984 onwards
# For weekends and holidays, returns last available business day
# Date accepts: String (YYYY-MM-DD), Date, DateTime, NaiveDateTime

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_exchange_rate("INVALID", "2025-02-13")
{:error, %{message: "Invalid date format. Must be YYYY-MM-DD"}} = Brasilapi.get_exchange_rate("USD", "invalid-date")
{:error, %{message: "Currency must be a string and date must be a valid date"}} = Brasilapi.get_exchange_rate(123, "2025-02-13")
```

### CEP (Postal Codes)

```elixir
# Find address by CEP (postal code)
# By default uses v2 endpoint which includes geolocation data and multiple providers with fallback
{:ok, address} = Brasilapi.get_cep("89010025")
# address =>
# %Brasilapi.Cep.Address{
#   cep: "89010025",
#   state: "SC",
#   city: "Blumenau",
#   neighborhood: "Centro",
#   street: "Rua Doutor Luiz de Freitas Melro",
#   service: "viacep",
#   location: %{type: "Point", coordinates: %{}}
# }

# Use v1 endpoint (without geolocation data)
{:ok, address} = Brasilapi.get_cep("89010025", version: :v1)
# address =>
# %Brasilapi.Cep.Address{
#   cep: "89010025",
#   state: "SC",
#   city: "Blumenau",
#   neighborhood: "Centro",
#   street: "Rua Doutor Luiz de Freitas Melro",
#   service: "open-cep",
#   location: nil
# }

# Explicitly use v2 endpoint
{:ok, address} = Brasilapi.get_cep("89010025", version: :v2)

# CEP can be provided as string or integer, with or without formatting
{:ok, address} = Brasilapi.get_cep("89010-025")  # formatted
{:ok, address} = Brasilapi.get_cep(89010025)     # integer
{:ok, address} = Brasilapi.get_cep("89010025")   # string

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_cep("00000000")
{:error, %{message: "CEP must be exactly 8 digits"}} = Brasilapi.get_cep("123")
```

### CNPJ (Company Registry)

```elixir
# Find company information by CNPJ
{:ok, company} = Brasilapi.get_cnpj("11000000000197")
# company =>
# %Brasilapi.Cnpj.Company{
#   cnpj: "11000000000197",
#   razao_social: "ACME INC",
#   nome_fantasia: "ACME CORPORATION",
#   uf: "SP",
#   municipio: "SAO PAULO",
#   situacao_cadastral: 2,
#   descricao_situacao_cadastral: "ATIVA",
#   cnae_fiscal: 6201500,
#   cnae_fiscal_descricao: "Desenvolvimento de programas de computador sob encomenda",
#   capital_social: 100000,
#   # ... many more fields available
# }

# CNPJ can be provided as string or integer, with or without formatting
{:ok, company} = Brasilapi.get_cnpj("11.000.000/0001-97")  # formatted
{:ok, company} = Brasilapi.get_cnpj(11000000000197)        # integer
{:ok, company} = Brasilapi.get_cnpj("11000000000197")      # string

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_cnpj("00000000000000")
{:error, %{message: "Invalid CNPJ format. Must be 14 digits."}} = Brasilapi.get_cnpj("123")
```

### Brokers (Brokerage Firms)

```elixir
# Get all active brokerage firms registered with CVM
{:ok, brokers} = Brasilapi.get_brokers()
# brokers =>
# [%Brasilapi.Brokers.Broker{
#    cnpj: "00000000000191",
#    nome_social: "CORRETORA EXEMPLO S.A.",
#    nome_comercial: "CORRETORA EXEMPLO",
#    bairro: "CENTRO",
#    cep: "01010000",
#    codigo_cvm: "12345",
#    complemento: "ANDAR 10",
#    data_inicio_situacao: "2020-01-01",
#    data_patrimonio_liquido: "2023-12-31",
#    data_registro: "2019-06-15",
#    email: "contato@exemplo.com.br",
#    logradouro: "RUA EXEMPLO, 123",
#    municipio: "SAO PAULO",
#    pais: "BRASIL",
#    status: "ATIVA",
#    telefone: "11999999999",
#    type: "CORRETORA",
#    uf: "SP",
#    valor_patrimonio_liquido: "50000000.00"
#  },
#  # ... more brokers
# ]

# Get specific brokerage firm by CNPJ
{:ok, broker} = Brasilapi.get_broker_by_cnpj("02332886000104")
# broker =>
# %Brasilapi.Brokers.Broker{
#   bairro: "LEBLON",
#   cep: "22440032",
#   cnpj: "02332886000104",
#   codigo_cvm: "3247",
#   complemento: "SALA 201",
#   data_inicio_situacao: "1998-02-10",
#   data_patrimonio_liquido: "2021-12-31",
#   data_registro: "1997-12-05",
#   email: "juridico.regulatorio@xpi.com.br",
#   logradouro: "AVENIDA ATAULFO DE PAIVA 153",
#   municipio: "RIO DE JANEIRO",
#   nome_social: "XP INVESTIMENTOS CCTVM S.A.",
#   nome_comercial: "XP INVESTIMENTOS",
#   pais: "",
#   status: "EM FUNCIONAMENTO NORMAL",
#   telefone: "30272237",
#   type: "CORRETORAS",
#   uf: "RJ",
#   valor_patrimonio_liquido: "5514593491.29"
# }

# CNPJ can be provided as string or integer, with or without formatting
{:ok, broker} = Brasilapi.get_broker_by_cnpj("02.332.886/0001-04")  # formatted
{:ok, broker} = Brasilapi.get_broker_by_cnpj(2332886000104)         # integer
{:ok, broker} = Brasilapi.get_broker_by_cnpj("02332886000104")      # string

# Error handling
{:error, %{status: 500, message: "Internal server error"}} = Brasilapi.get_brokers()  # when API is down
{:error, %{status: 404, message: "Não foi encontrado este CNPJ na listagem da CVM."}} = Brasilapi.get_broker_by_cnpj("00000000000000")
{:error, %{message: "Invalid CNPJ format. Must be 14 digits."}} = Brasilapi.get_broker_by_cnpj("123")
```

### CPTEC (Weather and Ocean Forecasts)

```elixir
# List all cities available in CPTEC services
{:ok, all_cities} = Brasilapi.list_cptec_cities()
# all_cities =>
# [%Brasilapi.Cptec.City{nome: "São Paulo", estado: "SP", id: 244},
#  %Brasilapi.Cptec.City{nome: "Rio de Janeiro", estado: "RJ", id: 241},
#  %Brasilapi.Cptec.City{nome: "Belo Horizonte", estado: "MG", id: 220},
#  # ... more cities
# ]

# Search for cities by name
{:ok, cities} = Brasilapi.search_cptec_cities("São Paulo")
# cities =>
# [%Brasilapi.Cptec.City{nome: "São Paulo", estado: "SP", id: 244},
#  %Brasilapi.Cptec.City{nome: "São Paulo do Potengi", estado: "RN", id: 5100},
#  # ... more matching cities
# ]

# Get current weather conditions for all state capitals
{:ok, weather} = Brasilapi.get_capitals_weather()
# weather =>
# [%Brasilapi.Cptec.AirportConditions{
#    codigo_icao: "SBGR",
#    atualizado_em: "2021-01-27T15:00:00.974Z",
#    pressao_atmosferica: "1014",
#    visibilidade: "9000",
#    vento: 29,
#    direcao_vento: 90,
#    umidade: 74,
#    condicao: "ps",
#    condicao_desc: "Predomínio de Sol",
#    temp: 28
#  },
#  # ... more capitals
# ]

# Get current weather at a specific airport by ICAO code
{:ok, airport_weather} = Brasilapi.get_airport_weather("SBGR")
# airport_weather =>
# %Brasilapi.Cptec.AirportConditions{
#   codigo_icao: "SBGR",
#   atualizado_em: "2021-01-27T15:00:00.974Z",
#   pressao_atmosferica: "1014",
#   visibilidade: "9000",
#   vento: 29,
#   direcao_vento: 90,
#   umidade: 74,
#   condicao: "ps",
#   condicao_desc: "Predomínio de Sol",
#   temp: 28
# }

# ICAO code is case-insensitive and auto-normalized to uppercase
{:ok, airport_weather} = Brasilapi.get_airport_weather("sbgr")  # Works fine

# Get weather forecast for a city (1 day)
{:ok, forecast} = Brasilapi.get_city_forecast(244)  # São Paulo city code
# forecast =>
# %Brasilapi.Cptec.CityForecast{
#   cidade: "São Paulo",
#   estado: "SP",
#   atualizado_em: "2021-01-27",
#   clima: [
#     %Brasilapi.Cptec.ClimateData{
#       data: "2021-01-27",
#       condicao: "ps",
#       min: 18,
#       max: 28,
#       indice_uv: 11.5,
#       condicao_desc: "Predomínio de Sol"
#     }
#   ]
# }

# Get weather forecast for multiple days (up to 6 days)
{:ok, forecast} = Brasilapi.get_city_forecast(244, 3)
# forecast =>
# %Brasilapi.Cptec.CityForecast{
#   cidade: "São Paulo",
#   estado: "SP",
#   atualizado_em: "2021-01-27",
#   clima: [
#     %Brasilapi.Cptec.ClimateData{data: "2021-01-27", min: 18, max: 28, ...},
#     %Brasilapi.Cptec.ClimateData{data: "2021-01-28", min: 19, max: 29, ...},
#     %Brasilapi.Cptec.ClimateData{data: "2021-01-29", min: 17, max: 25, ...}
#   ]
# }

# Get ocean/wave forecast for a coastal city (1 day)
{:ok, ocean_forecast} = Brasilapi.get_ocean_forecast(241)  # Rio de Janeiro city code
# ocean_forecast =>
# %Brasilapi.Cptec.OceanForecast{
#   cidade: "Rio de Janeiro",
#   estado: "RJ",
#   atualizado_em: "2021-01-27",
#   ondas: [
#     %Brasilapi.Cptec.DailyWaves{
#       data: "2021-01-27",
#       ondas_data: [
#         %Brasilapi.Cptec.HourlyWaves{
#           vento: 15.5,
#           direcao_vento: "S",
#           direcao_vento_desc: "Sul",
#           altura_onda: 1.2,
#           direcao_onda: "SE",
#           direcao_onda_desc: "Sudeste",
#           agitacao: "Fraco",
#           hora: "00h Z"
#         },
#         # ... more hourly data
#       ]
#     }
#   ]
# }

# Get ocean forecast for multiple days (up to 6 days)
{:ok, ocean_forecast} = Brasilapi.get_ocean_forecast(241, 3)
# Returns forecast for 3 days with hourly wave conditions

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.search_cptec_cities("InvalidCity")
{:error, %{message: "ICAO code must be exactly 4 uppercase letters"}} = Brasilapi.get_airport_weather("SB")
{:error, %{message: "Days parameter must be between 1 and 6"}} = Brasilapi.get_city_forecast(244, 7)
{:error, %{message: "Days parameter must be between 1 and 6"}} = Brasilapi.get_ocean_forecast(241, 10)
```

### DDD (Area Codes)

```elixir
# Get DDD information (area codes) - includes state and list of cities
{:ok, ddd_info} = Brasilapi.get_ddd(11)
# ddd_info =>
# %Brasilapi.Ddd.Info{
#   state: "SP",
#   cities: [
#     "EMBU",
#     "VÁRZEA PAULISTA",
#     "VARGEM GRANDE PAULISTA",
#     "SÃO PAULO",
#     # ... more cities
#   ]
# }

# DDD can be provided as string or integer
{:ok, ddd_info} = Brasilapi.get_ddd("11")  # string
{:ok, ddd_info} = Brasilapi.get_ddd(11)    # integer

# Error handling
{:error, %{status: 404, message: "DDD não encontrado"}} = Brasilapi.get_ddd(99)
{:error, %{message: "DDD must be exactly 2 digits"}} = Brasilapi.get_ddd("123")
```

### Feriados Nacionais (National Holidays)

```elixir
# Get national holidays for a specific year
{:ok, holidays} = Brasilapi.get_holidays(2021)
# holidays =>
# [
#   %Brasilapi.Holidays.Holiday{
#     date: "2021-01-01",
#     name: "Confraternização mundial",
#     type: "national",
#     full_name: nil
#   },
#   %Brasilapi.Holidays.Holiday{
#     date: "2021-04-21",
#     name: "Tiradentes",
#     type: "national",
#     full_name: nil
#   },
#   # ... more holidays
# ]

# Year can be provided as string or integer
{:ok, holidays} = Brasilapi.get_holidays("2021")  # string
{:ok, holidays} = Brasilapi.get_holidays(2021)    # integer

# Error handling
{:error, %{status: 404, message: "Ano fora do intervalo suportado."}} = Brasilapi.get_holidays(1900)
{:error, %{message: "Year must be a valid positive integer"}} = Brasilapi.get_holidays("invalid")
```

### FIPE (Fundação Instituto de Pesquisas Econômicas)

```elixir
# Get vehicle brands by type
{:ok, brands} = Brasilapi.get_fipe_brands("carros")
# brands =>
# [
#   %Brasilapi.Fipe.Brand{
#     nome: "AGRALE",
#     valor: "102"
#   },
#   %Brasilapi.Fipe.Brand{
#     nome: "FIAT",
#     valor: "21"
#   },
#   # ... more brands
# ]

# Get brands for all vehicle types
{:ok, brands} = Brasilapi.get_fipe_brands(nil)
{:ok, brands} = Brasilapi.get_fipe_brands("")

# Get brands for specific vehicle types
{:ok, brands} = Brasilapi.get_fipe_brands("carros")     # cars
{:ok, brands} = Brasilapi.get_fipe_brands("motos")      # motorcycles
{:ok, brands} = Brasilapi.get_fipe_brands("caminhoes")  # trucks

# Get brands with specific reference table
{:ok, brands} = Brasilapi.get_fipe_brands("carros", tabela_referencia: 295)

# Get vehicle price by FIPE code
{:ok, prices} = Brasilapi.get_fipe_price("001004-9")
# prices =>
# [
#   %Brasilapi.Fipe.Price{
#     valor: "R$ 6.022,00",
#     marca: "Fiat",
#     modelo: "Palio EX 1.0 mpi 2p",
#     ano_modelo: 1998,
#     combustivel: "Álcool",
#     codigo_fipe: "001004-9",
#     mes_referencia: "junho de 2021 ",
#     tipo_veiculo: 1,
#     sigla_combustivel: "Á",
#     data_consulta: "segunda-feira, 7 de junho de 2021 23:05"
#   }
# ]

# Get price with specific reference table
{:ok, prices} = Brasilapi.get_fipe_price("001004-9", tabela_referencia: 295)

# Get all FIPE reference tables
{:ok, tables} = Brasilapi.get_fipe_reference_tables()
# tables =>
# [
#   %Brasilapi.Fipe.ReferenceTable{
#     codigo: 271,
#     mes: "junho/2021 "
#   },
#   %Brasilapi.Fipe.ReferenceTable{
#     codigo: 270,
#     mes: "maio/2021 "
#   },
#   # ... more tables
# ]

# Get vehicles by brand and type
{:ok, vehicles} = Brasilapi.get_fipe_vehicles("carros", 21)
# vehicles =>
# [
#   %Brasilapi.Fipe.Vehicle{
#     modelo: "Palio EX 1.0 mpi 2p"
#   },
#   %Brasilapi.Fipe.Vehicle{
#     modelo: "Uno Mille 1.0"
#   },
#   # ... more vehicles
# ]

# Get vehicles with brand code as string
{:ok, vehicles} = Brasilapi.get_fipe_vehicles("carros", "21")

# Get vehicles with specific reference table
{:ok, vehicles} = Brasilapi.get_fipe_vehicles("carros", 21, tabela_referencia: 295)

# Error handling
{:error, %{message: "Invalid vehicle type: invalid. Valid types are: caminhoes, carros, motos"}} = Brasilapi.get_fipe_brands("invalid")
{:error, %{message: "Invalid FIPE code format. Must be in format XXXXXX-X (e.g., 001004-9)"}} = Brasilapi.get_fipe_price("invalid")
{:error, %{message: "Vehicle type must be a string or nil"}} = Brasilapi.get_fipe_brands(123)
{:error, %{status: 500, message: "Server error"}} = Brasilapi.get_fipe_reference_tables()  # when API is down
```

### IBGE (Instituto Brasileiro de Geografia e Estatística)

```elixir
# Get all Brazilian states
{:ok, states} = Brasilapi.get_states()
# states =>
# [
#   %Brasilapi.Ibge.State{
#     id: 35,
#     sigla: "SP",
#     nome: "São Paulo",
#     regiao: %Brasilapi.Ibge.Region{
#       id: 3,
#       sigla: "SE",
#       nome: "Sudeste"
#     }
#   },
#   %Brasilapi.Ibge.State{
#     id: 43,
#     sigla: "RS",
#     nome: "Rio Grande do Sul",
#     regiao: %Brasilapi.Ibge.Region{
#       id: 4,
#       sigla: "S",
#       nome: "Sul"
#     }
#   },
#   # ... more states
# ]

# Get specific state by code (ID) or abbreviation (sigla)
{:ok, state} = Brasilapi.get_state("SP")
# state =>
# %Brasilapi.Ibge.State{
#   id: 35,
#   sigla: "SP",
#   nome: "São Paulo",
#   regiao: %Brasilapi.Ibge.Region{
#     id: 3,
#     sigla: "SE",
#     nome: "Sudeste"
#   }
# }

# State code can be provided as string (sigla) or integer (ID)
{:ok, state} = Brasilapi.get_state("SP")  # sigla (string)
{:ok, state} = Brasilapi.get_state(35)    # ID (integer)

# Get municipalities for a state
{:ok, municipalities} = Brasilapi.get_municipalities("SC")
# municipalities =>
# [
#   %Brasilapi.Ibge.Municipality{
#     nome: "Tubarão",
#     codigo_ibge: "421870705"
#   },
#   %Brasilapi.Ibge.Municipality{
#     nome: "Tunápolis",
#     codigo_ibge: "421875605"
#   },
#   # ... more municipalities
# ]

# Specify data providers (optional)
{:ok, municipalities} = Brasilapi.get_municipalities("SP", providers: ["gov"])
{:ok, municipalities} = Brasilapi.get_municipalities("SP", providers: ["gov", "wikipedia"])

# Available providers: "dados-abertos-br", "gov", "wikipedia"
# If no providers specified, uses all available data sources

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_state("XX")
{:error, %{message: "Code must be a string or integer"}} = Brasilapi.get_state(nil)
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_municipalities("XX")
{:error, %{message: "UF must be a string"}} = Brasilapi.get_municipalities(123)
{:error, %{message: "Invalid providers: invalid. Valid providers are: dados-abertos-br, gov, wikipedia"}} = Brasilapi.get_municipalities("SP", providers: ["invalid"])
```

### ISBN (International Standard Book Number)

```elixir
# Get book information by ISBN (supports both ISBN-10 and ISBN-13)
{:ok, book} = Brasilapi.get_book("9788545702870")
# book =>
# %Brasilapi.Isbn.Book{
#   isbn: "9788545702870",
#   title: "Akira",
#   subtitle: nil,
#   authors: [
#     "KATSUHIRO OTOMO",
#     "DRIK SADA",
#     "CASSIUS MEDAUAR",
#     "MARCELO DEL GRECO",
#     "DENIS TAKATA"
#   ],
#   publisher: "Japorama Editora e Comunicação",
#   synopsis: "Um dos marcos da ficção científica oriental que revolucionou...",
#   dimensions: %Brasilapi.Isbn.Dimensions{
#     width: 17.5,
#     height: 25.7,
#     unit: "CENTIMETER"
#   },
#   year: 2017,
#   format: "PHYSICAL",
#   page_count: 364,
#   subjects: ["Cartoons; caricaturas e quadrinhos", "mangá", "motocicleta", "gangue", "Delinquência"],
#   location: "SÃO PAULO, SP",
#   retail_price: nil,
#   cover_url: nil,
#   provider: "cbl"
# }

# ISBN can be provided in multiple formats
{:ok, book} = Brasilapi.get_book("978-85-457-0287-0")  # ISBN-13 with dashes
{:ok, book} = Brasilapi.get_book("9788545702870")      # ISBN-13 without dashes
{:ok, book} = Brasilapi.get_book("85-457-0287-6")      # ISBN-10 with dashes
{:ok, book} = Brasilapi.get_book("8545702876")         # ISBN-10 without dashes

# Specify providers for faster results (optional)
{:ok, book} = Brasilapi.get_book("9788545702870", providers: ["cbl"])
{:ok, book} = Brasilapi.get_book("9788545702870", providers: ["cbl", "google-books"])

# Available providers: "cbl", "mercado-editorial", "open-library", "google-books"
# If no providers specified, uses all providers and returns fastest response

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_book("1234567890123")
{:error, %{status: 400, message: "Bad request"}} = Brasilapi.get_book("invalid-isbn")
{:error, %{message: "Invalid ISBN format. Must be 10 or 13 digits."}} = Brasilapi.get_book("123")
{:error, %{message: "Invalid providers: invalid. Valid providers are: cbl, mercado-editorial, open-library, google-books"}} = Brasilapi.get_book("9788545702870", providers: ["invalid"])
```

### NCM (Nomenclatura Comum do Mercosul)

```elixir
# Get all NCM codes
{:ok, ncms} = Brasilapi.get_ncm_by_codes()
# ncms =>
# [
#   %Brasilapi.Ncm.Ncm{
#     codigo: "3305.10.00",
#     descricao: "- Xampus",
#     data_inicio: "2022-04-01",
#     data_fim: "9999-12-31",
#     tipo_ato: "Res Camex",
#     numero_ato: "000272",
#     ano_ato: "2021"
#   },
#   # ... more NCM codes
# ]

# Search NCM codes by keyword or partial code
{:ok, ncms} = Brasilapi.search_ncms("xampu")
# ncms =>
# [
#   %Brasilapi.Ncm.Ncm{
#     codigo: "3305.10.00",
#     descricao: "- Xampus",
#     data_inicio: "2022-04-01",
#     data_fim: "9999-12-31",
#     tipo_ato: "Res Camex",
#     numero_ato: "000272",
#     ano_ato: "2021"
#   }
# ]

# Search by partial code
{:ok, ncms} = Brasilapi.search_ncms("3305")
# Returns all NCM codes starting with 3305

# Get specific NCM by code (formatted or unformatted)
{:ok, ncm} = Brasilapi.get_ncm_by_code("33051000")
# ncm =>
# %Brasilapi.Ncm.Ncm{
#   codigo: "3305.10.00",
#   descricao: "- Xampus",
#   data_inicio: "2022-04-01",
#   data_fim: "9999-12-31",
#   tipo_ato: "Res Camex",
#   numero_ato: "000272",
#   ano_ato: "2021"
# }

# Code can be provided with or without formatting
{:ok, ncm} = Brasilapi.get_ncm_by_code("3305.10.00")  # formatted
{:ok, ncm} = Brasilapi.get_ncm_by_code("33051000")    # unformatted
{:ok, ncm} = Brasilapi.get_ncm_by_code(33051000)      # integer

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_ncm_by_code("99999999")
{:error, %{message: "Code must be a string or integer"}} = Brasilapi.get_ncm_by_code(nil)
{:error, %{message: "Search query must be a string"}} = Brasilapi.search_ncms(123)
```

### PIX (Brazilian Instant Payment System)

```elixir
# Get all PIX participants
{:ok, participants} = Brasilapi.get_pix_participants()
# participants =>
# [
#   %Brasilapi.Pix.Participant{
#     ispb: "360305",
#     nome: "CAIXA ECONOMICA FEDERAL",
#     nome_reduzido: "CAIXA ECONOMICA FEDERAL",
#     modalidade_participacao: "PDCT",
#     tipo_participacao: "DRCT",
#     inicio_operacao: "2020-11-03T09:30:00.000Z"
#   },
#   %Brasilapi.Pix.Participant{
#     ispb: "00000000",
#     nome: "BANCO DO BRASIL S.A.",
#     nome_reduzido: "BCO DO BRASIL S.A.",
#     modalidade_participacao: "DRCT",
#     tipo_participacao: "DRCT",
#     inicio_operacao: "2020-10-16T08:00:00.000Z"
#   },
#   # ... more participants
# ]

# Error handling
{:error, %{status: 500, message: "Server error"}} = Brasilapi.get_pix_participants()  # when API is down
```

### RegistroBR (Brazilian Domain Registration)

```elixir
# Get domain information for .br domains
{:ok, domain} = Brasilapi.get_domain_info("brasilapi.com.br")
# domain =>
# %Brasilapi.RegistroBr.Domain{
#   status_code: 2,
#   status: "REGISTERED",
#   fqdn: "brasilapi.com.br",
#   hosts: ["bob.ns.cloudflare.com", "lily.ns.cloudflare.com"],
#   publication_status: "published",
#   expires_at: "2022-09-23T00:00:00-03:00",
#   suggestions: ["agr.br", "app.br", "art.br", "blog.br", "dev.br", ...]
# }

# Check if domain is available
{:ok, domain} = Brasilapi.get_domain_info("available-domain.com.br")
# domain.status => "AVAILABLE"
# domain.suggestions => ["net.br", "org.br", "edu.br", ...]

# Error handling
{:error, %{status: 400, message: "Bad request"}} = Brasilapi.get_domain_info("invalid-domain")
{:error, %{message: "Domain must be a string"}} = Brasilapi.get_domain_info(123)
```

### Rates (Tax Rates and Official Indices)

```elixir
# Get all available tax rates and indices
{:ok, rates} = Brasilapi.get_rates()
# rates =>
# [
#   %Brasilapi.Rates.Rate{nome: "CDI", valor: 13.65},
#   %Brasilapi.Rates.Rate{nome: "SELIC", valor: 13.75},
#   %Brasilapi.Rates.Rate{nome: "IPCA", valor: 4.62},
#   # ... more rates and indices
# ]

# Get a specific tax rate by its name/acronym
{:ok, rate} = Brasilapi.get_rate_by_acronym("CDI")
# rate => %Brasilapi.Rates.Rate{nome: "CDI", valor: 13.65}

{:ok, rate} = Brasilapi.get_rate_by_acronym("SELIC")
# rate => %Brasilapi.Rates.Rate{nome: "SELIC", valor: 13.75}

# Error handling
{:error, %{status: 404, message: "Not found"}} = Brasilapi.get_rate_by_acronym("INVALID_RATE")
{:error, %{message: "Acronym must be a string"}} = Brasilapi.get_rate_by_acronym(123)
```

## Response Types with Structs

For better type safety and developer experience, BrasilAPI provides struct definitions for all response types. You can use these structs to work with typed data instead of raw maps:

```elixir
# Bank struct
{:ok, %Brasilapi.Banks.Bank{} = bank} = Brasilapi.get_bank_by_code(1)
# bank => %Brasilapi.Banks.Bank{ispb: "00000000", name: "BCO DO BRASIL S.A.", code: 1, full_name: "Banco do Brasil S.A."}

# Broker struct
{:ok, brokers} = Brasilapi.get_brokers()
[%Brasilapi.Brokers.Broker{} = broker | _] = brokers
# broker => %Brasilapi.Brokers.Broker{cnpj: "00000000000191", nome_social: "CORRETORA EXEMPLO S.A.", nome_comercial: "CORRETORA EXEMPLO", codigo_cvm: "12345", status: "ATIVA", ...}

# Address struct
{:ok, %Brasilapi.Cep.Address{} = address} = Brasilapi.get_cep("89010025")
# address => %Brasilapi.Cep.Address{cep: "89010025", state: "SC", city: "Blumenau", neighborhood: "Centro", street: "Rua Doutor Luiz de Freitas Melro", service: "viacep", location: %{type: "Point", coordinates: %{}}}

# Company struct
{:ok, %Brasilapi.Cnpj.Company{} = company} = Brasilapi.get_cnpj("11000000000197")
# company => %Brasilapi.Cnpj.Company{cnpj: "11000000000197", razao_social: "ACME INC", nome_fantasia: "ACME CORPORATION", uf: "SP", municipio: "SAO PAULO", situacao_cadastral: 2, descricao_situacao_cadastral: "ATIVA", ...}

# CPTEC city struct
{:ok, cities} = Brasilapi.search_cptec_cities("São Paulo")
[%Brasilapi.Cptec.City{} = city | _] = cities
# city => %Brasilapi.Cptec.City{nome: "São Paulo", estado: "SP", id: 244}

# CPTEC airport conditions struct
{:ok, %Brasilapi.Cptec.AirportConditions{} = weather} = Brasilapi.get_airport_weather("SBGR")
# weather => %Brasilapi.Cptec.AirportConditions{codigo_icao: "SBGR", temp: 28, umidade: 74, vento: 29, condicao_desc: "Predomínio de Sol", ...}

# CPTEC city forecast struct
{:ok, %Brasilapi.Cptec.CityForecast{} = forecast} = Brasilapi.get_city_forecast(244, 3)
# forecast => %Brasilapi.Cptec.CityForecast{cidade: "São Paulo", estado: "SP", clima: [%Brasilapi.Cptec.ClimateData{min: 18, max: 28, indice_uv: 11.5, ...}, ...]}

# CPTEC ocean forecast struct
{:ok, %Brasilapi.Cptec.OceanForecast{} = ocean} = Brasilapi.get_ocean_forecast(241)
# ocean => %Brasilapi.Cptec.OceanForecast{cidade: "Rio de Janeiro", estado: "RJ", ondas: [%Brasilapi.Cptec.DailyWaves{data: "2021-01-27", ondas_data: [%Brasilapi.Cptec.HourlyWaves{altura_onda: 1.2, ...}]}]}

# Book struct
{:ok, %Brasilapi.Isbn.Book{} = book} = Brasilapi.get_book("9788545702870")
# book => %Brasilapi.Isbn.Book{isbn: "9788545702870", title: "Akira", authors: ["KATSUHIRO OTOMO", ...], publisher: "Japorama Editora e Comunicação", year: 2017, ...}

# DDD info struct
{:ok, %Brasilapi.Ddd.Info{} = ddd_info} = Brasilapi.get_ddd(11)
# ddd_info => %Brasilapi.Ddd.Info{state: "SP", cities: ["EMBU", "VÁRZEA PAULISTA", "SÃO PAULO", ...]}

# Holiday struct
{:ok, holidays} = Brasilapi.get_holidays(2021)
[%Brasilapi.Holidays.Holiday{} = holiday | _] = holidays
# holiday => %Brasilapi.Holidays.Holiday{date: "2021-01-01", name: "Confraternização mundial", type: "national", full_name: nil}

# Tax rate struct
{:ok, %Brasilapi.Rates.Rate{} = rate} = Brasilapi.get_rate_by_acronym("CDI")
# rate => %Brasilapi.Rates.Rate{nome: "CDI", valor: 13.65}

# PIX participant struct
{:ok, participants} = Brasilapi.get_pix_participants()
[%Brasilapi.Pix.Participant{} = participant | _] = participants
# participant => %Brasilapi.Pix.Participant{ispb: "360305", nome: "CAIXA ECONOMICA FEDERAL", nome_reduzido: "CAIXA ECONOMICA FEDERAL", modalidade_participacao: "PDCT", tipo_participacao: "DRCT", inicio_operacao: "2020-11-03T09:30:00.000Z"}

# RegistroBR domain struct
{:ok, %Brasilapi.RegistroBr.Domain{} = domain} = Brasilapi.get_domain_info("brasilapi.com.br")
# domain => %Brasilapi.RegistroBr.Domain{status_code: 2, status: "REGISTERED", fqdn: "brasilapi.com.br", hosts: ["bob.ns.cloudflare.com", "lily.ns.cloudflare.com"], publication_status: "published", expires_at: "2022-09-23T00:00:00-03:00", suggestions: ["agr.br", "app.br", ...]}

# Exchange currency struct
{:ok, currencies} = Brasilapi.get_exchange_currencies()
[%Brasilapi.Exchange.Currency{} = currency | _] = currencies
# currency => %Brasilapi.Exchange.Currency{simbolo: "USD", nome: "Dólar dos Estados Unidos", tipo_moeda: "A"}

# Daily exchange rate struct
{:ok, %Brasilapi.Exchange.DailyExchangeRate{} = daily_exchange_rate} = Brasilapi.get_exchange_rate("USD", "2025-02-13")
# daily_exchange_rate => %Brasilapi.Exchange.DailyExchangeRate{moeda: "USD", data: "2025-02-13", cotacoes: [%Brasilapi.Exchange.ExchangeRate{paridade_compra: 1, paridade_venda: 1, cotacao_compra: 5.7624, cotacao_venda: 5.763, ...}]}
```

### Available Structs

- `Brasilapi.Banks.Bank` - Bank information
- `Brasilapi.Brokers.Broker` - CVM-registered brokerage firm information with comprehensive registration and financial data
- `Brasilapi.Cep.Address` - CEP/Address information with location data
- `Brasilapi.Cnpj.Company` - Company information with comprehensive business data
- `Brasilapi.Cptec.City` - CPTEC city information with name, state, and ID code
- `Brasilapi.Cptec.AirportConditions` - Current weather conditions at airports/capitals with temperature, wind, humidity, and pressure
- `Brasilapi.Cptec.CityForecast` - Weather forecast wrapper with city information and climate data array
- `Brasilapi.Cptec.ClimateData` - Daily climate forecast with min/max temperature, UV index, and weather conditions
- `Brasilapi.Cptec.OceanForecast` - Ocean forecast wrapper with city information and wave data array
- `Brasilapi.Cptec.DailyWaves` - Daily wave data container with hourly wave conditions
- `Brasilapi.Cptec.HourlyWaves` - Hourly wave conditions with wind speed/direction, wave height/direction, and sea state
- `Brasilapi.Ddd.Info` - DDD/Area code information with state and cities
- `Brasilapi.Holidays.Holiday` - National holiday information with date, name, and type
- `Brasilapi.Fipe.Brand` - FIPE vehicle brand information with name and code
- `Brasilapi.Fipe.Price` - FIPE vehicle price information with detailed specifications, fuel type, and reference date
- `Brasilapi.Fipe.ReferenceTable` - FIPE reference table with code and month
- `Brasilapi.Fipe.Vehicle` - FIPE vehicle model information
- `Brasilapi.Ibge.Region` - Brazilian region information with ID, abbreviation, and name
- `Brasilapi.Ibge.State` - Brazilian state (UF) information with ID, abbreviation, name, and region
- `Brasilapi.Ibge.Municipality` - Brazilian municipality information with name and IBGE code
- `Brasilapi.Isbn.Book` - Book information with title, authors, publisher, synopsis, dimensions, and metadata
- `Brasilapi.Isbn.Dimensions` - Book dimensions with width, height, and unit measurements
- `Brasilapi.Ncm.Ncm` - NCM (Nomenclatura Comum do Mercosul) code information with classification, description, and legal act details
- `Brasilapi.Pix.Participant` - PIX participant information with ISPB, names, and participation details
- `Brasilapi.Rates.Rate` - Tax rates and official indices with name and value
- `Brasilapi.RegistroBr.Domain` - Brazilian domain registration information with status, hosts, and suggestions
- `Brasilapi.Exchange.Currency` - Currency information with symbol, name, and type
- `Brasilapi.Exchange.DailyExchangeRate` - Daily exchange rate information with currency, date, and list of quotations
- `Brasilapi.Exchange.ExchangeRate` - Individual exchange rate quotation with buy/sell rates, parity, date/time, and bulletin type

## CNPJ Validation and Formatting

BrasilAPI-Ex (this library) provides convenient CNPJ handling with lightweight validation and automatic formatting:

### Validation Approach

- **Lightweight validation**: Only checks for 14-digit length and numeric characters
- **No checksum validation**: Does not validate actual CNPJ checksums
- **Format flexibility**: Accepts integers, formatted strings, and plain strings
- **Automatic formatting**: Converts all inputs to clean 14-digit strings

### Supported CNPJ Formats

```elixir
# All of these are equivalent and valid:
Brasilapi.get_cnpj("11000000000197")        # Plain string
Brasilapi.get_cnpj("11.000.000/0001-97")    # Formatted string
Brasilapi.get_cnpj(11000000000197)          # Integer
Brasilapi.get_cnpj("11-000-000-0001-97")    # Custom formatting

# Integers with leading zeros are automatically padded:
Brasilapi.get_cnpj(197)  # Becomes "00000000000197"
```

### Full CNPJ Validation

For complete CNPJ validation including checksum verification, consider using a dedicated library such as [brcpfcnpj](https://hex.pm/packages/brcpfcnpj):

```elixir
# Add to your dependencies for full validationnet
{:brcpfcnpj, "~> 2.0"}

# Use before calling BrasilAPI
if Brcpfcnpj.cnpj_valid?("11.000.000/0001-97") do
  {:ok, company} = Brasilapi.get_cnpj("11.000.000/0001-97")
else
  {:error, "Invalid CNPJ checksum"}
end
```

## Configuration

You can configure the BrasilAPI client with custom settings (you usually don't need to change these):

```elixir
# In your config/config.exs
config :brasilapi,
  base_url: "https://brasilapi.com.br/api",
  timeout: 30_000,
  retry_attempts: 3,
  req_options: []
```

### Configuration Options

- `base_url` - Base URL for BrasilAPI (default: "https://brasilapi.com.br/api")
- `timeout` - Request timeout in milliseconds (default: 30,000)
- `retry_attempts` - Number of retry attempts for failed requests (default: 3)
- `req_options` - Additional options passed to the Req HTTP client (default: [])

### TODOs

- Implement changelog for tracking changes and versions
- Implement CI/CD pipeline for automated testing

Contributions welcome!

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/stlucasgarcia/brasilapi-ex. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/stlucasgarcia/brasilapi/blob/main/CODE_OF_CONDUCT.md).

## License

The library is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

## Code of Conduct

Everyone interacting in the Brasilapi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/stlucasgarcia/brasilapi/blob/main/CODE_OF_CONDUCT.md).