
# Actualizar una API

`PATCH /v1/apis/{api_id}` · permiso `apis:write`

Actualiza una API con un parche de fusión JSON (RFC 7396, `application/merge-patch+json`): los campos que envías cambian, los campos que omites se quedan. `If-Match` es requerido. `key` e `identifier` no pueden cambiar después de la creación, y la API del sistema no puede ser modificada. Un parche que no cambia nada no escribe nada y mantiene la versión.

## Solicitud

| Parámetro | En | Descripción |
|---|---|---|
| `api_id` | path | El id de la API. |
| `If-Match` | header | El `ETag` de la última lectura (requerido). |

Cuerpo: cualquiera de los campos escribibles abajo (todos opcionales). Un campo no puede ser removido con `null`.

| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| `key` | string | no | Clave de configuración estable, `^[a-z0-9_-]{1,64}$`. Inmutable. |
| `identifier` | string | no | Audiencia de token: una URL https (sin consulta ni fragmento) o `urn:<nid>:<nss>`; almacenada en forma canónica. Inmutable. El espacio de nombres `urn:idp:` está reservado. |
| `name` | string | no | 1-128 caracteres. |
| `access_token_lifetime_s` | integer | no | 60-86400. Por defecto 3600 (600 con `mcp_profile`). |
| `token_format` | string | no | `jwt` (por defecto) u `opaque`. |
| `rbac_mode` | string | no | Cómo llegan los roles y permisos al token: `claims` (por defecto) añade `permissions` y `org_permissions`; `off` no añade ninguno (`org_roles` se sigue añadiendo); `enforce` los añade y además limita los scopes concedidos a los permisos efectivos del usuario. |
| `allow_offline_access` | boolean | no | Por defecto `true`. |
| `require_resource_indicator` | boolean | no | Por defecto `false` (`true` con `mcp_profile`). |
| `dpop_required` | boolean | no | Por defecto `false`. |
| `mcp_profile` | boolean | no | Requiere un `identifier` https y `require_resource_indicator: true`. |
| `first_party_access` | string | no | `all` (por defecto) u `granted_only`. |
| `introspection_applications` | array | no | Aplicaciones (`app_…`) que pueden introspeccionar los tokens de esta API, máximo 50, sin duplicados. Solo aplicaciones `web` y `m2m` del mismo entorno. La lista se reemplaza en su totalidad; `[]` la vacía, `null` se rechaza. |

### Aplicaciones de introspeción

El punto de conexión de introspeción (`POST <issuer>/oauth2/introspect`, RFC 7662) responde con `active: true` para un token de acceso solo a dos llamadores: la aplicación a la que se emitió el token, o una aplicación en la lista `introspection_applications` de la API nombrada en el `aud` del token. Todos los demás llamadores reciben `active: false`, por lo que las API no pueden leer los tokens entre sí. Solo las aplicaciones confidenciales (`web`, `m2m`) del mismo entorno pueden listarse; la lista se almacena como un conjunto y se devuelve en orden de ID. Una aplicación suspendida permanece en la lista pero no puede introspeccionar. La lista de la API del sistema no se puede escribir.

## Respuesta

`200 OK` con el nuevo `ETag`:

```json
{
  "id": "api_01h455vb4pex5vsknk084sn02q",
  "key": "orders",
  "identifier": "https://api.example.com/orders",
  "name": "Orders API",
  "is_system": false,
  "access_token_lifetime_s": 3600,
  "token_format": "jwt",
  "rbac_mode": "off",
  "allow_offline_access": true,
  "require_resource_indicator": false,
  "dpop_required": false,
  "mcp_profile": false,
  "first_party_access": "all",
  "introspection_applications": ["app_01h455vb4pex5vsknk084sn02r"],
  "created_at": "2026-10-05T12:00:00Z",
  "updated_at": "2026-10-05T12:00:00Z"
}
```

## Errores

| HTTP | código | Cuándo |
|---|---|---|
| 400 | `unknown_field` | El parche contiene un campo de solo lectura o desconocido. |
| 404 | `not_found` | No existe tal API en este entorno. |
| 409 | `conflict` | La API es la API del sistema. |
| 412 | `precondition_failed` | `If-Match` no coincide con la versión actual. |
| 415 | `unsupported_media_type` | El tipo de contenido no es `application/merge-patch+json`. |
| 422 | `validation_failed` | Un valor es inválido, un campo fue configurado a `null`, o `key` / `identifier` fue cambiado. |
| 422 | `validation_failed` | Un elemento `introspection_applications` no existe en este entorno (`unknown_application`), no es confidencial (`not_confidential`), es un duplicado (`duplicate`) o no es un ID `app_…` (`invalid_value`); el puntero nombra el índice en la solicitud. |
| 428 | `precondition_required` | `If-Match` está faltando. |

## Ejemplo

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/apis/api_01h455vb4pex5vsknk084sn02q \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "1"' \
  -d '{"access_token_lifetime_s":900}'
```
