Consulta de placa com PHP e Laravel: dados do veículo e tabela FIPE

Capa do artigo Consulta de placa com PHP e Laravel: dados do veículo e tabela FIPE

Uma consulta de placa com PHP cabe em poucas linhas quando a API já entrega o veículo pronto: marca e modelo, cor, município, chassi, situação e, com uma segunda chamada, o código e o valor na tabela FIPE. É o que você precisa para pré-preencher um cadastro de frota, cotar um seguro, avaliar um usado ou validar o carro de um motorista parceiro.

O guia monta essa integração em Laravel, com o Http client nativo do framework: configuração do token, um service com validação de placa, cruzamento com a FIPE, cache de 24 horas, tratamento de erros e um endpoint JSON pronto para a sua tela. Os campos e endpoints foram conferidos na documentação da APIBrasil em doc.apibrasil.io.

Você precisa de PHP 8.2 ou superior, Laravel 10 ou mais novo, uma conta na APIBrasil com Bearer Token e créditos nas APIs de veículos. Para desenvolver sem gastar, o modo de homologação responde de uma base fixa sem cobrança.

O que a consulta de placa retorna

A APIBrasil concentra as consultas veiculares em um único endpoint, POST /consulta/veiculos/credits, e o campo tipo no corpo escolhe a base. Para este guia interessam dois tipos: veiculos-dados-v1, com os dados cadastrais do veículo, e fipe, que cruza a placa com a tabela FIPE e ainda devolve as características básicas.

tipoPrincipais campos em dataQuando usar
veiculos-dados-v1placa, modelo_marca, cor, cidade_placa, estado_placa, chassi, renavam, combustivel, potencia, tipo_veiculo, statusCadastro completo, conferência de renavam e situação
fipedata[] com marca, modelo, anoModelo, anoFabricacao, codigoFipe, valor, mesReferencia, principal; veiculo com cor, municipio, uf, chassi, combustivelPrecificação e características básicas em uma chamada
fipe-chassiresultados[] com os mesmos campos FIPE e o chassiQuando a placa sozinha gera ambiguidade de modelo

Um ponto que surpreende quem vem de outras APIs: a consulta FIPE por placa pode devolver mais de um modelo candidato (por exemplo, a versão 2 portas e a 4 portas do mesmo carro), e o campo principal marca o mais provável. O seu código precisa escolher, e a seção de cruzamento mostra como.

Para uma visão geral dos tipos de consulta e dos casos de uso antes de codar, o Programador Brasil tem um panorama da API Consulta Placa: dados do veículo em segundos, com o fluxo de ponta a ponta. Aqui o foco é a implementação em Laravel.

Setup Laravel + Http client

Para a consulta de placa com PHP, o Http client do Laravel (um wrapper do Guzzle) já resolve autenticação, timeout e retry de forma declarativa. A configuração fica em config/services.php, lendo do .env, para que o token nunca apareça no código nem no repositório.

// config/services.php
'apibrasil' => [
    'token'    => env('APIBRASIL_TOKEN'),
    'base_url' => env('APIBRASIL_BASE_URL', 'https://gateway.apibrasil.io/api/v2'),
    'homolog'  => env('APIBRASIL_HOMOLOG', false),
],
# .env
APIBRASIL_TOKEN=seu_bearer_token
APIBRASIL_HOMOLOG=true   # troque para false em produção

Duas exceções próprias deixam o restante do código mais legível: uma para placa inválida (erro do usuário, HTTP 422 na borda) e outra para saldo insuficiente (erro operacional, que precisa de alerta e não de retry).

// app/Exceptions/PlacaInvalidaException.php
namespace App\Exceptions;

class PlacaInvalidaException extends \InvalidArgumentException {}

// app/Exceptions/SaldoInsuficienteException.php
namespace App\Exceptions;

class SaldoInsuficienteException extends \RuntimeException
{
    protected $message = 'Saldo insuficiente na APIBrasil (HTTP 402)';
}

Service para consulta de placa com PHP

O service concentra três responsabilidades: normalizar a placa (aceitando os formatos antigo e Mercosul), montar a requisição com o tipo certo e interpretar a resposta, incluindo o caso em que o erro vem com HTTP 200 e error: true.

// app/Services/ConsultaPlacaService.php
namespace App\Services;

use App\Exceptions\PlacaInvalidaException;
use App\Exceptions\SaldoInsuficienteException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class ConsultaPlacaService
{
    private PendingRequest $http;

    public function __construct()
    {
        $this->http = Http::baseUrl(config('services.apibrasil.base_url'))
            ->withToken(config('services.apibrasil.token'))
            ->acceptJson()
            ->timeout(20)
            // Repete só falhas de conexão e 5xx; 4xx é erro de entrada, token ou saldo.
            ->retry(3, 500, function ($exception) {
                return $exception instanceof ConnectionException
                    || ($exception instanceof RequestException && $exception->response->serverError());
            }, throw: false);
    }

    /** Aceita ABC1234, ABC-1234 e ABC1D23; devolve só letras e números em maiúsculas. */
    public static function normalizar(string $placa): string
    {
        $limpa = strtoupper(preg_replace('/[^a-z0-9]/i', '', $placa));

        if (!preg_match('/^[A-Z]{3}\d[A-Z0-9]\d{2}$/', $limpa)) {
            throw new PlacaInvalidaException("Placa inválida: {$placa}");
        }

        return $limpa;
    }

    public function dados(string $placa): array
    {
        return $this->consultar('veiculos-dados-v1', self::normalizar($placa));
    }

    public function fipe(string $placa): array
    {
        return $this->consultar('fipe', self::normalizar($placa));
    }

    private function consultar(string $tipo, string $placa): array
    {
        $resposta = $this->http->post('/consulta/veiculos/credits', [
            'tipo'    => $tipo,
            'placa'   => $placa,
            'homolog' => (bool) config('services.apibrasil.homolog'),
        ]);

        if ($resposta->status() === 402) {
            throw new SaldoInsuficienteException();
        }

        $corpo = $resposta->json() ?? [];

        // A API pode responder 200 com error = true: nunca confie só no status HTTP.
        if (!$resposta->successful() || ($corpo['error'] ?? true)) {
            throw new RuntimeException($corpo['message'] ?? "HTTP {$resposta->status()}");
        }

        return $corpo['data'] ?? [];
    }
}

A expressão regular aceita os dois padrões brasileiros: três letras, um dígito, uma letra ou dígito e dois dígitos. Isso cobre ABC1234 e ABC1D23 sem precisar distinguir os formatos, e rejeita qualquer coisa fora disso antes de gastar uma consulta.

Infográfico com os dados que a consulta de placa com PHP entrega via APIBrasil: marca, modelo e ano, cor e município, código FIPE, valor de mercado e situação do veículo

Cruzando com a FIPE

O tipo fipe devolve, em data.data, uma lista de modelos candidatos com valor, código FIPE e mês de referência, e em data.veiculo as características físicas (cor, município, UF, chassi, combustível). Se o seu caso é só precificar e mostrar o básico, uma única chamada resolve. Quando você também precisa de renavam, situação ou do nome exato de modelo e marca, some o tipo veiculos-dados-v1.

// Dentro de ConsultaPlacaService
public function veiculoComFipe(string $placa, bool $incluirCadastro = true): array
{
    $placa = self::normalizar($placa);

    $fipe     = $this->fipe($placa);
    $modelos  = $fipe['data'] ?? [];
    $veiculo  = $fipe['veiculo'] ?? [];
    $cadastro = $incluirCadastro ? $this->dados($placa) : [];

    // Prefere o modelo marcado como principal; senão, o primeiro da lista.
    $principal = collect($modelos)->firstWhere('principal', true) ?? ($modelos[0] ?? null);

    return [
        'placa'        => $placa,
        'modelo_marca' => $cadastro['modelo_marca'] ?? ($principal ? "{$principal['marca']} {$principal['modelo']}" : null),
        'ano_modelo'   => $principal['anoModelo'] ?? null,
        'cor'          => $veiculo['cor'] ?? $cadastro['cor'] ?? null,
        'municipio'    => $veiculo['municipio'] ?? $cadastro['cidade_placa'] ?? null,
        'uf'           => $veiculo['uf'] ?? $cadastro['estado_placa'] ?? null,
        'chassi'       => $veiculo['chassi'] ?? $cadastro['chassi'] ?? null,
        'situacao'     => $cadastro['status'] ?? null,
        'fipe'         => $principal ? [
            'codigo'         => $principal['codigoFipe'],
            'valor'          => $principal['valor'],
            'mes_referencia' => $principal['mesReferencia'],
            'combustivel'    => $principal['combustivel'],
        ] : null,
        'modelos_alternativos' => max(count($modelos) - 1, 0),
    ];
}

Guarde o campo modelos_alternativos: quando ele é maior que zero, vale mostrar na tela as outras versões e deixar o usuário confirmar, principalmente em cotação de seguro, em que a diferença de valor entre versões muda o prêmio. O tipo fipe-chassi existe para esses casos; ele usa o chassi para reduzir a ambiguidade.

Repare que os nomes dos campos FIPE vêm em camelCase (codigoFipe, anoModelo, mesReferencia), enquanto os do cadastro vêm em snake_case (modelo_marca, cidade_placa). O método acima normaliza tudo para snake_case na saída, para o front não precisar conhecer esse detalhe.

Cache, limites e tratamento de erros

Cada consulta é cobrada por chamada e os dados de um veículo mudam pouco de um dia para o outro. Um cache de 24 horas por placa e tipo elimina a maior parte das repetições: o usuário que recarrega a página, o vendedor que abre o mesmo carro três vezes, o job que reprocessa a fila.

// Dentro de ConsultaPlacaService (adicione use Illuminate\Support\Facades\Cache; no topo do arquivo)
public function veiculoComFipeCache(string $placa): array
{
    $placa = self::normalizar($placa);

    return Cache::remember("veiculo:{$placa}", now()->addHours(24), fn () => $this->veiculoComFipe($placa));
}

Para os erros, a regra é separar por quem resolve:

  • Placa inválida (PlacaInvalidaException): erro do usuário. Responda 422 com mensagem clara e não chame a API.
  • HTTP 401 ou 403: token errado ou ausente. Erro de configuração; registre e avise o time.
  • HTTP 402 (SaldoInsuficienteException): saldo zerado. Nenhum retry resolve; dispare um alerta para quem recarrega.
  • HTTP 200 com error: true: placa não encontrada ou erro de negócio. A mensagem está em message e pode ir para o usuário.
  • 5xx e timeout: instabilidade. O retry do Http client já repete até três vezes com intervalo crescente.

Um cuidado de LGPD: dependendo do tipo consultado, a resposta pode trazer dados do proprietário. Só use esses campos se houver base legal para o seu caso, não os grave no cache nem em logs, e devolva ao front apenas o que a tela precisa. O exemplo acima já filtra a saída por esse motivo.

Sobre limites: a APIBrasil informa no catálogo o limite mensal de cada API, e o campo balance de cada resposta mostra o saldo restante. Em lotes grandes (uma frota inteira, por exemplo), processe em fila com poucas consultas simultâneas e monitore o saldo antes de começar.

Exemplo de tela/endpoint

O endpoint abaixo recebe a placa na URL, valida, consulta com cache e devolve JSON. Ele serve tanto para uma tela Blade com fetch quanto para um app mobile. O throttle evita que um formulário em loop consuma seus créditos.

// routes/api.php
use App\Http\Controllers\VeiculoController;

Route::get('/veiculos/{placa}', [VeiculoController::class, 'show'])
    ->middleware('throttle:30,1'); // 30 consultas por minuto por IP
// app/Http/Controllers/VeiculoController.php
namespace App\Http\Controllers;

use App\Exceptions\PlacaInvalidaException;
use App\Exceptions\SaldoInsuficienteException;
use App\Services\ConsultaPlacaService;
use Illuminate\Http\JsonResponse;
use RuntimeException;

class VeiculoController extends Controller
{
    public function show(string $placa, ConsultaPlacaService $service): JsonResponse
    {
        try {
            return response()->json($service->veiculoComFipeCache($placa));
        } catch (PlacaInvalidaException $e) {
            return response()->json(['erro' => $e->getMessage()], 422);
        } catch (SaldoInsuficienteException $e) {
            report($e); // alerta para o time: precisa recarregar
            return response()->json(['erro' => 'Consulta temporariamente indisponível'], 503);
        } catch (RuntimeException $e) {
            return response()->json(['erro' => $e->getMessage()], 502);
        }
    }
}

Teste com curl http://localhost:8000/api/veiculos/ABC1D23 com APIBRASIL_HOMOLOG=true. A resposta de homologação vem com valores fictícios, mas com a estrutura real, o que é suficiente para desenvolver a tela. Na interface, mostre modelo, ano, cor e valor FIPE em destaque, o mês de referência em letra menor e, se modelos_alternativos for maior que zero, um aviso para o usuário confirmar a versão.

Perguntas frequentes

A consulta de placa funciona com placas Mercosul?

Sim. A API aceita os dois formatos, e a função normalizar() do service valida ambos com a mesma expressão regular. Envie só letras e números, sem hífen; o código já remove qualquer caractere extra antes da chamada.

Por que a FIPE retorna mais de um modelo para a mesma placa?

Porque a placa identifica o veículo, mas a tabela FIPE é organizada por versão (motor, número de portas, acabamento), e nem sempre há correspondência única. O campo principal indica o candidato mais provável; para desempatar, use o tipo fipe-chassi ou peça confirmação ao usuário.

Posso usar só a consulta FIPE sem a de dados cadastrais?

Pode. O tipo fipe já devolve cor, município, UF, chassi e combustível em data.veiculo, além dos valores. Chame veiculos-dados-v1 apenas quando precisar de renavam, situação ou da descrição exata de marca e modelo do cadastro.

Como testar sem consumir créditos?

Com homolog: true no corpo, a API responde de uma base fixa sem tarifar e sem contabilizar no limite. Deixe APIBRASIL_HOMOLOG=true no .env de desenvolvimento e nos testes automatizados, e só desligue no ambiente de produção.

Quanto custa cada consulta?

O valor varia por tipo de consulta e está no catálogo em doc.apibrasil.io e no painel; cada resposta também informa o quanto foi descontado no campo tax. Com o cache de 24 horas, o custo efetivo por veículo cai bastante em sistemas em que a mesma placa é consultada várias vezes.

Próximos passos

Com o service, o cache e o endpoint prontos, a consulta de placa com PHP pode crescer para o seu caso: cotação automática, cadastro de frota com conferência de renavam ou avaliação de usados com histórico FIPE. Se outro time usa JavaScript, a versão para consulta de placa e FIPE com Node.js usa o mesmo endpoint, e o guia de variáveis de ambiente e arquivo .env ajuda a manter o token fora do repositório em qualquer linguagem.

Crie sua conta em platform.apibrasil.com.br e rode o exemplo em homologação antes de ligar a base real: a estrutura da resposta é a mesma, e o custo só começa quando você desliga o modo de teste.

Loading

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *