# Clasificar API V2

API V2 es la versión actual de Clasificar. Autenticá tus consultas con una API key y consultá los permisos y cupos de tu cuenta en el endpoint de usage.

## API v2

Documentación: https://clasific.ar/docs

Consultá información básica de un vehículo por patente y sumá sólo los módulos que necesitás: multas, VTV/RTO, deuda de patente y GNC.

### Una API, dos tipos de consulta

Con V2 podés consultar los datos básicos de una patente o pedir información adicional mediante módulos. Ambos consumos son independientes.

#### Datos del vehículo

```text
GET /v2/vehicles/:plate
```

Devuelve la información disponible para la patente consultada. Podés agregar opciones como codia=true o sheet=true si tu plan las incluye.

#### Módulos

```text
POST /v2/vehicles/:plate/modules
```

Consultá una o varias fuentes adicionales sin necesidad de hacer antes una consulta básica del vehículo.

- Multas — fines
- VTV / RTO — vtv
- Deuda de patente — tax_debt
- GNC — gnc

### Consultas asincrónicas

Los módulos pueden tardar algunos segundos mientras consultamos las distintas fuentes.

Al iniciar una consulta recibís un ID de operación. Usalo para consultar el estado y obtener el resultado:

```text
GET /v2/operations/:id
```

También podés recibir el resultado mediante webhook. Consultar una operación no consume nuevamente tu cupo de módulos.

- [Configurar un webhook](https://clasific.ar/docs/v2/webhooks)

### Cómo se cuenta el uso

El consumo es simple.

```text
GET /v2/vehicles/ZZ000ZZ
```

→ 1 consulta de patente

```text
POST /v2/vehicles/ZZ000ZZ/modules
{"modules":["fines","vtv","tax_debt"]}
```

→ 3 consultas de módulos

Consultar módulos no consume una consulta de patente.

Los límites dependen de tu plan y se comparten entre todas las API keys de tu cuenta.

- [Hacer mi primera consulta →](https://clasific.ar/docs/v2/quickstart)

## Quickstart

Documentación: https://clasific.ar/docs/v2/quickstart

Necesitás una API key activa. Si todavía no tenés una, podés crearla desde Cuenta → API Keys.

- [Cuenta → API Keys](https://clasific.ar/account/keys)

### 1. Consultá un vehículo

Usá la patente directamente en la URL:

```bash
curl 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ' \
  -H 'x-api-key: <TU_API_KEY>'
```

La respuesta contiene la información básica disponible para el vehículo.

Cada request aceptado a este endpoint consume 1 consulta de patente.

### 2. Consultá módulos adicionales

Podés solicitar uno o varios módulos para la misma patente:

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["fines","vtv","tax_debt"]}'
```

Cada módulo solicitado consume 1 consulta de módulo. En este ejemplo se consumen 3.

No necesitás consultar primero los datos básicos del vehículo.

La API devuelve una operación. Copiá el valor de id de la respuesta para consultar su estado en el siguiente paso.

### 3. Obtené el resultado

Reemplazá <OPERATION_ID> por el id recibido y consultá la operación hasta que termine:

```bash
curl 'https://api.clasific.ar/v2/operations/<OPERATION_ID>' \
  -H 'x-api-key: <TU_API_KEY>'
```

Mientras la operación está en curso, su estado es queued o processing. Dejá de hacer polling cuando recibas completed, partial o failed.

Un módulo también puede indicar partial cuando obtuvo resultados pero alguna de sus fuentes no estuvo disponible.

El polling no consume consultas de patente ni de módulos. Cada request cuenta para el límite de requests por minuto de tu plan.

### Repetir una consulta mientras está en curso

Si volvés a solicitar para la misma patente exactamente el mismo conjunto de módulos mientras la operación sigue activa, Clasificar devuelve la operación existente en lugar de iniciar otra.

```text
ZZ000ZZ + fines + vtv
ZZ000ZZ + vtv + fines
```

Estos dos pedidos se consideran la misma consulta mientras esté en curso. No se inicia nuevamente el trabajo y no se consume cupo adicional de módulos.

Cuando una operación ya terminó, una nueva solicitud vuelve a ejecutar los módulos y consume el cupo correspondiente.

## Autenticación

Documentación: https://clasific.ar/docs/v2/authentication

Podés crear y administrar tus credenciales desde Cuenta → API Keys.

- [Cuenta → API Keys](https://clasific.ar/account/keys)

### Usar una API key

En cada request autenticado enviá tu API key mediante el header:

```text
x-api-key: <TU_API_KEY>
```

```bash
curl 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ' \
  -H 'x-api-key: <TU_API_KEY>'
```

Las API keys deben utilizarse desde tu servidor. No las expongas en aplicaciones frontend, repositorios públicos ni código distribuido al cliente.

### Ambientes

| Ambiente | URL base |
| --- | --- |
| Producción | https://api.clasific.ar |
| Sandbox | https://sandbox.clasific.ar |

El entorno Sandbox utiliza datos de prueba y tiene consumo separado de Producción. Usá la misma estructura de endpoints en ambos ambientes:

```text
https://api.clasific.ar/v2/...
https://sandbox.clasific.ar/v2/...
```

### Cuenta y límites

Todas las API keys de una misma cuenta comparten, dentro de cada ambiente:

- El plan contratado.
- Los cupos de consultas.
- Los límites de requests por minuto.
- Las operaciones creadas.

Podés rotar o crear nuevas API keys sin reiniciar el consumo de tu cuenta.

### Errores de autenticación

Una API key ausente o inválida devuelve HTTP 401 Unauthorized. Una key revocada deja de poder utilizarse inmediatamente.

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "An active x-api-key is required.",
    "requestId": "req_example",
    "retryable": false
  }
}
```

Revisá que estés enviando una API key activa y que el header esté escrito como x-api-key.

## Consultas de patente

Documentación: https://clasific.ar/docs/v2/vehicles

### Consultar un vehículo

Usá la patente directamente en la URL: `GET /v2/vehicles/:plate`.

```bash
curl 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ' \
  -H 'x-api-key: <TU_API_KEY>'
```

La API normaliza la patente y valida los formatos argentinos soportados antes de realizar la consulta. Se admiten patentes de autos en formato antiguo y Mercosur.

Cada consulta aceptada consume 1 consulta de patente. Los requests rechazados antes de ser aceptados no consumen cupo de consultas.

### Respuesta

La respuesta indica si hay información disponible para la patente:

- success indica que el request se procesó correctamente.
- found indica si encontramos información.
- data contiene los datos del vehículo.

Ejemplo abreviado con datos de prueba; la respuesta completa puede incluir más campos:

```json
{
  "success": true,
  "found": true,
  "data": {
    "plate": "ZZ000ZZ",
    "make": "TOYOTA",
    "model": "COROLLA 1.8",
    "year": 2020
  }
}
```

Si no encontramos información disponible:

```json
{
  "success": true,
  "found": false,
  "data": null
}
```

found: false significa que la consulta fue válida pero no encontramos información disponible para esa patente. Es una consulta procesada normalmente y consume 1 consulta de patente.

### Opciones

Podés agregar opciones mediante query parameters para enriquecer la respuesta. Por ejemplo: ?codia=true o ?sheet=true.

| Parámetro | Descripción |
| --- | --- |
| codia | Agrega información de identificación CODIA cuando está disponible. |
| sheet | Agrega información de ficha técnica. |
| tires | Agrega medidas de neumáticos disponibles. |
| classification | Agrega la clasificación pública del vehículo. |
| debug | Agrega información adicional de diagnóstico cuando está disponible. |
| onMiss=search | Si no encontramos datos, inicia una búsqueda adicional y puede devolver una operación asincrónica. |
| sandbox_scenario | Sólo Sandbox. Elegí un escenario: default, clean, debt, partial, unavailable o failed. |
| sandbox_seed | Sólo Sandbox. Fija datos reproducibles para pruebas; hasta 128 caracteres. |

codia, sheet, tires y onMiss=search están disponibles en planes pagos y Custom.

debug=true puede agregar información de diagnóstico en data.debug. No cambia la consulta ni su consumo.

### Buscar cuando no hay datos

Si no encontramos información disponible, podés solicitar una búsqueda adicional con onMiss=search:

```bash
curl 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ?onMiss=search' \
  -H 'x-api-key: <TU_API_KEY>'
```

Si ya hay información suficiente, recibís la respuesta normal. Si hace falta buscar más, la API responde HTTP 202 con una operación asincrónica:

```json
{
  "id": "op_92cff723-014a-4b1a-aa26-82bb5287e6aa",
  "kind": "vehicle",
  "status": "queued",
  "plate": "ZZ000ZZ",
  "result": null,
  "createdAt": "2026-09-06T12:00:00.000Z",
  "updatedAt": "2026-09-06T12:00:00.000Z"
}
```

Usá el ID para consultar el resultado:

```text
GET /v2/operations/:id
```

El polling no vuelve a consumir una consulta de patente. Cuando la operación termina, result contiene el resultado de la búsqueda.

- [Consultar una operación →](https://clasific.ar/docs/v2/operations)

Para ver todos los campos, parámetros y códigos de respuesta, consultá la referencia OpenAPI.

- [Referencia OpenAPI →](https://api.clasific.ar/v2/openapi.json)

## Módulos

Documentación: https://clasific.ar/docs/v2/modules

### Consultar módulos

Usá `POST /v2/vehicles/:plate/modules` para consultar uno o varios módulos para una patente.

No necesitás hacer antes una consulta de patente. Cada módulo solicitado consume 1 consulta de módulo.

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["fines","vtv","tax_debt"]}'
```

### Módulos disponibles

| Módulo | ID |
| --- | --- |
| [Multas](https://clasific.ar/docs/v2/modules/tickets) | fines |
| [VTV / RTO](https://clasific.ar/docs/v2/modules/vtv) | vtv |
| [Deuda de patente](https://clasific.ar/docs/v2/modules/tax-debt) | tax_debt |
| [GNC](https://clasific.ar/docs/v2/modules/cng) | gnc |

### Request

El body recibe una lista de módulos:

```json
{
  "modules": [
    "fines",
    "vtv"
  ]
}
```

Podés solicitar entre 1 y 4 módulos distintos en una misma operación.

### Cómo se cuenta el uso

Cada módulo solicitado consume 1 consulta de módulo.

```json
[
  "fines"
]
```

→ 1 consulta de módulo

```json
[
  "fines",
  "vtv",
  "tax_debt"
]
```

→ 3 consultas de módulos

Consultar módulos no consume consultas de patente.

Si no tenés suficiente cupo para todos los módulos solicitados, la operación no se inicia.

### Resultado de la operación

Las consultas de módulos son asincrónicas. Al iniciar una operación nueva, recibís su ID, estado, patente y módulos solicitados. Por ejemplo, para fines y vtv:

```json
{
  "id": "op_92cff723-014a-4b1a-aa26-82bb5287e6aa",
  "kind": "modules",
  "status": "queued",
  "plate": "ZZ000ZZ",
  "modules": {
    "fines": {
      "status": "queued",
      "data": null,
      "error": null
    },
    "vtv": {
      "status": "queued",
      "data": null,
      "error": null
    }
  },
  "createdAt": "2026-09-06T12:00:00.000Z",
  "updatedAt": "2026-09-06T12:00:00.000Z"
}
```

Usá el ID para consultar el resultado:

```text
GET /v2/operations/:id
```

También podés recibir el resultado mediante webhook.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Webhooks →](https://clasific.ar/docs/v2/webhooks)

### Repetir una consulta en curso

Si volvés a pedir exactamente los mismos módulos para la misma patente mientras la operación sigue en curso, Clasificar devuelve la operación existente en lugar de iniciar otra.

```text
ZZ000ZZ + fines + vtv
ZZ000ZZ + vtv + fines
```

Estos pedidos se consideran la misma consulta mientras esté activa. No se inicia nuevamente el trabajo y no se consume cupo adicional de módulos.

Cuando la operación termina, una nueva solicitud vuelve a ejecutar los módulos y consume el cupo correspondiente.

Si cambiás la combinación de módulos, se crea una nueva operación.

```text
Primera operación: ["fines"]
Nueva solicitud: ["fines", "vtv"]
→ nueva operación
```

### Resultados parciales

Cada módulo informa su propio estado. Si una fuente no está disponible, otros módulos de la misma operación pueden completarse normalmente.

En algunos casos un módulo puede devolver partial cuando obtuvo información con cobertura reducida.

Para consultar schemas, códigos de respuesta y todos los campos disponibles, revisá la referencia de API.

- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Multas

Documentación: https://clasific.ar/docs/v2/modules/tickets

### Qué devuelve

El módulo de Multas consulta las infracciones disponibles para la patente en las jurisdicciones cubiertas por Clasificar.

### Consultar multas

Usá fines en `POST /v2/vehicles/:plate/modules` para consultar Multas.

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["fines"]}'
```

Cada solicitud nueva consume 1 consulta de módulo.

- [Cómo consultar y combinar módulos →](https://clasific.ar/docs/v2/modules)

### Campos del resultado

Estos campos están dentro de data. Los campos marcados con null pueden no tener información disponible. Las fechas se devuelven como fecha o fecha y hora ISO; si no se pueden interpretar, su valor es null.

| Campo | Tipo | Descripción |
| --- | --- | --- |
| total | number | Cantidad de infracciones en items. |
| amountTotal | number | Suma de amount disponibles, redondeada a dos decimales. No suma importes con descuento. |
| currency | "ARS" | Moneda de los importes: pesos argentinos. |
| items | array | Listado de infracciones. |
| items[].date | string \| null | Fecha de la infracción, cuando está disponible. |
| items[].amount | number \| null | Importe informado en ARS; null si no está disponible. |
| items[].status | string | Estado informado, normalizado; unknown si no se puede identificar. |
| items[].authority | string \| null | Autoridad u organismo informado. |
| items[].reference | string \| null | Referencia de la infracción. |
| items[].description | string \| null | Descripción disponible de la infracción. |
| items[].jurisdiction | string | Jurisdicción asociada al registro. |
| items[].dueDate | string \| null | Fecha de vencimiento disponible. |
| items[].discountedAmount | number \| null | Importe con descuento informado, en ARS. |

### Cobertura y estados

La información disponible depende de la jurisdicción y de la disponibilidad de sus sistemas al momento de la consulta.

completed con total: 0 significa que la consulta se completó correctamente sin encontrar infracciones en las fuentes consultadas. No es un error.

Un resultado sin infracciones no reemplaza un certificado oficial de libre deuda.

Multas puede indicar partial con un warning reduced_coverage cuando se obtuvieron respuestas, pero no se pudo verificar parte relevante de la cobertura. Puede ocurrir incluso si items está vacío.

Si no fue posible completar el módulo, el resultado indica failed e incluye un error.

- [Errores →](https://clasific.ar/docs/v2/errors)

### Ejemplo de respuesta

Este es el resultado de modules.fines dentro de la operación.

Ejemplo con datos de prueba:

```json
{
  "status": "completed",
  "data": {
    "total": 1,
    "amountTotal": 47499.5,
    "currency": "ARS",
    "items": [
      {
        "date": "2024-05-03T15:53:00-03:00",
        "amount": 47499.5,
        "status": "voluntary_payment",
        "authority": null,
        "reference": "I06223680",
        "description": "Conducir sin portar la licencia",
        "jurisdiction": "CABA",
        "dueDate": null,
        "discountedAmount": null
      }
    ]
  },
  "error": null
}
```

Ejemplo de consulta completada sin registros:

```json
{
  "status": "completed",
  "data": {
    "total": 0,
    "amountTotal": 0,
    "currency": "ARS",
    "items": []
  },
  "error": null
}
```

Para ver el schema completo y todos los códigos de respuesta, consultá la referencia de API.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## VTV / RTO

Documentación: https://clasific.ar/docs/v2/modules/vtv

### Qué devuelve

El módulo VTV / RTO reúne información disponible sobre verificaciones técnicas e inspecciones asociadas a la patente.

### Consultar VTV / RTO

Usá vtv en `POST /v2/vehicles/:plate/modules` para consultar VTV / RTO.

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["vtv"]}'
```

Cada solicitud nueva consume 1 consulta de módulo.

- [Cómo consultar y combinar módulos →](https://clasific.ar/docs/v2/modules)

### Campos del resultado

Estos campos están dentro de data. Los campos marcados con null pueden no tener información disponible. Las fechas se devuelven como fecha o fecha y hora ISO; si no se pueden interpretar, su valor es null.

| Campo | Tipo | Descripción |
| --- | --- | --- |
| total | number | Cantidad de inspecciones en items. |
| items | array | Listado de inspecciones. |
| items[].type | string | Tipo de registro normalizado, por ejemplo vtv o rto; unknown si no se identifica. |
| items[].date | string \| null | Fecha de la inspección disponible. |
| items[].sticker | string \| null | Identificador de oblea informado. |
| items[].inspectionType | string | inspection, reinspection o unknown: inspección, reinspección o tipo no identificado. |
| items[].detail | string \| null | Detalle disponible de la inspección. |
| items[].result | string | Resultado normalizado. Consultá los valores en Resultado de inspección. |
| items[].certificate | string \| null | Identificador de certificado informado. |
| items[].jurisdiction | string | Jurisdicción asociada al registro. |
| items[].facility | string \| null | Planta o taller informado. |
| items[].dueDate | string \| null | Fecha de vencimiento disponible. |

### Resultado de inspección

result describe el resultado informado de cada inspección; no es el status de la consulta.

| Valor | Significado |
| --- | --- |
| approved | Resultado informado como aprobado, apto o vigente. |
| conditional | Resultado informado como condicional. |
| rejected | Resultado informado como rechazado o no apto. |
| expired | Resultado informado como vencido. |
| unknown | No se pudo identificar el resultado informado. |

Estos valores representan la información recibida. No se recalculan como una certificación de vigencia a la fecha de tu consulta.

### Cobertura y estados

La cobertura depende de la jurisdicción y de la disponibilidad de información sobre inspecciones al momento de consultar.

completed con total: 0 significa que la consulta se completó correctamente sin encontrar inspecciones en las fuentes consultadas. No es un error.

Un resultado sin registros no certifica por sí solo la situación técnica actual del vehículo.

Si no fue posible completar el módulo, el resultado indica failed e incluye un error.

- [Errores →](https://clasific.ar/docs/v2/errors)

### Ejemplo de respuesta

Este es el resultado de modules.vtv dentro de la operación.

Ejemplo de consulta completada sin registros:

```json
{
  "status": "completed",
  "data": {
    "total": 0,
    "items": []
  },
  "error": null
}
```

Para ver el schema completo y todos los códigos de respuesta, consultá la referencia de API.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Deuda de patente

Documentación: https://clasific.ar/docs/v2/modules/tax-debt

### Qué devuelve

El módulo de Deuda de patente consulta obligaciones e impuestos vehiculares disponibles para la patente en las jurisdicciones cubiertas.

### Consultar deuda de patente

Usá tax_debt en `POST /v2/vehicles/:plate/modules` para consultar Deuda de patente.

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["tax_debt"]}'
```

Cada solicitud nueva consume 1 consulta de módulo.

- [Cómo consultar y combinar módulos →](https://clasific.ar/docs/v2/modules)

### Campos del resultado

Estos campos están dentro de data. Los campos marcados con null pueden no tener información disponible. Las fechas se devuelven como fecha o fecha y hora ISO; si no se pueden interpretar, su valor es null.

| Campo | Tipo | Descripción |
| --- | --- | --- |
| total | number | Cantidad de obligaciones en items. |
| amountTotal | number | Suma updatedAmount cuando está disponible; en cada otro registro usa amount. Los importes ausentes no se suman. Total redondeado a dos decimales. |
| currency | "ARS" | Moneda de los importes: pesos argentinos. |
| items | array | Listado de obligaciones. |
| items[].period | string \| null | Período de la obligación tal como fue informado. |
| items[].description | string \| null | Descripción disponible del impuesto u obligación. |
| items[].dueDate | string \| null | Fecha de vencimiento disponible. |
| items[].amount | number \| null | Importe informado en ARS. |
| items[].updatedAmount | number \| null | Importe actualizado informado, en ARS, cuando está disponible. |
| items[].status | string | Estado informado, normalizado; unknown si no se puede identificar. |
| items[].jurisdiction | string | Jurisdicción asociada a la obligación. |

### Cobertura y estados

La información disponible depende de la jurisdicción y de la disponibilidad de los organismos consultados.

completed con total: 0 significa que la consulta se completó correctamente sin encontrar obligaciones en las fuentes consultadas. No es un error.

Un resultado sin obligaciones no reemplaza un certificado oficial de libre deuda.

Si no fue posible completar el módulo, el resultado indica failed e incluye un error.

- [Errores →](https://clasific.ar/docs/v2/errors)

### Ejemplo de respuesta

Este es el resultado de modules.tax_debt dentro de la operación.

Ejemplo con datos de prueba:

```json
{
  "status": "completed",
  "data": {
    "total": 1,
    "amountTotal": 10000.13,
    "currency": "ARS",
    "items": [
      {
        "period": "2026/1",
        "description": "Impuesto automotor",
        "dueDate": "2026-06-13",
        "amount": 9000,
        "updatedAmount": 10000.13,
        "status": "with_debt",
        "jurisdiction": "Pergamino"
      }
    ]
  },
  "error": null
}
```

Ejemplo de consulta completada sin registros:

```json
{
  "status": "completed",
  "data": {
    "total": 0,
    "amountTotal": 0,
    "currency": "ARS",
    "items": []
  },
  "error": null
}
```

Para ver el schema completo y todos los códigos de respuesta, consultá la referencia de API.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## GNC

Documentación: https://clasific.ar/docs/v2/modules/cng

### Qué devuelve

El módulo GNC consulta los registros disponibles asociados al equipo de GNC del vehículo.

### Consultar GNC

Usá gnc en `POST /v2/vehicles/:plate/modules` para consultar GNC.

```bash
curl -X POST 'https://api.clasific.ar/v2/vehicles/ZZ000ZZ/modules' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"modules":["gnc"]}'
```

Cada solicitud nueva consume 1 consulta de módulo.

- [Cómo consultar y combinar módulos →](https://clasific.ar/docs/v2/modules)

### Campos del resultado

Estos campos están dentro de data. Los campos marcados con null pueden no tener información disponible. Las fechas se devuelven como fecha o fecha y hora ISO; si no se pueden interpretar, su valor es null.

| Campo | Tipo | Descripción |
| --- | --- | --- |
| total | number | Cantidad de registros en items. |
| items | array | Listado de registros. |
| items[].operationDate | string \| null | Fecha de la operación registrada. |
| items[].workshop | string \| null | Nombre o identificación del taller informado. |
| items[].workshopTaxId | string \| null | Identificación tributaria del taller, cuando está disponible. |
| items[].workshopCode | string \| null | Código del taller informado. |
| items[].sticker | string \| null | Identificador de oblea informado para el registro. |
| items[].previousSticker | string \| null | Identificador de la oblea anterior, cuando está disponible. |

### Cobertura y estados

La disponibilidad depende de la información provista por el sistema consultado al momento de la operación.

completed con total: 0 significa que la consulta se completó correctamente sin encontrar registros en las fuentes consultadas. No es un error.

Un resultado sin registros no constituye una certificación del estado actual del equipo.

Si no fue posible completar el módulo, el resultado indica failed e incluye un error.

- [Errores →](https://clasific.ar/docs/v2/errors)

### Ejemplo de respuesta

Este es el resultado de modules.gnc dentro de la operación.

Ejemplo de consulta completada sin registros:

```json
{
  "status": "completed",
  "data": {
    "total": 0,
    "items": []
  },
  "error": null
}
```

Para ver el schema completo y todos los códigos de respuesta, consultá la referencia de API.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Operaciones

Documentación: https://clasific.ar/docs/v2/operations

### Qué es una operación

Algunas consultas se procesan de forma asincrónica. Cuando eso ocurre, Clasificar devuelve un ID de operación que podés guardar para seguir su estado y obtener el resultado final.

Consultá `GET /v2/operations/:id` con el ID recibido:

```bash
curl 'https://api.clasific.ar/v2/operations/op_92cff723-014a-4b1a-aa26-82bb5287e6aa' \
  -H 'x-api-key: <TU_API_KEY>'
```

Consultar una operación no consume consultas de patente ni consultas de módulos. Sí está sujeto al límite de requests por minuto de tu cuenta.

Sólo podés consultar operaciones de tu propia cuenta.

- [Errores →](https://clasific.ar/docs/v2/errors)

### Tipos de operación

kind indica qué consulta dio origen a la operación y dónde leer su resultado.

| kind | Origen | Resultado |
| --- | --- | --- |
| vehicle | Una búsqueda de vehículo que necesitó procesamiento adicional. | result |
| modules | Una consulta de uno o varios módulos. | modules |

La respuesta incluye id, kind, status, plate, createdAt y updatedAt. Las fechas indican cuándo se creó y cuándo se actualizó la operación.

### Estados

| Estado | Significado |
| --- | --- |
| queued | La operación fue aceptada y está esperando comenzar. |
| processing | La operación está siendo procesada. |
| completed | El procesamiento terminó correctamente. |
| partial | El procesamiento terminó con resultados parciales o cobertura reducida. |
| failed | No fue posible completar la operación. |

completed, partial y failed son estados finales. Cuando una operación alcanza uno de ellos, podés dejar de consultar.

Un 200 OK al consultar una operación significa que pudimos recuperar su estado. La operación dentro de la respuesta puede igualmente haber terminado como failed.

### Resultado de módulos

En una operación modular, modules contiene el estado y resultado de cada módulo solicitado. Revisá status, data y error de cada uno; también puede incluir warnings.

La operación termina como partial si algún módulo tuvo cobertura reducida o si hubo módulos que fallaron y otros que pudieron responder. Si todos fallaron, termina como failed. Un resultado sin registros puede ser una consulta completada correctamente.

```json
{
  "id": "op_92cff723-014a-4b1a-aa26-82bb5287e6aa",
  "kind": "modules",
  "status": "completed",
  "plate": "ZZ000ZZ",
  "modules": {
    "gnc": {
      "status": "completed",
      "data": {
        "total": 0,
        "items": []
      },
      "error": null
    }
  },
  "createdAt": "2026-09-06T12:00:00.000Z",
  "updatedAt": "2026-09-06T12:00:00.000Z"
}
```

Para ver todos los campos y códigos de respuesta, consultá la referencia de API.

- [Polling →](https://clasific.ar/docs/v2/polling)
- [Webhooks →](https://clasific.ar/docs/v2/webhooks)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Polling

Documentación: https://clasific.ar/docs/v2/polling

### Consultar una operación

Usá el ID recibido al iniciar una operación para consultar `GET /v2/operations/:id`:

```bash
curl 'https://api.clasific.ar/v2/operations/op_92cff723-014a-4b1a-aa26-82bb5287e6aa' \
  -H 'x-api-key: <TU_API_KEY>'
```

Mientras el estado sea queued o processing, la operación sigue en curso. Podés dejar de consultar cuando llegue a completed, partial o failed.

- Iniciás una consulta y guardás el ID de operación recibido.
- Consultás la operación con ese ID.
- Si sigue en curso, esperás y volvés a consultar.
- Cuando termina, revisás su estado y usás el resultado disponible.

- [Estados de operación →](https://clasific.ar/docs/v2/operations#states)

### Consumo

El polling no consume consultas de patente ni consultas de módulos.

Cada request sí cuenta dentro del límite de requests por minuto de tu plan.

### Frecuencia de polling

No es necesario consultar continuamente. Dejá unos segundos entre requests y adaptá la frecuencia al tiempo de respuesta de tu integración.

Si recibís 429 Too Many Requests por alcanzar el límite de requests, respetá Retry-After antes de volver a intentar. El header indica cuántos segundos esperar.

- [Rate limits →](https://clasific.ar/docs/v2/rate-limits)

### Usar webhooks

También podés recibir el resultado mediante webhook, sin hacer polling.

- [Webhooks →](https://clasific.ar/docs/v2/webhooks)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Webhooks

Documentación: https://clasific.ar/docs/v2/webhooks

### Recibir resultados automáticamente

En lugar de consultar una operación mediante polling, podés configurar una URL para que Clasificar te envíe una notificación cuando el resultado esté disponible.

V1, V2 y Cuenta → Webhook comparten una configuración por cuenta y ambiente, con destinos independientes para Sandbox y Producción. Habilitá el webhook antes de que termine la operación que querés recibir.

### Configurar un webhook

Usá `GET /v2/webhooks/config` para consultar la configuración y `PATCH /v2/webhooks/config` para definir o actualizar el destino. La configuración también se administra desde `/v1/webhooks/config` o Cuenta → Webhook; cambiar la URL, el secreto o la activación afecta a ambas versiones en ese ambiente. La URL debe ser HTTPS y accesible públicamente.

| Campo | Tipo | Descripción |
| --- | --- | --- |
| url | string \| null | URL de destino, hasta 2048 caracteres. Usá null para quitarla, junto con enabled: false si estaba habilitada. |
| enabled | boolean | Habilita o deshabilita el webhook. Para habilitarlo necesitás una URL. |
| rotateSecret | boolean | Con true genera un nuevo secreto de firma. |

```bash
curl -X PATCH 'https://api.clasific.ar/v2/webhooks/config' \
  -H 'x-api-key: <TU_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://tu-dominio.example/webhooks/clasificar","enabled":true}'
```

Todos los campos del PATCH son opcionales. Los valores omitidos se conservan.

GET y PATCH devuelven url, enabled y secret. El secreto está disponible cada vez que consultás la configuración; no se muestra sólo al crearlo.

Usá secret para verificar las firmas. Guardalo de forma segura en tu servidor y no lo expongas en el frontend. Si lo rotás, las entregas posteriores se firman con el nuevo secreto, incluidos los reintentos.

### Eventos

Las operaciones asincrónicas generan eventos cuando terminan. Las consultas de vehículo que se resuelven de forma inmediata no generan estos eventos.

| Evento | Cuándo se envía |
| --- | --- |
| modules.completed | Una operación de módulos termina como completed o partial. |
| modules.failed | Una operación de módulos termina como failed. |
| vehicle.completed | Una búsqueda asincrónica de vehículo termina correctamente. |
| vehicle.failed | Una búsqueda asincrónica de vehículo termina como failed. |
| webhook.test | Solicitás un evento de prueba. |

Una operación modular con estado partial llega mediante modules.completed. Revisá siempre data.status y el estado de cada módulo.

### Payload

| Campo | Descripción |
| --- | --- |
| id | ID del evento. Usalo para reconocer duplicados. |
| type | Tipo de evento, por ejemplo modules.completed. |
| apiVersion | Versión de la API: v2. |
| createdAt | Fecha y hora del evento en formato ISO. |
| data | En eventos de operación, la misma representación de la operación que obtenés mediante polling. En webhook.test contiene un mensaje de prueba. |

```json
{
  "id": "evt_7c62755f-6270-47b1-9c08-503e3e44bcf4",
  "type": "modules.completed",
  "apiVersion": "v2",
  "createdAt": "2026-09-06T12:00:00.000Z",
  "data": {
    "id": "op_92cff723-014a-4b1a-aa26-82bb5287e6aa",
    "kind": "modules",
    "status": "completed",
    "plate": "ZZ000ZZ",
    "modules": {
      "gnc": {
        "status": "completed",
        "data": {
          "total": 0,
          "items": []
        },
        "error": null
      }
    },
    "createdAt": "2026-09-06T12:00:00.000Z",
    "updatedAt": "2026-09-06T12:00:00.000Z"
  }
}
```

### Reintentos y duplicados

Las entregas pueden repetirse (at-least-once). En algunos casos podés recibir el mismo evento más de una vez: usá payload.id para deduplicarlo y evitar procesarlo de nuevo.

Respondé con cualquier status HTTP 2xx para confirmar la entrega.

Si tu endpoint no responde correctamente, Clasificar vuelve a intentar hasta un máximo de 5 intentos en total, incluido el primero. Al agotar los intentos, la entrega queda como failed.

### Verificar la firma

Verificá la firma antes de procesar cada webhook. Usá el secreto de la configuración y estos headers:

| Header | Descripción |
| --- | --- |
| x-clasificar-signature | Firma HMAC-SHA256, con formato v1=<hex>. |
| x-clasificar-timestamp | Timestamp Unix en segundos, enviado como texto y utilizado para calcular la firma. |
| x-clasificar-event | Tipo de evento con prefijo v2., por ejemplo v2.modules.completed. En payload.type el valor es modules.completed. |
| x-clasificar-delivery | ID de la entrega, sin el prefijo evt_ del ID de evento. |

La firma se calcula sobre el timestamp, un punto y el body exacto recibido, codificados en UTF-8. No vuelvas a serializar el JSON antes de verificarlo.

```text
signedPayload = timestamp + "." + rawBody
hex = HMAC-SHA256(secret, signedPayload).hex
expectedSignature = "v1=" + hex
```

Compará expectedSignature con x-clasificar-signature usando una comparación de tiempo constante. Usá el texto de x-clasificar-timestamp tal como lo recibiste.

El prefijo v1= identifica la versión del formato de firma, no la versión de la API. El payload sigue teniendo apiVersion: "v2".

### Historial de entregas

Consultá `GET /v2/webhooks/deliveries` para revisar entregas recientes de V1 y V2 en el ambiente actual y diagnosticar problemas. La respuesta contiene deliveries, limit y offset.

| Parámetro | Valores | Por defecto |
| --- | --- | --- |
| limit | Entero de 1 a 100 | 25 |
| offset | Entero de 0 a 10000 | 0 |

| Campo de entrega | Descripción |
| --- | --- |
| id | Identificador de la entrega. |
| event | Tipo de evento. Los eventos V2 se muestran sin el prefijo v2. del header. |
| apiVersion | Versión que originó el evento: v1 o v2. El payload conserva el formato de esa versión. |
| status | pending: pendiente o esperando reintento; processed: confirmada con 2xx; failed: agotó los intentos. |
| attempts | Cantidad de intentos realizados. |
| responseStatus | Último código HTTP recibido; puede ser null si no hubo respuesta. |
| nextAttemptAt | Fecha prevista para el próximo intento. Revisá status para saber si sigue pendiente. |
| createdAt | Fecha de creación de la entrega. |
| payload | Contenido del evento enviado. |

El estado de una entrega indica si tu endpoint la recibió; no es el estado de la operación.

### Probar la configuración

Usá `POST /v2/webhooks/test` para enviar un evento webhook.test a la URL configurada. Requiere una URL y el webhook habilitado; no necesita body.

```bash
curl -X POST 'https://api.clasific.ar/v2/webhooks/test' \
  -H 'x-api-key: <TU_API_KEY>'
```

Recibís HTTP 202 con la entrega pendiente:

```json
{
  "id": "7c62755f-6270-47b1-9c08-503e3e44bcf4",
  "status": "pending"
}
```

Consultá el historial para comprobar si tu endpoint confirmó la recepción.

Configurar, consultar y probar webhooks no consume consultas de patente ni de módulos. Estas requests sí están sujetas al rate limit de tu cuenta.

- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Precios y consumo

Documentación: https://clasific.ar/docs/v2/usage

### Cómo se cuenta el uso

Tu plan tiene dos cupos independientes: consultas de patente y consultas de módulos.

#### Consultas de patente

Cada request aceptado a `GET /v2/vehicles/:plate` consume 1 consulta de patente.

```text
GET /v2/vehicles/ZZ000ZZ
→ 1 consulta de patente
```

#### Consultas de módulos

Cada módulo solicitado en una nueva consulta a `POST /v2/vehicles/:plate/modules` consume 1 consulta de módulo. Consultar módulos no consume consultas de patente.

```text
POST /v2/vehicles/ZZ000ZZ/modules
{"modules":["fines","vtv","tax_debt"]}
→ 3 consultas de módulos
```

Total de esta integración: 1 consulta de patente y 3 consultas de módulos.

Si repetís exactamente la misma consulta de módulos mientras la operación sigue en curso, Clasificar devuelve la operación existente y no consume nuevamente. Cuando la operación ya terminó, una nueva solicitud vuelve a consumir el cupo correspondiente.

Una consulta aceptada consume normalmente aunque no encontremos registros: found: false sigue siendo una consulta de patente, y un módulo completado con total: 0 sigue siendo una consulta de módulo.

Los requests rechazados antes de ser aceptados, por ejemplo por una patente inválida o falta de cupo, no consumen consultas de patente ni de módulos.

Consultar `GET /v2/operations/:id` no consume consultas de patente ni de módulos. Sí cuenta dentro del límite de requests por minuto.

- [Consultar módulos →](https://clasific.ar/docs/v2/modules)
- [Polling →](https://clasific.ar/docs/v2/polling)
- [Errores →](https://clasific.ar/docs/v2/errors)

### Planes

| Plan | Consultas de patente | Consultas de módulos | RPM |
| --- | --- | --- | --- |
| Free | 200 / mes | 10 / mes | 30 |
| Basic | 2.500 / mes | 50 / mes | 60 |
| Growth | 10.000 / mes | 100 / mes | 120 |
| Scale | Sin cupo mensual* | 500 / mes | 240 |

*Scale permite hasta 1.000 patentes distintas por día.

¿Necesitás más volumen? Los planes Custom permiten límites adaptados a tu integración.

- [Ver precios completos →](https://clasific.ar/precios-api)

### Scale

Scale no tiene un límite mensual de consultas de patente. En cambio, admite hasta 1.000 patentes distintas por día.

Consultar ZZ000ZZ varias veces durante el mismo día cuenta como una sola patente distinta para ese límite diario.

Después de consultar 1.000 patentes distintas, podés seguir consultando cualquiera de esas mismas patentes durante el día. Una patente nueva adicional se rechaza hasta el siguiente período diario.

El límite diario se reinicia cada día según la hora de Argentina. El límite de requests por minuto sigue aplicando.

### Consultar tu uso

Usá `GET /v2/usage` para revisar tu plan, las consultas usadas y disponibles, las patentes distintas del día cuando aplica, el límite de requests por minuto y las fechas de reinicio.

```bash
curl 'https://api.clasific.ar/v2/usage' \
  -H 'x-api-key: <TU_API_KEY>'
```

Ejemplo abreviado de una respuesta Free: se muestran los cupos y entitlements.plan. El endpoint incluye campos adicionales.

```json
{
  "entitlements": {
    "plan": "free"
  },
  "plateQueries": {
    "limit": 200,
    "used": 1,
    "remaining": 199,
    "startsAt": "2026-09-01T03:00:00.000Z",
    "endsAt": "2026-10-01T03:00:00.000Z"
  },
  "moduleQueries": {
    "limit": 10,
    "used": 3,
    "remaining": 7,
    "startsAt": "2026-09-01T03:00:00.000Z",
    "endsAt": "2026-10-01T03:00:00.000Z"
  },
  "uniquePlates": {
    "limit": null,
    "used": 1,
    "remaining": null,
    "startsAt": "2026-09-06T03:00:00.000Z",
    "endsAt": "2026-09-07T03:00:00.000Z"
  },
  "requests": {
    "limit": 30,
    "used": 3,
    "remaining": 27,
    "startsAt": "2026-09-06T12:00:00.000Z",
    "endsAt": "2026-09-06T12:01:00.000Z"
  }
}
```

Cada grupo incluye limit, used, remaining, startsAt y endsAt. En limit y remaining, null indica que ese límite no aplica al plan.

- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Rate limits

Documentación: https://clasific.ar/docs/v2/rate-limits

### Requests por minuto

Cada plan define una cantidad máxima de requests por minuto. El límite se comparte entre todas las API keys de tu cuenta: crear varias keys no multiplica los requests disponibles.

Cuentan las consultas de vehículo, el inicio de módulos, el polling, las consultas de uso y la administración de webhooks.

| Plan | Requests por minuto |
| --- | --- |
| Free | 30 |
| Basic | 60 |
| Growth | 120 |
| Scale | 240 |

- [Ver precios y consumo →](https://clasific.ar/docs/v2/usage)

### Headers

En las respuestas que pasan la comprobación del límite podés leer estos headers:

| Header | Descripción |
| --- | --- |
| X-RateLimit-Limit | Cantidad máxima de requests permitidos en la ventana actual. |
| X-RateLimit-Remaining | Requests restantes en la ventana actual, después del request realizado. |
| X-RateLimit-Reset | Momento en que se reinicia la ventana: timestamp Unix, en segundos. |

Si tu plan Custom no tiene límite de RPM, se omiten X-RateLimit-Limit y X-RateLimit-Remaining.

### Cuando alcanzás el límite

Cuando alcanzás el límite, la API responde 429 Too Many Requests con error.code: rate_limit_exceeded.

Retry-After indica cuántos segundos esperar antes de volver a intentar. El error también incluye details.resetAt con la fecha y hora de reinicio.

- Respetá Retry-After antes de reintentar.
- Reducí la frecuencia de requests y evitá polling innecesariamente frecuente.
- Usá backoff: aumentá la espera entre reintentos si el problema continúa.

- [Cómo manejar errores →](https://clasific.ar/docs/v2/errors)

### Rate limit y consumo

El rate limit mide tráfico HTTP; es independiente del consumo de consultas.

Hacer polling con `GET /v2/operations/:id` no consume consultas de patente ni de módulos, pero sí cuenta dentro del rate limit.

Una request autenticada puede contar para el rate limit aunque luego sea rechazada por validación.

- [Precios y consumo →](https://clasific.ar/docs/v2/usage)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## Errores

Documentación: https://clasific.ar/docs/v2/errors

### Formato de error

Los errores HTTP usan una estructura consistente. Usá error.code para manejarlos programáticamente; message está pensado para lectura humana.

| Campo | Descripción |
| --- | --- |
| code | Código estable para manejar el error desde tu integración. |
| message | Descripción legible del error. |
| requestId | Identificador de la request, útil para soporte y diagnóstico. |
| retryable | Indica si el error puede ser transitorio y tiene sentido volver a intentar. |
| details | Información adicional específica del error, cuando está disponible. |

```json
{
  "error": {
    "code": "quota_exceeded",
    "message": "Account limit exceeded.",
    "requestId": "req_example",
    "retryable": true,
    "details": {
      "meter": "module_queries",
      "limit": 10,
      "used": 10,
      "resetAt": "2026-10-01T03:00:00.000Z"
    }
  }
}
```

Guardá requestId cuando reportes un problema a soporte: permite identificar la request correspondiente.

retryable: true no significa que debas repetir inmediatamente una consulta. Cuando estén disponibles, respetá Retry-After y details.resetAt. En quota_exceeded, esperá al reinicio del cupo o revisá los límites de tu plan.

Para quota_exceeded, details.meter identifica el cupo: plate_queries, module_queries o unique_plates. En rate_limit_exceeded, el valor es requests.

### Errores comunes

| Código | HTTP | Qué hacer |
| --- | --- | --- |
| invalid_api_key | 401 | Verificá que estés enviando una API key activa. |
| access_denied | 403 | Tu cuenta no tiene acceso a esa operación o capacidad. |
| invalid_plate | 400 | Verificá el formato de la patente: ABC123 o AB123CD. |
| invalid_request | 400 / 404 | Revisá los parámetros, el body y la ruta solicitada. |
| unsupported_module | 400 | Usá fines, vtv, tax_debt o gnc. |
| duplicate_module | 400 | No repitas el mismo módulo en una solicitud. |
| module_not_enabled | 403 | El módulo no está habilitado para tu cuenta. |
| quota_exceeded | 429 | Alcanzaste el cupo disponible. Revisá el límite y su fecha de reinicio. |
| rate_limit_exceeded | 429 | Esperá el tiempo indicado por Retry-After. |
| operation_not_found | 404 | Verificá el ID de la operación. |
| temporarily_unavailable | 503 | Esperá y volvé a intentar si el error lo indica. |
| internal_error | 500 | Guardá requestId y revisá retryable antes de reintentar. |

### Consumo y errores

Los requests rechazados antes de ser aceptados no consumen consultas de patente ni de módulos. Por ejemplo: una patente inválida, un módulo no soportado o falta de cupo.

Una operación ya aceptada puede terminar posteriormente con un error de módulo. En ese caso, el resultado muestra el error dentro del módulo correspondiente.

- [Precios y consumo →](https://clasific.ar/docs/v2/usage)

### Errores durante una operación

Una consulta modular puede ser aceptada correctamente y, más tarde, uno de sus módulos puede terminar como failed.

`GET /v2/operations/:id` puede devolver 200 OK porque pudimos recuperar su estado, mientras el resultado de un módulo contiene:

```json
{
  "status": "failed",
  "data": null,
  "error": {
    "code": "module_unavailable",
    "message": "No se pudo completar el módulo.",
    "retryable": true
  }
}
```

Un módulo con status: completed y total: 0 se procesó correctamente y no encontró registros. No es lo mismo que status: failed.

- [Operaciones →](https://clasific.ar/docs/v2/operations)
- [Rate limits →](https://clasific.ar/docs/v2/rate-limits)
- [Referencia de API →](https://api.clasific.ar/v2/openapi.json)

## API reference

Documentación: https://clasific.ar/docs/v2/reference

### Rutas V2

Todas las rutas autenticadas suman 1 al RPM. Basic y Modules tienen consumo independiente. No existe `POST /v2/lookup`.

| Método | Path | Consumo | HTTP exitoso |
| --- | --- | --- | --- |
| GET | `/v2/vehicles/:plate` | 1 plate query | 200 o 202 |
| POST | `/v2/vehicles/:plate/modules` | N module queries; 0 si reutiliza activo | 202 nueva / 200 reutilizada |
| GET | `/v2/operations/:id` | 0 unidades comerciales | 200 |
| GET | `/v2/status` | 0 unidades comerciales | 200 |
| GET | `/v2/usage` | 0 unidades comerciales | 200 |
| GET | `/v2/plans` | Pública; 0 RPM | 200 |
| GET | `/v2/openapi.json` | Pública; 0 RPM | 200 |
| GET | `/v2/webhooks/config` | 0 unidades comerciales | 200 |
| PATCH | `/v2/webhooks/config` | 0 unidades comerciales | 200 |
| GET | `/v2/webhooks/deliveries` | 0 unidades comerciales | 200 |
| POST | `/v2/webhooks/test` | 0 unidades comerciales | 202 |

- [OpenAPI V2 completo](https://api.clasific.ar/v2/openapi.json)

### Disponibilidad y convivencia con V1

API V2 es la versión actual de Clasificar. Autenticá tus consultas con una API key y consultá los permisos y cupos de tu cuenta en el endpoint de usage.

V1 conserva sus rutas, IDs, cuotas, estados e idempotencia. Consultá la documentación V1 para mantener integraciones de esa versión.

- [Referencia V1](https://clasific.ar/docs/reference)
## Documentación de la versión anterior (V1)
# Documentación API de Clasificar

Esta es la documentación completa de integración para agentes, LLMs y desarrolladores.

## Introducción

Clasificar API expone datos vehiculares argentinos para consulta de patentes, datos registrales normalizados y reportes inteligentes asincrónicos.

Base URLs:
- Producción: `https://api.clasific.ar`
- Sandbox: `https://sandbox.clasific.ar`

Formatos de patente aceptados:
- `ABC123`: formato viejo, 3 letras + 3 números.
- `AB123CD`: formato Mercosur, 2 letras + 3 números + 2 letras.

Las patentes son case-insensitive. La API normaliza a mayúsculas.

En sandbox se acepta cualquier patente no vacía para facilitar pruebas end-to-end.

Versionado:
- Esta referencia describe V1. API V2 es la versión actual y comparte cuenta, keys y consumo con V1.

## Primeros pasos

1. Crear una cuenta en Clasificar.
2. Activar un plan desde la cuenta.
3. Crear una API key en la sección de llaves API.
4. Enviar requests con el header `x-api-key`.

Límites visibles en la página:
- 200 consultas Basic por mes compartidas entre V1 y V2.
- 10 módulos mensuales disponibles en V2; módulos V1 requiere Custom.
- 30 requests por minuto por cuenta y entorno, entre todas las keys y ambas versiones.

## Precios API

Los cupos reales de los clientes existentes se consultan en Account. Los planes comerciales
se presentan únicamente en `/precios-api`.

Resumen:
- Gratis: activación self-service, sin tarjeta, con cuota para pruebas iniciales.
- Planes pagos: checkout online, mayor volumen mensual y más capacidad por minuto.
- Todos los planes usan la misma autenticación por `x-api-key` y métricas de uso visibles.

## Autenticación

Todos los endpoints privados requieren:

```http
x-api-key: clk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Notas:
- Las API keys tienen prefijo `clk_`.
- La key completa se muestra una sola vez al crearla.
- Externamente se identifican por `prefix` y `last4`.

## Sistema de cuotas

Los cupos Basic y módulos se registran por cuenta, mes y entorno:

- V1 y V2 comparten los mismos contadores entre todas las API keys.
- Rotar keys, cambiar de versión o de plan no reinicia el uso del mes.
- 600 Basic en V1 + 400 en V2 = 1.000 usadas.
- El RPM también se comparte por cuenta y entorno e incluye polling y status.

Consumo:
- Basic autenticado, con o sin datos: una consulta del cupo Basic mensual compartido.
- Módulos Custom: una unidad por módulo aceptado, compartida con V2.
- Reporte inteligente aceptado o servido desde cache: consume `intelligent` + cuota mensual.
- Polling de reporte inteligente: solo analytics, no consume cuota.

Importante:
- Basic autenticado ya no aplica las cuotas diarias legacy `fast` y `miss`.
- Los reportes V1 conservan sus condiciones y su consumo propios.
- `/v1/status` devuelve `sharedUsage`; `/v2/status` expone el mismo consumo en `data.usage`.
- Los contadores compartidos indican sus fechas de reinicio y zona horaria; por defecto, Argentina.

## Sandbox

Sandbox usa el host `https://sandbox.clasific.ar` y el mismo header `x-api-key`. Cualquier API key válida puede probar consultas básicas, reportes y webhooks en sandbox, incluso si el owner está en plan gratuito. Los endpoints de módulos mantienen el requisito de contrato Custom.

Reglas de sandbox:
- No consulta información real ni crea reportes reales en la base productiva.
- Devuelve placeholders determinísticos a partir de `sandbox_seed` cuando se envía.
- La configuración de webhooks de sandbox está separada de producción.
- `POST /v1/reports` queda en estado `processing` durante una demora realista de 1 a 3 minutos antes de completar o fallar.
- El webhook de reporte se dispara al terminar esa demora. `POST /v1/webhooks/test` sigue siendo un test inmediato de conectividad.
- El query param `sandbox_scenario` permite forzar respuestas: `default`, `clean`, `debt`, `partial`, `unavailable`, `failed`.
- `sandbox_delay_ms` existe para pruebas controladas, pero en el sandbox público se normaliza a la ventana realista de demora.

Walkthrough recomendado:

1. Activar el plan gratuito desde `/account/activate` y crear una key en `/account/keys`.
2. Configurar el webhook de sandbox:

```bash
curl -X PATCH "https://sandbox.clasific.ar/v1/webhooks/config" \
  -H "x-api-key: clk_tu_api_key" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://webhook.site/tu-url-unica",
    "enabled": true,
    "rotate_secret": false
  }'
```

3. Enviar un webhook de prueba:

```bash
curl -X POST "https://sandbox.clasific.ar/v1/webhooks/test" \
  -H "x-api-key: clk_tu_api_key"
```

4. Probar consulta básica:

```bash
curl -G "https://sandbox.clasific.ar/v1/vehicles/basic" \
  -H "x-api-key: clk_tu_api_key" \
  --data-urlencode "plate=CLIENTE-123" \
  --data-urlencode "classification=true" \
  --data-urlencode "sandbox_scenario=debt" \
  --data-urlencode "sandbox_seed=demo-cliente"
```

Respuesta básica de sandbox:
```json
{
  "success": true,
  "data": {
    "plate": "CLIENTE123",
    "make": "TOYOTA",
    "model": "COROLLA XEI CVT",
    "year": 2021,
    "firstRegistered": "2021-04-18",
    "totalOwners": 2,
    "currentOwners": 1,
    "currentLocation": {
      "city": "CABA",
      "province": "Ciudad Autónoma de Buenos Aires"
    },
    "locations": [
      "CABA, Ciudad Autónoma de Buenos Aires",
      "LA PLATA, Buenos Aires"
    ],
    "sourceDate": "2026-07-07",
    "classification": {
      "vehicleType": "AUTOMOTOR",
      "bodyType": "SEDAN",
      "vehicleCategory": "auto",
      "confidence": "high",
      "source": "sandbox",
      "matchedModel": "COROLLA XEI CVT 4 PUERTAS",
      "matchedYear": 2021,
      "approximateMatch": false,
      "description": "Respuesta mock de sandbox",
      "cacheHit": false
    },
    "sandbox": {
      "enabled": true,
      "scenario": "debt",
      "seed": "demo-cliente"
    }
  }
}
```

5. Crear un reporte completo:

```bash
curl -X POST "https://sandbox.clasific.ar/v1/reports?sandbox_scenario=debt&sandbox_seed=demo-cliente" \
  -H "x-api-key: clk_tu_api_key" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: demo-cliente-001" \
  -d '{
    "plate": "CLIENTE-123"
  }'
```

Respuesta inicial:
```json
{
  "id": "rep_11111111111141118111111111111111",
  "status": "processing",
  "plate": "CLIENTE123",
  "report": null,
  "createdAt": "2026-07-07T12:00:00.000Z",
  "updatedAt": "2026-07-07T12:00:00.000Z",
  "completedAt": null
}
```

6. Poll hasta completar:

```bash
curl "https://sandbox.clasific.ar/v1/reports/rep_11111111111141118111111111111111" \
  -H "x-api-key: clk_tu_api_key"
```

Respuesta completa:
```json
{
  "id": "rep_11111111111141118111111111111111",
  "status": "completed",
  "plate": "CLIENTE123",
  "report": {
    "vehicle": {
      "plate": "CLIENTE123",
      "make": "TOYOTA",
      "model": "COROLLA XEI CVT",
      "year": 2021,
      "vehicleType": "AUTOMOTOR",
      "firstRegistered": "2010-09-07",
      "totalOwners": 2,
      "currentOwners": 1,
      "registryOffice": "02044 - CAPITAL FEDERAL N 044",
      "currentLocation": {
        "city": "CABA",
        "province": "CAPITAL FEDERAL"
      },
      "locations": [
        "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
        "CABA, CAPITAL FEDERAL"
      ],
      "sourceDate": "2026-05-15",
      "queryDate": "2026-05-15",
      "quote": null
    },
    "possibleOwners": [
      {
        "name": "V******, S*",
        "id": {
          "type": "CUIT",
          "value": "30*"
        }
      }
    ],
    "results": {
      "fines": {
        "total": 1,
        "amountTotal": 427495.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2023-09-24T08:48:00-03:00",
            "amount": 427495.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "Q29775884",
            "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      },
      "vtv": {
        "total": 1,
        "items": [
          {
            "type": "vtv",
            "date": "2024-08-26",
            "sticker": "4581967",
            "inspectionType": "inspection",
            "detail": "Particulares",
            "result": "approved",
            "certificate": "6227143",
            "jurisdiction": "CABA",
            "facility": "9 de Julio Sur",
            "dueDate": "2025-06-01"
          }
        ]
      },
      "tax_debt": {
        "total": 1,
        "amountTotal": 235757.86,
        "currency": "ARS",
        "items": [
          {
            "period": "2026/3",
            "description": "POSICION",
            "dueDate": "2026-06-22",
            "amount": 235757.86,
            "updatedAmount": 235757.86,
            "status": "expired",
            "jurisdiction": "CABA"
          }
        ]
      },
      "cng": {
        "total": 1,
        "items": [
          {
            "operationDate": "2024-03-12",
            "workshop": "Taller GNC Ejemplo",
            "workshopTaxId": "30-00000000-0",
            "workshopCode": "GNC-123",
            "wafer": "12345678",
            "previousWafer": "87654321"
          }
        ]
      }
    },
    "digest": {
      "summary": "El historial registra infracciones y deuda de patente en CABA.",
      "headline": "Multas y deuda de patente detectadas",
      "riskLevel": "high",
      "highlights": [
        "1 infracción en CABA por al menos $427.496.",
        "Deuda de patente en CABA por aproximadamente $235.758."
      ],
      "issues": [
        {
          "title": "Infracciones en CABA",
          "severity": "high",
          "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
        }
      ],
      "positives": [],
      "noRecords": [],
      "ownershipEstimate": {
        "estimatedHistoricalOwnerCount": 2,
        "confidence": "medium",
        "basis": "El resumen registral consolidado muestra 2 titulares históricos."
      },
      "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
    }
  },
  "createdAt": "2026-07-07T12:00:00.000Z",
  "updatedAt": "2026-07-07T12:01:44.000Z",
  "completedAt": "2026-07-07T12:01:44.000Z"
}
```

7. Revisar entregas de webhook:

```bash
curl "https://sandbox.clasific.ar/v1/webhooks/deliveries?limit=10" \
  -H "x-api-key: clk_tu_api_key"
```

Entrega de sandbox:
```json
{
  "success": true,
  "data": {
    "deliveries": [
      {
        "id": "whd_sandbox_123",
        "report_request_id": "rep_11111111111141118111111111111111",
        "owner_ref": "user_cliente",
        "plate": "CLIENTE123",
        "report_status": "completed",
        "event_type": "report.completed",
        "is_test": false,
        "url": "https://webhook.site/tu-url-unica",
        "status": "processed",
        "attempts": 1,
        "max_attempts": 5,
        "response_status": 200,
        "last_error": null,
        "payload": {
          "eventId": "evt_22222222222242228222222222222222",
          "event": "report.completed",
          "id": "rep_11111111111141118111111111111111",
          "status": "completed",
          "plate": "CLIENTE123",
          "report": {
            "vehicle": {
              "plate": "CLIENTE123",
              "make": "TOYOTA",
              "model": "COROLLA XEI CVT",
              "year": 2021,
              "vehicleType": "AUTOMOTOR",
              "firstRegistered": "2010-09-07",
              "totalOwners": 2,
              "currentOwners": 1,
              "registryOffice": "02044 - CAPITAL FEDERAL N 044",
              "currentLocation": {
                "city": "CABA",
                "province": "CAPITAL FEDERAL"
              },
              "locations": [
                "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
                "CABA, CAPITAL FEDERAL"
              ],
              "sourceDate": "2026-05-15",
              "queryDate": "2026-05-15",
              "quote": null
            },
            "possibleOwners": [
              {
                "name": "V******, S*",
                "id": {
                  "type": "CUIT",
                  "value": "30*"
                }
              }
            ],
            "results": {
              "fines": {
                "total": 1,
                "amountTotal": 427495.5,
                "currency": "ARS",
                "items": [
                  {
                    "date": "2023-09-24T08:48:00-03:00",
                    "amount": 427495.5,
                    "status": "voluntary_payment",
                    "authority": null,
                    "reference": "Q29775884",
                    "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
                    "jurisdiction": "CABA",
                    "dueDate": null,
                    "discountedAmount": null
                  }
                ]
              },
              "vtv": {
                "total": 1,
                "items": [
                  {
                    "type": "vtv",
                    "date": "2024-08-26",
                    "sticker": "4581967",
                    "inspectionType": "inspection",
                    "detail": "Particulares",
                    "result": "approved",
                    "certificate": "6227143",
                    "jurisdiction": "CABA",
                    "facility": "9 de Julio Sur",
                    "dueDate": "2025-06-01"
                  }
                ]
              },
              "tax_debt": {
                "total": 1,
                "amountTotal": 235757.86,
                "currency": "ARS",
                "items": [
                  {
                    "period": "2026/3",
                    "description": "POSICION",
                    "dueDate": "2026-06-22",
                    "amount": 235757.86,
                    "updatedAmount": 235757.86,
                    "status": "expired",
                    "jurisdiction": "CABA"
                  }
                ]
              },
              "cng": {
                "total": 1,
                "items": [
                  {
                    "operationDate": "2024-03-12",
                    "workshop": "Taller GNC Ejemplo",
                    "workshopTaxId": "30-00000000-0",
                    "workshopCode": "GNC-123",
                    "wafer": "12345678",
                    "previousWafer": "87654321"
                  }
                ]
              }
            },
            "digest": {
              "summary": "El historial registra infracciones y deuda de patente en CABA.",
              "headline": "Multas y deuda de patente detectadas",
              "riskLevel": "high",
              "highlights": [
                "1 infracción en CABA por al menos $427.496.",
                "Deuda de patente en CABA por aproximadamente $235.758."
              ],
              "issues": [
                {
                  "title": "Infracciones en CABA",
                  "severity": "high",
                  "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
                }
              ],
              "positives": [],
              "noRecords": [],
              "ownershipEstimate": {
                "estimatedHistoricalOwnerCount": 2,
                "confidence": "medium",
                "basis": "El resumen registral consolidado muestra 2 titulares históricos."
              },
              "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
            }
          },
          "createdAt": "2026-07-07T12:00:00.000Z",
          "updatedAt": "2026-07-07T12:01:44.000Z",
          "completedAt": "2026-07-07T12:01:44.000Z"
        },
        "next_attempt_at": null,
        "processed_at": "2026-07-07T12:01:45.000Z",
        "created_at": "2026-07-07T12:01:44.000Z",
        "updated_at": "2026-07-07T12:01:45.000Z"
      }
    ],
    "total": 1
  }
}
```

## Contrato de reporte inteligente

Toda operación async de Reports devuelve el mismo DTO público, sin wrappers `success/data`:
- `id`: identificador opaco `rep_...`.
- `status`: `queued | processing | completed | failed`.
- `plate`.
- `report`: `null` mientras procesa o falla; objeto normalizado al completar.
- `createdAt`
- `updatedAt`
- `completedAt`

No se publican `reportRequestId`, `pollUrl`, `pollCount`, passes, progreso, nombres o conteos de sources ni errores de scrapers. Construí el polling como `GET /v1/reports/:id` con el `id` recibido.

Estados de reporte:
- `queued`: Solicitud aceptada; aún no comenzó la ejecución.
- `processing`: El reporte se está procesando. La ejecución interna no forma parte del contrato.
- `completed`: Reporte utilizable terminado; report contiene el contrato final.
- `failed`: No se pudo producir el reporte; report es null y aparece error.

El reporte final vive en `report`. Los webhooks agregan `eventId` y `event` al mismo DTO; `eventId` es estable durante reintentos y sirve para deduplicar entregas.

Campos principales del reporte:
- `vehicle`
- `possibleOwners`
- `results`: `fines`, `vtv`, `tax_debt`, `cng`.
- `digest`: síntesis de producto sin scrapers, fuentes fallidas ni cobertura interna.

`vehicle` consolida el antiguo resumen duplicado y los candidatos de titularidad se publican siempre en `possibleOwners[]`. Los items no repiten la patente ni exponen `source`. Reports reutiliza literalmente los mismos DTOs y normalizadores que Modules para `fines`, `vtv` y `tax_debt`; el cuarto dominio se llama siempre `cng`, nunca `gas` o `gnc`.

`vehicle.queryDate` es exclusivamente la fecha de consulta registral y no reutiliza `createdAt`; si el dato registral no informa esa fecha, vale `null`. `vehicle.quote` vale `null` cuando no hubo cotización o contiene exactamente `estimatedPrice`, `referencePrice`, `minPrice`, `maxPrice`, `currency` y `confidence`. Proveedores, inputs y metadatos del cálculo no forman parte del contrato público.

`digest.ownershipEstimate.estimatedHistoricalOwnerCount` es una estimación del historial de titulares. No representa la cantidad de elementos de `possibleOwners`, que contiene únicamente candidatos concretos identificados, ni reemplaza el conteo registral `vehicle.totalOwners`.

El digest nunca contradice el detalle: si los conteos o importes de un agregado histórico no reconcilian con los items públicos, se devuelve `digest: null`.

Si falla una fuente secundaria pero el producto sigue siendo utilizable, Reports termina `completed`. Por ahora Reports no emite `partial`.

Contrato de `results.fines.items[]`:
- `date`
- `amount`
- `status`
- `authority`
- `reference`
- `description`
- `jurisdiction`
- `dueDate`
- `discountedAmount`

Ejemplo de reporte normalizado:
```json
{
  "vehicle": {
    "plate": "JFK106",
    "make": "LAND ROVER",
    "model": "RANGE ROVER 3.6 HSE TDV8",
    "year": 2010,
    "vehicleType": "AUTOMOTOR",
    "firstRegistered": "2010-09-07",
    "totalOwners": 2,
    "currentOwners": 1,
    "registryOffice": "02044 - CAPITAL FEDERAL N 044",
    "currentLocation": {
      "city": "CABA",
      "province": "CAPITAL FEDERAL"
    },
    "locations": [
      "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
      "CABA, CAPITAL FEDERAL"
    ],
    "sourceDate": "2026-05-15",
    "queryDate": "2026-05-15",
    "quote": null
  },
  "possibleOwners": [
    {
      "name": "V******, S*",
      "id": {
        "type": "CUIT",
        "value": "30*"
      }
    }
  ],
  "results": {
    "fines": {
      "total": 1,
      "amountTotal": 427495.5,
      "currency": "ARS",
      "items": [
        {
          "date": "2023-09-24T08:48:00-03:00",
          "amount": 427495.5,
          "status": "voluntary_payment",
          "authority": null,
          "reference": "Q29775884",
          "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
          "jurisdiction": "CABA",
          "dueDate": null,
          "discountedAmount": null
        }
      ]
    },
    "vtv": {
      "total": 1,
      "items": [
        {
          "type": "vtv",
          "date": "2024-08-26",
          "sticker": "4581967",
          "inspectionType": "inspection",
          "detail": "Particulares",
          "result": "approved",
          "certificate": "6227143",
          "jurisdiction": "CABA",
          "facility": "9 de Julio Sur",
          "dueDate": "2025-06-01"
        }
      ]
    },
    "tax_debt": {
      "total": 1,
      "amountTotal": 235757.86,
      "currency": "ARS",
      "items": [
        {
          "period": "2026/3",
          "description": "POSICION",
          "dueDate": "2026-06-22",
          "amount": 235757.86,
          "updatedAmount": 235757.86,
          "status": "expired",
          "jurisdiction": "CABA"
        }
      ]
    },
    "cng": {
      "total": 1,
      "items": [
        {
          "operationDate": "2024-03-12",
          "workshop": "Taller GNC Ejemplo",
          "workshopTaxId": "30-00000000-0",
          "workshopCode": "GNC-123",
          "wafer": "12345678",
          "previousWafer": "87654321"
        }
      ]
    }
  },
  "digest": {
    "summary": "El historial registra infracciones y deuda de patente en CABA.",
    "headline": "Multas y deuda de patente detectadas",
    "riskLevel": "high",
    "highlights": [
      "1 infracción en CABA por al menos $427.496.",
      "Deuda de patente en CABA por aproximadamente $235.758."
    ],
    "issues": [
      {
        "title": "Infracciones en CABA",
        "severity": "high",
        "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
      }
    ],
    "positives": [],
    "noRecords": [],
    "ownershipEstimate": {
      "estimatedHistoricalOwnerCount": 2,
      "confidence": "medium",
      "basis": "El resumen registral consolidado muestra 2 titulares históricos."
    },
    "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
  }
}
```

## Estados y resultados de Modules

El job usa `queued → processing → completed|failed`. El job no usa `partial`: si la ejecución terminó y al menos un módulo produjo un resultado utilizable, el job queda `completed`.

Todo job async usa el mismo lifecycle público: `id`, `status`, `plate`, `createdAt`, `updatedAt` y `completedAt`. `startedAt` es telemetría interna y no forma parte del contrato.

Cada módulo usa `queued → processing → completed|partial|failed`:
- `completed`: terminó y el resultado es utilizable con la cobertura material esperada.
- `partial`: terminó y el resultado sigue siendo utilizable, pero una parte material de la cobertura no pudo verificarse. En este caso aparece `warnings[]` con `code=reduced_coverage`.
- `failed`: no se pudo producir un resultado utilizable para ese módulo.

Los nombres y conteos de fuentes internas nunca forman parte del response. No dependas de cantidad de scrapers, IDs internos ni métricas `checked/unavailable/total`. Una falla secundaria tampoco degrada automáticamente el módulo.

Cada módulo tiene su propio schema de datos: `fines` y `tax_debt` incluyen `total`, `amountTotal`, `currency: "ARS"` e `items`; `vtv` incluye sólo `total` e `items`. Como no hay paginación ni truncamiento, siempre se cumple `total === items.length`; los totales monetarios corresponden a esos mismos items. La patente vive a nivel job y no se repite en cada item. En VTV/RTO, `result` describe el resultado, `inspectionType` distingue `inspection`, `reinspection` o `unknown`, y `status` queda reservado para el job o módulo.

Los items tienen claves públicas estables en inglés:
- `fines.items[]`: `date`, `amount`, `status`, `authority`, `reference`, `description`, `jurisdiction`, `dueDate`, `discountedAmount`.
- `vtv.items[]`: `type`, `date`, `sticker`, `inspectionType`, `detail`, `result`, `certificate`, `jurisdiction`, `facility`, `dueDate`.
- `tax_debt.items[]`: `period`, `description`, `dueDate`, `amount`, `updatedAmount`, `status`, `jurisdiction`.

Los valores categóricos de `status`, `result`, `inspectionType` y `type` se normalizan como `lower_snake_case`. Los resultados conocidos de VTV/RTO son `approved`, `conditional`, `rejected` o `expired`, incluso cuando el proveedor usa códigos numéricos; cualquier otro valor se convierte en `unknown` y no se filtra al cliente. Cuando falta otro estado categórico también se devuelve `unknown`, nunca `null`. Las fechas válidas sin hora usan `YYYY-MM-DD`; cuando existe hora local se devuelve un timestamp ISO 8601 con offset. Un valor irreconocible o una fecha calendario inválida se devuelve como `null`, nunca con el formato interno del scraper.

## Dedupe e idempotencia de Modules

`POST /v1/vehicles/modules` admite como máximo un job activo por owner, entorno y patente. Se considera activo un job en estado `queued` o `processing`. Pueden coexistir jobs activos de patentes distintas. La regla se comparte entre todas las API keys del mismo owner y no bloquea reportes Intelligent.

Casos:
- No existe un job activo para esa patente: 202 · job nuevo. Consume 1 unidad por módulo.
- Misma Idempotency-Key + mismo payload: 200 · job original. No suma consumo, aunque ya haya terminado.
- Misma patente activa + mismos módulos: 200 · job activo. No suma consumo.
- Misma patente activa + otros módulos: 409 · module_query_in_progress. No suma consumo; devuelve el job a consultar.

Reglas de integración:
- La comparación usa la patente normalizada y el conjunto de módulos; el orden del array no crea un request diferente.
- Si coinciden patente y módulos con un job activo, la API responde `200` con el mismo ID, `meta.reused=true` y `meta.reason=query_in_progress`.
- Si la patente coincide pero cambia el conjunto de módulos, la API responde `409 module_query_in_progress` con el lifecycle de `active_query`. Construí el polling como `GET /v1/vehicles/modules/:id` usando ese `id`. No se crea ni se factura otro job.
- `Idempotency-Key` es un header opcional, no un atributo del body. Es recomendable usar una key nueva por operación lógica.
- Repetir la misma key con el mismo payload devuelve el job original con `200` y `meta.reason=idempotency_replay`, incluso si ya terminó. No suma consumo.
- Repetir la misma key con otro payload devuelve `409 idempotency_conflict` y no suma consumo.
- Una vez terminal el job, un request sin su key idempotente se considera nuevo y consume una unidad por cada módulo aceptado.

Respuesta al reutilizar un job activo:
```json
{
  "id": "vmq_0198abc123def456",
  "status": "processing",
  "plate": "ABC123",
  "modules": [
    "fines",
    "vtv"
  ],
  "createdAt": "2026-08-14T12:00:00.000Z",
  "updatedAt": "2026-08-14T12:00:01.000Z",
  "completedAt": null,
  "meta": {
    "reused": true,
    "reason": "query_in_progress"
  }
}
```

Respuesta si la patente está activa con otros módulos:
```json
{
  "error_code": "module_query_in_progress",
  "message": "Ya existe una consulta activa para esa patente con otros módulos.",
  "active_query": {
    "id": "vmq_0198abc123def456",
    "status": "processing",
    "plate": "ABC123",
    "modules": [
      "fines",
      "vtv"
    ],
    "createdAt": "2026-08-14T12:00:00.000Z",
    "updatedAt": "2026-08-14T12:00:01.000Z",
    "completedAt": null
  }
}
```

## Endpoints

### Consulta Básica de Vehículo

`GET /v1/vehicles/basic`

Retorna datos del vehículo desde la base histórica sin datos personales. La consulta estándar está disponible en Free; las búsquedas ante misses y los enriquecimientos CODIA y neumáticos requieren un plan pago o Custom.

Acceso a opciones pagas: onMiss=search, codia=true y tires=true requieren Starter, Growth, Scale o Custom. Con una API key Free, la API responde 403 PAID_PLAN_REQUIRED.


- Auth: requiere header `x-api-key`
- Cuota: una consulta Basic mensual compartida entre V1 y V2
- Planes: free, starter, growth, scale, custom

Parámetros:
- `plate` (string, requerido, query): Patente argentina (ABC123 o AB123CD)
- `classification` (string, opcional, query): Usá true para incluir clasificación inferida y cacheada del vehículo
- `onMiss` (string, opcional, query): Usá search para encolar una búsqueda nueva cuando la patente no está en la base histórica; responde 202 mientras se procesa
- `codia` (string, opcional, query): Usá true para incluir candidatos del catálogo CODIA activo
- `tires` (string, opcional, query): Usá true para incluir medidas de neumáticos compatibles de los catálogos activos

Headers específicos:
No aplica.

Ejemplos:
- Buscar cuando no existe en la base histórica: `https://api.clasific.ar/v1/vehicles/basic?plate=ABC123&onMiss=search` — Si no hay datos locales, devuelve 202 con status.state=searching.
- Agregar CODIA y medidas de neumáticos: `https://api.clasific.ar/v1/vehicles/basic?plate=ABC123&codia=true&tires=true`


Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "success": true,
  "data": {
    "plate": "ABC123",
    "make": "TOYOTA",
    "model": "COROLLA 1.8 SE-G CVT",
    "year": 2019,
    "firstRegistered": "2019-03-15",
    "totalOwners": 3,
    "currentOwners": 1,
    "currentLocation": {
      "city": "CAPITAL FEDERAL",
      "province": "Ciudad Autónoma de Buenos Aires"
    },
    "locations": [
      "CAPITAL FEDERAL, Ciudad Autónoma de Buenos Aires",
      "LA PLATA, Buenos Aires"
    ],
    "sourceDate": "2024-08-01",
    "classification": {
      "vehicleType": "AUTOMOTOR",
      "bodyType": "SEDAN",
      "vehicleCategory": "auto",
      "confidence": "medium",
      "source": "afip_model_match",
      "matchedModel": "COROLLA SE-G 1.8 CVT 4 PUERTAS",
      "matchedYear": 2019,
      "approximateMatch": true,
      "description": null,
      "cacheHit": false
    }
  }
}
```

Response 200 not found example:
```json
{
  "found": false
}
```


Response 202 Accepted — búsqueda en proceso example:
```json
{
  "success": true,
  "data": null,
  "status": {
    "state": "searching",
    "plate": "ABC123"
  }
}
```

Response 403 Forbidden — requiere plan pago example:
```json
{
  "error_code": "PAID_PLAN_REQUIRED",
  "message": "onMiss=search, codia=true, and tires=true require a paid or Custom plan."
}
```

---

### Crear consulta modular

`POST /v1/vehicles/modules`

Ejecuta de forma asíncrona sólo los módulos habilitados en el contrato Custom. El tiempo de resolución depende de los módulos y organismos consultados: esperá el resultado mediante polling o webhook, sin asumir una duración fija. Idempotency-Key es opcional pero recomendado. Mientras una patente tenga un job activo, no puede crearse otro: un request con los mismos módulos reutiliza el job y uno con módulos diferentes devuelve 409. Sólo un job realmente nuevo consume unidades.


- Auth: requiere header `x-api-key`
- Cuota: una unidad por módulo
- Planes: custom

Parámetros:
No aplica.

Headers específicos:
- `Idempotency-Key` (opcional): Recomendado para retries. La misma key + payload devuelve el job original sin doble consumo, incluso después de finalizar



Body JSON:
- `plate` (string, requerido, body): Patente argentina (ABC123 o AB123CD)
- `modules` (string[], requerido, body): Lista sin duplicados: fines, vtv y/o tax_debt

Request example:
```json
{
  "plate": "ABC123",
  "modules": [
    "fines",
    "vtv",
    "tax_debt"
  ]
}
```

Response 202 example:
```json
{
  "id": "vmq_0198abc123def456",
  "status": "queued",
  "plate": "ABC123",
  "modules": [
    "fines",
    "vtv",
    "tax_debt"
  ],
  "createdAt": "2026-08-14T12:00:00.000Z",
  "updatedAt": "2026-08-14T12:00:00.000Z",
  "completedAt": null
}
```


Response 200 OK — job activo reutilizado example:
```json
{
  "id": "vmq_0198abc123def456",
  "status": "processing",
  "plate": "ABC123",
  "modules": [
    "fines",
    "vtv"
  ],
  "createdAt": "2026-08-14T12:00:00.000Z",
  "updatedAt": "2026-08-14T12:00:01.000Z",
  "completedAt": null,
  "meta": {
    "reused": true,
    "reason": "query_in_progress"
  }
}
```

Response 409 Conflict — patente con otros módulos en curso example:
```json
{
  "error_code": "module_query_in_progress",
  "message": "Ya existe una consulta activa para esa patente con otros módulos.",
  "active_query": {
    "id": "vmq_0198abc123def456",
    "status": "processing",
    "plate": "ABC123",
    "modules": [
      "fines",
      "vtv"
    ],
    "createdAt": "2026-08-14T12:00:00.000Z",
    "updatedAt": "2026-08-14T12:00:01.000Z",
    "completedAt": null
  }
}
```

Response 409 Conflict — Idempotency-Key usada con otro payload example:
```json
{
  "error_code": "idempotency_conflict",
  "message": "Ese Idempotency-Key ya fue utilizado con otra consulta."
}
```

Response 403 Forbidden — módulo no contratado example:
```json
{
  "error_code": "module_not_enabled",
  "message": "Uno de los módulos solicitados no está habilitado en el contrato."
}
```

---

### Consultar estado modular

`GET /v1/vehicles/modules/:id`

Consulta el job sin consumo comercial adicional. El job usa queued, processing, completed o failed. Cada módulo usa queued, processing, completed, partial o failed; partial significa que terminó con datos utilizables pero con una reducción material de cobertura.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: custom

Parámetros:
- `id` (string, requerido, path): ID vmq_ devuelto al crear la consulta

Headers específicos:
No aplica.



Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "id": "vmq_0198abc123def456",
  "status": "completed",
  "plate": "ABC123",
  "modules": {
    "fines": {
      "status": "completed",
      "data": {
        "total": 1,
        "amountTotal": 47499.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2024-05-03T15:53:00-03:00",
            "amount": 47499.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "I06223680",
            "description": "Conducir sin portar la licencia",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      }
    },
    "vtv": {
      "status": "completed",
      "data": {
        "total": 1,
        "items": [
          {
            "type": "rto",
            "date": "2012-10-01",
            "sticker": null,
            "inspectionType": "unknown",
            "detail": "Uso Particular",
            "result": "approved",
            "certificate": "B-207381",
            "jurisdiction": "Nacional",
            "facility": "108",
            "dueDate": "2013-09-25"
          }
        ]
      }
    },
    "tax_debt": {
      "status": "completed",
      "data": {
        "total": 0,
        "amountTotal": 0,
        "currency": "ARS",
        "items": []
      }
    }
  },
  "createdAt": "2026-08-14T12:00:00.000Z",
  "updatedAt": "2026-08-14T12:00:18.000Z",
  "completedAt": "2026-08-14T12:00:18.000Z"
}
```


Response 200 OK — módulo con cobertura material reducida example:
```json
{
  "id": "vmq_0198abc123def456",
  "status": "completed",
  "plate": "ABC123",
  "modules": {
    "fines": {
      "status": "partial",
      "warnings": [
        {
          "code": "reduced_coverage",
          "message": "Some material coverage could not be verified during this request."
        }
      ],
      "data": {
        "total": 0,
        "amountTotal": 0,
        "currency": "ARS",
        "items": []
      }
    }
  },
  "createdAt": "2026-08-14T12:00:00.000Z",
  "updatedAt": "2026-08-14T12:00:18.000Z",
  "completedAt": "2026-08-14T12:00:18.000Z"
}
```

---

### Crear Reporte Inteligente

`POST /v1/reports`

Crea un reporte inteligente asincrónico y devuelve directamente el DTO público del job. El ID opaco usa prefijo rep_; si existe cache lista puede devolver el reporte final en la misma respuesta. Soporta Idempotency-Key para retries sin doble consumo. El resultado se envía al webhook activo configurado para el owner y el entorno.


- Auth: requiere header `x-api-key`
- Cuota: intelligent
- Planes: starter, growth, scale, custom

Parámetros:
No aplica.

Headers específicos:
- `Idempotency-Key` (opcional): Opcional y recomendado para retries sin crear otro reporte ni consumir cuota dos veces



Body JSON:
- `plate` (string, requerido, body): Patente argentina (ABC123 o AB123CD)

Request example:
```json
{
  "plate": "ABC123"
}
```

Response 202 example:
```json
{
  "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
  "status": "processing",
  "plate": "ABC123",
  "report": null,
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:00:03.000Z",
  "completedAt": null
}
```


Response 200 OK — reporte completado disponible example:
```json
{
  "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
  "status": "completed",
  "plate": "JFK106",
  "report": {
    "vehicle": {
      "plate": "JFK106",
      "make": "LAND ROVER",
      "model": "RANGE ROVER 3.6 HSE TDV8",
      "year": 2010,
      "vehicleType": "AUTOMOTOR",
      "firstRegistered": "2010-09-07",
      "totalOwners": 2,
      "currentOwners": 1,
      "registryOffice": "02044 - CAPITAL FEDERAL N 044",
      "currentLocation": {
        "city": "CABA",
        "province": "CAPITAL FEDERAL"
      },
      "locations": [
        "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
        "CABA, CAPITAL FEDERAL"
      ],
      "sourceDate": "2026-05-15",
      "queryDate": "2026-05-15",
      "quote": null
    },
    "possibleOwners": [
      {
        "name": "V******, S*",
        "id": {
          "type": "CUIT",
          "value": "30*"
        }
      }
    ],
    "results": {
      "fines": {
        "total": 1,
        "amountTotal": 427495.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2023-09-24T08:48:00-03:00",
            "amount": 427495.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "Q29775884",
            "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      },
      "vtv": {
        "total": 1,
        "items": [
          {
            "type": "vtv",
            "date": "2024-08-26",
            "sticker": "4581967",
            "inspectionType": "inspection",
            "detail": "Particulares",
            "result": "approved",
            "certificate": "6227143",
            "jurisdiction": "CABA",
            "facility": "9 de Julio Sur",
            "dueDate": "2025-06-01"
          }
        ]
      },
      "tax_debt": {
        "total": 1,
        "amountTotal": 235757.86,
        "currency": "ARS",
        "items": [
          {
            "period": "2026/3",
            "description": "POSICION",
            "dueDate": "2026-06-22",
            "amount": 235757.86,
            "updatedAmount": 235757.86,
            "status": "expired",
            "jurisdiction": "CABA"
          }
        ]
      },
      "cng": {
        "total": 1,
        "items": [
          {
            "operationDate": "2024-03-12",
            "workshop": "Taller GNC Ejemplo",
            "workshopTaxId": "30-00000000-0",
            "workshopCode": "GNC-123",
            "wafer": "12345678",
            "previousWafer": "87654321"
          }
        ]
      }
    },
    "digest": {
      "summary": "El historial registra infracciones y deuda de patente en CABA.",
      "headline": "Multas y deuda de patente detectadas",
      "riskLevel": "high",
      "highlights": [
        "1 infracción en CABA por al menos $427.496.",
        "Deuda de patente en CABA por aproximadamente $235.758."
      ],
      "issues": [
        {
          "title": "Infracciones en CABA",
          "severity": "high",
          "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
        }
      ],
      "positives": [],
      "noRecords": [],
      "ownershipEstimate": {
        "estimatedHistoricalOwnerCount": 2,
        "confidence": "medium",
        "basis": "El resumen registral consolidado muestra 2 titulares históricos."
      },
      "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
    }
  },
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:03:00.000Z",
  "completedAt": "2026-05-15T12:03:00.000Z"
}
```

---

### Consultar Estado de Reporte

`GET /v1/reports/:id`

Consulta el DTO público del reporte. El polling no consume cuota del plan ni expone contadores, passes, progreso o fuentes. Al completar, report contiene el informe normalizado completo.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: starter, growth, scale, custom

Parámetros:
- `id` (string, requerido, path): ID rep_ devuelto por POST /v1/reports

Headers específicos:
No aplica.

Ejemplos:
- Request: `https://api.clasific.ar/v1/reports/rep_8d2c7ef49d8b4c8fa27d1d8906229341`


Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
  "status": "completed",
  "plate": "JFK106",
  "report": {
    "vehicle": {
      "plate": "JFK106",
      "make": "LAND ROVER",
      "model": "RANGE ROVER 3.6 HSE TDV8",
      "year": 2010,
      "vehicleType": "AUTOMOTOR",
      "firstRegistered": "2010-09-07",
      "totalOwners": 2,
      "currentOwners": 1,
      "registryOffice": "02044 - CAPITAL FEDERAL N 044",
      "currentLocation": {
        "city": "CABA",
        "province": "CAPITAL FEDERAL"
      },
      "locations": [
        "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
        "CABA, CAPITAL FEDERAL"
      ],
      "sourceDate": "2026-05-15",
      "queryDate": "2026-05-15",
      "quote": null
    },
    "possibleOwners": [
      {
        "name": "V******, S*",
        "id": {
          "type": "CUIT",
          "value": "30*"
        }
      }
    ],
    "results": {
      "fines": {
        "total": 1,
        "amountTotal": 427495.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2023-09-24T08:48:00-03:00",
            "amount": 427495.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "Q29775884",
            "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      },
      "vtv": {
        "total": 1,
        "items": [
          {
            "type": "vtv",
            "date": "2024-08-26",
            "sticker": "4581967",
            "inspectionType": "inspection",
            "detail": "Particulares",
            "result": "approved",
            "certificate": "6227143",
            "jurisdiction": "CABA",
            "facility": "9 de Julio Sur",
            "dueDate": "2025-06-01"
          }
        ]
      },
      "tax_debt": {
        "total": 1,
        "amountTotal": 235757.86,
        "currency": "ARS",
        "items": [
          {
            "period": "2026/3",
            "description": "POSICION",
            "dueDate": "2026-06-22",
            "amount": 235757.86,
            "updatedAmount": 235757.86,
            "status": "expired",
            "jurisdiction": "CABA"
          }
        ]
      },
      "cng": {
        "total": 1,
        "items": [
          {
            "operationDate": "2024-03-12",
            "workshop": "Taller GNC Ejemplo",
            "workshopTaxId": "30-00000000-0",
            "workshopCode": "GNC-123",
            "wafer": "12345678",
            "previousWafer": "87654321"
          }
        ]
      }
    },
    "digest": {
      "summary": "El historial registra infracciones y deuda de patente en CABA.",
      "headline": "Multas y deuda de patente detectadas",
      "riskLevel": "high",
      "highlights": [
        "1 infracción en CABA por al menos $427.496.",
        "Deuda de patente en CABA por aproximadamente $235.758."
      ],
      "issues": [
        {
          "title": "Infracciones en CABA",
          "severity": "high",
          "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
        }
      ],
      "positives": [],
      "noRecords": [],
      "ownershipEstimate": {
        "estimatedHistoricalOwnerCount": 2,
        "confidence": "medium",
        "basis": "El resumen registral consolidado muestra 2 titulares históricos."
      },
      "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
    }
  },
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:03:00.000Z",
  "completedAt": "2026-05-15T12:03:00.000Z"
}
```


---

### Estado de Cuenta

`GET /v1/status`

Retorna el uso de cuotas a nivel owner y todas las API keys asociadas. En Custom incluye el estimado ARS de Basic + Reportes y, por separado, consumo de módulos en la moneda del período. Basic y módulos comparten consumo entre V1/V2; sharedUsage muestra el detalle por versión.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: free, starter, growth, scale, custom

Parámetros:
No aplica.

Headers específicos:
No aplica.



Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "success": true,
  "data": {
    "owner": "user_martin",
    "plan": "custom",
    "usage": {
      "fast": {
        "used": 42,
        "limit": 100
      },
      "intelligent": {
        "used": 2,
        "limit": 5
      },
      "miss": {
        "used": 8,
        "limit": 500
      }
    },
    "rateLimits": {
      "rpm_total": 60,
      "rpm_intelligent": 5
    },
    "resetAt": "2026-02-23T00:00:00.000Z",
    "customBilling": {
      "contract": {
        "status": "active"
      },
      "period": {
        "key": "2026-08",
        "start": "2026-08-01",
        "end": "2026-09-01"
      },
      "usage": {
        "basic": {
          "used": 43,
          "limit": 10000,
          "remaining": 9957
        },
        "report": {
          "used": 0,
          "limit": 10,
          "remaining": 10
        },
        "modules": {
          "used": 2004,
          "limit": 2000,
          "remaining": 0,
          "overage": 4,
          "subtotal_cents": 300,
          "currency": "USD"
        }
      },
      "billing": {
        "base": {
          "estimated_total_cents": 249400,
          "currency": "ARS",
          "pricing_mode": "automatic",
          "tier_name": "Basic"
        },
        "modules": {
          "estimated_overage_cents": 300,
          "currency": "USD"
        }
      }
    },
    "keys": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "prefix": "clk_AbCd",
        "last4": "xYzW",
        "name": "Production Key",
        "status": "active",
        "lastUsedAt": "2026-02-22T21:15:00.000Z",
        "createdAt": "2026-02-01T10:00:00.000Z"
      }
    ]
  }
}
```


---

### Ver configuración de Webhook

`GET /v1/webhooks/config`

Obtiene la URL, estado y secret del webhook del owner. No consume cuota de reporte.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: starter, growth, scale, custom

Parámetros:
No aplica.

Headers específicos:
No aplica.



Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "success": true,
  "data": {
    "owner_ref": "user_martin",
    "url": "https://tu-app.com/webhooks/clasificar",
    "secret": "whsec_1234567890",
    "enabled": true,
    "updated_at": "2026-05-15T12:00:00.000Z"
  }
}
```


---

### Actualizar Webhook

`PATCH /v1/webhooks/config`

Configura el webhook compartido por V1, V2 y Cuenta para el ambiente actual. rotate_secret=true rota el secreto de verificación de ambas versiones.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: starter, growth, scale, custom

Parámetros:
No aplica.

Headers específicos:
No aplica.



Body JSON:
- `url` (string | null, opcional, body): URL HTTPS que recibirá eventos. null desconfigura la URL.
- `enabled` (boolean, opcional, body): Activa o desactiva entregas.
- `rotate_secret` (boolean, opcional, body): Genera un secret nuevo para validar próximos eventos.

Request example:
```json
{
  "url": "https://tu-app.com/webhooks/clasificar",
  "enabled": true,
  "rotate_secret": false
}
```

Response 200 example:
```json
{
  "success": true,
  "data": {
    "owner_ref": "user_martin",
    "url": "https://tu-app.com/webhooks/clasificar",
    "secret": "whsec_1234567890",
    "enabled": true,
    "updated_at": "2026-05-15T12:00:00.000Z"
  }
}
```


---

### Listar entregas de Webhook

`GET /v1/webhooks/deliveries`

Lista entregas recientes. Permite filtrar por status=pending|processed|failed, limit y offset.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: starter, growth, scale, custom

Parámetros:
- `status` (string, opcional, query): pending, processed o failed
- `limit` (number, opcional, query): 1 a 100
- `offset` (number, opcional, query): Offset de paginación

Headers específicos:
No aplica.



Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "success": true,
  "data": {
    "deliveries": [
      {
        "id": "whd_123",
        "report_request_id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
        "module_query_id": null,
        "owner_ref": "user_martin",
        "environment": "production",
        "plate": "JFK106",
        "report_status": "completed",
        "event_type": "report.completed",
        "is_test": false,
        "url": "https://tu-app.com/webhooks/clasificar",
        "status": "processed",
        "attempts": 1,
        "max_attempts": 5,
        "response_status": 200,
        "last_error": null,
        "payload": {
          "eventId": "evt_7c684544c40e4e3a8d526eafc2f9514d",
          "event": "report.completed",
          "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
          "status": "completed",
          "plate": "JFK106",
          "report": {
            "vehicle": {
              "plate": "JFK106",
              "make": "LAND ROVER",
              "model": "RANGE ROVER 3.6 HSE TDV8",
              "year": 2010,
              "vehicleType": "AUTOMOTOR",
              "firstRegistered": "2010-09-07",
              "totalOwners": 2,
              "currentOwners": 1,
              "registryOffice": "02044 - CAPITAL FEDERAL N 044",
              "currentLocation": {
                "city": "CABA",
                "province": "CAPITAL FEDERAL"
              },
              "locations": [
                "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
                "CABA, CAPITAL FEDERAL"
              ],
              "sourceDate": "2026-05-15",
              "queryDate": "2026-05-15",
              "quote": null
            },
            "possibleOwners": [
              {
                "name": "V******, S*",
                "id": {
                  "type": "CUIT",
                  "value": "30*"
                }
              }
            ],
            "results": {
              "fines": {
                "total": 1,
                "amountTotal": 427495.5,
                "currency": "ARS",
                "items": [
                  {
                    "date": "2023-09-24T08:48:00-03:00",
                    "amount": 427495.5,
                    "status": "voluntary_payment",
                    "authority": null,
                    "reference": "Q29775884",
                    "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
                    "jurisdiction": "CABA",
                    "dueDate": null,
                    "discountedAmount": null
                  }
                ]
              },
              "vtv": {
                "total": 1,
                "items": [
                  {
                    "type": "vtv",
                    "date": "2024-08-26",
                    "sticker": "4581967",
                    "inspectionType": "inspection",
                    "detail": "Particulares",
                    "result": "approved",
                    "certificate": "6227143",
                    "jurisdiction": "CABA",
                    "facility": "9 de Julio Sur",
                    "dueDate": "2025-06-01"
                  }
                ]
              },
              "tax_debt": {
                "total": 1,
                "amountTotal": 235757.86,
                "currency": "ARS",
                "items": [
                  {
                    "period": "2026/3",
                    "description": "POSICION",
                    "dueDate": "2026-06-22",
                    "amount": 235757.86,
                    "updatedAmount": 235757.86,
                    "status": "expired",
                    "jurisdiction": "CABA"
                  }
                ]
              },
              "cng": {
                "total": 1,
                "items": [
                  {
                    "operationDate": "2024-03-12",
                    "workshop": "Taller GNC Ejemplo",
                    "workshopTaxId": "30-00000000-0",
                    "workshopCode": "GNC-123",
                    "wafer": "12345678",
                    "previousWafer": "87654321"
                  }
                ]
              }
            },
            "digest": {
              "summary": "El historial registra infracciones y deuda de patente en CABA.",
              "headline": "Multas y deuda de patente detectadas",
              "riskLevel": "high",
              "highlights": [
                "1 infracción en CABA por al menos $427.496.",
                "Deuda de patente en CABA por aproximadamente $235.758."
              ],
              "issues": [
                {
                  "title": "Infracciones en CABA",
                  "severity": "high",
                  "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
                }
              ],
              "positives": [],
              "noRecords": [],
              "ownershipEstimate": {
                "estimatedHistoricalOwnerCount": 2,
                "confidence": "medium",
                "basis": "El resumen registral consolidado muestra 2 titulares históricos."
              },
              "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
            }
          },
          "createdAt": "2026-05-15T12:00:00.000Z",
          "updatedAt": "2026-05-15T12:03:00.000Z",
          "completedAt": "2026-05-15T12:03:00.000Z"
        },
        "next_attempt_at": null,
        "processed_at": "2026-05-15T12:03:04.000Z",
        "created_at": "2026-05-15T12:03:00.000Z",
        "updated_at": "2026-05-15T12:03:04.000Z"
      }
    ],
    "total": 1
  }
}
```


---

### Enviar Webhook de Prueba

`POST /v1/webhooks/test`

Envía un report.completed de prueba usando la URL configurada. Puede probarse aunque las entregas automáticas estén desactivadas.


- Auth: requiere header `x-api-key`
- Cuota: analytics-only
- Planes: starter, growth, scale, custom

Parámetros:
No aplica.

Headers específicos:
No aplica.



Body JSON:
No aplica.

Request example:
No aplica.

Response 200 example:
```json
{
  "success": true,
  "data": {
    "id": "whd_test_123",
    "event_type": "report.completed",
    "is_test": true,
    "status": "pending",
    "payload": {
      "eventId": "evt_7c684544c40e4e3a8d526eafc2f9514d",
      "event": "report.completed",
      "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
      "status": "completed",
      "plate": "JFK106",
      "report": {
        "vehicle": {
          "plate": "JFK106",
          "make": "LAND ROVER",
          "model": "RANGE ROVER 3.6 HSE TDV8",
          "year": 2010,
          "vehicleType": "AUTOMOTOR",
          "firstRegistered": "2010-09-07",
          "totalOwners": 2,
          "currentOwners": 1,
          "registryOffice": "02044 - CAPITAL FEDERAL N 044",
          "currentLocation": {
            "city": "CABA",
            "province": "CAPITAL FEDERAL"
          },
          "locations": [
            "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
            "CABA, CAPITAL FEDERAL"
          ],
          "sourceDate": "2026-05-15",
          "queryDate": "2026-05-15",
          "quote": null
        },
        "possibleOwners": [
          {
            "name": "V******, S*",
            "id": {
              "type": "CUIT",
              "value": "30*"
            }
          }
        ],
        "results": {
          "fines": {
            "total": 1,
            "amountTotal": 427495.5,
            "currency": "ARS",
            "items": [
              {
                "date": "2023-09-24T08:48:00-03:00",
                "amount": 427495.5,
                "status": "voluntary_payment",
                "authority": null,
                "reference": "Q29775884",
                "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
                "jurisdiction": "CABA",
                "dueDate": null,
                "discountedAmount": null
              }
            ]
          },
          "vtv": {
            "total": 1,
            "items": [
              {
                "type": "vtv",
                "date": "2024-08-26",
                "sticker": "4581967",
                "inspectionType": "inspection",
                "detail": "Particulares",
                "result": "approved",
                "certificate": "6227143",
                "jurisdiction": "CABA",
                "facility": "9 de Julio Sur",
                "dueDate": "2025-06-01"
              }
            ]
          },
          "tax_debt": {
            "total": 1,
            "amountTotal": 235757.86,
            "currency": "ARS",
            "items": [
              {
                "period": "2026/3",
                "description": "POSICION",
                "dueDate": "2026-06-22",
                "amount": 235757.86,
                "updatedAmount": 235757.86,
                "status": "expired",
                "jurisdiction": "CABA"
              }
            ]
          },
          "cng": {
            "total": 1,
            "items": [
              {
                "operationDate": "2024-03-12",
                "workshop": "Taller GNC Ejemplo",
                "workshopTaxId": "30-00000000-0",
                "workshopCode": "GNC-123",
                "wafer": "12345678",
                "previousWafer": "87654321"
              }
            ]
          }
        },
        "digest": {
          "summary": "El historial registra infracciones y deuda de patente en CABA.",
          "headline": "Multas y deuda de patente detectadas",
          "riskLevel": "high",
          "highlights": [
            "1 infracción en CABA por al menos $427.496.",
            "Deuda de patente en CABA por aproximadamente $235.758."
          ],
          "issues": [
            {
              "title": "Infracciones en CABA",
              "severity": "high",
              "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
            }
          ],
          "positives": [],
          "noRecords": [],
          "ownershipEstimate": {
            "estimatedHistoricalOwnerCount": 2,
            "confidence": "medium",
            "basis": "El resumen registral consolidado muestra 2 titulares históricos."
          },
          "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
        }
      },
      "createdAt": "2026-05-15T12:00:00.000Z",
      "updatedAt": "2026-05-15T12:03:00.000Z",
      "completedAt": "2026-05-15T12:03:00.000Z"
    }
  }
}
```



## Webhooks

Los webhooks se envían como `POST` al endpoint configurado. Tu endpoint debe devolver cualquier `2xx` para marcar la entrega como procesada.

Config:
- URL única por owner y entorno: `PATCH /v1/webhooks/config`.
- Intelligent y Modules usan esta misma configuración; los requests no aceptan una URL alternativa.
- Secret: devuelto por `GET /v1/webhooks/config`.

Headers enviados:
- `x-clasificar-event`: `report.completed`, `report.failed` o `vehicle.modules.completed`.
- `x-clasificar-delivery`: ID único de entrega para idempotencia/logs.
- `x-clasificar-timestamp`: epoch Unix usado para limitar replay.
- `x-clasificar-signature`: HMAC-SHA256 `v1=<hex>` de `timestamp.rawBody` con el secret.

Validación recomendada:
- Rechazar timestamps fuera de una ventana breve (por ejemplo, cinco minutos).
- Recalcular el HMAC sobre el body crudo y compararlo en tiempo constante con `x-clasificar-signature`.
- Procesar cada `x-clasificar-delivery` de forma idempotente.

Eventos:
- `report.completed`: El reporte inteligente terminó correctamente. Payload: Agrega eventId y event al mismo DTO público que devuelve el polling; report contiene el informe normalizado.
- `report.failed`: El reporte inteligente no pudo completarse. Payload: Agrega eventId y event al mismo DTO público fallido; report=null y error usa un código estable.
- `vehicle.modules.completed`: Una consulta modular terminó en completed o failed. Payload: Incluye el mismo lifecycle público (id, status, plate y timestamps) y el resultado completed/partial/failed de cada módulo.

Estados de entrega:
- `pending`: Entrega pendiente o esperando reintento.
- `processed`: Tu endpoint respondió 2xx y la entrega quedó confirmada.
- `failed`: Se agotaron los intentos o el endpoint respondió error.

Payload `report.completed`:
```json
{
  "eventId": "evt_7c684544c40e4e3a8d526eafc2f9514d",
  "event": "report.completed",
  "id": "rep_8d2c7ef49d8b4c8fa27d1d8906229341",
  "status": "completed",
  "plate": "JFK106",
  "report": {
    "vehicle": {
      "plate": "JFK106",
      "make": "LAND ROVER",
      "model": "RANGE ROVER 3.6 HSE TDV8",
      "year": 2010,
      "vehicleType": "AUTOMOTOR",
      "firstRegistered": "2010-09-07",
      "totalOwners": 2,
      "currentOwners": 1,
      "registryOffice": "02044 - CAPITAL FEDERAL N 044",
      "currentLocation": {
        "city": "CABA",
        "province": "CAPITAL FEDERAL"
      },
      "locations": [
        "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
        "CABA, CAPITAL FEDERAL"
      ],
      "sourceDate": "2026-05-15",
      "queryDate": "2026-05-15",
      "quote": null
    },
    "possibleOwners": [
      {
        "name": "V******, S*",
        "id": {
          "type": "CUIT",
          "value": "30*"
        }
      }
    ],
    "results": {
      "fines": {
        "total": 1,
        "amountTotal": 427495.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2023-09-24T08:48:00-03:00",
            "amount": 427495.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "Q29775884",
            "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      },
      "vtv": {
        "total": 1,
        "items": [
          {
            "type": "vtv",
            "date": "2024-08-26",
            "sticker": "4581967",
            "inspectionType": "inspection",
            "detail": "Particulares",
            "result": "approved",
            "certificate": "6227143",
            "jurisdiction": "CABA",
            "facility": "9 de Julio Sur",
            "dueDate": "2025-06-01"
          }
        ]
      },
      "tax_debt": {
        "total": 1,
        "amountTotal": 235757.86,
        "currency": "ARS",
        "items": [
          {
            "period": "2026/3",
            "description": "POSICION",
            "dueDate": "2026-06-22",
            "amount": 235757.86,
            "updatedAmount": 235757.86,
            "status": "expired",
            "jurisdiction": "CABA"
          }
        ]
      },
      "cng": {
        "total": 1,
        "items": [
          {
            "operationDate": "2024-03-12",
            "workshop": "Taller GNC Ejemplo",
            "workshopTaxId": "30-00000000-0",
            "workshopCode": "GNC-123",
            "wafer": "12345678",
            "previousWafer": "87654321"
          }
        ]
      }
    },
    "digest": {
      "summary": "El historial registra infracciones y deuda de patente en CABA.",
      "headline": "Multas y deuda de patente detectadas",
      "riskLevel": "high",
      "highlights": [
        "1 infracción en CABA por al menos $427.496.",
        "Deuda de patente en CABA por aproximadamente $235.758."
      ],
      "issues": [
        {
          "title": "Infracciones en CABA",
          "severity": "high",
          "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
        }
      ],
      "positives": [],
      "noRecords": [],
      "ownershipEstimate": {
        "estimatedHistoricalOwnerCount": 2,
        "confidence": "medium",
        "basis": "El resumen registral consolidado muestra 2 titulares históricos."
      },
      "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
    }
  },
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:03:00.000Z",
  "completedAt": "2026-05-15T12:03:00.000Z"
}
```

Payload `report.failed`:
```json
{
  "eventId": "evt_9fd393058c5c4a87ab65ff507f0b4db1",
  "event": "report.failed",
  "id": "rep_fbeccf4d234d4dd289e476590c32e249",
  "status": "failed",
  "plate": "ABC123",
  "report": null,
  "createdAt": "2026-05-15T09:45:18.323Z",
  "updatedAt": "2026-05-15T09:46:39.093Z",
  "completedAt": "2026-05-15T09:46:39.093Z",
  "error": {
    "code": "report_generation_failed",
    "message": "The report could not be generated."
  }
}
```

Payload sandbox `report.completed`:
```json
{
  "eventId": "evt_22222222222242228222222222222222",
  "event": "report.completed",
  "id": "rep_11111111111141118111111111111111",
  "status": "completed",
  "plate": "CLIENTE123",
  "report": {
    "vehicle": {
      "plate": "CLIENTE123",
      "make": "TOYOTA",
      "model": "COROLLA XEI CVT",
      "year": 2021,
      "vehicleType": "AUTOMOTOR",
      "firstRegistered": "2010-09-07",
      "totalOwners": 2,
      "currentOwners": 1,
      "registryOffice": "02044 - CAPITAL FEDERAL N 044",
      "currentLocation": {
        "city": "CABA",
        "province": "CAPITAL FEDERAL"
      },
      "locations": [
        "C.AUTONOMA DE BS.AS, Ciudad Autonoma de Buenos Aires",
        "CABA, CAPITAL FEDERAL"
      ],
      "sourceDate": "2026-05-15",
      "queryDate": "2026-05-15",
      "quote": null
    },
    "possibleOwners": [
      {
        "name": "V******, S*",
        "id": {
          "type": "CUIT",
          "value": "30*"
        }
      }
    ],
    "results": {
      "fines": {
        "total": 1,
        "amountTotal": 427495.5,
        "currency": "ARS",
        "items": [
          {
            "date": "2023-09-24T08:48:00-03:00",
            "amount": 427495.5,
            "status": "voluntary_payment",
            "authority": null,
            "reference": "Q29775884",
            "description": "Exceso de velocidad de 10% a 30% más de la velocidad permitida",
            "jurisdiction": "CABA",
            "dueDate": null,
            "discountedAmount": null
          }
        ]
      },
      "vtv": {
        "total": 1,
        "items": [
          {
            "type": "vtv",
            "date": "2024-08-26",
            "sticker": "4581967",
            "inspectionType": "inspection",
            "detail": "Particulares",
            "result": "approved",
            "certificate": "6227143",
            "jurisdiction": "CABA",
            "facility": "9 de Julio Sur",
            "dueDate": "2025-06-01"
          }
        ]
      },
      "tax_debt": {
        "total": 1,
        "amountTotal": 235757.86,
        "currency": "ARS",
        "items": [
          {
            "period": "2026/3",
            "description": "POSICION",
            "dueDate": "2026-06-22",
            "amount": 235757.86,
            "updatedAmount": 235757.86,
            "status": "expired",
            "jurisdiction": "CABA"
          }
        ]
      },
      "cng": {
        "total": 1,
        "items": [
          {
            "operationDate": "2024-03-12",
            "workshop": "Taller GNC Ejemplo",
            "workshopTaxId": "30-00000000-0",
            "workshopCode": "GNC-123",
            "wafer": "12345678",
            "previousWafer": "87654321"
          }
        ]
      }
    },
    "digest": {
      "summary": "El historial registra infracciones y deuda de patente en CABA.",
      "headline": "Multas y deuda de patente detectadas",
      "riskLevel": "high",
      "highlights": [
        "1 infracción en CABA por al menos $427.496.",
        "Deuda de patente en CABA por aproximadamente $235.758."
      ],
      "issues": [
        {
          "title": "Infracciones en CABA",
          "severity": "high",
          "detail": "Registra 1 infracción por al menos $427.496 en pago voluntario."
        }
      ],
      "positives": [],
      "noRecords": [],
      "ownershipEstimate": {
        "estimatedHistoricalOwnerCount": 2,
        "confidence": "medium",
        "basis": "El resumen registral consolidado muestra 2 titulares históricos."
      },
      "recommendation": "Pedí el detalle actualizado de multas y deuda antes de avanzar."
    }
  },
  "createdAt": "2026-07-07T12:00:00.000Z",
  "updatedAt": "2026-07-07T12:01:44.000Z",
  "completedAt": "2026-07-07T12:01:44.000Z"
}
```

Payload sandbox `report.failed`:
```json
{
  "eventId": "evt_33333333333343338333333333333333",
  "event": "report.failed",
  "id": "rep_44444444444444448444444444444444",
  "status": "failed",
  "plate": "CLIENTE123",
  "report": null,
  "createdAt": "2026-07-07T12:00:00.000Z",
  "updatedAt": "2026-07-07T12:01:17.000Z",
  "completedAt": "2026-07-07T12:01:17.000Z",
  "error": {
    "code": "report_generation_failed",
    "message": "The report could not be generated."
  }
}
```

## Errores

Formato de errores de autenticación, permisos y cuota:
```json
{
  "error_code": "PAID_PLAN_REQUIRED",
  "message": "The requested option requires a paid or Custom plan."
}
```

Algunos errores de validación conservan el formato compatible:
```json
{
  "success": false,
  "error": "Mensaje de error legible"
}
```

Códigos:
- HTTP 401, `INVALID_API_KEY`: API key inválida, revocada o faltante
- HTTP 403, `PAID_PLAN_REQUIRED`: La opción solicitada requiere un plan pago o Custom
- HTTP 429, `RATE_LIMIT_EXCEEDED`: Límite RPM compartido por cuenta y entorno entre V1 y V2
- HTTP 429, `QUOTA_EXCEEDED`: Cupo Basic compartido agotado; details identifica el límite y su reset
- HTTP 429, `QUOTA_EXCEEDED_INTELLIGENT`: Cuota diaria intelligent agotada
- HTTP 429, `MONTHLY_QUOTA_EXCEEDED`: Cuota mensual agotada
- HTTP 429, `MISS_LIMIT_EXCEEDED`: Control legacy de misses para operaciones V1 que aún lo aplican; no rige Basic autenticado
- HTTP 403, `custom_plan_required`: El endpoint de módulos requiere un contrato Custom
- HTTP 403, `module_not_enabled`: Uno o más módulos no están habilitados en el contrato
- HTTP 409, `idempotency_conflict`: La Idempotency-Key ya fue utilizada con otro payload
- HTTP 409, `module_query_in_progress`: Ya existe una consulta activa para la patente con otro conjunto de módulos

Ejemplo de error de cuota:
```json
{
  "success": false,
  "error_code": "QUOTA_EXCEEDED",
  "message": "Account limit exceeded.",
  "reset_at": "2026-02-23T00:00:00.000Z"
}
```


---

Fuente: [Clasificar](https://clasific.ar) — Base de datos vehiculares argentinos.
Contenido informativo. No reemplaza una consulta oficial ni una validación legal.
