
# Actualizar un usuario

`PATCH /v1/users/{user_id}` · permiso `users:write`

Actualiza un usuario con un parche de fusión JSON (`Content-Type: application/merge-patch+json`). Solo cambian los campos que envíe. Los campos de texto se borran con `null` o con una cadena vacía; `email_verified` y `phone_verified` no se pueden borrar; `address` se reemplaza por completo; `user_metadata` y `app_metadata` se fusionan clave por clave, y un valor `null` elimina esa clave; enviar `user_metadata` o `app_metadata` como `null` elimina todo su contenido y deja `{}`. El estado no se puede cambiar aquí; use bloquear y desbloquear. No se puede actualizar un usuario que está pendiente de eliminación. `If-Match` es opcional: si se envía, debe coincidir con el `ETag` actual del usuario.

## Solicitud

| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
| `user_id` | path | sí | Identificador del usuario, con el prefijo `usr_`. |
| `If-Match` | header | no | El `ETag` de una lectura anterior. |

El cuerpo acepta los mismos campos que [Crear un usuario](/docs/api/management/users/create), todos opcionales, más los campos de edad indicados a continuación. Los campos desconocidos se rechazan con `unknown_field`.

### Edad y consentimiento parental

Cuando su propio proceso de verificación de edad o de consentimiento parental termine, registre su resultado en el usuario con estos campos. También se devuelven en cada objeto de usuario; `null` indica que el usuario no ha sido evaluado.

| Campo | Valores | Notas |
|---|---|---|
| `age_group` | `adult`, `minor`, `unknown` | No se puede borrar; envíe `unknown` cuando no se conozca la edad. |
| `parental_consent_status` | `not_required`, `pending`, `granted`, `denied`, `revoked` | `null` lo borra. Cualquier valor distinto de `not_required` solo se acepta si el `age_group` del usuario es `minor`. |
| `age_assurance_level` | `self_declared`, `birthdate_declared`, `third_party_verified` | `null` lo borra. Indica con qué firmeza su proceso estableció la edad. |

Un cambio en `age_group` o en `parental_consent_status` emite un evento `user.updated`. No incluya una fecha de nacimiento ni un número de documento de identidad en estos campos ni en los metadatos para respaldarlos; envíe solo el resultado.

## Respuesta

`200 OK`. El cuerpo es el objeto de usuario. La cabecera `ETag` contiene la versión del usuario.

Los datos personales se enmascaran cuando la clave no tiene el permiso `users:read_pii`: `email` pasa a ser `a***@e***.com`, `phone_number` conserva solo sus 2 últimos dígitos (`+**********67`), `name`, `given_name`, `family_name`, `middle_name` y `nickname` pasan a ser iniciales (`A. L.`), `username` conserva solo su primer carácter (`a***`), `birthdate` pasa a ser `****-**-**` y `address` pasa a ser `{"redacted": true}`. `user_metadata` y `app_metadata` no se enmascaran.

```json
{
  "id": "usr_0a1b2c3d4e5f6g7h8j9k0m1n2p",
  "kind": "regular",
  "status": "active",
  "external_id": null,
  "username": null,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "email_verified": true,
  "phone_number": null,
  "phone_verified": false,
  "age_group": "adult",
  "parental_consent_status": null,
  "age_assurance_level": "self_declared",
  "user_metadata": {},
  "app_metadata": {},
  "created_via": "admin_api",
  "created_at": "2026-01-15T09:30:00Z",
  "updated_at": "2026-01-15T09:30:00Z",
  "last_sign_in_at": null,
  "deletion_requested_at": null,
  "deletion_due_at": null
}
```

## Errores

Cada error es un cuerpo `application/problem+json` con un `code` estable.

| HTTP | `code` | Cuándo |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | El parche no es un objeto JSON, o un campo no está en el esquema; o `If-Match` tiene un formato incorrecto. |
| 409 | `conflict` | El valor ya está en uso, o el usuario está pendiente de eliminación (`errors[].code` es `user_pending_deletion`). |
| 412 | `precondition_failed` | `If-Match` no coincide con la versión actual. |
| 415 | `unsupported_media_type` | `Content-Type` no es `application/merge-patch+json`. |
| 422 | `validation_failed` | Un valor no es válido o tiene un tipo incorrecto; `errors[].pointer` indica el campo. |
| 404 | `not_found` | El usuario no existe en este entorno (o el identificador tiene un formato incorrecto). |
| 401 | `unauthenticated` | Falta la clave de API, no es válida o ha sido revocada. |
| 403 | `insufficient_permissions` | La clave no tiene el permiso `users:write`. |
| 429 | `rate_limited` | Se superó el límite de uso; respete `Retry-After`. |
| 503 | `service_unavailable` | Una dependencia no está disponible temporalmente; reintente más tarde. |

## Ejemplo

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"name": "Ada King", "app_metadata": {"tier": "gold"}}'
```

Registre un consentimiento parental concedido para un menor:

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"age_group": "minor", "parental_consent_status": "granted", "age_assurance_level": "third_party_verified"}'
```

## Datos personales

Este endpoint contiene datos personales: perfil de usuario final. [Datos personales en las API de KeenAuth](/docs/policies/personal-data) describe qué se almacena, durante cuánto tiempo se conserva y cómo se elimina. No envíe categorías especiales de datos personales (salud, datos biométricos, religión, opiniones políticas, orientación sexual) en ningún campo.
