> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rail.cl/llms.txt
> Use this file to discover all available pages before exploring further.

# Errores

> Shape estándar y catálogo de error codes.

## Shape estándar

Todos los errores 4xx/5xx siguen este shape:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_field",
    "message": "El campo 'link_id' es requerido.",
    "param": "link_id",
    "doc_url": "https://docs.rail.cl/guides/errors#missing_field"
  }
}
```

| Field     | Tipo    | Significa                                    |
| --------- | ------- | -------------------------------------------- |
| `type`    | string  | Categoría del error                          |
| `code`    | string  | Identificador específico (usar para lógica)  |
| `message` | string  | Texto en español para mostrar al user        |
| `param`   | string? | Field específico que rompió (opcional)       |
| `doc_url` | string? | URL a esta doc con anchor al code (opcional) |

## Types

| `type`                  | HTTP | Significa                                                     |
| ----------------------- | ---- | ------------------------------------------------------------- |
| `invalid_request_error` | 400  | Validación de body/query/params                               |
| `authentication_error`  | 401  | Credencial inválida o ausente                                 |
| `permission_error`      | 403  | Credencial válida pero sin scope                              |
| `not_found_error`       | 404  | Recurso no existe o no pertenece al cliente                   |
| `conflict_error`        | 409  | Estado conflictivo (idempotency\_conflict, in\_progress, etc) |
| `rate_limit_error`      | 429  | Demasiados requests — backoff                                 |
| `bank_error`            | 502  | El banco rebotó (transient)                                   |
| `internal_error`        | 500  | Error nuestro — reintenta                                     |

## Catálogo de codes

### Auth

| `code`               | Significa                                                 |
| -------------------- | --------------------------------------------------------- |
| `invalid_credential` | Key inexistente, malformada o revocada                    |
| `insufficient_scope` | Key válida pero sin permisos para el endpoint             |
| `wrong_mode`         | Estás usando `sk_test` contra recursos `live` o viceversa |

### Validación

| `code`          | Significa                              |
| --------------- | -------------------------------------- |
| `missing_field` | Falta un campo requerido (ver `param`) |
| `invalid_field` | Campo con valor inválido (ver `param`) |
| `unknown_field` | Campo no reconocido en el body         |

### Recursos

| `code`               | Significa                                        |
| -------------------- | ------------------------------------------------ |
| `link_not_found`     | El `link_id` no existe o no pertenece al cliente |
| `account_not_found`  | El `account_id` no existe                        |
| `bank_not_supported` | El `bank_id` no está habilitado                  |

### Refresh intents

| `code`                       | Significa                                          |
| ---------------------------- | -------------------------------------------------- |
| `refresh_intent_in_progress` | Ya hay un intent activo para este link             |
| `refresh_intent_cooldown`    | Espera los 5min de cooldown desde el último intent |

### Bancos (transient — reintentables)

| `code`                 | Significa                                                                                    |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| `bank_unavailable`     | Banco caído o muy lento                                                                      |
| `bank_format_change`   | Banco cambió HTML, parser nuestro está roto. Rail está sobre eso.                            |
| `rejected_credentials` | Banco rechazó las creds. User debe re-conectar (no es transient técnicamente — es del user). |

### Idempotency

| `code`                 | Significa                                  |
| ---------------------- | ------------------------------------------ |
| `idempotency_conflict` | Misma `Idempotency-Key` con body diferente |

## Manejo recomendado

```ts theme={null}
async function railFetch<T>(path: string, opts: RequestInit): Promise<T> {
  const res = await fetch(`https://api.rail.cl${path}`, opts);
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    const err = body.error ?? {};
    throw new RailError(res.status, err.code, err.message, err.param);
  }
  return res.json();
}

class RailError extends Error {
  constructor(
    public status: number,
    public code: string,
    message: string,
    public param?: string,
  ) {
    super(message);
  }
}
```

Después, en tu app:

```ts theme={null}
try {
  await railFetch('/v1/refresh_intents', { ... });
} catch (e) {
  if (e instanceof RailError) {
    if (e.code === 'refresh_intent_in_progress') {
      // No es error real — ya está corriendo
      return;
    }
    if (e.code === 'rejected_credentials') {
      // Notifica al user para que re-conecte
    }
    if (e.status >= 500) {
      // Transient, reintentar con backoff
    }
  }
}
```
