Primeiros passos
- Crie a conta e, no painel, abra Chaves de API para criar uma chave com os escopos de que precisa.
- Mande a chave no cabeçalho
Authorization: Bearer xqp_…em toda requisição. - Use o endereço base
https://app.xeqplaca.com/api/public/v1.
Campos e eventos em inglês; mensagens de erro em português. Datas sempre em ISO 8601 UTC, com Z (2026-11-07T12:00:00Z): converta para o seu fuso na hora de exibir.
curl "https://app.xeqplaca.com/api/public/v1/recognitions?plate=ABC1D23&limit=20" \
-H "Authorization: Bearer xqp_SUA_CHAVE"Autenticação e escopos
A chave (xqp_ seguido de 40 caracteres) é mostrada uma vez, quando é criada; guarde-a num cofre ou variável de
ambiente, nunca no código do navegador. Perdeu? Revogue no painel e crie outra. Cada chave só faz o que os escopos dela permitem:
| Escopo | Rotas |
|---|---|
recognitions:read | GET /recognitions, GET /recognitions/{id} |
recognitions:write | POST /recognize (gasta 1 crédito por foto) |
cameras:read | GET /cameras, GET /cameras/{id} |
alerts:read | GET /alerts |
alerts:write | POST /alerts, PUT /alerts/{id}, DELETE /alerts/{id} |
webhooks:read | GET /webhooks |
webhooks:write | POST /webhooks, DELETE /webhooks/{id}, POST /webhooks/{id}/test |
qualquer chave válida | GET /credits |
Toda chave enxerga só os dados da própria conta.
Erros
Erros voltam com o código HTTP abaixo e a mensagem em português no corpo.
| Código | Quando |
|---|---|
| 400 | Pedido inválido: parâmetro fora do formato, foto que não é JPEG/PNG ou acima de 10 MB. Não gasta crédito. |
| 401 | Chave ausente, inválida ou revogada. |
| 402 | Sem créditos para reconhecer a foto. Compre um pacote ou uma recarga no painel. |
| 403 | A chave não tem o escopo que a rota pede. |
| 404 | O recurso não existe ou é de outra conta (as duas situações respondem igual). |
| 409 | Conflito com o estado atual (por exemplo, cadastro repetido). |
| 429 | Limite por minuto atingido. Espere os segundos do cabeçalho Retry-After e tente de novo. |
| 503 | Serviço de leitura indisponível no momento. Não gasta crédito; tente de novo em instantes. |
Em 429 e 503, repita depois da espera:
import time
import requests
def pedir(metodo, url, tentativas=5, **kwargs):
"""Repete a requisição em 429 e 503, esperando o Retry-After."""
for _ in range(tentativas):
resposta = requests.request(metodo, url, timeout=30, **kwargs)
if resposta.status_code not in (429, 503):
return resposta
time.sleep(int(resposta.headers.get("Retry-After", "5")))
return respostaPaginação
As listas usam cursor: peça com limit (até 100) e, enquanto a resposta trouxer next_cursor,
mande-o em cursor para a próxima página. Em GET /recognitions os filtros são plate (aceita *, como ABC*), from, to, camera_id e origin.
import requests
BASE = "https://app.xeqplaca.com/api/public/v1"
CABECALHOS = {"Authorization": "Bearer xqp_SUA_CHAVE"}
params = {"from": "2026-11-01T00:00:00Z", "to": "2026-11-02T00:00:00Z", "limit": 100}
while True:
resposta = requests.get(f"{BASE}/recognitions", params=params, headers=CABECALHOS, timeout=30)
resposta.raise_for_status()
pagina = resposta.json()
for item in pagina["data"]:
print(item)
if not pagina.get("next_cursor"):
break
params["cursor"] = pagina["next_cursor"]Reconhecer foto
POST /recognize (escopo recognitions:write) recebe uma foto JPEG ou PNG de até 10 MB, em multipart/form-data no campo image ou como corpo binário (application/octet-stream),
e devolve até 8 placas, cada uma com a confiança, a confiança por caractere, os 4 cantos em pixels, o padrão
(Mercosul ou antigo) e o veículo. Gasta 1 crédito por foto processada; foto recusada não gasta.
O parâmetro mode diz que tipo de foto é:
scene: quadro de câmera, com a placa pequena na imagem.closeup: foto de perto, como a do celular na traseira do carro.auto(padrão): tentascenee, sem placa com boa confiança,closeup. Continua sendo 1 crédito.
A foto não é guardada, a menos que a conta ligue "guardar fotos do Snapshot por 48 h" nas configurações. Sem placa, plates vem vazio e diagnostic diz em que etapa a leitura parou.
curl -X POST "https://app.xeqplaca.com/api/public/v1/recognize?mode=auto" \
-H "Authorization: Bearer xqp_SUA_CHAVE" \
-F "[email protected]"{
"plates": [
{
"plate": "ABC1D23",
"confidence": 0.97,
"char_confidences": [0.99, 0.98, 0.99, 0.97, 0.95, 0.98, 0.99],
"corners": [[412, 388], [548, 390], [547, 431], [411, 429]],
"pattern": "mercosul",
"vehicle": { "type": "carro", "year": 2021 }
}
],
"processing_time_ms": 85,
"credits_remaining": 41,
"diagnostic": null
}Webhooks
Cadastre uma URL https no painel (Webhooks) ou pela API e escolha os eventos. Cada evento chega num POST JSON:
| Evento | Quando |
|---|---|
recognition.created | Uma placa foi lida por uma câmera ou por foto. |
alert.triggered | Uma passagem bateu numa regra de alerta. |
camera.offline | A câmera parou de enviar imagens. |
camera.online | A câmera voltou a enviar imagens. |
POST https://seu-sistema.com.br/webhook
Content-Type: application/json
X-XeqPlaca-Assinatura: t=1762516800,v1=5f2b…(64 caracteres hexadecimais)
{
"id": "…",
"event": "recognition.created",
"created_at": "2026-11-07T12:00:00Z",
"data": { … }
}Responda com um código 2xx em até 5 segundos. Sem resposta ou com erro, o envio é tentado de novo depois de 1, 5 e 30 segundos;
use o id do evento para não processar o mesmo duas vezes.
Verificar a assinatura
Confira que o evento veio daqui antes de usá-lo. O cabeçalho X-XeqPlaca-Assinatura traz t=<epoch>,v1=<assinatura>, em que a assinatura é o HMAC-SHA256, em hexadecimal, de <t>.<corpo> com o segredo do webhook (mostrado uma vez, quando ele é criado). Calcule sobre o corpo cru, como chegou, compare em tempo constante e recuse t muito antigo:
import hashlib
import hmac
import time
TOLERANCIA_S = 300 # recusa envio com mais de 5 minutos (reaproveitamento de um envio antigo)
def assinatura_valida(segredo: str, cabecalho: str, corpo: bytes) -> bool:
"""corpo = os bytes da requisição como chegaram, ANTES de interpretar o JSON."""
partes = dict(p.strip().split("=", 1) for p in (cabecalho or "").split(",") if "=" in p)
t, v1 = partes.get("t", ""), partes.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > TOLERANCIA_S:
return False
esperado = hmac.new(segredo.encode(), t.encode() + b"." + corpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, v1) # comparação em tempo constante
# Exemplo com Flask:
# @app.post("/webhook")
# def receber():
# if not assinatura_valida(SEGREDO, request.headers.get("X-XeqPlaca-Assinatura"), request.get_data()):
# return "", 401
# evento = request.get_json()
# ...
# return "", 200Limites e prazos
- 60 requisições por minuto por chave; 30 fotos por minuto em
POST /recognize. Acima disso, 429 comRetry-After. - Foto: JPEG ou PNG de até 10 MB; até 8 placas por foto.
- Endereços de imagem (
image_url,plate_image_urlno detalhe do reconhecimento) valem 1 hora; peça o detalhe de novo para ter um endereço novo. - Imagens das câmeras ficam 48 horas; os registros das passagens, 90 dias (ou o prazo do seu plano). Veja a política de privacidade.
MCP: conectar à sua IA
O servidor MCP em https://app.xeqplaca.com/mcp deixa o seu assistente de IA (Claude, Cursor ou um agente próprio)
consultar a conta em linguagem natural, como "onde a placa ABC1D23 passou ontem?". Ele usa a mesma chave de API, com os mesmos
escopos e limites: a IA só vê as ferramentas que os escopos da chave permitem. No painel, Conectar à sua IA gera a chave e mostra a configuração pronta para colar.
| Ferramenta | Escopo | O que faz |
|---|---|---|
buscar_passagens | recognitions:read | Passagens por placa (aceita curinga), período, câmera e veículo. |
ultima_passagem | recognitions:read | Onde e quando uma placa foi vista por último. |
contar_passagens | recognitions:read | Totais por câmera, por hora ou por tipo de veículo num período. |
listar_cameras | cameras:read | Câmeras com situação, última captura e saúde. |
listar_alertas | alerts:read | Regras de alerta da conta. |
criar_alerta | alerts:write | Cria uma regra de alerta por placa. |
reconhecer_imagem | recognitions:write | Lê as placas de uma imagem (URL ou base64); gasta 1 crédito. |
Horários saem em UTC com o fuso da conta ao lado, para a IA responder na hora local.
claude mcp add --transport http xeqplaca https://app.xeqplaca.com/mcp --header "Authorization: Bearer xqp_SUA_CHAVE"