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.
| tipo | Principais campos em data | Quando usar |
|---|---|---|
| veiculos-dados-v1 | placa, modelo_marca, cor, cidade_placa, estado_placa, chassi, renavam, combustivel, potencia, tipo_veiculo, status | Cadastro completo, conferência de renavam e situação |
| fipe | data[] com marca, modelo, anoModelo, anoFabricacao, codigoFipe, valor, mesReferencia, principal; veiculo com cor, municipio, uf, chassi, combustivel | Precificação e características básicas em uma chamada |
| fipe-chassi | resultados[] com os mesmos campos FIPE e o chassi | Quando 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.

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.
![]()









