# Validador de RFC

> Verifica ante el SAT si un RFC está registrado y puede recibir facturas, y coteja nombre y código postal.

API publicada en idoo.dev por Jonathan Fonseca (categoría: Facturación).
Página: https://idoo.dev/apis/valida-rfc

## Cómo llamar a la API

- **URL:** `https://idoo.dev/v1/valida-rfc/{ruta}` — cualquier método HTTP; query, body y `Content-Type` llegan intactos a la API.
- **Autenticación:** header `Authorization: Bearer {api_key}` (o `X-API-KEY: {api_key}`). Una sola key de idoo.dev sirve para todas las APIs a las que estés suscrito.
- **Ambiente según la key:** `idoo_sk_test_...` → sandbox (gratis, no descuenta cuota); `idoo_sk_live_...` → producción.
- **Seguridad:** guarda la key en una variable de entorno (p. ej. `IDOO_API_KEY`) y llama a la API solo desde el servidor; nunca la expongas en el frontend ni la subas al repositorio.
- **Obtener acceso:** inicia sesión en https://idoo.dev/login, suscríbete a la API en https://idoo.dev/apis/valida-rfc y crea tu key en https://idoo.dev/panel/apikeys.

## Respuestas

La respuesta de la API se devuelve **tal cual** (status, headers y body del proveedor). Solo los errores del propio gateway usan este sobre JSON:

```json
{"valid": false, "status": 402, "message": "…", "data": null, "timestamp": "2026-01-01 12:00:00"}
```

| Status | Causa | Qué hacer |
|---|---|---|
| 401 | No se envió la key | Agrega el header `Authorization: Bearer {api_key}` |
| 403 | Key inválida o revocada, o sin suscripción activa a la API | Revisa la key y la suscripción en el panel |
| 402 | Cuota mensual agotada (`data.periodo_reinicia` indica cuándo se renueva) | Mejora el plan o compra un paquete; no reintentes |
| 404 | La API no existe o no está publicada | Revisa el slug de la URL |
| 429 | Rate limit por minuto excedido | Espera los segundos del header `Retry-After` y reintenta |
| 502 | La API no respondió o no tiene URL para tu ambiente | Reintenta con backoff; si persiste, contacta al creador |

Headers útiles en cada respuesta: `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-Idoo-Cuota-Restante` (si el plan tiene cuota).
Solo descuentan cuota las llamadas de producción que la API responde con 2xx/3xx; los errores no se cobran.

## Planes

| Plan | Precio (MXN) | Llamadas/mes | Peticiones/min |
|---|---|---|---|
| Gratis | Gratis | 50 | 2 |
| Inicial | $299.00/mes | 1,000 | 30 |
| Negocio | $999.00/mes | 5,000 | 60 |

## Documentación

## Validador de RFC

API para verificar ante el **SAT** si un RFC está registrado y es susceptible de recibir facturas, y para cotejar que el **nombre o razón social** y el **código postal** coincidan con lo que el SAT tiene registrado. Cada consulta se hace en vivo contra el servicio oficial del SAT: no hay padrones cacheados ni copias locales.

### URL base y autenticación

Todas las peticiones van al gateway de idoo.dev con tu API key:

```
https://idoo.dev/v1/valida-rfc/{accion}
```

```bash
curl -X POST "https://idoo.dev/v1/valida-rfc/validar" \
  -H "Authorization: Bearer {tu_api_key}" \
  -H "Content-Type: application/json" \
  -d '{"rfc": "FCA130603EX5"}'
```

También se acepta el header `X-API-KEY: {tu_api_key}`. Las keys se crean en tu [panel de idoo.dev](/panel/apikeys).

### Ambientes: sandbox vs producción

El ambiente lo determina tu API key, no un parámetro:

| Key | Ambiente | Comportamiento |
|---|---|---|
| `idoo_sk_test_...` | **Sandbox** | Devuelve una respuesta simulada al instante, **sin consultar al SAT**. Gratis e ilimitado: no consume la cuota de tu plan. La forma de la respuesta es idéntica a producción y trae `"simulado": true`. |
| `idoo_sk_live_...` | **Producción** | Consulta real al SAT. Cada consulta descuenta 1 de tu plan. |

Sandbox siempre responde que el RFC es válido, sin importar cuál mandes: sirve para integrar el contrato de la API, no para probar casos de negocio. Los formatos inválidos (`422`) sí se validan en sandbox.

`validar` es el único endpoint que descuenta cuota. `salud` es libre (aplica únicamente al rate limit de tu plan). El header `X-Idoo-Cuota-Restante` de cada respuesta cobrable te dice cuántas consultas te quedan en el periodo.

### Formato de respuesta

Todas las respuestas (éxito o error) usan el mismo sobre JSON:

```json
{
  "valid": true,
  "status": 200,
  "message": "RFC válido, y susceptible de recibir facturas",
  "data": { "...": "..." },
  "timestamp": "2026-09-09 07:26:07"
}
```

En los errores, `valid` es `false` y se agrega el campo `error` con un código estable, pensado para que lo compares en tu código — a diferencia de `message`, que es texto legible y puede cambiar.

> **`status: 200` no significa "RFC válido".** Significa que la consulta se realizó con éxito. El veredicto está en `data.valido`.

#### Tiempos de respuesta

Tarda típicamente **entre 2 y 4 segundos**, y puede llegar a **20** cuando se necesitan varios intentos. **Configura el timeout de tu cliente en al menos 30 segundos** y haz la llamada en segundo plano si tu interfaz no puede esperar. En sandbox la respuesta es inmediata.

#### Códigos de estado

| Código | Significado |
|---|---|
| 200 | Consulta realizada (revisa `data.valido` para el veredicto). |
| 400 | El cuerpo no es un JSON válido. |
| 401 | No se envió API key, o alguien llamó al backend directo sin pasar por el gateway. |
| 402 | Cuota de consultas de tu plan agotada (solo producción). |
| 403 | API key inválida o sin suscripción activa a esta API. |
| 404 | La ruta no existe. |
| 405 | Método HTTP no permitido. |
| 422 | Los datos enviados no son válidos: falta el RFC, o su formato (o el del CP) es incorrecto. |
| 429 | Excediste el rate limit por minuto de tu plan (`Retry-After` indica cuándo reintentar). |
| 502 | El SAT no respondió, o devolvió algo que no se pudo interpretar. |
| 503 | No se pudo obtener tras varios intentos. |
| 504 | La consulta al SAT excedió el tiempo máximo. |

### Catálogo de endpoints

| Método | Endpoint | Descripción | ¿Descuenta cuota? |
|---|---|---|---|
| POST | `validar` | Valida un RFC ante el SAT; opcionalmente coteja nombre y CP. | **Sí** |
| GET | `salud` | Estado del servicio, para monitoreo. | No |

---

### POST validar

Consulta un RFC ante el SAT.

**Body:**

```json
{
  "rfc": "FON2010129P5",
  "nombre": "FONDO DE CULTURA ECONOMICA",
  "cp": "14738"
}
```

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `rfc` | string | Sí | RFC a validar: 12 caracteres para persona moral, 13 para persona física. No distingue mayúsculas. |
| `nombre` | string | No | Nombre, denominación o razón social a cotejar. |
| `cp` | string | No | Código postal del domicilio fiscal, 5 dígitos. |

Enviar `nombre` o `cp` activa la validación cruzada del SAT: **el RFC puede existir y aun así responder que los datos no coinciden**. El cotejo del nombre es literal — el SAT compara contra la razón social exacta que tiene registrada, sin régimen de capital (`SA DE CV`) salvo que así esté inscrita.

**Respuesta 200 — solo RFC:**

```json
{
  "valid": true,
  "status": 200,
  "message": "RFC válido, y susceptible de recibir facturas",
  "data": {
    "rfc": "FCA130603EX5",
    "nombre": null,
    "cp": null,
    "resultado": "RFC válido, y susceptible de recibir facturas",
    "valido": true,
    "simulado": false,
    "idoo": { "consumidor_id": 4242, "plan": "Pro", "ambiente": "produccion" }
  },
  "timestamp": "2026-09-09 07:26:07"
}
```

**Respuesta 200 — con nombre y CP que no coinciden:**

```json
{
  "data": {
    "rfc": "FCA130603EX5",
    "nombre": "FONDO DE CULTURA ECONOMICA",
    "cp": "14738",
    "resultado": "EL Nombre o Razón Social y CP no coinciden con lo registrado en el RFC",
    "valido": false,
    "simulado": false
  },
  "message": "EL Nombre o Razón Social y CP no coinciden con lo registrado en el RFC"
}
```

**Campos de `data`:**

| Campo | Tipo | Descripción |
|---|---|---|
| `rfc` | string | RFC consultado, normalizado a mayúsculas. |
| `nombre` | string \| null | Nombre cotejado, o `null` si no lo enviaste. |
| `cp` | string \| null | Código postal cotejado, o `null` si no lo enviaste. |
| `resultado` | string | Veredicto textual del SAT, tal cual lo devuelve. |
| `valido` | boolean | `true` solo si el SAT respondió que el RFC es válido. Si enviaste `nombre` o `cp` y no coinciden, es `false`. |
| `simulado` | boolean | `true` cuando la respuesta viene de sandbox y no del SAT. |
| `idoo` | object | Contexto que el gateway asoció a tu key: `consumidor_id`, `plan` y `ambiente`. Útil para confirmar con qué key estás llamando. |

**Errores específicos:**

| Código | `error` | Cuándo ocurre |
|---|---|---|
| 422 | `rfc_requerido` | No enviaste el campo `rfc`. |
| 422 | `rfc_formato_invalido` | El RFC no tiene forma de RFC (12 o 13 caracteres, con la estructura oficial). |
| 422 | `cp_formato_invalido` | El código postal no tiene 5 dígitos. |
| 400 | `json_invalido` | El cuerpo no es JSON válido. |
| 502 | `sat_inalcanzable` | El SAT no respondió. Transitorio: reintenta. |
| 502 | `respuesta_inesperada` | El SAT respondió algo que no se pudo interpretar. Transitorio. |
| 503 | `no_resuelto` | No hay respuesta del SAT. Transitorio: reintenta. |
| 504 | `tiempo_agotado` | La consulta excedió el tiempo máximo. Transitorio. |

### GET salud

Comprueba que el servicio responde. Útil para monitoreo; no consulta al SAT ni descuenta cuota.

```bash
curl "https://idoo.dev/v1/valida-rfc/salud" \
  -H "Authorization: Bearer {tu_api_key}"
```

**Respuesta 200:**

```json
{
  "valid": true,
  "status": 200,
  "message": "Servicio operativo",
  "data": {
    "servicio": "valida-rfc",
    "idoo": { "consumidor_id": 4242, "plan": "Pro", "ambiente": "produccion" }
  },
  "timestamp": "2026-09-09 07:25:36"
}
```

---

### Flujo recomendado de integración

1. **Integra en sandbox** con tu key `idoo_sk_test_...` — es gratis e ilimitado. Ahí armas el manejo del sobre JSON y de los errores `422` sin gastar consultas.
2. **Valida el formato del RFC de tu lado** antes de llamar. Una consulta con formato inválido te devuelve `422` y no descuenta cuota, pero te cuesta un viaje de red; validarlo en tu formulario es mejor experiencia.
3. Llama a `validar` con el RFC solo, o con `nombre` y `cp` si necesitas el cotejo completo (por ejemplo, para dar de alta a un cliente que va a recibir facturas).
4. **Lee el veredicto en `data.valido`, no en el `status`.** Un `200` con `"valido": false` es una respuesta correcta del SAT diciendo que ese RFC no sirve para facturar.
5. Maneja `502`/`503`/`504` con reintentos acotados y espera entre ellos: el SAT se cae seguido. Recuerda que cada reintento descuenta cuota.
6. Como cada consulta tarda segundos, **guarda el resultado en tu sistema** en vez de volver a consultar el mismo RFC en cada pantalla. Los datos del padrón cambian con poca frecuencia.
7. Cuando estés listo, crea tu key de producción y elige el plan según tu volumen de consultas.

---

### Ejercicios guiados

Todos corren en sandbox y no consumen cuota. Sustituye `{tu_api_key}` por tu key `idoo_sk_test_...`.

#### Ejercicio 1 — Validar un RFC

El caso más simple: ¿este RFC puede recibir facturas?

```bash
curl -X POST "https://idoo.dev/v1/valida-rfc/validar" \
  -H "Authorization: Bearer {tu_api_key}" \
  -H "Content-Type: application/json" \
  -d '{"rfc": "FCA130603EX5"}'
```

En producción, `data.valido` será `true` si el SAT lo tiene registrado y activo para facturación.

#### Ejercicio 2 — Cotejar nombre y código postal

Al dar de alta un cliente conviene verificar que los tres datos que te dio coinciden entre sí; es lo que evita rechazos al timbrar después.

```bash
curl -X POST "https://idoo.dev/v1/valida-rfc/validar" \
  -H "Authorization: Bearer {tu_api_key}" \
  -H "Content-Type: application/json" \
  -d '{
        "rfc": "FCA130603EX5",
        "nombre": "FONDO DE CULTURA ECONOMICA",
        "cp": "14738"
      }'
```

Si el SAT responde que no coinciden, el `status` sigue siendo `200`: la consulta funcionó. El problema está en `data.valido: false` y en `data.resultado`, que te dice qué falló.

#### Ejercicio 3 — Manejar un fallo transitorio

El SAT no siempre responde. Este es el patrón mínimo de reintento, en PHP:

```php
$intentos = 0;
do {
    $respuesta = miCliente()->post('validar', ['rfc' => $rfc]);
    $codigo = $respuesta['status'];
    if ($codigo < 500) {
        break;                  // 200, 422, 400: no tiene caso reintentar
    }
    $intentos++;
    sleep(2 * $intentos);       // espera creciente
} while ($intentos < 3);
```

Tres intentos son suficientes: si el SAT sigue caído, avísale al usuario y guarda el RFC para reintentar más tarde. **No lo pongas en un bucle infinito**: cada vuelta descuenta cuota.

---

### Errores frecuentes

- **Tratar el `status` como el veredicto.** `200` significa "consulté al SAT y esto es lo que dijo". El veredicto está en `data.valido`.
- **Mandar la razón social con régimen de capital.** Si el SAT tiene registrada `FONDO DE CULTURA ECONOMICA` y tú envías `FONDO DE CULTURA ECONOMICA SA DE CV`, el cotejo falla. Manda el nombre tal como está en la constancia de situación fiscal.
- **Timeouts cortos.** Un cliente con timeout de 5 segundos va a cortar consultas que iban a responder bien. Usa 30 segundos.
- **Consultar el mismo RFC repetidamente.** Cada llamada descuenta cuota y tarda segundos. Guarda el resultado.
- **Reintentar un `422`.** Ese error es de tus datos, no del SAT: reintentar da exactamente lo mismo.
