# Documentación API Nexcar

> Referencia para integradores: casos, documentos, webhooks y tipos soportados.

> Archivo generado automáticamente: una sola pieza de Markdown con todas las páginas, pensada como contexto para LLMs.

---

## Introducción

_La API de Nexcar te permite digitalizar y validar casos vehiculares — facturas, INE, REPUVE, tenencias y más — desde tus propios sistemas._

### ¿Qué es la API de Nexcar?

Nexcar procesa **casos vehiculares** de extremo a extremo: tú nos envías documentos (factura, identificación del titular, comprobante de pago de tenencia, consulta REPUVE, comprobante de domicilio, etc.) y nosotros aplicamos OCR, clasificación automática y validaciones de negocio para que tu equipo o tu sistema reciba datos estructurados listos para decidir.

La API está pensada para integraciones con:

- **Aseguradoras** que reciben siniestros y necesitan validar la documentación del vehículo afectado.
- **Compradores y vendedores de seminuevos** que requieren una verificación documental rápida.
- **Plataformas financieras** que originan crédito automotriz y deben validar al titular.
- **Reguladoras de cumplimiento** que necesitan trazabilidad de cada documento.

### Conceptos clave

- **Caso (`case`)**: el contenedor lógico de un vehículo. Cada caso tiene un `case_id` (UUID) y agrupa todos los documentos asociados a ese vehículo.
- **Documento (`document`)**: un archivo individual (PDF, imagen, XML) que pertenece a un caso. Cada documento se identifica con un `document_id`.
- **Tipo de documento**: la categoría del archivo (factura, INE, REPUVE, tenencia, etc.). Puede asignarse manualmente al subir o detectarse automáticamente.
- **Webhook**: notificación HTTP que enviamos a tu servidor cuando un documento o un caso cambia de estado (carga completada, OCR listo, validación finalizada).

### Flujo típico de integración

1. **Crea un caso** con `POST /v1/cases`. Recibes un `case_id`.
2. **Adjunta documentos** al caso con `POST /v1/documents`, uno por archivo. Cada uno devuelve un `document_id`.
3. **Recibe webhooks** conforme avanzan los procesos: clasificación, OCR, extracción de datos.
4. **Consulta resultados** con `GET /v1/documents/{id}` o `GET /v1/cases/{id}` cuando lo necesites.

### Ambientes

| Ambiente | Base URL | Uso |
|---|---|---|
| Producción | `https://api.nexcar.mx` | Tráfico real |
| Sandbox | `https://api-sandbox.nexcar.mx` | Pruebas e integración |

### Soporte

¿Listo para integrar? Continúa en [Autenticación](/docs/es/autenticacion) o escríbenos a [soporte@nexcar.mx](mailto:soporte@nexcar.mx).

---

## Autenticación

_La API acepta dos métodos de autenticación. Elige el que mejor se ajuste a tu integración._

### Métodos disponibles

| Método | Encabezado | Recomendado para |
|---|---|---|
| **API Key** | `x-api-key: <api_key>` | Integraciones servidor-a-servidor de larga vida |
| **Bearer Token (JWT)** | `Authorization: Bearer <token>` | Integraciones donde el token se renueva por sesión |

> Todos los endpoints de las series `/v1/cases` y `/v1/documents` aceptan al menos uno de los dos métodos.

### Obtener credenciales

Solicita tus credenciales a tu contacto en Nexcar o a [soporte@nexcar.mx](mailto:soporte@nexcar.mx). Recibirás una `api_key` para producción y otra para el ambiente de sandbox.

> **Nunca expongas tu `api_key` en clientes web ni la subas a un repositorio público.**

### Ejemplo: subir un documento con API Key

```bash
curl -X POST https://api.nexcar.mx/v1/documents \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
    "url": "https://tu-storage.example.com/factura-vehiculo.pdf",
    "mime_type": "application/pdf",
    "type": "factura"
  }'
```

### Ejemplo: crear caso con Bearer Token

```bash
curl -X POST https://api.nexcar.mx/v1/cases \
  -H "Authorization: Bearer tu_token_jwt" \
  -H "Content-Type: application/json" \
  -d '{
    "internal_id": "EXP-2026-0001",
    "vehicle_origin": "Nacional",
    "status": "processing"
  }'
```

### Errores de autenticación

| HTTP | Código | Causa |
|---|---|---|
| `401` | `UNAUTHORIZED` | Falta el encabezado o el token expiró |
| `401` | `INVALID_CREDENTIALS` | API key inválida o token mal firmado |
| `403` | `FORBIDDEN` | Las credenciales son válidas pero no tienen permiso sobre el recurso |

---

## Casos

_Un caso representa un vehículo. Aquí defines el `case_id` que vas a usar para adjuntar documentos y consultar resultados._

### Endpoints

| Método | Ruta | Descripción |
|---|---|---|
| `POST` | `/v1/cases` | Crea un caso, opcionalmente con documentos iniciales |
| `GET` | `/v1/cases/{case_id}` | Consulta el caso y sus documentos con OCR |
| `DELETE` | `/v1/cases/{case_id}` | Da de baja el caso (baja lógica) |
| `GET` | `/v1/cases/{case_id}/names` | Nombres consolidados detectados en los documentos |
| `GET` | `/v1/cases/{case_id}/vins` | VIN principal y VINs detectados por documento |
| `GET` | `/v1/cases/{case_id}/plates` | Placas detectadas en el caso con datos de vigencia |
| `GET` | `/v1/cases/{case_id}/repuve` | Datos de inscripción REPUVE del vehículo |
| `GET` | `/v1/cases/{case_id}/pedimento` | Resultado más reciente de consulta de pedimento |
| `POST` | `/v1/cases/{case_id}/pedimento` | Lanza una nueva consulta de pedimento con datos del OCR |
| `GET` | `/v1/cases/{case_id}/rapi` | Resultado más reciente de consulta RAPI (FGJCDMX actividad ilícita) |
| `POST` | `/v1/cases/{case_id}/rapi` | Lanza una consulta RAPI para el VIN principal del caso |
| `POST` | `/v1/cases/{case_id}/taxes` | Lanza consulta masiva de tenencias para todas las placas del caso |
| `GET` | `/v1/cases/{case_id}/invoices` | Facturas asociadas con su estado de procesamiento |
| `GET` | `/v1/cases/{case_id}/metadata` | Datos de negocio asociados al caso |
| `POST` | `/v1/cases/{case_id}/metadata` | Agrega una entrada al historial de metadata |
| `POST` | `/v1/cases/{case_id}/process` | Lanza el procesamiento masivo de los documentos |
| `GET` | `/v1/cases/{case_id}/status` | Estado del último job de procesamiento |

### POST `/v1/cases` — Crear caso

Crea un caso para un vehículo. Puedes adjuntar documentos en la misma llamada o subirlos después.

#### Cuerpo

| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| `internal_id` | string | Sí | Identificador del caso en tu sistema. No puede estar vacío. |
| `status` | string | Sí | Estado inicial del caso (p. ej. `processing`, `nuevo`). |
| `vehicle_origin` | string | No | `Nacional` (default) o `Importado`. |
| `initial_status` | string | No | Estado inicial alternativo cuando se configuró un catálogo de estatus. |
| `use_case` | UUID | No | Caso de uso del expediente cuando aplique catálogo. |
| `metadata` | object | No | Datos de negocio adicionales (ver más abajo). |
| `files` | array | No | Documentos a cargar de inmediato. |

#### Ejemplo con curl

```bash
curl -X POST https://api.nexcar.mx/v1/cases \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "internal_id": "EXP-2026-0001",
    "status": "processing",
    "vehicle_origin": "Nacional",
    "metadata": {
      "numero_siniestro": "128890890",
      "tipo_afectado": "TERCERO",
      "nombre_afectado": "JUAN PÉREZ GARCÍA"
    },
    "files": [
      {
        "url": "https://tu-storage.example.com/factura.pdf",
        "mime_type": "application/pdf",
        "document_type": "factura"
      },
      {
        "url": "https://tu-storage.example.com/ine-titular.jpg",
        "mime_type": "image/jpeg",
        "document_type": "ine"
      }
    ]
  }'
```

#### Cuerpo del request

```json
{
  "internal_id": "EXP-2026-0001",
  "status": "processing",
  "vehicle_origin": "Nacional",
  "metadata": {
    "numero_siniestro": "128890890",
    "tipo_afectado": "TERCERO",
    "nombre_afectado": "JUAN PÉREZ GARCÍA"
  },
  "files": [
    {
      "url": "https://tu-storage.example.com/factura.pdf",
      "mime_type": "application/pdf",
      "document_type": "factura"
    },
    {
      "url": "https://tu-storage.example.com/ine-titular.jpg",
      "mime_type": "image/jpeg",
      "document_type": "ine"
    }
  ]
}
```

#### Respuestas

**`201 Created`** — caso creado sin archivos:

```json
{ "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c", "status": "completed" }
```

**`202 Accepted`** — caso creado con archivos en procesamiento:

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "job_id": "31b4ecf2-9a0b-4f98-8a91-2ab6e93f4f10",
  "total_files": 2,
  "status": "processing"
}
```

#### Errores comunes

| HTTP | Código | Causa |
|---|---|---|
| `400` | `VALIDATION_ERROR` | `status` ausente o `internal_id` vacío |
| `400` | `DUPLICATE_INTERNAL_ID` | Ya existe un caso con ese `internal_id` y archivos cargados |
| `422` | `INVALID_STATUS` | `status` no está dentro del catálogo configurado |

### GET `/v1/cases/{case_id}`

Devuelve el caso y todos sus documentos con su OCR (cuando esté disponible).

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "internal_id": "EXP-2026-0001",
  "status": "nuevo",
  "vehicle_origin": "Nacional",
  "documents": [
    {
      "document_id": "abc123",
      "type": "factura",
      "mime_type": "application/pdf",
      "url": "https://...nexcar.mx/storage/.../factura.pdf",
      "parsed_data": { "vin": "3VWFE21C04M000001", "monto_total": 285000 }
    }
  ]
}
```

### DELETE `/v1/cases/{case_id}`

Marca el caso como inactivo. No borra archivos físicamente; deja de aparecer en consultas y reportes.

```bash
curl -X DELETE https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c \
  -H "x-api-key: tu_api_key"
```

### POST `/v1/cases/{case_id}/process`

Encola el procesamiento masivo (OCR + extracción) de los documentos del caso. Útil cuando subiste archivos sin tipo y quieres lanzar el flujo en batch.

```bash
curl -X POST https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/process \
  -H "x-api-key: tu_api_key"
```

```json
{ "job_id": "31b4ecf2-9a0b-4f98-8a91-2ab6e93f4f10", "status": "processing" }
```

Consulta el avance con `GET /v1/cases/{case_id}/status`.

### GET `/v1/cases/{case_id}/plates`

Devuelve las placas detectadas en los documentos del caso, enriquecidas con NIV, número de motor, nombre del propietario y — para placas emplaçadas en el Estado de México — los datos de vigencia (estatus, fechas de vigencia y placa anterior cuando aplique) consultados al portal oficial.

La lista se construye y persiste automáticamente como parte del pipeline de procesamiento del caso. Este endpoint es de solo lectura.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/plates \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "internal_id": "EXP-2026-0001",
  "plates": [
    {
      "plate": "MJM626C",
      "entity": "MEX",
      "document_type": "vehicle_certificate",
      "document_date": "01/09/2025",
      "file_id": "0126537c-8dad-402a-a559-0bbbcafd53a3",
      "ocr_id": "f2dd2062-d11d-480b-9c0b-1b6d03b6f1df",
      "niv": "JN1BE6DSXS9121418",
      "motor_number": "QR258585700Q",
      "owner_name": null,
      "plate_status": "Vigente",
      "plate_valid_from": "01/09/2025",
      "plate_valid_until": "01/09/2030",
      "previous_plate": null,
      "previous_plate_lookup_status": null
    }
  ]
}
```

#### Referencia de campos

| Campo | Tipo | Descripción |
|---|---|---|
| `plate` | string | Placa normalizada (mayúsculas, sin separadores) |
| `entity` | string | Código de tres letras del estado (`MEX`, `NLE`, `JAL`, …) |
| `document_type` | string | Documento de origen: `certificate_title`, `tax_payment`, `alta_vehicular`, `vehicle_certificate`, `vehicle_plate`, `lumo_checklist` o `repuve` |
| `document_date` | string | Fecha asociada al documento de origen (`DD/MM/YYYY` cuando esté disponible) |
| `niv` | string \| null | Número de identificación vehicular (17 caracteres cuando está disponible; parcial en otros casos) |
| `motor_number` | string \| null | Número de motor del documento de origen |
| `owner_name` | string \| null | Propietario declarado en el documento de origen |
| `plate_status` | string \| null | Estatus actual de la placa según el portal: `Vigente`, `Vencida` o `Inactiva` |
| `plate_valid_from` | string \| null | Inicio de vigencia de la placa actual (`DD/MM/YYYY`) |
| `plate_valid_until` | string \| null | Fin de vigencia de la placa actual (`DD/MM/YYYY`) |
| `previous_plate` | string \| null | Placa anterior, cuando existió una previa a la actual |
| `previous_plate_lookup_status` | string \| null | Estado de la consulta — ver tabla |

#### Valores de `previous_plate_lookup_status`

| Valor | Significado |
|---|---|
| `null` | La consulta no aplica (placa no emplaçada en MEX) o el portal confirmó que no hay placa anterior |
| `"pending"` | Consulta en curso en background — vuelve a llamar en unos segundos |
| `"found"` | El portal devolvió una placa anterior (poblada en `previous_plate`) |
| `"error"` | La consulta falló tras los reintentos (portal caído, error de parseo, etc.) |

La misma placa puede aparecer en varios elementos del arreglo cuando se detectó en más de un documento. Todos los elementos con la misma placa comparten `plate_status`, fechas de vigencia y campos `previous_plate_*` una vez que el lookup en background termina.

#### Errores comunes

| HTTP | Causa |
|---|---|
| `404` | Caso no encontrado, inactivo, o aún no se han calculado las placas para este caso |

### GET `/v1/cases/{case_id}/repuve`

Retorna el job de REPUVE más reciente para el caso. Si el job terminó en error pero existe una respuesta válida de placas.info, el endpoint aplica auto-recuperación y devuelve los datos correctos.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/repuve \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "codigo": "ok",
  "info": {
    "niv": "3N6AD35A9LK875826",
    "placa": "Y83BGS",
    "marca": "NISSAN",
    "modelo": "NP300/NP300 FRONTIER/FRONTIER",
    "tipo": "CAB. Y CHASIS ESTACAS",
    "clase": "CAMIONETA",
    "anio_modelo": 2020,
    "entidad_emplacado": "CIUDAD DE MEXICO",
    "fecha_inscripcion": "22/09/20",
    "fecha_actualizacion": "12/10/24",
    "reporte_robo": [
      { "tipo_reporte": "FGJ", "estatus": "SIN REPORTE DE ROBO" },
      { "tipo_reporte": "OCRA", "estatus": "SIN REPORTE DE ROBO" }
    ],
    "robo_usa_can": { "tiene_robo": false },
    "aviso_judicial": { "tiene_aviso_judicial": false }
  },
  "job_id": "4a201139-7945-4ae7-8a17-dc8e048c7437",
  "job_status": "completed"
}
```

#### Referencia de campos

| Campo | Tipo | Descripción |
|---|---|---|
| `codigo` | string | `ok` — datos de REPUVE disponibles. `error` — todos los servicios fallaron. `processing` — job aún en curso. |
| `info` | object \| null | Datos de inscripción del vehículo en REPUVE. `null` cuando el vehículo no está inscrito o el job no ha completado. |
| `info.niv` | string | Número de identificación vehicular (NIV/VIN) |
| `info.placa` | string \| null | Placa registrada en REPUVE |
| `info.marca` | string | Marca |
| `info.modelo` | string | Modelo |
| `info.anio_modelo` | integer | Año modelo |
| `info.entidad_emplacado` | string \| null | Estado de emplacamiento |
| `info.fecha_inscripcion` | string \| null | Fecha de inscripción en REPUVE |
| `info.fecha_actualizacion` | string \| null | Fecha de última actualización en REPUVE |
| `info.reporte_robo` | array | Reportes de robo de las fuentes FGJ y OCRA |
| `info.robo_usa_can` | object | Estado de reporte de robo en USA/Canadá |
| `info.aviso_judicial` | object | Estado de aviso judicial |
| `job_id` | UUID | Identificador interno del job |
| `job_status` | string | `completed`, `error`, `processing` o `pending` |

#### Errores comunes

| HTTP | Causa |
|---|---|
| `404` | Caso no encontrado, inactivo, o no se ha realizado ninguna consulta REPUVE para este caso |

### GET `/v1/cases/{case_id}/pedimento`

Devuelve el resultado más reciente de consulta de pedimento para el caso. Los datos se leen de `pedimento_responses` (pipeline legacy) y `processing_jobs` (API v1), se deduplican por contenido y se ordenan por fecha.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/pedimento \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "job_id": "4a201139-7945-4ae7-8a17-dc8e048c7437",
  "status": "completed",
  "source": "processing_jobs",
  "result": {
    "success": true,
    "data": { "pedimento": "39317003384", "aduana": "MANZANILLO", "vin": "LUCGM6669J3104807" }
  }
}
```

#### Errores comunes

| HTTP | Causa |
|---|---|
| `404` | Caso no encontrado, inactivo, o no se ha realizado ninguna consulta de pedimento. Llama a `POST /v1/cases/{id}/pedimento` primero. |

### GET `/v1/cases/{case_id}/taxes` (sin `?plate`)

Devuelve todos los resultados de tenencias por placa del caso, agrupados por placa. Lee de `processing_jobs` en todas las fuentes (pipeline en background y consulta on-demand v1).

Cuando se proporciona `?plate` el comportamiento original de consulta por placa individual se mantiene sin cambios.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/taxes \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "plates_count": 2,
  "taxes": [
    {
      "plate": "MES452A",
      "job_id": "abc123",
      "status": "completed",
      "source": "api_v1_cases_taxes_bulk",
      "result": { "codigo": "ok", "info": [...] },
      "created_at": "2026-06-05T10:00:00+00:00"
    }
  ]
}
```

### POST `/v1/cases/{case_id}/taxes`

Lanza una consulta masiva de tenencias para todas las placas únicas del caso (placas principales + placas anteriores), usando los datos de `GET /v1/cases/:id/plates` internamente (sin HTTP). Se crea un registro en `processing_jobs` por cada placa con su propio `job_id`, `external_payload` y `external_response`. El workflow corre en Temporal (cola `invoice-background-queue`).

#### Ejemplo con curl

```bash
curl -X POST https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/taxes \
  -H "x-api-key: tu_api_key"
```

#### Respuesta `202 Accepted`

```json
{
  "job_id": "31b4ecf2-9a0b-4f98-8a91-2ab6e93f4f10",
  "plates": ["MES452A", "NDJ3622"],
  "status": "pending"
}
```

Consulta `GET /v1/cases/{case_id}/taxes` (sin `?plate`) para ver los resultados por placa a medida que se completan.

#### Errores comunes

| HTTP | Causa |
|---|---|
| `400` | No se encontraron placas para este caso. Llama a `POST /v1/cases/{id}/plates` primero. |
| `404` | Caso no encontrado o inactivo |

### POST `/v1/cases/{case_id}/pedimento`

Inicia una nueva consulta de pedimento usando los datos extraídos del OCR del documento principal del caso (la factura vinculada al VIN principal). Se ejecuta de forma asíncrona en Temporal. Devuelve un `job_id` inmediatamente; consulta `GET /v1/cases/{id}/pedimento` para obtener el resultado.

Los parámetros requeridos (`aduana`, `ano_vehiculo`) se leen del OCR automáticamente. Si no están disponibles en el documento, el endpoint devuelve un `400` indicando los campos que faltan.

#### Ejemplo con curl

```bash
curl -X POST https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/pedimento \
  -H "x-api-key: tu_api_key"
```

#### Respuesta `202 Accepted`

```json
{ "job_id": "4a201139-7945-4ae7-8a17-dc8e048c7437", "status": "pending" }
```

#### Errores comunes

| HTTP | Causa |
|---|---|
| `400` | No se encontró VIN principal, o faltan campos OCR requeridos (`aduana`, `ano_vehiculo`) |
| `404` | Caso no encontrado o inactivo |

### GET `/v1/cases/{case_id}/rapi`

Devuelve el resultado más reciente de consulta RAPI (FGJCDMX — actividad ilícita vehicular) para el caso. Lee de `rapi_responses` (pipeline legacy) y `processing_jobs` (API v1), deduplicados por contenido y ordenados por fecha.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/rapi \
  -H "x-api-key: tu_api_key"
```

#### Respuesta

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "job_id": "4a201139-7945-4ae7-8a17-dc8e048c7437",
  "status": "completed",
  "source": "processing_jobs",
  "result": { ... }
}
```

#### Errores comunes

| HTTP | Causa |
|---|---|
| `404` | Caso no encontrado, inactivo, o no se ha realizado ninguna consulta RAPI. Llama a `POST /v1/cases/{id}/rapi` primero. |

### POST `/v1/cases/{case_id}/rapi`

Lanza una consulta RAPI para el VIN principal del caso (obtenido de `GET /v1/cases/:id/vins`). Se ejecuta de forma asíncrona en Temporal (`invoice-background-queue`). Devuelve un `job_id` inmediatamente.

#### Ejemplo con curl

```bash
curl -X POST https://api.nexcar.mx/v1/cases/7040fd87-5f49-4187-b2a3-b4a19670825c/rapi \
  -H "x-api-key: tu_api_key"
```

#### Respuesta `202 Accepted`

```json
{ "job_id": "4a201139-7945-4ae7-8a17-dc8e048c7437", "vin": "3N6AD35A9LK875826", "status": "pending" }
```

#### Errores comunes

| HTTP | Causa |
|---|---|
| `400` | No se encontró VIN principal. Procesa los documentos con `POST /v1/cases/{id}/process` primero. |
| `404` | Caso no encontrado o inactivo |

---

## Documentos

_Sube archivos individuales a un caso, consulta su OCR y reclasifícalos cuando lo necesites._

### Endpoints

| Método | Ruta | Descripción |
|---|---|---|
| `POST` | `/v1/documents` | Sube un documento a un caso |
| `GET` | `/v1/documents/{document_id}` | Consulta metadata y OCR del documento |
| `GET` | `/v1/documents/{document_id}/extraction-data` | Extracción avanzada (vigencia, códigos QR/barras, titular) |
| `DELETE` | `/v1/documents/{document_id}` | Da de baja el documento (baja lógica) |
| `POST` | `/v1/documents/{document_id}/restore` | Restaura un documento dado de baja |
| `PATCH` | `/v1/documents/{document_id}/ocr` | Inyecta OCR de forma manual |
| `POST` | `/v1/documents/{document_id}/classify` | Reclasifica el documento (manual o automático) |
| `POST` | `/v1/documents/{document_id}/process` | Lanza OCR automático |
| `POST` | `/v1/documents/{document_id}/reclassify` | Cambia el tipo y vuelve a procesar |

### POST `/v1/documents` — Subir documento

Sube un archivo y lo asocia a un caso.

#### Cuerpo

| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| `case_id` | UUID | Sí | Caso al que pertenece el documento |
| `mime_type` | string | Sí | MIME del archivo (ver tabla de soportados) |
| `url` | string | Condicional | URL pública del archivo. **XOR** con `base64`. |
| `base64` | string | Condicional | Contenido en base64. **XOR** con `url`. |
| `type` | string | No | Tipo del documento (ver [Tipos de documento](/docs/es/tipos-de-documento)). Si no lo envías, se intentará detectar automáticamente. |
| `parent_file_id` | UUID | No | Documento padre dentro del mismo caso (p. ej. anexo de una factura). |

> **Límite de tamaño**: 20 MB por archivo. Debes enviar **uno y solo uno** entre `url` y `base64`.

#### MIME types soportados

`application/pdf` · `application/xml` · `image/jpeg` · `image/jpg` · `image/png` · `image/tiff` · `image/tif` · `image/x-tiff`

#### Ejemplo con curl (URL)

```bash
curl -X POST https://api.nexcar.mx/v1/documents \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
    "url": "https://tu-storage.example.com/repuve-consulta.pdf",
    "mime_type": "application/pdf",
    "type": "repuve"
  }'
```

#### Ejemplo con curl (base64)

```bash
BASE64=$(base64 -i ./factura.pdf)
curl -X POST https://api.nexcar.mx/v1/documents \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d "{
    \"case_id\": \"7040fd87-5f49-4187-b2a3-b4a19670825c\",
    \"base64\": \"$BASE64\",
    \"mime_type\": \"application/pdf\",
    \"type\": \"factura\"
  }"
```

#### Respuesta `201`

```json
{
  "document_id": "abc123",
  "url": "https://...nexcar.mx/storage/.../repuve-consulta.pdf"
}
```

#### Errores

| HTTP | Código | Causa |
|---|---|---|
| `400` | `MISSING_PARAMETER` | Falta `case_id`, `mime_type` o la fuente del archivo |
| `400` | `VALIDATION_ERROR` | UUID inválido, MIME no soportado, archivo > 20 MB, ambos `url` y `base64` |
| `404` | `RESOURCE_NOT_FOUND` | El `case_id` o el `parent_file_id` no existe |

### GET `/v1/documents/{document_id}`

Devuelve metadata, OCR y datos extraídos del documento.

#### Ejemplo con curl

```bash
curl https://api.nexcar.mx/v1/documents/abc123 \
  -H "x-api-key: tu_api_key"
```

#### Respuesta `200`

```json
{
  "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
  "url": "https://...nexcar.mx/storage/.../factura.pdf",
  "mime_type": "application/pdf",
  "type": "factura",
  "json_ocr": { "...": "..." },
  "parsed_data": {
    "vin": "3VWFE21C04M000001",
    "rfc_emisor": "ABC010101AAA",
    "monto_total": 285000.00,
    "fecha_emision": "2024-08-15"
  },
  "validity": {
    "document_validity": "vigente",
    "codes": ["QR detectado"],
    "userInfo": { "nombre": "JUAN PÉREZ GARCÍA" }
  }
}
```

`json_ocr` y `parsed_data` son `null` mientras el OCR no haya terminado. Para no estar haciendo polling, configura un [webhook](/docs/es/webhooks).

### POST `/v1/documents/{document_id}/classify`

Cambia o asigna el tipo del documento. Útil cuando subiste un archivo sin `type` y deseas clasificarlo manualmente, o cuando quieres forzar una nueva detección automática.

```bash
curl -X POST https://api.nexcar.mx/v1/documents/abc123/classify \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "type": "tenencia" }'
```

Si omites el cuerpo, ejecuta clasificación automática con base en el contenido.

### POST `/v1/documents/{document_id}/reclassify`

Cambia el tipo del documento y **relanza** el OCR con la nueva clasificación. La respuesta es `202 Accepted` con el `job_id` del nuevo procesamiento.

```bash
curl -X POST https://api.nexcar.mx/v1/documents/abc123/reclassify \
  -H "x-api-key: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "type": "factura_xml" }'
```

---

## Webhooks

_Recibe notificaciones HTTP cuando un documento o un caso cambia de estado._

### ¿Cómo funcionan?

Cuando un documento o caso avanza en su procesamiento (clasificación, OCR, extracción de datos, validación), Nexcar envía una solicitud HTTP `POST` a la URL que hayas configurado, con un payload JSON describiendo el evento.

Configura tu URL receptora con tu contacto en Nexcar.

### Eventos disponibles

| Evento | Cuándo se dispara |
|---|---|
| `document.classified` | Se asignó (o reasignó) el tipo de un documento |
| `document.ready` | El OCR del documento terminó y los datos están disponibles |
| `document.validity` | La extracción de vigencia/códigos terminó |
| `case.upload.completed` | Terminó la carga inicial de archivos al crear el caso |
| `case.status.changed` | El estado del caso cambió |

### Estructura del payload

Todos los eventos comparten el mismo envelope:

```json
{
  "event": "document.ready",
  "occurred_at": "2026-04-09T23:04:18.515738Z",
  "data": {
    "case_id": "7040fd87-5f49-4187-b2a3-b4a19670825c",
    "document_id": "abc123",
    "type": "factura",
    "parsed_data": {
      "vin": "3VWFE21C04M000001",
      "monto_total": 285000
    }
  }
}
```

### Verificación

Cada solicitud incluye el encabezado `x-nexcar-signature` con un HMAC SHA-256 calculado sobre el body usando el secreto del webhook que te compartimos. Verifícalo así (Node.js):

```js
const crypto = require("crypto");

function verify(req, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(req.rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(req.headers["x-nexcar-signature"])
  );
}
```

### Reintentos

Si tu endpoint responde con un código `>= 400` o no responde en menos de **10 segundos**, reintentamos con backoff exponencial hasta 5 veces a lo largo de 24 horas. Después de eso el evento se descarta y queda registrado en tu bitácora.

> Responde lo antes posible con `2xx` y procesa el evento de forma asíncrona en tu lado.

### Idempotencia

Cada evento incluye un identificador único en `data.event_id`. Tu sistema debe ignorar eventos repetidos en caso de reintento.

---

## Tipos de documento

_Catálogo de tipos que puedes asignar al subir un documento. Si omites el tipo, Nexcar intenta detectarlo automáticamente._

### Catálogo

| Tipo (`type`) | Descripción | Datos típicos extraídos |
|---|---|---|
| `factura` | Factura (PDF) o XML CFDI del vehículo | VIN, RFC emisor/receptor, monto total, fecha |
| `factura_xml` | XML CFDI 4.0 | UUID, RFC, totales, conceptos |
| `ine` | Identificación oficial INE/IFE del titular | Nombre, CURP, vigencia, clave de elector |
| `pasaporte` | Pasaporte mexicano o extranjero | Nombre, fecha de nacimiento, vigencia |
| `repuve` | Consulta REPUVE (constancia oficial) | VIN, marca, modelo, año, status del vehículo |
| `tenencia` | Comprobante de pago de tenencia (estatal) | Año, monto, fecha de pago, placa |
| `placas` | Tarjeta de circulación o constancia de placas | Placa, vigencia, propietario |
| `comprobante_domicilio` | CFE, agua, teléfono, predial | Dirección, titular, fecha de emisión |
| `comprobante_pago` | Comprobante bancario / SPEI | Monto, banco, fecha, beneficiario |
| `pedimento` | Pedimento aduanal (vehículos importados) | Número de pedimento, aduana, fecha |
| `verificacion` | Constancia de verificación vehicular | Holograma, vigencia, placa |
| `multas` | Constancia de no infracciones | Adeudos, fecha de emisión |
| `acta_nacimiento` | Acta de nacimiento del titular | Nombre, CURP, fecha y lugar |
| `comprobante_ingresos` | Estado de cuenta o recibo de nómina | Titular, periodo, monto |
| `responsiva` | Carta responsiva firmada | Comprador, vendedor, fecha, VIN |

### Reglas

- Si subes un documento **con** `type`, Nexcar respeta tu clasificación pero puede sugerir un cambio si detecta inconsistencias.
- Si subes **sin** `type`, ejecutamos clasificación automática por contenido. El resultado llega vía el webhook `document.classified`.
- Puedes cambiar el tipo en cualquier momento con `POST /v1/documents/{id}/classify` o `/reclassify` (este último relanza OCR).

### Documentos vinculados

Algunos tipos admiten un **documento padre** mediante `parent_file_id`. Por ejemplo:

- Una `factura_xml` puede vincularse a su `factura` (PDF) correspondiente.
- Un `comprobante_pago` puede vincularse al `tenencia` que paga.

Esto permite que las consultas devuelvan grupos coherentes de documentos relacionados.

---

## Errores

_Formato estándar de errores y guía rápida de los códigos más frecuentes._

### Formato

Todos los errores usan el mismo envelope JSON:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "case_id no es un UUID válido",
    "details": { "field": "case_id" }
  }
}
```

- `code` es estable: tu sistema **debe** decidir el comportamiento con base en él.
- `message` es legible para humanos y puede cambiar (no lo parsees).
- `details` es opcional y específico del error.

### Códigos comunes

#### 4xx — errores del cliente

| HTTP | `code` | Significado | Acción sugerida |
|---|---|---|---|
| `400` | `MISSING_PARAMETER` | Falta un campo requerido | Revisa el cuerpo del request |
| `400` | `VALIDATION_ERROR` | Un campo tiene formato inválido | Revisa el detalle en `details.field` |
| `400` | `DUPLICATE_INTERNAL_ID` | Ya existe un caso con ese `internal_id` | Reusa el `case_id` que devolvemos en `details` |
| `401` | `UNAUTHORIZED` | Credenciales ausentes o expiradas | Renueva tu token o revisa la API key |
| `401` | `INVALID_CREDENTIALS` | Credenciales inválidas | Verifica que estés usando la key correcta del ambiente |
| `403` | `FORBIDDEN` | Sin permiso sobre el recurso | Contacta a soporte para revisar tu acceso |
| `404` | `RESOURCE_NOT_FOUND` | Caso o documento inexistente | Verifica el ID o si fue dado de baja |
| `409` | `CONFLICT` | El recurso está en un estado incompatible con la operación | Consulta el estado actual y reintenta |
| `413` | `PAYLOAD_TOO_LARGE` | Archivo > 20 MB | Reduce el tamaño antes de reintentar |
| `415` | `UNSUPPORTED_MEDIA_TYPE` | MIME no soportado | Usa un MIME del catálogo |
| `422` | `INVALID_STATUS` | El `status` no es válido para tu catálogo | Usa un valor del catálogo configurado |
| `429` | `RATE_LIMITED` | Excediste el límite de peticiones | Aplica backoff y reintenta |

#### 5xx — errores del servidor

| HTTP | `code` | Significado |
|---|---|---|
| `500` | `INTERNAL_ERROR` | Error inesperado de Nexcar |
| `502` | `UPSTREAM_ERROR` | Falla de un servicio externo (OCR, almacenamiento) |
| `503` | `SERVICE_UNAVAILABLE` | Mantenimiento en curso |

> Para `5xx`, reintenta con backoff exponencial. Si persiste más de 5 minutos, escríbenos a [soporte@nexcar.mx](mailto:soporte@nexcar.mx) con el `request_id` que viene en los encabezados de respuesta.
