Documentação para desenvolvedores
API & MCP de orçamentos solares
Gere orçamentos de energia solar com a sua formação de preço — no seu site, no seu CRM ou no seu agente de IA. Uma chamada devolve potência, valor, payback e economia; opcionalmente grava a proposta e entrega um PDF e uma página interativa prontos. Sem SDK obrigatório: é HTTP + JSON.
Início rápido
Base URL: https://app.ionluz.com.br/api/v1. Autentique com a sua chave e faça uma cotação de simulação (não grava nada):
curl -X POST https://app.ionluz.com.br/api/v1/quote \
-H "Authorization: Bearer ilz_sua_chave" \
-H "Content-Type: application/json" \
-d '{ "consumoKwhMes": 650, "uf": "SP", "fases": "bi", "preview": true }'preview: true. Você calcula à vontade (calculadoras, testes, agentes de IA) sem criar leads. Quando quiser registrar a proposta e obter o PDF, use preview: false (o padrão da API é gravar).Autenticação
Toda requisição usa a sua chave de API no cabeçalho Authorization, no formato Bearer. A chave começa com ilz_ e é gerada (e revogada) na página API & Integração do seu painel.
Authorization: Bearer ilz_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- A chave aparece uma única vez na criação — guarde em local seguro (variável de ambiente, cofre de segredos).
- Guardamos apenas o hash (SHA-256): não conseguimos recuperar a chave; se perder, gere outra.
- Envie a chave somente pelo header
Authorization. Nunca em query string (?key=), que vaza em logs e histórico. - Cada integrador tem a sua chave, ligada à sua conta, aos seus produtos, kits e à sua formação de preço.
Limites & headers
As respostas trazem cabeçalhos de rastreio e de limite de uso. Os X-RateLimit-* aparecem nas respostas autenticadas (do limite por chave) — o 401 chave-invalida sai sem eles.
| Header | Significado |
|---|---|
X-Request-Id | Id único da requisição (também vem no corpo como requestId). Cite-o ao pedir suporte. |
X-Api-Version | Versão da API (atualmente 1). |
X-RateLimit-Limit | Máximo de requisições na janela. |
X-RateLimit-Remaining | Quantas ainda restam na janela atual. |
X-RateLimit-Reset | Segundos até a janela reiniciar. |
Retry-After | Presente nos 429 de janela (rate): espere estes segundos antes de repetir. O 429 teto-leads não o envia — esse limite não reseta com o tempo. |
Limites atuais: 60 cotações/min por chave em /v1/quote e 120/min em /v1/coverage. Antes da autenticação há ainda um freio por IP de 300 requisições/min (anti-flood; vale pra REST e MCP). No MCP, o transporte /api/mcp aceita 120 mensagens JSON-RPC/min por chave (initialize, tools/list etc.), mas as ferramentas debitam os mesmos buckets do REST:quote_solar conta nas 60 cotações/min e check_coverage nas 120/min — usar os dois canais não multiplica o limite. Ao estourar no REST, você recebe 429 com Retry-After. No MCP são dois casos: estourar o teto do transporte (ou o freio por IP) devolve HTTP 429 com erro JSON-RPC -32000 e o header Retry-After; estourar o limite de uma ferramenta devolve um resultado de ferramenta com isError e (rate) na mensagem. Há também um teto absoluto de propostas por conta (429 teto-leads) — esse vem sem Retry-After, porque não reseta sozinho: fale com o suporte.
Idempotência
Para requisições que gravam (não-preview), envie um cabeçalho Idempotency-Key único por operação (ex.: o id do pedido no seu sistema). Se a mesma chave chegar de novo com o mesmo corpo — típico de retry por timeout de rede — devolvemos a mesma proposta (com "idempotent": true), sem duplicar o lead.
curl -X POST https://app.ionluz.com.br/api/v1/quote \
-H "Authorization: Bearer ilz_sua_chave" \
-H "Idempotency-Key: pedido-8421" \
-H "Content-Type: application/json" \
-d '{ "consumoKwhMes": 650, "nome": "Ana", "email": "ana@ex.com", "consent": true }'
# Reenviar com a MESMA Idempotency-Key e o MESMO corpo devolve a MESMA proposta
# (campo "idempotent": true), sem criar um lead duplicado.Se a mesma Idempotency-Key for reusada com um corpo diferente, respondemos 422 idempotencia. Se duas requisições com a mesma chave chegarem ao mesmo tempo, a segunda recebe 409 em-processamento (retentável) — garantindo que nunca se crie proposta duplicada. A janela de deduplicação é de 15 minutos.
proposalId devolvido antes de reenviar. No MCP, o mesmo mecanismo existe via argumento idempotencyKey de quote_solar (chaves independentes das do REST).POST/api/v1/quote
Gera um orçamento a partir do consumo. Campos do corpo (JSON):
| Campo | Tipo | Descrição |
|---|---|---|
consumoKwhMes | number | Obrigatório. Consumo médio mensal em kWh. |
uf | string | UF (2 letras). Ajusta a irradiação (HSP). Padrão SP. |
fases | string | mono, bi ou tri (aceitamos também monofasico/bifasico/trifasico, com ou sem acento, qualquer caixa). Padrão mono. Qualquer outro valor → 422 fase-invalida. |
kwp | number | Potência desejada. Se omitido, dimensionamos pelo consumo. |
tarifa | number | Tarifa em R$/kWh. Se omitida, assumimos um padrão (veja assumptions). |
nome, email, telefone, cidade | string | Dados do lead (opcionais; só usados ao gravar). |
telhado | string | Opcional: ceramico · metalico · fibrocimento · laje · solo · carport. Na cotação por combinação automática, aplica o preço global de estrutura configurado na aba API (por telhado × nº de módulos). Omisso usa o telhado padrão da conta. A resposta indica em assumptions.telhado quando entrou na conta. |
preview | boolean | true = só simula (não grava, não gera PDF). Padrão false. |
consent | boolean | Obrigatório ao gravar (preview: false): true confirma que o titular consentiu (LGPD) com o registro dos dados. Registramos como prova a data, a versão do texto da política e metadados técnicos da requisição (IP, user-agent e origem, quando presentes). Sem ele, 400 consentimento-obrigatorio. Em preview, é ignorado. |
Resposta — modo simulação (preview: true)
{
"ok": true,
"orcamento": {
"potenciaKwp": 5.4,
"valorFinal": 28500,
"paybackAnos": 6.3,
"economiaMes1": 336,
"economia25Anos": 216247,
"sePaga": true
},
"assumptions": {
"uf": "SP", "fase": "bi", "hspHoraDia": 5.2,
"tarifaBrlKwh": 0.95, "tarifaAssumida": true,
"kwpAlvo": 5.28, "kwpAlvoAssumido": true
},
"preview": true,
"aviso": "Estimativa sujeita a visita técnica.",
"requestId": "req_9f2c1a7b3d4e5f6a90"
}O bloco assumptions revela o que o motor assumiu quando você omitiu algo — por exemplo,tarifaAssumida: true significa que caímos na tarifa padrão porque você não enviou tarifa. Use isso para deixar claro ao usuário final o que foi estimado.
Deslocamento por km (opcional): se você configurar a sua cidade-sede no perfil, cotações para clientes em outra cidade recebem um acréscimo de R$/km (padrão R$ 5,00, editável) pela distância rodoviária estimada entre as sedes municipais (IBGE, fator rodoviário 1,35). O acréscimo já entra no valorFinal antes do cálculo — payback e economia saem coerentes com o valor cobrado — e a resposta ganha o bloco deslocamento: { "km": 87, "valorBrl": 435 }para o seu controle (ele não aparece no documento do cliente). Mesma cidade não altera o preço. Cidade do cliente não reconhecida: no mesmo estado, sem acréscimo; em outro estado, cobra até o centro do estado.
Resposta — modo gravação (preview: false, exige consent: true)
{
"ok": true,
"orcamento": { "potenciaKwp": 5.4, "valorFinal": 28500, "paybackAnos": 6.3,
"economiaMes1": 336, "economia25Anos": 216247, "sePaga": true },
"assumptions": { "uf": "SP", "fase": "bi", "hspHoraDia": 5.2,
"tarifaBrlKwh": 0.95, "tarifaAssumida": true, "kwpAlvo": 5.28, "kwpAlvoAssumido": true },
"proposalId": "clx8h2k9a0001abcd",
"pdfUrl": "https://app.ionluz.com.br/api/pub/clx8h2k9a0001abcd/pdf?t=...",
"propostaUrl": "https://app.ionluz.com.br/p/clx8h2k9a0001abcd?t=...",
"aviso": "Estimativa sujeita a visita técnica.",
"requestId": "req_9f2c1a7b3d4e5f6a90"
}Ao gravar, você recebe proposalId, o link do pdfUrl (PDF pronto, com a sua marca) e opropostaUrl (página interativa que o cliente abre no celular — antes/depois da conta, gráfico de payback e financiamento). Ambos os links já vêm assinados e são públicos para compartilhar.
Exemplos
// Node.js / navegador (fetch nativo). Use preview:true p/ simular sem gravar.
async function cotar(consumoKwhMes, uf = "SP") {
const res = await fetch("https://app.ionluz.com.br/api/v1/quote", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.IONLUZ_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ consumoKwhMes, uf, preview: true }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error + ": " + data.message);
return data.orcamento;
}import os, requests
def cotar(consumo_kwh_mes, uf="SP", persist=False):
corpo = {"consumoKwhMes": consumo_kwh_mes, "uf": uf, "preview": not persist}
if persist:
corpo["consent"] = True # obrigatório ao gravar: o titular consentiu (LGPD)
r = requests.post(
"https://app.ionluz.com.br/api/v1/quote",
headers={"Authorization": f"Bearer {os.environ['IONLUZ_API_KEY']}"},
json=corpo,
timeout=30,
)
data = r.json()
if not data["ok"]:
raise RuntimeError(f"{data['error']}: {data['message']}")
return data["orcamento"]GET/api/v1/coverage
Descobre quais faixas × fase você atende antes de cotar — assim seu sistema (ou agente) evita o errofaixa-nao-atendida em runtime. Mesma autenticação por chave.
curl https://app.ionluz.com.br/api/v1/coverage -H "Authorization: Bearer ilz_sua_chave"{
"ok": true,
"modo": "custo",
"ativa": true,
"cobertas": 5,
"total": 6,
"tetoKw": 75,
"unidadeFaixas": "kw-saida-inversor",
"fases": [ { "key": "mono", "label": "Monofásico" }, ... ],
"matriz": [
{ "fase": "mono", "deKw": 0, "ateKw": 3, "atende": true },
{ "fase": "mono", "deKw": 3, "ateKw": 10, "atende": true },
{ "fase": "tri", "deKw": 0, "ateKw": 75, "atende": false }
],
"requestId": "req_..."
}As faixas são do integrador (editáveis — a matriz pode mudar) e são denominadas em kW de saída de inversor (CA), na régua 0–tetoKw (75 kW, o limite da microgeração). Cada célula de matriz vai de deKw a ateKw e diz se aquela combinação com fase tem um kit precificável (atende: true). Fase com combinação automática aparece como uma faixa única 0–75.
Erros
Erros têm sempre o mesmo formato: ok: false, um error estável (para o seu código), uma message humana e o requestId.
{
"ok": false,
"error": "faixa-nao-atendida",
"message": "Nenhum kit padrão precificável cobre essa faixa de potência para essa fase. Verifique a cobertura da sua API.",
"docs": "https://app.ionluz.com.br/docs/api",
"requestId": "req_9f2c1a7b3d4e5f6a90",
"kwp": 12.3,
"fase": "tri"
}| error | HTTP | O que fazer |
|---|---|---|
chave-invalida | 401 | Confira o header Authorization: Bearer ilz_...; a chave pode ter sido revogada. |
json | 400 | Corpo não é JSON válido. Verifique o Content-Type: application/json. |
payload-grande | 413 | Corpo passou de 4 KB. |
consumo | 400 | consumoKwhMes ausente ou ≤ 0. |
consumo-fora-de-faixa | 400 | consumoKwhMes acima do plausível (1.000.000 kWh/mês). Confira a unidade — é kWh por mês, não por ano. |
fase-invalida | 422 | fases não reconhecida. Envie mono, bi ou tri (ou monofasico/bifasico/trifasico, com ou sem acento). |
consentimento-obrigatorio | 400 | Gravação (não-preview) sem consent: true. Colete o consentimento do titular e reenvie. |
plano-sem-api | 403 | Chave válida, mas o plano da conta não inclui API — a API pública é do plano Total + API (R$97/mês). Assine em /plano. |
metodo-nao-permitido | 405 | Método HTTP errado (ex.: GET em /v1/quote, POST em /v1/coverage). A resposta indica o método certo. |
faixa-nao-atendida | 422 | Não há kit para essa faixa/fase. Consulte /v1/coverage. |
sem-preco-configurado | 422 | Sua formação de preço não gerou valor. Revise Preços no painel. |
idempotencia | 422 | Idempotency-Key reutilizada com corpo diferente. Use uma chave nova. |
em-processamento | 409 | Outra requisição com a mesma Idempotency-Key ainda está processando. Repita em instantes. |
rate | 429 | Muitas requisições. Espere o Retry-After. |
teto-leads | 429 | Limite absoluto de propostas da conta atingido. Vem sem Retry-After — não adianta repetir; fale com o suporte. |
interno | 500 | Erro nosso. Repita; se persistir, contate o suporte com o requestId. |
MCP — para agentes de IA
O Model Context Protocol deixa um agente de IA (Claude, ChatGPT via conectores, ou o seu próprio) usar a ionluz como ferramenta. Aponte o cliente MCP para https://app.ionluz.com.br/api/mcp com a sua chave e o agente ganha duas ferramentas prontas — sem você escrever integração alguma.
Configuração (clientes com MCP remoto/HTTP)
{
"mcpServers": {
"ionluz": {
"url": "https://app.ionluz.com.br/api/mcp",
"headers": { "Authorization": "Bearer ilz_sua_chave" }
}
}
}Ferramentas expostas
// tools/list devolve:
[
{ "name": "quote_solar", "description": "Gera um orçamento de energia solar a partir do consumo..." },
{ "name": "check_coverage","description": "Lista quais faixas (kW de saída de inversor) × fase o integrador atende..." }
]quote_solar— cota a partir do consumo. Por padrão simula; passepersist: truepara gravar a proposta e receber os links. Ao gravar,consent: trueé obrigatório (consentimento LGPD do titular — sem ele a ferramenta devolve o erroconsentimento-obrigatorio).idempotencyKey(argumento opcional dequote_solar, só compersist) — mesma garantia daIdempotency-Keydo REST: repetir a mesma chave com os mesmos argumentos devolve a mesma proposta(idempotent: truenostructuredContent), sem duplicar o lead; duas chamadas simultâneas nunca criam duas. As chaves são independentes das do REST. SemidempotencyKey, não há deduplicação — retries podem duplicar.check_coverage— lista as faixas atendidas, para o agente cotar só o que existe.
O transporte é Streamable HTTP (JSON-RPC 2.0). A negociação (initialize), a listagem (tools/list) e a execução (tools/call) seguem o protocolo padrão — qualquer cliente MCP compatível conecta. Falhas internas chegam como erro JSON-RPC estruturado (-32603, erro-interno) ou como resultado de ferramenta com isError — nunca um 500 sem envelope.
/v1/quote devolveria — e consome os mesmos limites (ver Limites & headers).Calculadora no seu site
O modo preview foi feito para calculadoras públicas: cotar sem poluir seus leads. Mas não coloque a chave no navegador — qualquer visitante a leria. O padrão seguro é um proxy no seu backend que injeta a chave:
<!-- Calculadora no seu site — NUNCA exponha a chave no navegador.
Chame o seu backend, e o SEU backend chama a ionluz com a chave. -->
<form id="calc">
<input name="consumo" type="number" placeholder="Consumo mensal (kWh)" required />
<select name="uf"><option>SP</option><option>MG</option><option>RJ</option></select>
<button>Simular economia</button>
</form>
<p id="resultado"></p>
<script>
document.getElementById("calc").onsubmit = async (e) => {
e.preventDefault();
const f = e.target;
// /minha-cotacao é uma rota SUA que injeta a chave e repassa p/ a ionluz (preview)
const r = await fetch("/minha-cotacao", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ consumoKwhMes: +f.consumo.value, uf: f.uf.value }),
});
const { orcamento } = await r.json();
document.getElementById("resultado").textContent =
"Sistema de " + orcamento.potenciaKwp + " kWp — economia de R$ " +
orcamento.economiaMes1 + "/mês.";
};
</script>O seu endpoint /minha-cotacao recebe o consumo, chama https://app.ionluz.com.br/api/v1/quote com a chave (server-side, preview: true) e repassa só o orcamento ao navegador.
Boas práticas
- Segurança: chave só no servidor. Rotacione ao suspeitar de vazamento (revogar + gerar nova é instantâneo).
- Simule antes de gravar:
preview: trueem calculadoras e testes; grave só quando virar lead de verdade. - Trate os
assumptions: mostre ao usuário quando a tarifa/potência foi estimada, para transparência. - Consentimento primeiro: ao gravar, colete o aceite do titular e envie
consent: true— é obrigatório e fica registrado como prova (LGPD). - Retries com idempotência: em POSTs que gravam, sempre envie
Idempotency-Key(no MCP, o argumentoidempotencyKey). - Respeite o rate-limit: use os headers
X-RateLimit-*e recue no429. - Cobertura primeiro: consulte
/v1/coverage(oucheck_coverage) para não cotar faixas que você não atende. - Estimativas: todo valor é sujeito a visita técnica — deixe isso visível ao cliente final.
requestId ao contatar o suporte.