SDK oficial Lua da plataforma APIBrasil — WhatsApp, SMS, consultas de CPF/CNPJ, veículos, CEP, correios, pagamentos PIX/boleto e muito mais.
luarocks install apibrasilRequer 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
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)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.
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.
-- 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 completalocal 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.
client.vehicles:dados({ placa = "ABC1234" })
client.vehicles:fipe({ placa = "ABC1234" })
client.fipe:consultar_marcas({ codigoTabelaReferencia = 300 })client.sms:send({ number = "5511999999999", message = "Olá!" })
client.sms:send_with_credits({ number = "5511999999999", message = "Olá!" })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()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" })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)
endCada 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.
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)
endPara um trecho isolado, sem trocar de cliente, use apibrasil.unwrap:
local envelope = apibrasil.unwrap(client.whatsapp:send_text(body))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 })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").
Lua não tem cliente HTTP na biblioteca padrão, então a SDK detecta o que existe no ambiente, nesta ordem:
resty.http— OpenResty (espera sem bloquear o worker);http.request— lua-http;socket.http+ssl.https— LuaSocket/LuaSec, quando há um bundle de CAs;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.
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 = ... }).
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.lualocal 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" } }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
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.
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.
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.rockspecscripts/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.luaOs exemplos de examples/ rodam direto do repositório:
export APIBRASIL_BEARER_TOKEN=...
export APIBRASIL_DEVICE_TOKEN=...
lua examples/basico.lua
lua examples/whatsapp.luaA 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.1Requisitos, uma vez só:
- o secret
LUAROCKS_API_KEYno repositório (luarocks.org → Settings → API keys). Sem ele o release sai com o artefato anexado, mas o passo de publicação é pulado; versionesource.tagdo rockspec batendo com a tag —check_rockspec.luafalha 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_CHAVEO luarocks pack a partir do rockspec busca o fonte pela source.url, então
só funciona depois que a tag está no GitHub.
MIT — veja LICENSE.