# 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.

[![Hex.pm](https://img.shields.io/hexpm/v/apibrasil.svg)](https://hex.pm/packages/apibrasil)
[![HexDocs](https://img.shields.io/badge/hex-docs-8e7cc3.svg)](https://hexdocs.pm/apibrasil)
[![CI](https://github.com/APIBrasil/apigratis-sdk-elixir/actions/workflows/ci.yml/badge.svg)](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)

[![WhatsApp Group](https://img.shields.io/badge/WhatsApp-Channel-25D366?logo=whatsapp)](https://whatsapp.com/channel/0029VaMiaT6B4hdX3hrUcz3X)
[![Telegram Group](https://img.shields.io/badge/Telegram-Group-32AFED?logo=telegram)](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).
