API Mágica: CEP e Pix de graça, sem cartão
Olá meus Unicórnios! 🦄✨
Sabe aquela coisa que você refaz em todo projeto? 😅 Pois é. Consultar CEP para preencher endereço. Gerar um QR Code Pix para receber. Inventar um CPF que passe na validação para testar um formulário. Descobrir de que país veio aquele IP.
Eu já tinha escrito isso tudo tantas vezes, em tantos sistemas diferentes, que resolvi parar de repetir e juntar num lugar só. E aí pensei: se serve para mim, serve para mais gente. Então coloquei no ar, de graça, para qualquer pessoa usar.
Esta é a API Mágica. 🪄
API MágicaAPI REST gratuita para desenvolvedores brasileiros. Cadastre-se, gere a chave e use. Sem cartão de crédito.apimagica.com.brNeste artigo eu passo por tudo o que tem dentro dela, módulo por módulo, e mostro como usar na prática: dois scripts em Node.js puro, curtinhos, que consultam um CEP e geram um QR Code Pix.
🎁 O que tem dentro
A API é toda organizada sob /v1, no endereço https://api.apimagica.com.br/v1. São 43 rotas hoje, divididas nestes módulos. Vou passar por cada um mostrando o que dá para fazer, porque tem coisa ali que nem quem já usa descobriu. 😄
📮 Endereços
O carro-chefe. A base de CEP é própria, com cerca de 1,3 milhão de registros, e tem dois jeitos de consultar:
| Rota | O que faz |
|---|---|
GET /ceps/{cep} | O CEP direto, com ou sem hífen |
GET /ceps | Busca reversa: acha os CEPs de uma rua |
GET /paises | Lista de países, com bandeira |
A busca reversa é a que costuma passar despercebida, e é ótima para autocompletar endereço na tela. Você manda uf e cidade (obrigatórios) e um pedaço do logradouro, que casa por trecho, não precisa ser o nome exato:
curl "https://api.apimagica.com.br/v1/ceps?uf=SP&cidade=São Paulo&logradouro=Paulista&por_pagina=2" \
-H "X-API-Key: mgk_sua_chave_aqui"
E a resposta vem paginada, dizendo quantos achou no total:
{
"itens": [
{
"cep": "03551010",
"uf": "SP",
"cidade": "São Paulo",
"bairro": "Cidade Patriarca",
"logradouro": "Avenida Cabrália Paulista",
"complemento": null
},
{
"cep": "03551000",
"uf": "SP",
"cidade": "São Paulo",
"bairro": "Cidade Patriarca",
"logradouro": "Avenida Cachoeira Paulista",
"complemento": null
}
],
"paginacao": {
"pagina": 1,
"por_pagina": 2,
"total": 171,
"total_paginas": 86
}
}
Repare que "Paulista" trouxe 171 ruas em São Paulo, e nenhuma delas é a Avenida Paulista nas duas primeiras. 😅 É busca por trecho mesmo: "Cabrália Paulista" e "Cachoeira Paulista" contêm a palavra. Use pagina e por_pagina para andar no resultado.
🎲 Geradores
O módulo maior, e o que mais me poupa tempo no dia a dia. Dez rotas:
| Rota | Gera |
|---|---|
POST /geradores/pix | QR Code Pix (BR Code EMV + imagem) |
POST /geradores/qrcodes | QR Code genérico, PNG ou SVG |
POST /geradores/cpf | CPFs válidos e fictícios |
POST /geradores/cnpj | CNPJs válidos e fictícios |
POST /geradores/enderecos | Endereços completos |
POST /geradores/pessoas-fisicas | Pessoa com nome, CPF e nascimento |
POST /geradores/pessoas-juridicas | Empresa com razão social e CNPJ |
POST /geradores/senhas | Senhas com regras que você escolhe |
POST /geradores/uuid | UUIDs |
POST /geradores/lorem-ipsum | Texto de preenchimento |
Quase todos aceitam quantidade (até 100 por chamada), então dá para popular uma base de teste inteira numa requisição só. O de CPF e o de CNPJ ainda aceitam pontuado, que decide se vem com máscara ou limpo:
{
"itens": [
"81.597.147/3013-07",
"68.665.051/6790-12"
]
}
O de pessoa física devolve o perfil coerente, não campos soltos: o CPF passa no dígito verificador e a data de nascimento combina com uma pessoa de verdade.
{
"itens": [
{
"nome": "Théo Albuquerque",
"cpf": "16020535452",
"genero": "masculino",
"data_nascimento": "1953-03-31"
}
]
}
O de senhas é o que tem mais opção, e todas aquelas que a gente sempre precisa e nunca tem: comprimento, maiusculas, minusculas, numeros, simbolos, e três que resolvem dor de suporte:
evitar_ambiguos, que tira0,O,1,l,Ie|, que são os que o cliente lê errado no telefone;simbolo_unico, no máximo um símbolo por senha;iniciar_com_letra, porque alguns sistemas antigos recusam senha que começa com número.
{
"itens": [
"eAc8UN9MQmaHM3hu",
"Q4ydNMgRFP7WDaCN"
]
}
E o QR Code genérico vai muito além de link: o campo tipo aceita WiFi, vCard, e-mail, SMS, telefone, evento e coordenadas, e o conteudo muda de formato conforme o tipo. Um QR Code de WiFi, por exemplo, recebe ssid, senha e seguranca, e quem apontar a câmera entra na rede sem digitar nada. Dá ainda para escolher tamanho, margem, nível de correcao de erro, a cor dos módulos, o fundo e o formato (PNG ou SVG).
🌐 Rede
Duas rotas: POST /geoip, que diz de onde vem um IP (v4 ou v6), e POST /rede/email-temporario, que é a que eu mais uso. Ela responde se o domínio é de e-mail descartável, daqueles que a pessoa usa para fugir da confirmação de cadastro:
{
"email": "[email protected]",
"dominio": "mailinator.com",
"temporario": true,
"motivo": "dominio_na_lista"
}
O motivo vem junto de propósito, para você saber por que aquele e-mail foi marcado, em vez de receber um true sem explicação.
💱 Financeiro
GET /cambio/brl traz a cotação do real contra todas as moedas de uma vez, com o horário da última atualização. E POST /cambio/brl/converter faz a conta para você, sem precisar multiplicar na mão:
{
"de": "BRL",
"para": "USD",
"valor": 100,
"taxa": 0.1951,
"resultado": 19.51,
"atualizado_em": "Tue, 22 Sep 2026 00:00:01 +0000"
}
📄 Documentos
Duas rotas que geram texto jurídico no formato que a LGPD pede: POST /documentos/politica-privacidade e POST /documentos/termos-condicoes. Você manda os dados da empresa e o site, e pode detalhar encarregado, dados_coletados, finalidades, compartilhamento e retencao_meses. O formato escolhe como o documento sai.
🔮 E o lado divertido
Horóscopo do dia dos doze signos, em GET /tarot/horoscopo (todos de uma vez) ou POST /tarot/horoscopo/consultar (um só). Vem com o período do signo, o elemento, e dois tamanhos de texto, curto e longo, para você encaixar no espaço que tiver:
{
"signo": "aquario",
"nome": "Aquário",
"periodo": "21/01 a 19/02",
"elemento": "Ar",
"data": "2026-09-22",
"texto_curto": "O horizonte se amplia. Um estudo, viagem, experiência espiritual ou contato com uma realidade diferente pode mexer com suas convicções..."
}
E a base de Harry Potter, que sozinha tem 22 rotas. 🧙♀️ Personagens, magias, filmes, livros, criaturas, poções, locais, objetos, casas e citações, cada uma com uma rota de lista e uma de detalhe pelo id. Mais duas de brincadeira: GET /harrypotter/citacoes/aleatoria e o POST /harrypotter/sorteio-casa, que é o Chapéu Seletor em forma de endpoint:
{
"casa": {
"id": "corvinal",
"nome": "Corvinal",
"fundador": "Rowena Ravenclaw",
"mascote": "Águia",
"elemento": "ar",
"cores": "azul e bronze",
"chefe": "Filius Flitwick",
"fantasma": "Dama Cinzenta",
"caracteristicas": "Inteligência, criatividade, sabedoria e curiosidade."
}
}
Corvinal. 💙 O chapéu não errou.
🔑 A chave, e o cabeçalho que todo mundo esquece
Todo endpoint de /v1 pede autenticação. Você se cadastra no painel pelo navegador e gera a sua chave por lá. Ela tem esta cara:
mgk_sua_chave_aqui
O mgk_ na frente é só para você bater o olho e saber o que é aquilo quando encontrar a string perdida num arquivo de configuração daqui a seis meses. 😄 Depois do prefixo vêm 43 caracteres.
A chave vai num cabeçalho chamado X-API-Key. Do jeito mais simples possível, com curl:
curl https://api.apimagica.com.br/v1/ceps/01310-100 \
-H "X-API-Key: mgk_sua_chave_aqui"
E se você esquecer o cabeçalho, a API não te deixa no escuro. Ela responde assim:
{
"erro": {
"codigo": "API_KEY_AUSENTE",
"mensagem": "Envie sua chave no cabecalho X-API-Key. Gere uma no painel da API Magica.",
"detalhes": []
}
}
Esse envelope é o mesmo em toda a API: um objeto erro com codigo, mensagem e detalhes. Sempre. Isso foi de propósito, porque o que mais me irrita em API alheia é cada erro vir num formato diferente e o meu catch virar uma árvore de if adivinhando onde está a mensagem. 🙄
O codigo é para o seu código ler e decidir. A mensagem é para o humano ler e entender. E o detalhes é uma lista que vem preenchida quando o erro é de validação, dizendo qual campo está errado.
📮 Consultando um CEP com Node.js
Vamos ao primeiro script de verdade. Ele recebe um CEP na linha de comando e imprime o endereço. É Node.js puro, sem instalar nada: o fetch já vem dentro do Node há tempos.
// Consulta um CEP na API Magica e mostra o endereco.
// Rode assim: node consultar-cep.js 01310-100
const BASE = "https://api.apimagica.com.br/v1";
const CHAVE = process.env.API_MAGICA_CHAVE;
async function consultarCep(cep) {
const resposta = await fetch(BASE + "/ceps/" + cep, {
headers: { "X-API-Key": CHAVE }
});
// A API responde JSON tanto no sucesso quanto no erro.
// Por isso a gente le o corpo ANTES de olhar o status:
// e no corpo que esta a mensagem que explica o que deu errado.
const dados = await resposta.json();
if (resposta.status !== 200) {
throw new Error(dados.erro.codigo + ": " + dados.erro.mensagem);
}
return dados;
}
async function principal() {
const cep = process.argv[2];
if (!cep) {
console.log("Informe o CEP. Exemplo: node consultar-cep.js 01310-100");
return;
}
try {
const endereco = await consultarCep(cep);
console.log(endereco.logradouro);
console.log(endereco.bairro);
console.log(endereco.cidade + " - " + endereco.uf);
} catch (erro) {
console.log("Nao deu para consultar o CEP.");
console.log(erro.message);
}
}
principal();
Repare no comentário do meio, porque ele evita um bug chato: ler o corpo antes de olhar o status. A tentação é escrever if (!resposta.ok) throw new Error("deu erro") e pronto. Só que aí você joga fora justamente a parte útil da resposta, que é a mensagem dizendo qual erro foi. Você fica com "deu erro" na tela e sem ideia do motivo.
A chave sai de uma variável de ambiente, nunca escrita no meio do arquivo. No Linux e no Mac:
export API_MAGICA_CHAVE=mgk_sua_chave_aqui
node consultar-cep.js 01310-100
E o CEP pode ir com ou sem hífen, tanto faz: 01310-100 e 01310100 chegam no mesmo lugar. Rodando com a Avenida Paulista, sai isto na tela:
Avenida Paulista
Bela Vista
São Paulo - SP
E a resposta que veio pela rede é um objeto direto, sem embrulho nenhum em volta:
{
"cep": "01310100",
"uf": "SP",
"cidade": "São Paulo",
"bairro": "Bela Vista",
"logradouro": "Avenida Paulista",
"complemento": "- de 612 a 1510 - lado par"
}
Repare no complemento: quando o CEP cobre uma faixa de números, ele vem preenchido dizendo qual pedaço da rua é aquele. Em CEP de endereço único, vem vazio.
💸 Gerando um QR Code Pix
Agora o endpoint que eu mais uso. Você manda a chave Pix, o nome e a cidade do recebedor, e recebe de volta o BR Code (aquele código EMV enorme que dá para copiar e colar no app do banco) e a imagem PNG do QR Code pronta.
// Gera um QR Code Pix e salva a imagem PNG num arquivo.
// Rode assim: node gerar-pix.js
const fs = require("fs");
const BASE = "https://api.apimagica.com.br/v1";
const CHAVE = process.env.API_MAGICA_CHAVE;
async function gerarPix(dados) {
const resposta = await fetch(BASE + "/geradores/pix", {
method: "POST",
headers: {
"X-API-Key": CHAVE,
"Content-Type": "application/json"
},
body: JSON.stringify(dados)
});
const corpo = await resposta.json();
// Repare no 201, nao 200: o Pix CRIA um recurso.
// Quem testa "resposta.ok" nao cai nessa, mas quem
// escreve "status === 200" quebra sem entender por que.
if (resposta.status !== 201) {
throw new Error(corpo.erro.codigo + ": " + corpo.erro.mensagem);
}
return corpo;
}
function salvarPng(dataUrl, arquivo) {
// O campo vem como "data:image/png;base64,iVBORw0..."
// Para gravar o arquivo a gente precisa jogar fora o prefixo.
const partes = dataUrl.split(",");
const base64 = partes[1];
fs.writeFileSync(arquivo, Buffer.from(base64, "base64"));
}
async function principal() {
try {
const pix = await gerarPix({
chave: "[email protected]",
nome: "Paloma Macetko",
cidade: "Sao Paulo",
valor: 49.9
});
console.log(pix.brcode);
salvarPng(pix.qrcode_base64, "pix.png");
console.log("Imagem salva em pix.png");
} catch (erro) {
console.log("Nao deu para gerar o QR Code.");
console.log(erro.message);
}
}
principal();
Rodando, sai o BR Code na tela e o arquivo no disco:
00020126430014br.gov.bcb.pix0121paloma@exemplo.com.br520400005303986540549.905802BR5914Paloma Macetko6009Sao Paulo62070503***63049D45
Imagem salva em pix.png
Aquele calhamaço é o BR Code de verdade, pronto para o "copia e cola" do app do banco. E o pix.png é um QR Code de 300x300 que abre em qualquer visualizador. 🎉
Dois detalhes que valem o comentário no código:
O status é 201, não 200. É o código certo para "criei um recurso", e a API segue isso à risca. Mas é exatamente o tipo de coisa que derruba quem escreveu === 200 na mão e some com a resposta sem erro nenhum aparecendo. Se preferir não pensar nisso, use resposta.ok, que aceita qualquer 2xx.
A imagem vem como data URL. O campo qrcode_base64 não é base64 puro: ele começa com data:image/png;base64,. Se você jogar a string inteira no Buffer.from(..., "base64") vai gravar um arquivo corrompido. Por isso o split(",") e o descarte da primeira parte. É ótimo para colocar direto num <img src="..."> numa página, e é uma pedra no caminho de quem quer o arquivo.
🔑 A chave Pix aceita qualquer formato
Essa parte eu fiz com carinho, porque é onde eu mais sofri em projetos passados: a chave Pix pode ir com máscara ou sem. A API normaliza antes de montar o BR Code, então você não precisa limpar nada na sua tela.
O que acontece com cada tipo:
| Tipo | Você envia | Vai para o BR Code |
|---|---|---|
| CPF | 123.456.789-00 | 12345678900 |
| CNPJ | 12.345.678/0001-95 | 12345678000195 |
| Celular | (11) 98765-4321 | +5511987654321 |
| Telefone fixo | (11) 3456-7890 | +551134567890 |
[email protected] | igual, sem mexer | |
| Aleatória (UUID) | 71d9c6fc-8b0e-... | igual, sem mexer |
A regra por trás é simples: chave que tem letra (e-mail e UUID) é usada exatamente como veio; chave só de dígitos perde a máscara e, se for telefone, ganha o +55 na frente.
E tem um caso ambíguo que merece ser dito em voz alta: um número de 11 dígitos pode ser um CPF ou um celular. Os dois têm 11 dígitos! Quando o terceiro dígito é 9, a API trata como celular, porque na prática é o que quase sempre é. Se a sua chave é um CPF que por azar cai nesse padrão, mande ele com a máscara de CPF para não dar confusão.
⏱️ O limite de requisições, e como não apanhar dele
Como é tudo de graça, precisa ter um freio, senão o primeiro script mal escrito derruba a festa para todo mundo. O limite é de 5 requisições por minuto por endpoint, contadas por chave, mais um teto geral por conta por hora.
Só que você não precisa adivinhar nada, porque a API conta para você. Toda resposta, inclusive as de sucesso, traz estes cabeçalhos:
| Cabeçalho | O que diz |
|---|---|
RateLimit-Limit | Quantas requisições cabem na janela |
RateLimit-Remaining | Quantas ainda sobram |
RateLimit-Reset | Em quantos segundos a janela zera |
Retry-After | Só na 429: quantos segundos esperar |
Se você estourar, vem um 429 com o envelope de sempre e o Retry-After dizendo o tempo de espera. Tratar isso são poucas linhas:
async function chamarComEspera(url, opcoes) {
const resposta = await fetch(url, opcoes);
if (resposta.status !== 429) {
return resposta;
}
// A API diz em SEGUNDOS quanto esperar. O setTimeout
// quer MILISSEGUNDOS. Esquecer o x1000 faz o retry sair
// quase junto com a primeira chamada, e tomar 429 de novo.
const segundos = Number(resposta.headers.get("retry-after"));
await new Promise(function (pronto) {
setTimeout(pronto, segundos * 1000);
});
return fetch(url, opcoes);
}
O comentário ali é o erro que eu já cometi mais de uma vez na vida: o cabeçalho fala em segundos, o setTimeout fala em milissegundos. Esquecer o * 1000 faz a segunda tentativa sair praticamente junto com a primeira, tomar 429 de novo, e você concluir que o retry "não funciona".
E o melhor conselho é não chegar perto do limite: em vez de disparar dez chamadas em paralelo, use os parâmetros de quantidade. Os geradores aceitam quantidade e as listagens aceitam por_pagina. Pedir 50 CPFs numa chamada só é uma requisição; pedir um de cada vez são cinquenta, e você bate no teto no sexto. 😬
📚 A documentação, e um presente para agentes de IA
A documentação interativa fica em /docs, em Swagger UI. Dá para testar cada endpoint ali mesmo pelo navegador, com a sua chave, sem escrever uma linha. A especificação crua, para quem quer gerar cliente automaticamente, está em /openapi.json.
E tem uma coisa de que eu gostei bastante de fazer: a API serve uma skill pronta para agentes de IA, em /skill.md. É um arquivo de texto, sem autenticação, que descreve a API inteira no formato que o Claude e outros agentes entendem. Você aponta seu agente para lá e ele já sabe usar a API, com os endpoints, os formatos e as armadilhas explicadas.
curl https://api.apimagica.com.br/skill.md
Achei justo: se a API existe para poupar trabalho repetitivo, ela também devia poupar o trabalho de explicar ela mesma. 🤖
Tem ainda um GET /saude, sem autenticação, para você checar se a API está no ar antes de sair procurando problema no seu código:
{
"status": "ok",
"versao": "1.0.0"
}
🗺️ Por onde começar
Se você chegou até aqui e quer experimentar, o caminho é curto:
Um. Entre em www.apimagica.com.br/painel, cadastre-se e gere a sua chave. Não pede cartão.
Dois. Guarde a chave numa variável de ambiente, nunca no meio do código.
Três. Abra o /docs e brinque com os endpoints ali mesmo, antes de escrever qualquer script.
Foi feita por uma desenvolvedora brasileira que cansou de reescrever consulta de CEP, para outras pessoas desenvolvedoras brasileiras que também cansaram. 💜 Se faltar alguma coisa que você usaria, o contato está lá no site, e eu leio.
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟
Perguntas frequentes
A API Mágica é gratuita mesmo?
Como conseguir a chave da API?
www.apimagica.com.br/painel: você se cadastra pelo navegador e gera ela por lá. Ela tem o formato mgk_ seguido de 43 caracteres. Se perder, é só gerar outra no mesmo painel, e a anterior é revogada automaticamente.Dá para achar o CEP a partir do nome da rua?
GET /v1/ceps, passando uf e cidade (obrigatórios) e um pedaço do logradouro. A busca casa por trecho, então "Paulista" traz todas as ruas que contêm a palavra, e o resultado vem paginado com pagina e por_pagina.Qual é o limite de requisições?
RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset, e a 429 traz também o Retry-After em segundos.O QR Code Pix da API avisa quando o cliente paga?
Por que o endpoint do Pix responde 201 e não 200?
if (resposta.status === 200) na mão. Testar resposta.ok, que aceita qualquer 2xx, resolve de uma vez.Leia também
API Mágica: notificações Web Push no navegador com PHP
Web Push com a API Mágica em PHP puro: o navegador se inscreve, o seu site envia a notificação e a API conta o clique. Sem Composer e sem biblioteca.
Resend: enviando e recebendo e-mails com Node.js
Tutorial da Resend com Node.js: criar a chave, enviar com fetch, verificar o domínio e receber e-mails por webhook conferindo a assinatura.
Node.js: testando scripts de scraping no ScrapingCourse
O ScrapingCourse é um site feito para treinar scraping. Cinco desafios dele em Node.js, e a armadilha real que cada um esconde.