Skip to content

Repository files navigation

SDK LUA - APIGratis by API BRASIL 💧

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

LuaRocks CI GitHub issues GitHub forks GitHub stars

Canais de suporte (Comunidade)

WhatsApp Group Telegram Group

Instalação

luarocks install apibrasil

Requer Lua >= 5.1 — funciona em Lua 5.1/5.2/5.3/5.4, LuaJIT e OpenResty.

O codec JSON acompanha a SDK, sem dependência externa. Para o HTTP, o rockspec instala luasocket + luasec; a SDK também aceita lua-http, resty.http (OpenResty) e o curl da máquina — veja Transporte plugável.

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

Começando

local apibrasil = require("apibrasil")

local client = apibrasil.new({
  bearer_token = "SEU_BEARER_TOKEN",
  device_token = "SEU_DEVICE_TOKEN",
})

-- WhatsApp
local envelope, err = client.whatsapp:send_text({
  number = "5511999999999",
  text = "Olá! 👋",
})

-- Consulta CNPJ (por créditos)
local empresa = client.consulta:cnpj({ cnpj = "00000000000000" })
print(empresa.balance, empresa.data)

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() 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 devolve o cliente já autenticado, junto da sessão:

local client, session = apibrasil.login({
  email = "[email protected]",
  password = "******",
})

Contas com 2FA concluem o login em três passos, aplicando o token com client.auth:authenticate:

local client = apibrasil.from_env()
local session = client.auth:login({ email = email, password = senha })

if client.auth.requires_2fa(session) then
  client.auth:send_2fa({ challenge = session.challenge, method = "email" })
  session = client.auth:verify_2fa({ challenge = session.challenge, code = "000000" })
end

client = client.auth:authenticate(session)

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) client.consulta: cpf, cnpj, veiculos, 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:

local device = client
  :with_options({ secret_key = "SUA_SECRET_KEY" })
  .devices:store({ device_name = "meu-bot", type = "server" })

client = client:with_device(device.device_token)

O cliente é um valor imutável: with_device, with_bearer_token, with_secret_key, with_options e with_strict devolvem sempre um novo cliente.

Serviços disponíveis

Cada serviço é um campo do cliente, criado sob demanda:

Campo Descrição
client.whatsapp WhatsApp: start, qrcode, send_text, send_file, send_audio, fila (queue)...
client.evolution Evolution API: request(controller, action, body), call(path, body), queue(...)
client.whatsmeow WhatsMeow: send_text, instance_create, instance_qr, request(action, body)
client.sms SMS device-based (send) e por créditos (send_with_credits)
client.dados Dados cadastrais device-based (cpf, cnpj, lista_socios...)
client.vehicles Veículos por placa (dados, fipe, base_dados, base_fipe)
client.fipe Tabela FIPE (consultar_marcas, consultar_modelos...)
client.correios Correios (rastreio, request)
client.cep CEP + geolocalização (cep, cidades, estados, calcular_distancia)
client.geolocation / client.geomatrix Geocoding e matriz de distâncias
client.recognize OCR / Google Vision (base64, uri)
client.ddd / client.holidays / client.translate / client.weather DDD, feriados, tradução, clima
client.loterias Loterias (latest, resultado)
client.database_ip GeoIP (ip)
client.consulta Consultas por créditos: cpf, cnpj, cnh, cep, veiculos, telefone, generic
client.ura / client.chip_virtual URA reversa e chip virtual
client.bulk Execução em lote (direct, queue)
client.auth Login, 2FA, cadastro, recuperação de senha, perfil
client.devices CRUD de devices
client.catalog Catálogo de APIs, planos, documentações, servidores
client.account Saldo, faturas, notificações, tickets
client.payments Recargas e pagamentos PIX/boleto/cartão (Santander, Inter, Mercado Pago, Sicoob)
client.ip_whitelist / client.bearer_rate_limit Segurança da conta
client.reports Relatórios e dashboard de consumo

Todo método recebe o body como tabela (ou nil) e termina com uma tabela de opções.

WhatsApp

-- iniciar sessão e obter QR Code
client.whatsapp:start({ webhook_wh_message = "https://seu-webhook.com/mensagens" })

local qr = client.whatsapp:qrcode()
print(qr.response.qrcode) -- imagem do QR Code em base64

-- envios
client.whatsapp:send_text({ number = "5511999999999", text = "Olá!" })

client.whatsapp:send_file({
  number = "5511999999999",
  path = "https://exemplo.com/boleto.pdf",
})

client.whatsapp:send_location({
  number = "5511999999999",
  lat = -23.5,
  lng = -46.6,
})

-- qualquer action do catálogo
client.whatsapp:request("getAllChats")

-- fila assíncrona
client.whatsapp:queue("sendText", { number = "5511999999999", text = "por fila" })

O envelope device-based é o próprio JSON: os campos são lidos direto, e os poucos auxiliares têm nomes que não colidem com chaves da API.

local envelope = client.whatsapp:send_text({ number = numero, text = "Olá!" })

envelope.error       -- false
envelope.message     -- mensagem do gateway
envelope.response    -- payload do provedor
envelope.api_limit   -- limite do plano

envelope:is_error()            -- false
envelope:get("response.id")    -- acesso por caminho
envelope:raw()                 -- a tabela completa

Consultas por créditos

local Consulta = require("apibrasil").Consulta

local cpf = client.consulta:cpf({ cpf = "00000000000" })
print(cpf.balance, cpf.data)

-- o campo `tipo` define o produto consultado — use o builder
client.consulta:cnpj(
  Consulta.new("lista-socios"):field("cnpj", "00000000000000")
)

-- modo homologação (sandbox, sem cobrança)
client.consulta:cnpj(
  Consulta.new("serasa-score-pj")
    :homolog()
    :field("cnpj", "00000000000000")
)

-- qualquer serviço do catálogo, e os créditos disponíveis
client.consulta:generic("cnh", { cpf = "00000000000" })
client.consulta:credits("cpf")

O builder também aceita :lite(), :agrupados({...}), :extra({...}) e :fields({...}); qualquer método de serviço recebe o builder diretamente no lugar da tabela.

Veículos e FIPE (device-based)

client.vehicles:dados({ placa = "ABC1234" })
client.vehicles:fipe({ placa = "ABC1234" })
client.fipe:consultar_marcas({ codigoTabelaReferencia = 300 })

SMS

client.sms:send({ number = "5511999999999", message = "Olá!" })
client.sms:send_with_credits({ number = "5511999999999", message = "Olá!" })

Pagamentos e recargas

local Payments = require("apibrasil.platform.payments")

client.payments:recharge({ amount = 50, type = "pix" })
client.payments:pix_generate(Payments.SANTANDER, { amount = 50 })
client.payments:pix_status("santander", "TX_ID")

-- bytes crus do PDF
local pdf = client.payments:boleto_pdf(Payments.INTER, "ID")
local file = assert(io.open("boleto.pdf", "wb"))
file:write(pdf)
file:close()

Múltiplos devices

local bot1 = client:with_device("device_token_1")
local bot2 = client:with_device("device_token_2")

bot1.whatsapp:send_text({ number = numero, text = "do bot 1" })
bot2.whatsapp:send_text({ number = numero, text = "do bot 2" })

Tratamento de erros

Toda chamada devolve resultado ou nil, erro; 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
local consulta, err = client.consulta:cpf({ cpf = "00000000000" })

if err then
  if err:is_insufficient_balance() then
    print("Recarregue seus créditos")
  elseif err:is_rate_limit() then
    print("Aguarde " .. err.retry_after .. "ms")
  else
    -- mensagem já formatada com status e código
    print(tostring(err), err.status, err.code)
  end
else
  print(consulta.data)
end

Cada categoria tem o seu predicado: err:is_insufficient_balance(), err:is_rate_limit(), err:is_network()... e apibrasil.Error.is(valor) reconhece um erro da SDK.

Modo estrito

client:with_strict() devolve um cliente que levanta a falha em vez de devolvê-la — o par natural do pcall de Lua, útil em scripts e pipelines:

local client = apibrasil.from_env():with_strict()

local envelope = client.whatsapp:send_text({ number = numero, text = "Olá!" })
print(envelope.response)

local ok, err = pcall(function()
  return client.account:balance()
end)

if not ok then
  print(tostring(err), err.kind)
end

Para um trecho isolado, sem trocar de cliente, use apibrasil.unwrap:

local envelope = apibrasil.unwrap(client.whatsapp:send_text(body))

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.

local client = apibrasil.new({
  retry = {
    retries = 3,
    min_delay = 500,
    max_delay = 5000,
    retry_on_statuses = { 429, 503 },
  },
  hooks = {
    request = function(info)
      print("" .. info.method .. " " .. info.url .. " (#" .. info.attempt .. ")")
    end,
    response = function(info)
      print("" .. info.status .. " em " .. info.duration .. "ms")
    end,
    retry = function(info)
      print("retry em " .. info.delay .. "ms: " .. info.reason)
    end,
  },
})

-- ou desativando o retry
apibrasil.new({ retry = require("apibrasil.core.retry").none() })

Os hooks também podem ser um objeto com os métodos on_request, on_response e on_retry. 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:

client.whatsapp:send_text(body, { timeout = 10000 })

Opções por requisição

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

client
  :with_options({
    secret_key = "SUA_SECRET_KEY",
    headers = { ["X-Correlation-Id"] = "abc-123" },
    timeout = 5000,
  })
  .devices:store({ device_name = "meu-bot" })

-- ou só nesta chamada — a tabela de opções é sempre o último argumento
client.whatsapp:send_text(body, { device_token = "outro-device" })

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

Transporte plugável

Lua não tem cliente HTTP na biblioteca padrão, então a SDK detecta o que existe no ambiente, nesta ordem:

  1. resty.http — OpenResty (espera sem bloquear o worker);
  2. http.requestlua-http;
  3. socket.http + ssl.https — LuaSocket/LuaSec, quando há um bundle de CAs;
  4. curl — a CLI da máquina, que usa o repositório de certificados do sistema.

A verificação de certificado é sempre ligada. Como o LuaSec não traz um repositório de CAs, a SDK procura o bundle nos caminhos usuais do sistema e em SSL_CERT_FILE; sem ele, prefere o curl a abrir mão da verificação.

local curl = require("apibrasil.core.transport.curl")
local luasocket = require("apibrasil.core.transport.luasocket")

apibrasil.new({ transport = curl.new({ proxy = "http://proxy:3128" }) })
apibrasil.new({ transport = luasocket.new({ ca_file = "/etc/ssl/cert.pem" }) })

-- qual transporte foi detectado
print(require("apibrasil.core.transport").default_name())

Um transporte é uma função f(request) -> response, err — o atalho para testes sem rede:

local transport = require("apibrasil.core.transport")

local client = apibrasil.new({
  bearer_token = "token-de-teste",
  device_token = "device-de-teste",
  transport = function(request)
    assert(request.url:find("/whatsapp/sendText$"))
    return transport.response_json(200, { error = false, response = { id = "ABC" } })
  end,
})

local envelope = client.whatsapp:send_text({ number = "5511999999999", text = "oi" })
assert(envelope.response.id == "ABC")

Também aceita uma tabela com o campo request — é assim que os transportes que acompanham a SDK são implementados.

JSON: null, listas e objetos

Lua não distingue "tabela vazia" de "lista vazia", e nil não sobrevive dentro de uma tabela. O codec da SDK resolve as três coisas:

local apibrasil = require("apibrasil")

client.whatsapp:send_text({ text = apibrasil.null })   -- {"text":null}
client.ip_whitelist:set({})                            -- {"ip_whitelist":[]}
apibrasil.json.encode(apibrasil.array({}))             -- []
apibrasil.json.encode({})                              -- {}

Tabelas vazias sem marcação viram {} — o corpo das requisições da plataforma é sempre um objeto. Para usar um codec nativo (cjson, dkjson), troque com apibrasil.json.use({ encode = ..., decode = ... }).

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):

lua scripts/codegen.lua
local catalog = require("apibrasil").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_tipo("acerta-essencial")    -- { service = "cpf", fields = { "cpf" } }

Endpoint sem método dedicado?

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

client:request("post", "/consulta/cpf/credits", { cpf = "00000000000" })
client:request("get", "/reports/quick-stats")

-- corpo decodificado sem normalizar em objeto JSON (listas, texto)
local planos = client:execute("get", "/plans")

-- bytes crus (PDF de boleto, imagens)
local pdf = client:download("/inter/boleto/ID/pdf")

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

Configuração avançada

local client = apibrasil.new({
  -- ou APIBRASIL_BEARER_TOKEN
  bearer_token = "...",
  -- ou APIBRASIL_DEVICE_TOKEN
  device_token = "...",
  -- usada em client.devices:store (ou APIBRASIL_SECRET_KEY)
  secret_key = "...",
  -- padrão (ou APIBRASIL_BASE_URL)
  base_url = "https://gateway.apibrasil.io/api/v2",
  timeout = 30000,
  headers = { ["X-Correlation-Id"] = "abc-123" },
  transport = require("apibrasil.core.transport.curl"),
  retry = { retries = 3 },
  hooks = { response = function(info) print(info.status) end },
  options = { timeout = 15000 },
  strict = false,
})

Os mesmos campos podem virar padrão da aplicação inteira:

apibrasil.configure({
  base_url = "https://gateway.apibrasil.io/api/v2",
  timeout = 60000,
})

A precedência é: apibrasil.configure < variáveis de ambiente < apibrasil.new. Credenciais vazias contam como ausentes: informar "" é a forma de desligar o que veio do ambiente.

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 como resultado em vez de nil, erro.

local legacy = require("apibrasil").legacy.new()

local dados = [[{
  "action": "sendText",
  "credentials": {
    "DeviceToken": "SEU_DEVICE_TOKEN",
    "BearerToken": "SEU_BEARER_TOKEN"
  },
  "body": {"number": "5511999999999", "text": "Hello World for Lua"}
}]]

local resposta = legacy:whatsapp(dados)

Além de whatsapp, há sms, cpf, cnpj e request(servico, dados) (qualquer serviço).

Ela 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 métodos dedicados, erros com categoria, retry e hooks.

Desenvolvimento

luarocks install busted
luarocks install luacheck

busted                                        # testes (sem rede)
luacheck src spec examples scripts            # análise estática
lua scripts/check_rockspec.lua apibrasil-0.0.1-1.rockspec

scripts/smoke.lua carrega os 52 módulos e exercita o cliente sem nenhuma dependência — é como o CI cobre LuaJIT e OpenResty, onde o busted não é instalável (o manifesto do luarocks.org estoura o limite de 65536 constantes do bytecode do Lua 5.1). Serve também como teste rápido depois de instalar:

lua scripts/smoke.lua
luajit scripts/smoke.lua

Os exemplos de examples/ rodam direto do repositório:

export APIBRASIL_BEARER_TOKEN=...
export APIBRASIL_DEVICE_TOKEN=...

lua examples/basico.lua
lua examples/whatsapp.lua

Publicando uma versão

A publicação é automática: criar a tag dispara o workflow Release, que empacota o .src.rock, anexa ao release do GitHub e sobe para o luarocks.org.

git tag v0.0.1
git push origin v0.0.1

Requisitos, uma vez só:

  • o secret LUAROCKS_API_KEY no repositório (luarocks.org → SettingsAPI keys). Sem ele o release sai com o artefato anexado, mas o passo de publicação é pulado;
  • version e source.tag do rockspec batendo com a tag — check_rockspec.lua falha o build antes de publicar caso divirjam.

Para publicar à mão, ou conferir o pacote antes da tag:

# a partir do fonte local, sem depender da tag ainda existir
luarocks make --tree=./lua_modules
luarocks pack --tree=./lua_modules apibrasil 0.0.1-1

# depois que a tag existe: gera o .src.rock e publica
luarocks pack apibrasil-0.0.1-1.rockspec
luarocks upload apibrasil-0.0.1-1.rockspec --api-key=SUA_CHAVE

O luarocks pack a partir do rockspec busca o fonte pela source.url, então só funciona depois que a tag está no GitHub.

Licença

MIT — veja LICENSE.

About

A ideia desse SDK é otimizar o tempo de código dos usuários auxiliando na integração com a plataforma

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages