Current section
Files
Jump to
Current section
Files
README.md
# SDK ELIXIR - APIGratis by API BRASIL 💧
SDK oficial Elixir da plataforma [APIBrasil](https://apibrasil.com.br) — WhatsApp, SMS, consultas de CPF/CNPJ, veículos, CEP, correios, pagamentos PIX/boleto e muito mais.
[](https://hex.pm/packages/apibrasil)
[](https://hexdocs.pm/apibrasil)
[](https://github.com/APIBrasil/apigratis-sdk-elixir/actions/workflows/ci.yml)
<a href="https://github.com/APIBrasil/apigratis-sdk-elixir/issues" target="_blank"><img alt="GitHub issues" src="https://img.shields.io/github/issues/APIBrasil/apigratis-sdk-elixir"></a>
<a href="https://github.com/APIBrasil/apigratis-sdk-elixir/network" target="_blank"><img alt="GitHub forks" src="https://img.shields.io/github/forks/APIBrasil/apigratis-sdk-elixir"></a>
<a href="https://github.com/APIBrasil/apigratis-sdk-elixir/stargazers" target="_blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/APIBrasil/apigratis-sdk-elixir"></a>
## Canais de suporte (Comunidade)
[](https://whatsapp.com/channel/0029VaMiaT6B4hdX3hrUcz3X)
[](https://t.me/apibrasil1)
## Instalação
Adicione a dependência ao `mix.exs`:
```elixir
def deps do
[
{:apibrasil, "~> 0.0.1"}
]
end
```
```bash
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:
```elixir
{:jason, "~> 1.4"}
```
Obtenha suas credenciais em https://apibrasil.com.br
## Começando
```elixir
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:
```elixir
{: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`:
```elixir
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ília | Autenticação | Exemplos |
| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------- |
| **Device-based** | `Authorization: Bearer` + header `DeviceToken` | WhatsApp, SMS, veículos, CEP, correios, DDD, feriados, tradução, clima, OCR |
| **Por créditos** | apenas `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:
```elixir
{: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ódulo | Descrição |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `ApiBrasil.Messaging.WhatsApp` | WhatsApp: `start/3`, `qrcode/3`, `send_text/3`, `send_file/3`, `send_audio/3`, fila (`queue/4`)... |
| `ApiBrasil.Messaging.Evolution` | Evolution API: `request/5` (controller + action), `call/4`, `queue/5` |
| `ApiBrasil.Messaging.WhatsMeow` | WhatsMeow: `send_text/3`, `instance_create/3`, `instance_qr/3`, `request/4` |
| `ApiBrasil.Messaging.SMS` | SMS device-based (`send/3`) e por créditos (`send_with_credits/3`) |
| `ApiBrasil.Data.Dados` | Dados cadastrais device-based (`cpf/3`, `cnpj/3`, `lista_socios/3`...) |
| `ApiBrasil.Data.Vehicles` | Veículos por placa (`dados/3`, `fipe/3`, `consulta_fipe/3`, `base_dados/3`) |
| `ApiBrasil.Data.Fipe` | Tabela FIPE (`consultar_marcas/3`, `consultar_modelos/3`...) |
| `ApiBrasil.Data.Correios` | Correios (`rastreio/3`, `request/4`) |
| `ApiBrasil.Data.Cep` | CEP + geolocalização (`cep/3`, `cidades/3`, `estados/3`, `calcular_distancia/3`) |
| `ApiBrasil.Data.Geolocation` / `ApiBrasil.Data.Geomatrix` | Geocoding e matriz de distâncias |
| `ApiBrasil.Data.Recognize` | OCR / Google Vision (`base64/3`, `uri/3`) |
| `ApiBrasil.Data.Ddd` / `ApiBrasil.Data.Holidays` / `ApiBrasil.Data.Translate` / `ApiBrasil.Data.Weather` | DDD, feriados, tradução, clima |
| `ApiBrasil.Data.Loterias` | Loterias (`latest/4`, `resultado/5`) |
| `ApiBrasil.Data.DatabaseIp` | GeoIP (`ip/3`) |
| `ApiBrasil.Data.Consulta` | Consultas por créditos: `cpf/3`, `cnpj/3`, `cnh/3`, `cep/3`, `veiculos/3`, `telefone/3`, `generic/4` |
| `ApiBrasil.Data.Ura` / `ApiBrasil.Data.ChipVirtual` | URA reversa e chip virtual |
| `ApiBrasil.Data.Bulk` | Execução em lote (`direct/4`, `queue/4`) |
| `ApiBrasil.Platform.Auth` | Login, 2FA, cadastro, recuperação de senha, perfil |
| `ApiBrasil.Platform.Devices` | CRUD de devices |
| `ApiBrasil.Platform.Catalog` | Catálogo de APIs, planos, documentações, servidores |
| `ApiBrasil.Platform.Account` | Saldo, faturas, notificações, tickets |
| `ApiBrasil.Platform.Payments` | Recargas e pagamentos PIX/boleto/cartão (Santander, Inter, Mercado Pago, Sicoob) |
| `ApiBrasil.Platform.IpWhitelist` / `ApiBrasil.Platform.BearerRateLimit` | Segurança da conta |
| `ApiBrasil.Platform.Reports` | Relató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
```elixir
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`:
```elixir
{: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
```elixir
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)
```elixir
{: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
```elixir
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
```elixir
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
```elixir
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`:
| `:kind` | Quando |
| ----------------------- | ----------------------------------------------- |
| `:validation` | 400/422 — payload inválido |
| `:authentication` | 401 — token ausente/expirado |
| `:insufficient_balance` | 402 — sem saldo/créditos |
| `:permission` | 403 — sem permissão (ex: exige PJ) |
| `:not_found` | 404/410 — sem dados / rota desativada |
| `:rate_limit` | 429 — limite atingido (`:retry_after`, em ms) |
| `:server` | 5xx — erro do gateway/provedor |
| `:network` / `:timeout` | falha antes da resposta |
| `:api` | qualquer outra falha da API |
```elixir
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:
```elixir
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.
```elixir
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:
```elixir
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:
```elixir
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:
```elixir
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](https://hex.pm/packages/finch):
```elixir
# 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:
```elixir
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`):
```bash
mix apibrasil.codegen
```
```elixir
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:
```elixir
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
```elixir
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"}`:
```elixir
# 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, ...}`.
```elixir
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](LICENSE).