Pular para o conteúdo
API

API Mágica: CEP e Pix de graça, sem cartão

Atualizado em
A home da API Mágica, com o título "A API que resolve o chato do dev brasileiro" e um painel mostrando a consulta de CEP e a resposta em JSON

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

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

RotaO que faz
GET /ceps/{cep}O CEP direto, com ou sem hífen
GET /cepsBusca reversa: acha os CEPs de uma rua
GET /paisesLista 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:

RotaGera
POST /geradores/pixQR Code Pix (BR Code EMV + imagem)
POST /geradores/qrcodesQR Code genérico, PNG ou SVG
POST /geradores/cpfCPFs válidos e fictícios
POST /geradores/cnpjCNPJs válidos e fictícios
POST /geradores/enderecosEndereços completos
POST /geradores/pessoas-fisicasPessoa com nome, CPF e nascimento
POST /geradores/pessoas-juridicasEmpresa com razão social e CNPJ
POST /geradores/senhasSenhas com regras que você escolhe
POST /geradores/uuidUUIDs
POST /geradores/lorem-ipsumTexto 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 tira 0, O, 1, l, I e |, 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:

TipoVocê enviaVai para o BR Code
CPF123.456.789-0012345678900
CNPJ12.345.678/0001-9512345678000195
Celular(11) 98765-4321+5511987654321
Telefone fixo(11) 3456-7890+551134567890
E-mail[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çalhoO que diz
RateLimit-LimitQuantas requisições cabem na janela
RateLimit-RemainingQuantas ainda sobram
RateLimit-ResetEm quantos segundos a janela zera
Retry-AfterSó 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?
É. Todos os recursos são gratuitos, não existe plano pago nem endpoint reservado, e não é pedido cartão de crédito em momento nenhum. Você se cadastra no painel, gera a chave e começa a usar.
Como conseguir a chave da API?
A chave sai do painel, em 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?
Dá, com 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?
São 5 requisições por minuto por endpoint, contadas por chave, mais um teto geral por conta por hora. O mais restritivo dos dois é quem manda nos cabeçalhos. Toda resposta traz 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?
Não. Ele gera um Pix estático: o payload BR Code e a imagem PNG, o mesmo tipo de QR Code que se imprime numa etiqueta de balcão. Não há confirmação de pagamento nem webhook. Para saber que caiu o dinheiro você precisa da API do seu banco ou do seu PSP.
Por que o endpoint do Pix responde 201 e não 200?
Porque ele cria um recurso, e 201 é o código certo para isso. É uma pegadinha boba que derruba quem escreve if (resposta.status === 200) na mão. Testar resposta.ok, que aceita qualquer 2xx, resolve de uma vez.

Leia também