SDK ELIXIR - APIGratis by API BRASIL 💧

SDK oficial Elixir da plataforma APIBrasil — WhatsApp, SMS, consultas de CPF/CNPJ, veículos, CEP, correios, pagamentos PIX/boleto e muito mais.

Hex.pmHexDocsCIGitHub issuesGitHub forksGitHub stars

Canais de suporte (Comunidade)

WhatsApp GroupTelegram Group

Instalação

Adicione a dependência ao mix.exs:

def deps do
[
{:apibrasil, "~> 0.0.1"}
]
end
mix deps.get

Requer Elixir >= 1.14 e OTP >= 25. Não há dependência obrigatória: o HTTP usa o :httpc do Erlang/OTP (com verify_peer e checagem de hostname) e o JSON usa o JSON nativo do Elixir 1.18+ / o :json do OTP 27+.

Em versões anteriores, adicione um codec JSON:

{:jason, "~> 1.4"}

Obtenha suas credenciais em https://apibrasil.com.br

Começando

client =
ApiBrasil.new(
bearer_token: "SEU_BEARER_TOKEN",
device_token: "SEU_DEVICE_TOKEN"
)
# WhatsApp
{:ok, _envelope} =
ApiBrasil.Messaging.WhatsApp.send_text(client, %{
"number" => "5511999999999",
"text" => "Olá! 👋"
})
# Consulta CNPJ (por créditos)
{:ok, empresa} = ApiBrasil.Data.Consulta.cnpj(client, %{"cnpj" => "00000000000000"})
ApiBrasil.Core.CreditResponse.data(empresa)

O bearer_token é o JWT do login; o device_token é o device dos serviços device-based.

As credenciais também podem vir só do ambiente — ApiBrasil.from_env/0 lê automaticamente APIBRASIL_BEARER_TOKEN, APIBRASIL_DEVICE_TOKEN, APIBRASIL_SECRET_KEY e APIBRASIL_BASE_URL.

Também é possível autenticar por email/senha — ApiBrasil.login/2 devolve o cliente já autenticado, junto da sessão:

{:ok, client, _sessao} =
ApiBrasil.login(%{"email" => "voce@empresa.com.br", "password" => "******"})

Contas com 2FA concluem o login em três passos, aplicando o token com ApiBrasil.Platform.Auth.authenticate/2:

alias ApiBrasil.Platform.Auth
client = ApiBrasil.from_env()
{:ok, sessao} = Auth.login(client, %{"email" => email, "password" => senha})
client =
if Auth.requires_2fa?(sessao) do
desafio = sessao["challenge"]
{:ok, _} = Auth.send_2fa(client, %{"challenge" => desafio, "method" => "email"})
{:ok, sessao} = Auth.verify_2fa(client, %{"challenge" => desafio, "code" => "000000"})
Auth.authenticate(client, sessao)
else
Auth.authenticate(client, sessao)
end

Como a plataforma funciona

A API Brasil tem duas famílias de serviços:

FamíliaAutenticaçãoExemplos
Device-basedAuthorization: Bearer + header DeviceTokenWhatsApp, SMS, veículos, CEP, correios, DDD, feriados, tradução, clima, OCR
Por créditosapenas Authorization: Bearer (debita saldo)ApiBrasil.Data.Consulta: cpf/3, cnpj/3, veiculos/3, Serasa, CNH

Para os serviços device-based, crie um device com a SecretKey da API desejada (painel APIBrasil) e use o device_token retornado:

{:ok, device} =
client
|> ApiBrasil.with_options(secret_key: "SUA_SECRET_KEY")
|> ApiBrasil.Platform.Devices.store(%{"device_name" => "meu-bot", "type" => "server"})
client = ApiBrasil.put_device_token(client, device["device_token"])

O cliente é um valor imutável: ApiBrasil.put_device_token/2, ApiBrasil.put_bearer_token/2 e ApiBrasil.with_options/2 devolvem sempre um novo cliente.

Serviços disponíveis

MóduloDescrição
ApiBrasil.Messaging.WhatsAppWhatsApp: start/3, qrcode/3, send_text/3, send_file/3, send_audio/3, fila (queue/4)...
ApiBrasil.Messaging.EvolutionEvolution API: request/5 (controller + action), call/4, queue/5
ApiBrasil.Messaging.WhatsMeowWhatsMeow: send_text/3, instance_create/3, instance_qr/3, request/4
ApiBrasil.Messaging.SMSSMS device-based (send/3) e por créditos (send_with_credits/3)
ApiBrasil.Data.DadosDados cadastrais device-based (cpf/3, cnpj/3, lista_socios/3...)
ApiBrasil.Data.VehiclesVeículos por placa (dados/3, fipe/3, consulta_fipe/3, base_dados/3)
ApiBrasil.Data.FipeTabela FIPE (consultar_marcas/3, consultar_modelos/3...)
ApiBrasil.Data.CorreiosCorreios (rastreio/3, request/4)
ApiBrasil.Data.CepCEP + geolocalização (cep/3, cidades/3, estados/3, calcular_distancia/3)
ApiBrasil.Data.Geolocation / ApiBrasil.Data.GeomatrixGeocoding e matriz de distâncias
ApiBrasil.Data.RecognizeOCR / Google Vision (base64/3, uri/3)
ApiBrasil.Data.Ddd / ApiBrasil.Data.Holidays / ApiBrasil.Data.Translate / ApiBrasil.Data.WeatherDDD, feriados, tradução, clima
ApiBrasil.Data.LoteriasLoterias (latest/4, resultado/5)
ApiBrasil.Data.DatabaseIpGeoIP (ip/3)
ApiBrasil.Data.ConsultaConsultas por créditos: cpf/3, cnpj/3, cnh/3, cep/3, veiculos/3, telefone/3, generic/4
ApiBrasil.Data.Ura / ApiBrasil.Data.ChipVirtualURA reversa e chip virtual
ApiBrasil.Data.BulkExecução em lote (direct/4, queue/4)
ApiBrasil.Platform.AuthLogin, 2FA, cadastro, recuperação de senha, perfil
ApiBrasil.Platform.DevicesCRUD de devices
ApiBrasil.Platform.CatalogCatálogo de APIs, planos, documentações, servidores
ApiBrasil.Platform.AccountSaldo, faturas, notificações, tickets
ApiBrasil.Platform.PaymentsRecargas e pagamentos PIX/boleto/cartão (Santander, Inter, Mercado Pago, Sicoob)
ApiBrasil.Platform.IpWhitelist / ApiBrasil.Platform.BearerRateLimitSegurança da conta
ApiBrasil.Platform.ReportsRelatórios e dashboard de consumo

Toda função recebe o cliente no primeiro argumento, aceita o body como mapa (ou nil) e termina com uma keyword list de opções.

WhatsApp

alias ApiBrasil.Core.DeviceResponse
alias ApiBrasil.Messaging.WhatsApp
# iniciar sessão e obter QR Code
{:ok, _} = WhatsApp.start(client, %{"webhook_wh_message" => "https://seu-webhook.com/mensagens"})
{:ok, qr} = WhatsApp.qrcode(client)
DeviceResponse.response(qr)["qrcode"]
# => imagem do QR Code em base64
# envios
{:ok, _} = WhatsApp.send_text(client, %{"number" => "5511999999999", "text" => "Olá!"})
{:ok, _} =
WhatsApp.send_file(client, %{
"number" => "5511999999999",
"path" => "https://exemplo.com/boleto.pdf"
})
{:ok, _} =
WhatsApp.send_location(client, %{
"number" => "5511999999999",
"lat" => -23.5,
"lng" => -46.6
})
# qualquer action do catálogo
{:ok, _} = WhatsApp.request(client, "getAllChats")
# fila assíncrona
{:ok, _} = WhatsApp.queue(client, "sendText", %{"number" => "5511999999999", "text" => "por fila"})

O envelope device-based tem acessores nomeados — e continua sendo um mapa JSON, porque implementa Access:

{:ok, envelope} = WhatsApp.send_text(client, %{"number" => numero, "text" => "Olá!"})
DeviceResponse.error?(envelope) # false
DeviceResponse.message(envelope) # mensagem do gateway
DeviceResponse.response(envelope) # payload do provedor
DeviceResponse.api_limit(envelope) # limite do plano
envelope["response"] # acesso direto por chave
envelope.json # o envelope completo, como mapa
DeviceResponse.to_map(envelope) # o mesmo

Consultas por créditos

alias ApiBrasil.{Consulta, Data}
alias ApiBrasil.Core.CreditResponse
{:ok, cpf} = Data.Consulta.cpf(client, %{"cpf" => "00000000000"})
{CreditResponse.balance(cpf), CreditResponse.data(cpf)}
# o campo `tipo` define o produto consultado — use o builder ApiBrasil.Consulta
"lista-socios"
|> Consulta.new()
|> Consulta.field("cnpj", "00000000000000")
|> then(&Data.Consulta.cnpj(client, &1))
# modo homologação (sandbox, sem cobrança)
"serasa-score-pj"
|> Consulta.new()
|> Consulta.homolog(true)
|> Consulta.field("cnpj", "00000000000000")
|> then(&Data.Consulta.cnpj(client, &1))
# qualquer serviço do catálogo, e os créditos disponíveis
{:ok, _} = Data.Consulta.generic(client, "cnh", %{"cpf" => "00000000000"})
{:ok, _} = Data.Consulta.credits(client, "cpf")

O builder também aceita Consulta.lite/2, Consulta.agrupados/2, Consulta.extra/2 e Consulta.fields/2; qualquer função de serviço recebe a struct diretamente no lugar do mapa.

Veículos e FIPE (device-based)

{:ok, _} = ApiBrasil.Data.Vehicles.dados(client, %{"placa" => "ABC1234"})
{:ok, _} = ApiBrasil.Data.Vehicles.fipe(client, %{"placa" => "ABC1234"})
{:ok, _} = ApiBrasil.Data.Fipe.consultar_marcas(client, %{"codigoTabelaReferencia" => 300})

SMS

alias ApiBrasil.Messaging.SMS
{:ok, _} = SMS.send(client, %{"number" => "5511999999999", "message" => "Olá!"})
{:ok, _} = SMS.send_with_credits(client, %{"number" => "5511999999999", "message" => "Olá!"})

Pagamentos e recargas

alias ApiBrasil.Platform.Payments
{:ok, _} = Payments.recharge(client, %{"amount" => 50, "type" => "pix"})
{:ok, _} = Payments.pix_generate(client, Payments.provider_santander(), %{"amount" => 50})
{:ok, _} = Payments.pix_status(client, "santander", "TX_ID")
# bytes crus do PDF
{:ok, pdf} = Payments.boleto_pdf(client, Payments.provider_inter(), "ID")
File.write!("boleto.pdf", pdf)

Múltiplos devices

bot1 = ApiBrasil.with_device(client, "device_token_1")
bot2 = ApiBrasil.with_device(client, "device_token_2")
{:ok, _} = WhatsApp.send_text(bot1, %{"number" => numero, "text" => "do bot 1"})
{:ok, _} = WhatsApp.send_text(bot2, %{"number" => numero, "text" => "do bot 2"})

Tratamento de erros

Toda chamada devolve {:ok, resultado} ou {:error, %ApiBrasil.Core.Error{}}; a falha carrega a categoria em :kind:

:kindQuando
:validation400/422 — payload inválido
:authentication401 — token ausente/expirado
:insufficient_balance402 — sem saldo/créditos
:permission403 — sem permissão (ex: exige PJ)
:not_found404/410 — sem dados / rota desativada
:rate_limit429 — limite atingido (:retry_after, em ms)
:server5xx — erro do gateway/provedor
:network / :timeoutfalha antes da resposta
:apiqualquer outra falha da API
alias ApiBrasil.Core.{CreditResponse, Error}
case ApiBrasil.Data.Consulta.cpf(client, %{"cpf" => "00000000000"}) do
{:ok, consulta} ->
CreditResponse.data(consulta)
{:error, %Error{kind: :insufficient_balance}} ->
IO.puts("Recarregue seus créditos")
{:error, %Error{kind: :rate_limit} = error} ->
IO.puts("Aguarde #{error.retry_after}ms")
# detalhes completos da falha
{:error, %Error{} = error} ->
IO.puts("#{Exception.message(error)} #{error.status} #{error.code}")
end

Cada categoria também tem o seu predicado: Error.insufficient_balance?/1, Error.rate_limit?/1, Error.network?/1... O struct é uma exceção, então Exception.message/1 formata a mensagem com o status e o código.

Variantes !

Toda função tem um par com ! que devolve o resultado direto e levanta o ApiBrasil.Core.Error em caso de falha — útil em scripts e pipelines:

alias ApiBrasil.Messaging.WhatsApp
envelope = WhatsApp.send_text!(client, %{"number" => numero, "text" => "Olá!"})
ApiBrasil.Core.DeviceResponse.response(envelope)
consulta = ApiBrasil.Data.Consulta.cpf!(client, %{"cpf" => "00000000000"})
ApiBrasil.Core.CreditResponse.data(consulta)
try do
ApiBrasil.Platform.Account.balance!(client)
rescue
error in ApiBrasil.Core.Error -> IO.puts(Exception.message(error))
end

ApiBrasil.login!/2 segue a mesma ideia e devolve a tupla {client, sessao}.

Retry e observabilidade

Por padrão a SDK refaz a chamada em HTTP 429 e em falhas de conexão (2 tentativas extras, backoff exponencial com jitter, respeitando Retry-After). Timeouts e erros de negócio nunca são refeitos — evita duplicar cobranças e envios.

client =
ApiBrasil.new(
retry: %ApiBrasil.Core.Retry{
retries: 3,
min_delay: 500,
max_delay: 5_000,
retry_on_statuses: [429, 503]
},
hooks: %{
request: fn info -> IO.puts("→ #{info.method} #{info.url} (##{info.attempt})") end,
response: fn info -> IO.puts("← #{info.status} em #{info.duration}ms") end,
retry: fn info -> IO.puts("retry em #{info.delay}ms: #{info.reason}") end
}
)
# ou desativando o retry
ApiBrasil.new(retry: ApiBrasil.Core.Retry.none())

Os hooks também podem ser um módulo com o behaviour ApiBrasil.Core.Hooks (on_request/1, on_response/1, on_retry/1) — o caminho natural para :telemetry e Logger. Falhas dentro de um hook nunca derrubam a requisição.

Para limitar uma chamada, use :timeout (em ms) — no cliente ou só naquela requisição:

ApiBrasil.Messaging.WhatsApp.send_text(client, body, timeout: 10_000)

Opções por requisição

ApiBrasil.with_options/2 devolve um cliente que aplica as opções em todas as chamadas, mantendo base, credenciais e transporte:

client
|> ApiBrasil.with_options(
secret_key: "SUA_SECRET_KEY",
headers: %{"X-Correlation-Id" => "abc-123"},
timeout: 5_000
)
|> ApiBrasil.Platform.Devices.store(%{"device_name" => "meu-bot"})
# ou só nesta chamada — a keyword list é sempre o último argumento
ApiBrasil.Messaging.WhatsApp.send_text(client, body, device_token: "outro-device")

Opções aceitas: :query, :headers, :bearer_token, :device_token, :secret_key, :timeout e :response_type (:json ou :binary). Veja ApiBrasil.Core.HTTP.

Transporte plugável

O HTTP padrão é o :httpc (ApiBrasil.Core.Transport.Httpc, sem dependências), mas o behaviour ApiBrasil.Core.Transport permite trocar a camada inteira — proxy corporativo, instrumentação, mocks de teste:

defmodule MeuTransporte do
@behaviour ApiBrasil.Core.Transport
alias ApiBrasil.Core.Transport.{Request, Response}
@impl true
def request(%Request{} = request, _opts) do
# use o cliente HTTP que quiser e devolva status, headers e data
{:ok, Response.json(200, %{"ok" => true, "url" => request.url})}
end
end
ApiBrasil.new(transport: MeuTransporte)

Para pool de conexões, HTTP/2 e telemetria, use o Finch:

# mix.exs
{:finch, "~> 0.16"}
# na sua árvore de supervisão
children = [{Finch, name: MinhaApp.Finch}]
# cliente
ApiBrasil.new(transport: {ApiBrasil.Core.Transport.Finch, name: MinhaApp.Finch})

Em testes, o transporte pode ser uma função de aridade 1 — sem rede, sem mock library:

alias ApiBrasil.Core.Transport.Response
client =
ApiBrasil.new(
bearer_token: "token-de-teste",
device_token: "device-de-teste",
transport: fn request ->
send(self(), {:requisicao, request.method, request.url})
{:ok, Response.json(200, %{"error" => false, "response" => %{"id" => "ABC"}})}
end
)
{:ok, envelope} =
ApiBrasil.Messaging.WhatsApp.send_text(client, %{"number" => "5511999999999", "text" => "oi"})
assert ApiBrasil.Core.DeviceResponse.response(envelope) == %{"id" => "ABC"}
assert_received {:requisicao, :post, _url}

O transporte padrão também aceita opções: {ApiBrasil.Core.Transport.Httpc, profile: :default, ssl: [...], connect_timeout: 10_000}.

Catálogo gerado

As actions de WhatsApp/Evolution/WhatsMeow e os tipo das consultas são gerados do catálogo real da plataforma (GET /documentations):

mix apibrasil.codegen
alias ApiBrasil.Generated.Catalog
Catalog.service_actions("whatsapp") # todas as actions do WhatsApp
Catalog.service_actions("cep") # ["bairros", "cep", "cidades", ...]
Catalog.has_action?("cep", "estados") # true
Catalog.evolution_paths() # ["call/offer", "chat/deleteMessageForEveryone", ...]
Catalog.consulta_servicos() # serviços de /consulta/{servico}/credits
Catalog.consulta_tipos() # os `tipo` conhecidos das consultas
Catalog.consulta_tipo("acerta-essencial")
# => %{service: "cpf", fields: ["cpf"]}

Endpoint sem função dedicada?

Todo o gateway fica acessível pela porta de saída genérica, já com seus headers de autenticação:

ApiBrasil.request(client, :post, "/consulta/cpf/credits", %{"cpf" => "00000000000"})
ApiBrasil.request(client, :get, "/reports/quick-stats")
# levanta em caso de falha
ApiBrasil.request!(client, :get, "/reports/quick-stats")
# corpo decodificado sem normalizar em objeto JSON (listas, texto)
{:ok, dados} = ApiBrasil.execute(client, :get, "/plans")
# bytes crus (PDF de boleto, imagens)
{:ok, pdf} = ApiBrasil.download(client, "/inter/boleto/ID/pdf")

Documentação completa dos endpoints: https://doc.apibrasil.io

Configuração avançada

client =
ApiBrasil.new(
# ou APIBRASIL_BEARER_TOKEN
bearer_token: "...",
# ou APIBRASIL_DEVICE_TOKEN
device_token: "...",
# usada em Platform.Devices.store/3 (ou APIBRASIL_SECRET_KEY)
secret_key: "...",
# padrão (ou APIBRASIL_BASE_URL)
base_url: "https://gateway.apibrasil.io/api/v2",
timeout: 30_000,
headers: %{"X-Correlation-Id" => "abc-123"},
transport: ApiBrasil.Core.Transport.Httpc,
retry: %ApiBrasil.Core.Retry{retries: 3},
hooks: MinhaApp.ApiHooks,
options: [timeout: 15_000]
)

A mesma configuração pode viver na configuração da aplicação, inclusive com {:system, "VAR"}:

# config/runtime.exs
config :apibrasil,
bearer_token: {:system, "APIBRASIL_BEARER_TOKEN"},
device_token: {:system, "APIBRASIL_DEVICE_TOKEN"},
base_url: "https://gateway.apibrasil.io/api/v2",
timeout: 60_000

As variáveis de ambiente têm prioridade sobre o config :apibrasil, e o que você passa em ApiBrasil.new/1 tem prioridade sobre as duas. Credenciais vazias contam como ausentes: informar "" é a forma de desligar o que veio do ambiente. Veja ApiBrasil.Core.Config.

Interface legada

ApiBrasil.Legacy mantém o contrato das primeiras SDKs da plataforma — credenciais, body e action em uma única string JSON (credentials / body / action), com os erros da API devolvidos decodificados em {:ok, mapa} em vez de {:error, ...}.

legacy = ApiBrasil.Legacy.new()
dados = ~s({
"action": "sendText",
"credentials": {
"DeviceToken": "SEU_DEVICE_TOKEN",
"BearerToken": "SEU_BEARER_TOKEN"
},
"body": {"number": "5511999999999", "text": "Hello World for Elixir"}
})
{:ok, resposta} = ApiBrasil.Legacy.whatsapp(legacy, dados)

Além de whatsapp/3, há sms/3, cpf/3, cnpj/3 e request/4 (qualquer serviço), todos com a variante !.

Ele existe só para quem está migrando das SDKs PHP/Node com o formato antigo. Em código novo, prefira o cliente ApiBrasil, que cobre toda a plataforma com funções dedicadas, erros com categoria, retry e hooks.

Licença

MIT — veja LICENSE.