---
title: "Documentación — API REST de multas vehiculares | Multas MX"
description: "Guía completa de la API de multas: autenticación con API key, POST /lookup, webhooks firmados con HMAC, estados disponibles, rate limits y códigos de error."
url: https://multasmx.com/docs/
---

API Reference

# Documentación.

Todo lo que necesitas para consumir la API: endpoints, formato del webhook y códigos de error.

## Quickstart

1.  1

    Crea tu cuenta en [dashboard.multasmx.com/register](https://dashboard.multasmx.com/register)

2.  2

    Copia tu API key desde el dashboard principal.

3.  3

    Llama a los endpoints con el header `x-api-key`.

4.  4

    Prueba gratis con la placa `PRUEBA-ADEUDOS` antes de comprar créditos.

## Base URL

```
https://api.multasmx.com
```

La especificación completa está en [openapi.json](https://multasmx.com/openapi.json) (OpenAPI 3.1, con un `operationId` por endpoint y los webhooks), y el resumen para agentes en [llms.txt](https://multasmx.com/llms.txt). Esta página también se puede pedir en Markdown: [docs.md](https://multasmx.com/docs.md), o con el header `Accept: text/markdown`.

## Autenticación

Todas las rutas requieren el header `x-api-key` con tu API key del dashboard.

```
x-api-key: sk_live_22f5757d-26c9-4f63-...
```

## Pruebas sin costo

Para integrar sin gastar créditos, usa una **placa de prueba** con tu API key de siempre. La consulta es gratis y devuelve datos ficticios con la misma forma que una consulta real: el `201` con su `requestId`, los webhooks firmados y `GET /lookup/:requestId`. Sirve con cualquier estado o servicio de `GET /states`.

| plate · niv (REPUVE) | Qué devuelve |
| --- | --- |
| PRUEBA-ADEUDOS PRUEBA0000000002 | Con hallazgos Multas y adeudos por pagar. En REPUVE, un reporte de robo. |
| PRUEBA-LIMPIA PRUEBA0000000001 | Todo en orden Sin multas, adeudos ni reportes de robo. |
| PRUEBA-NOREG PRUEBA0000000003 | No registrada El servicio no tiene la placa en su padrón. |
| PRUEBA-FALLA PRUEBA0000000004 | Sin respuesta El servicio no contestó. Así llega un resultado pendiente. |

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-ADEUDOS","states":["cdmx","edomex"]}'
```

_201 Created_

```json
{
  "requestId": "9ce8ceb8-...",
  "status": "PROCESSING",
  "plate": "PRUEBA-ADEUDOS",
  "creditsCharged": 0,
  "creditsBreakdown": { "cdmx": 0, "edomex": 0 },
  "states": [ … ],
  "skipped": [],
  "sandbox": true
}
```

Cómo se comporta

-   `sandbox: true` en la respuesta de `POST /lookup`, en `GET /lookup/:requestId` y en la raíz de cada webhook. Las consultas reales no lo traen
-   `creditsCharged: 0`: no cambia tu saldo, y funciona aunque tu saldo esté en 0
-   Los webhooks `state_completed` y `completed` llegan a tus URLs con la misma firma `X-Multas-Signature` que los reales: sirven para probar tu endpoint
-   El resultado se puede consultar con `GET /lookup/:requestId` durante 7 días. No aparece en el historial del dashboard
-   Un estado que en ese momento no está dando servicio se salta en `skipped[]`, igual que en una consulta real
-   Hasta 200 consultas de prueba por día por cuenta (`429 sandbox_limit_exceeded`). Cuentan para tu límite por minuto
-   Toda placa que empiece con `PRUEBA` es de prueba: si no está en la tabla, la respuesta es `400 unknown_test_plate`, y combinar una de prueba con una real da `400 test_identifier_mismatch`

| Servicio | PRUEBA-LIMPIA Todo en orden | PRUEBA-ADEUDOS Con hallazgos | PRUEBA-NOREG No registrada |
| --- | --- | --- | --- |
| Multas | Sin infracciones | Multas o adeudos pendientes | Sin infracciones. En sinaloa, extra.plateRegistryStatus; en cdmx, extra.circulationCardStatus "Placa no localizada" |
| Tenencia (EdoMex) | Sin adeudos | Con adeudo | found: false |
| Tenencia (Chihuahua, Guanajuato, Aguascalientes) | Placa no registrada | Adeudo en debts\[\] | Placa no registrada |
| Vigencia de placa | VIGENTE | VENCIDA | found: false |
| Verificación (EdoMex) | Verificó en el semestre | found: false | found: false |
| Verificación (Jalisco) | Aprobada y vigente | Aprobada, ya vencida | found: false |
| REPUVE | Sin reportes | Con reporte de robo | No inscrito |

Con `PRUEBA-FALLA`, cada estado termina en `QUEUED_RETRY` con `errorCode: "scrape_failed"` y la consulta en `PARTIAL`. En pruebas no se reintenta.

**Construye sobre los campos base.** En varios estados de multas, cada infracción de prueba trae sólo `folio`, `date`, `amount`, `unit` y `status`. Una respuesta real puede traer además descripción, artículo o datos del vehículo cuando el portal del estado los publica: trátalos como opcionales (ver [Resultado de multas](https://multasmx.com/docs/#multas)).

**Si reenvías placas que escriben tus usuarios**, una que empiece con `PRUEBA` devuelve datos de prueba: revisa el campo `sandbox` antes de mostrarlos.

## Créditos y costos

Cada consulta descuenta créditos según su tipo. Un `POST /lookup` puede combinar varios estados y servicios: el cargo es la suma de sus costos, y se aplica **antes** de ejecutar la consulta. Las consultas con [placas de prueba](https://multasmx.com/docs/#pruebas) no cuestan.

| Consulta | stateCode | Créditos |
| --- | --- | --- |
| Multa estatal | edomex, cdmx, guadalajara, … | 1 |
| Tenencia | tenencia-edomex, tenencia-chihuahua, tenencia-guanajuato, tenencia-aguascalientes | 1 |
| Vigencia de placa | vigencia-edomex | 1 |
| Verificación vehicular | verificacion-edomex, verificacion-jalisco | 1 |
| REPUVE (reporte de robo) | repuve | 3 |

El costo vigente siempre se puede consultar en vivo: `GET /billing/pricing` lo devuelve en `creditCosts`, y `GET /states` incluye el campo `creditCost` en cada estado.

### Caducidad

Los créditos **vencen 12 meses después de la compra**. El consumo es **FIFO por caducidad**: se gastan primero los que están más próximos a vencer, de modo que ningún lote se pierda mientras haya uso.

_GET /billing/balance_

```json
{
  "credits": 147,
  "nextExpiry": {
    "credits": 47,
    "expiresAt": "2027-03-14T00:00:00.000Z"
  },
  "expiryMonths": 12,
  "lots": [ … ]
}
```

`nextExpiry` es el lote que vence antes, que es el próximo en consumirse. Es `null` si no tienes saldo. Cada respuesta de `POST /lookup` incluye también el saldo restante en `balance`.

### Saldo insuficiente

Si el saldo no cubre el costo del request, se rechaza **completo** con `402` y no se ejecuta ninguna consulta. La respuesta dice cuántos créditos faltaban:

_402 Payment Required_

```json
{
  "error": "insufficient_credits",
  "message": "Esta consulta cuesta 4 créditos y tu saldo disponible es de 2. Te faltan 2.",
  "required": 4,
  "available": 2,
  "missing": 2,
  "breakdown": { "edomex": 1, "repuve": 3 }
}
```

## POST /lookup

Solicita la consulta de una placa en uno o más estados. La respuesta es inmediata con un `requestId`. El resultado completo llega vía webhook.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"ABC1234","states":["cdmx","edomex"]}'
```

_201 Created_

```json
{
  "requestId": "05f4c751-...",
  "status": "PROCESSING",
  "creditsCharged": 2,
  "states": [
    { "stateCode": "cdmx", "status": "PENDING" },
    { "stateCode": "edomex", "status": "PENDING" }
  ],
  "skipped": []
}
```

Body

-   `plate` — alfanumérico, 1–16 caracteres
-   `states` — array de `stateCode`s únicos, al menos uno, de los que lista `GET /states`

Cada estado se cobra según su [costo en créditos](https://multasmx.com/docs/#creditos): 1 crédito los estados de multas, la tenencia, la vigencia y la verificación; 3 créditos REPUVE. El total del request es la suma, y viene desglosado en `creditsBreakdown`. Estados en `DOWN` / `MAINTENANCE` aparecen en `skipped[]` y no se cobran.

## GET /lookup/:requestId

Consulta el estado actual de un request. Útil para poll si no usas webhooks.

_200 OK_

```json
{
  "requestId": "05f4c751-...",
  "plate": "ABC1234",
  "status": "COMPLETED",
  "creditsCharged": 2,
  "webhookSent": true,
  "results": [
    { "state": "edomex", "status": "SUCCESS",
      "hasInfractions": true, "totalDebt": "905",
      "consultedAt": "2026-04-22T16:30:12.000Z" }
  ]
}
```

`status` puede ser `PROCESSING`, `COMPLETED`, `PARTIAL` o `QUEUED_RETRY`.

## GET /states

Lista de los estados y servicios disponibles ahora: los que en este momento no están dando servicio no aparecen.

```json
{
  "states": [
    { "stateCode": "campeche", "stateName": "Campeche",
      "type": "infracciones", "creditCost": 1 },
    { "stateCode": "repuve", "stateName": "REPUVE",
      "type": "repuve", "creditCost": 3 },
    { "stateCode": "tenencia-edomex", "stateName": "Tenencia — Estado de México",
      "type": "tenencia", "creditCost": 1 },
    …
  ],
  "count": 22
}
```

Campos de cada estado

-   `stateCode` — el código que va en `states[]` de `POST /lookup`
-   `stateName` — nombre legible del estado o servicio
-   `type` — tipo de consulta: `tenencia`, `vigencia`, `verificacion` o `repuve`; en los estados de multas varía según el estado (p. ej. `infracciones`)
-   `creditCost` — créditos que cuesta consultarlo (ver [Créditos y costos](https://multasmx.com/docs/#creditos))

`count` es el número de entradas de `states`. La respuesta puede traer campos adicionales que no forman parte del contrato: no dependas de ellos, pueden cambiar o desaparecer sin aviso.

## Resultado de multas

Cada estado de multas entrega en `data.infractions[]` una entrada por infracción, pendiente o pagada. Los campos opcionales sólo aparecen cuando el portal del estado publica ese dato.

_results[0].data.infractions[0]_

```json
{
  "folio": "PRUEBA/00000001/2026",
  "date": "2026-08-22T15:42:53Z",
  "amount": 905, "unit": "MXN",
  "status": "pending",
  "type": "Mexibús — Invasión al carril confinado",
  "article": "Art. 90 fracc. VII; Art. 90 fracc. VII y 118 fracc. IV",
  "description": "Por no sujetarse a los límites físicos señalados para los carriles… Por invadir el carril confinado…",
  "articles": [
    { "article": "Art. 90 fracc. VII", "description": "Por no sujetarse a los límites físicos…" },
    { "article": "Art. 90 fracc. VII y 118 fracc. IV", "description": "Por invadir el carril confinado…" }
  ],
  "regulation": "ESTATAL"
}
```

Campos de cada infracción

-   `folio` · `date` (ISO 8601) · `amount` \+ `unit` — importe y su unidad (`"MXN"` por defecto; CDMX usa `"UC"`)
-   `status` — `"pending"` o `"paid"`. Las pagadas también se entregan: es el historial completo
-   `description` — el porqué de la infracción: texto del reglamento, tal como lo publica el portal
-   `article` — artículo y fracción. Si la infracción cita varios, vienen unidos con `"; "`
-   `articles[]` — los mismos artículos por separado, cada uno con su `description` (cuando hay más de uno)
-   `type` — clasificación del portal (programa y conducta) · `regulation` — ámbito del reglamento · `paymentDate` — fecha de pago, si está pagada

En `edomex`, `description`, `article` y `articles[]` se entregan para las **tres infracciones más recientes** de la placa; las anteriores llevan folio, fecha, importe y `type`. Un campo ausente significa que el dato no está disponible, nunca que esté vacío.

## Datos extra de CDMX y Sinaloa

Las consultas de multas de `cdmx` y `sinaloa` entregan, además, datos del vehículo que publica el portal del estado. Van en `data.extra`, sin costo adicional, y cada clave aparece sólo cuando el portal publica ese dato.

_cdmx · results[0].data.extra_

```json
{
  "fotocivicaPoints": 10,
  "circulationCardStatus": "Vigente",
  "circulationCardValidUntil": "2027-03-31"
}
```

_sinaloa · results[0].data.extra_

```json
{
  "plateRegistryStatus": "deregistered",
  "plateRegistryMessage": "Su vehículo presenta un estatus de baja."
}
```

Ciudad de México · cdmx

-   `fotocivicaPoints` — puntos de fotocívicas de la placa, de 0 a 10
-   `circulationCardStatus` — estatus de la tarjeta de circulación: `"Vigente"`, `"Vencida"` o `"Placa no localizada"` (el portal de CDMX no localizó la placa; es lo habitual con placas de otros estados)
-   `circulationCardValidUntil` — fecha ISO del fin de vigencia de la tarjeta de circulación (ya pasada si está `"Vencida"`). No viene con `"Placa no localizada"`

Sinaloa · sinaloa

-   `plateRegistryStatus` — `"deregistered"` si la placa está dada de baja; `"not_registered"` si no está en el padrón vehicular del estado. Son casos distintos: estar fuera del padrón no significa estar dado de baja
-   `plateRegistryMessage` — el aviso del portal, tal cual

En los dos casos el resultado de `sinaloa` es exitoso y sin adeudos: revisa `plateRegistryStatus` antes de leer "sin adeudos" como "al corriente".

`data.extra` puede traer otras claves. Sólo las documentadas aquí forman parte del contrato; las demás pueden cambiar o dejar de enviarse.

## Vigencia de placa (Estado de México)

Además de multas, puedes verificar la **vigencia de una placa de EdoMex** usando el pseudo-estado `vigencia-edomex` en el mismo `POST /lookup`. Cuesta 1 crédito y es combinable con estados de multas en el mismo request.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-LIMPIA","states":["vigencia-edomex"]}'
```

_results[0].data_

```json
{
  "source": "edomex-controlv",
  "plate": "PRUEBA-LIMPIA",
  "found": true,
  "plateStatus": "VIGENTE",
  "validityStart": "2025-10-14",
  "validityEnd": "2030-10-14",
  "previousPlate": null,
  "message": "¡Estimado contribuyente, ..."
}
```

Campos del resultado

-   `found` — `false` si la placa no está registrada en el padrón (respuesta definitiva del portal, no un error)
-   `plateStatus` — `VIGENTE` · `VENCIDA` · `INACTIVA` · `EN_PROCESO`
-   `validityStart` / `validityEnd` — fechas ISO de inicio y fin de vigencia (la placa dura 5 años)
-   `previousPlate` — placa anterior si hubo reemplacamiento

En este servicio `hasInfractions` y `totalDebt` vienen en `null`: no es una consulta de multas.

## Verificación vehicular (Estado de México)

Consulta si un vehículo **ya cumplió con la verificación del semestre** usando el pseudo-estado `verificacion-edomex` en el mismo `POST /lookup`. Cuesta 1 crédito y es combinable con estados de multas en el mismo request. Devuelve el holograma obtenido, la fecha, el folio del certificado y el verificentro.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-LIMPIA","states":["verificacion-edomex"]}'
```

_results[0].data_

```json
{
  "source": "edomex-verificacion",
  "plate": "PRUEBA-LIMPIA",
  "found": true,
  "verifiedThisSemester": true,
  "verification": {
    "center": "CU-000 VERIFICENTRO DE PRUEBA S.A. DE C.V.",
    "date": "2026-08-07",
    "certificate": "Cero",
    "reason": "Normal",
    "folio": "PRUEBA0001",
    "vin": "PRUEBA0000000001"
  },
  "message": "El vehículo tiene una verificación en el semestre."
}
```

Campos del resultado

-   `found` — `false` si no hay verificaciones registradas para la placa (respuesta definitiva del portal, no un error). En ese caso `verification` viene en `null`
-   `verifiedThisSemester` — `true` si la verificación del semestre en curso ya está hecha
-   `verification.certificate` — holograma obtenido, tal como lo publica el portal (`"Cero"`, `"Doble Cero"`, …)
-   `verification.date` — fecha ISO de la verificación · `folio` — folio del certificado · `center` — verificentro · `reason` — motivo · `vin` — número de serie

En este servicio `hasInfractions` y `totalDebt` vienen en `null`: no es una consulta de multas. Suele tardar más que el resto de consultas de EdoMex: el resultado llega por webhook cuando termina.

## Verificación vehicular (Jalisco)

Consulta la **verificación vehicular registrada** para una placa en Jalisco usando el pseudo-estado `verificacion-jalisco` en el mismo `POST /lookup`. Cuesta 1 crédito y es combinable con estados de multas en el mismo request. Devuelve el resultado, la fecha, hasta qué mes es válida y los datos básicos del vehículo.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-LIMPIA","states":["verificacion-jalisco"]}'
```

_results[0].data_

```json
{
  "source": "jalisco-verificacion",
  "plate": "PRUEBA-LIMPIA",
  "consultedAt": "2026-09-28T17:05:12.000Z",
  "found": true,
  "current": true,
  "verification": {
    "result": "APROBADO",
    "date": "2026-03-12",
    "validUntil": "2027-02",
    "type": "Extemporánea",
    "center": "0000"
  },
  "vehicle": {
    "brand": "NISSAN",
    "model": "VERSA",
    "year": 2019,
    "fuel": "GASOLINA"
  },
  "message": null
}
```

Campos del resultado

-   `found` — `false` si no hay verificación registrada para la placa (respuesta definitiva del portal, no un error). En ese caso `current`, `verification` y `vehicle` vienen en `null` y `message` dice `"Sin verificación registrada"`
-   `current` — `true` si la verificación está aprobada y su `validUntil` no ha pasado; `false` si no fue aprobada o ya venció. Para evaluarla con tu propia fecha de corte, usa `verification.validUntil`
-   `verification.result` — `"APROBADO"` o `"NO APROBADO"`, tal como lo publica el portal
-   `verification.date` — fecha ISO de la verificación · `validUntil` — mes hasta el que es válida, `"AAAA-MM"` (el portal no publica el día) · `type` — tipo de verificación, tal como lo publica el portal · `center` — clave del verificentro. Cualquiera puede venir en `null` si el dato no está disponible
-   `vehicle` — marca, modelo, año y combustible

**`found:false` no significa que el vehículo no haya verificado.** La verificación no es obligatoria en todos los municipios de Jalisco, hay vehículos exentos, y un vehículo que cambió de placa puede tener su verificación registrada con la placa anterior.

A diferencia de `verificacion-edomex`, no incluye holograma, folio del certificado ni número de serie. En este servicio `hasInfractions` y `totalDebt` vienen en `null`: no es una consulta de multas.

## Adeudo de tenencia (Estado de México)

Consulta si una placa de EdoMex tiene **adeudos de tenencia** usando el pseudo-estado `tenencia-edomex` en el mismo `POST /lookup`. Cuesta 1 crédito y es combinable con estados de multas en el mismo request. Además del adeudo, devuelve datos del vehículo (NIV, clave vehicular, factura) y la vigencia de la placa.

**¿Placas de la Ciudad de México?** No hay un `tenencia-cdmx`: la tenencia de CDMX viene incluida en la consulta de multas `cdmx`, en `data.debts[]` junto a las infracciones, sin costo adicional.

**¿Chihuahua, Guanajuato o Aguascalientes?** Tienen su propio pseudo-estado de tenencia, con otra forma de respuesta: ver [Tenencia en Chihuahua, Guanajuato y Aguascalientes](https://multasmx.com/docs/#tenencia-estados).

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-LIMPIA","states":["tenencia-edomex"]}'
```

_results[0].data_

```json
{
  "source": "edomex-tenencia",
  "plate": "PRUEBA-LIMPIA",
  "found": true,
  "hasDebt": false,
  "debtDescription": "SIN ADEUDOS",
  "totalDebt": 0,
  "vehicle": {
    "model": "2019",
    "description": "NISSAN VERSA 4PTAS SENSE 4Cil",
    "vin": "PRUEBA0000000001"
  },
  "plateValidityStart": "2025-10-14",
  "plateValidityEnd": "2030-10-14"
}
```

Campos del resultado

-   `found` — `false` si la placa no existe en el padrón vehicular o está dada de baja (respuesta definitiva del portal, no un error)
-   `plateStatus` — `"registered"`, `"not_registered"` o `"deregistered"` (dada de baja); con `found:false`, `message` trae el aviso del portal
-   `hasDebt` — `true` si la placa tiene adeudos de tenencia pendientes
-   `debtDescription` — texto del portal tal cual (p.ej. `"SIN ADEUDOS"`)
-   `totalDebt` — monto total del adeudo en MXN (dentro de `data`)
-   `vehicle` — modelo, descripción, NIV, clave vehicular, cilindros, fecha e importe de factura
-   `plateValidityStart` / `plateValidityEnd` — vigencia de la placa (fechas ISO)

En este servicio los campos `hasInfractions` y `totalDebt` del resultado (nivel superior) vienen en `null`: no es una consulta de multas.

## Tenencia en Chihuahua, Guanajuato y Aguascalientes

Tres pseudo-estados consultan el **adeudo vehicular estatal** de cada entidad en el mismo `POST /lookup`. Cada uno cuesta 1 crédito y es combinable con estados de multas en el mismo request.

| stateCode | Qué consulta |
| --- | --- |
| tenencia-chihuahua | Tenencia y revalidación vehicular del Gobierno del Estado. |
| tenencia-guanajuato | Tenencia y refrendo vehicular del Gobierno del Estado. |
| tenencia-aguascalientes | Derechos de control vehicular del Gobierno del Estado. Es estatal: las multas de `aguascalientes` son las del municipio. |

A diferencia de `tenencia-edomex`, el resultado tiene la **misma forma que una consulta de multas**: si el portal publica un adeudo, llega en `data.debts[]` con sus totales, e `infractions[]` viene vacío. El nivel de detalle depende de lo que publique cada portal.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-NOREG","states":["tenencia-chihuahua"]}'
```

_results[0].data · placa no encontrada_

```json
{
  "state": "Tenencia — Chihuahua",
  "plate": "PRUEBA-NOREG",
  "consultedAt": "2026-09-28T17:05:12.000Z",
  "hasInfractions": false,
  "infractions": [],
  "totalInfractionDebt": 0,
  "hasDebts": false,
  "debts": [],
  "totalDebtAmount": 0,
  "totalDebt": 0,
  "totalPendingDebt": 0,
  "extra": {
    "plateRegistryStatus": "not_registered",
    "plateRegistryMessage": "La placa o folio del Permiso Provisional no se encuentra registrado(a) en el Padron."
  }
}
```

Campos del resultado

-   `hasDebts` / `debts[]` — el adeudo que publica el portal. Cada entrada trae `concept`, `amount` \+ `unit`, `total` y `status` (`"pending"` o `"paid"`), más `year` y `surcharges` cuando el portal los da
-   `totalDebt` / `totalPendingDebt` — suma de los adeudos y de los pendientes, dentro de `data`
-   `extra.plateRegistryStatus` — `"not_registered"`: el portal no dio información de la placa;`extra.plateRegistryMessage` trae su aviso tal cual. En ese caso `debts[]` viene vacío, y **no significa "sin adeudo"**
-   `extra.paymentStatus` — sólo `tenencia-aguascalientes`: `"al_corriente"` si el portal indica que la placa está al corriente de pagos, con su aviso en `extra.paymentMessage`
-   `extra.plateReplacementRequired` — sólo `tenencia-guanajuato`: `true` si el portal indica que la placa debe canjearse

En estos servicios los campos `hasInfractions` y `totalDebt` del resultado (nivel superior) vienen en `null`: no son consultas de multas. El adeudo se lee dentro de `data`.

## REPUVE — reporte de robo (nacional)

Consulta el **Registro Público Vehicular** con el pseudo-estado `repuve` en el mismo `POST /lookup`. **Cuesta 3 créditos** —es la consulta más costosa de servir— y es combinable con estados de multas en el mismo request. Devuelve si el vehículo tiene **reporte de robo** (FGJ, OCRA y Robo USA/CAN) más sus datos de registro.

Es el único servicio que acepta **placa o NIV**: manda el campo `niv` —con o sin `plate`— y podrás consultar vehículos que todavía no tienen placa. La cobertura es **nacional**, no estatal.

_request.sh_

```bash
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"plate":"PRUEBA-ADEUDOS","states":["repuve"]}'

# o por número de serie (NIV), sin placa:
$ curl -X POST https://api.multasmx.com/lookup \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"niv":"PRUEBA0000000002","states":["repuve"]}'
```

_results[0].data_

```json
{
  "source": "repuve",
  "queriedBy": "plate",
  "registered": true,
  "hasTheftReport": true,
  "vehicle": {
    "marca": "NISSAN",
    "modelo": "VERSA",
    "anioModelo": 2019,
    "niv": "PRUEBA0000000002",
    "entidadEmplaco": "ESTADO DE MEXICO"
  },
  "fgj": {
    "status": "con_reporte",
    "records": [{ "estatus": "ROBADO", "fechaRobo": "23/06/26" }]
  },
  "ocra": { "status": "sin_reporte" },
  "roboUsaCan": { "status": "sin_reporte" }
}
```

Campos del resultado

-   `registered` — `false` si el vehículo no está inscrito en el registro (respuesta definitiva del portal, no un error)
-   `hasTheftReport` — atajo: `true` si alguna de las tres fuentes reporta robo, `false` si FGJ y OCRA respondieron sin reporte, y `null` si ninguna reporta robo pero FGJ u OCRA no respondieron (no se puede confirmar)
-   `complete` — `false` si alguna sección no respondió; `missingSections` dice cuáles y `nextRetryAt` cuándo se vuelven a consultar
-   `vehicle` — marca, modelo, año, clase, tipo, NIV, placa, entidad de emplacamiento, armadora y más
-   `fgj` — Fiscalía General de Justicia, con el detalle de cada reporte en `records[]` (estatus, entidad, fecha de robo y de recuperación)
-   `ocra` — Oficina Coordinadora de Riesgos Asegurados
-   `roboUsaCan` — robo reportado en Estados Unidos o Canadá
-   `avisosMinisteriales` — avisos por delitos distintos del robo

Cada sección trae un `status`: `con_reporte`, `sin_reporte`, `requiere_niv` (esa fuente necesita el NIV para responder), `pendiente` (esa fuente no respondió y la volvemos a consultar: el valor va a cambiar) o `desconocido` (no respondió en ninguno de los reintentos). Trata `pendiente` y `desconocido` como **dato ausente, no como vehículo limpio**; el `message` de la sección lo explica.

Si el resultado llega con secciones `pendiente` (`complete: false`), lo volvemos a consultar en las siguientes horas sin costo y la consulta queda como `PARTIAL`. Cuando se completa recibes otra vez el webhook `completed`, con el mismo `requestId`, el resultado actualizado y la consulta en `COMPLETED`. Si al final una sección no responde, pasa a `desconocido`, también te avisamos por webhook y, si las que faltan son FGJ u OCRA, se te devuelven los créditos de esa consulta.

REPUVE no reporta multas, así que `hasInfractions` y `totalDebt` del resultado (nivel superior) vienen en `null`.

## Webhook

Cuando un request completa, la API hace un POST a cada URL de `webhookUrls` configurada en el dashboard (hasta **3 endpoints**: producción, staging, un colector interno…). Todos reciben el mismo payload, firmado con **HMAC-SHA256** usando tu `webhookSecret`.

_webhook.http_

```http
POST {client.webhookUrls[n]}
Content-Type: application/json
X-Multas-Signature: c6f2...3e89
X-Multas-Request-Id: 05f4c751-...

{
  "requestId": "05f4c751-...",
  "plate": "ABC1234",
  "status": "COMPLETED",
  "results": [
    { "state": "edomex", "totalDebt": "905",
      "data": {
        "vehicle": { "brand": "DODGE", "model": "RAM 700" },
        "infractions": [
          { "folio": "EDX-2024-00012345", "amount": 905 }
        ]
      }
    }
  ]
}
```

### Verificar la firma (Node.js)

_verify.ts_

```ts
import crypto from 'crypto';

const expected = crypto
  .createHmac('sha256', process.env.WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');

const ok = crypto.timingSafeEqual(
  Buffer.from(expected), Buffer.from(signature)
);
```

Si tu endpoint responde con error, reintentamos 3 veces con backoff (0s, 2s, 6s). Después de eso `webhookSent = false` y puedes recuperar los datos con `GET /lookup/:id`.

## Rate limits

Default **60 req/min** por API key, en una ventana fija de 60 s que empieza con la primera petición. Si necesitas más, escríbenos a [contacto@multasmx.com](mailto:contacto@multasmx.com).

Cada respuesta autenticada dice cuánto te queda, con las cabeceras de [draft-ietf-httpapi-ratelimit-headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/). Léelas para espaciar tus peticiones en vez de esperar al `429`.

_200 OK_

```
RateLimit-Policy: "api-key";q=60;w=60
RateLimit: "api-key";r=59;t=42
```

Cabeceras

-   `RateLimit-Policy` — tu cuota: `q` peticiones por ventana de `w` segundos
-   `RateLimit` — cómo vas: te quedan `r` peticiones y la ventana se reinicia en `t` segundos
-   `Retry-After` — sólo en el `429`: segundos que debes esperar antes de reintentar. Es el mismo valor que `retryAfterSeconds` del cuerpo

`GET /billing/pricing` no lleva API key y no cuenta para el límite.

_429 Too Many Requests_

```
Retry-After: 23
RateLimit-Policy: "api-key";q=60;w=60
RateLimit: "api-key";r=0;t=23

{
  "error": "rate_limit_exceeded",
  "message": "max 60 req/min",
  "retryAfterSeconds": 23
}
```

## Códigos de error

| Status | Código | Descripción |
| --- | --- | --- |
| 400 | invalid_body / unknown_states | Payload inválido o estado no reconocido. |
| 400 | unknown_test_plate · test_identifier_mismatch | Placa o NIV de prueba que no existe, o combinado con uno real (ver Pruebas sin costo). |
| 401 | Missing / Invalid API key | Header x-api-key ausente o incorrecto. |
| 402 | insufficient_credits · trial_expired | Saldo insuficiente (incluye required, available, missing y el desglose por estado). trial_expired sólo aparece en cuentas con el periodo de prueba anterior. |
| 404 | request_not_found | El requestId no existe o no pertenece a tu cuenta. |
| 429 | rate_limit_exceeded | Superaste el límite de requests por minuto. |
| 429 | sandbox_limit_exceeded | Superaste el límite diario de consultas de prueba. |
| 502 | scrape_failed | Error interno al comunicarse con el portal del estado. |
| 503 | sandbox_disabled | Las consultas de prueba no están disponibles en este momento. |

## Disponibilidad del servicio

**Multas MX es un servicio best-effort. No ofrecemos garantía de disponibilidad ni SLA.**

La API depende de portales gubernamentales de terceros que no controlamos. Esos portales pueden cambiar su estructura, entrar en mantenimiento, bloquear el acceso o dejar de responder sin previo aviso. Cuando eso ocurre, el estado afectado se marca como `DOWN`, `MAINTENANCE` o `BLOCKED` y deja de aparecer en `GET /states` hasta que se recupera.

No garantizamos tiempos de respuesta, tasas de éxito, ni la cobertura continua de un estado en particular. Diseña tu integración asumiendo que cualquier estado puede no estar disponible en un momento dado: revisa siempre `skipped[]` en la respuesta de `POST /lookup` y el estado de cada resultado en el webhook.

Los estados que no se pudieron procesar **no se cobran**.

Pruebas gratis

## Empieza a consultar hoy mismo.

Crea tu cuenta y consulta placas de prueba sin pagar nada. Compras créditos cuando vayas a consultar placas reales.

No pedimos tarjeta para crear la cuenta.
