# Crea y usa un token de API con permisos granulares
Los tokens de API permiten que scripts, bots y tareas de CI llamen a la API de Zaroz Cloud sin guardar la contraseña de tu cuenta ni tu sesión del navegador. Todo token deniega por defecto: tú eliges los permisos exactos que tiene y, en un token personal, los recursos exactos que puede tocar.

La lista completa de rutas está en la [referencia de la API](/api). Las convenciones que comparten todas las llamadas (URL base, errores, paginación) están en [Usar la API de Zaroz](/guides/api/overview).

## Token personal o de organización

| | Token personal | Token de organización |
|---|---|---|
| Actúa como | Tú | La organización |
| Alcance de cada permiso | Global, un tipo de recurso, un recurso, o un recurso y sus descendientes | Toda la organización |
| Se revoca cuando | Lo revocas o pierdes el acceso | Lo revocas; con `membership_tied` también cuando su creador se va |
| Se crea en | `POST /me/api-tokens` | `POST /organizations/{id}/api-tokens` (requiere Owner o Admin) |
| Ideal para | Un script, un servicio, mínimo privilegio | El CI de un equipo o un bot que debe sobrevivir a una persona |

Un token nunca tiene más autoridad que la persona u organización que lo creó, y se comprueba de nuevo en cada petición: si pierdes un rol, tus tokens lo pierden también.

## Antes de crear uno

Trata el token como una contraseña. Quien lo consiga puede hacer todo lo que permite hasta que caduque o lo revoques.

- Crea un token distinto para cada integración.
- Concede solo los permisos imprescindibles.
- Prefiere un servicio concreto a todos (solo tokens personales).
- Añade `expires_at` siempre que la integración pueda rotar el secreto.
- Nunca pongas un token en JavaScript de frontend, una app móvil, un repositorio público o un mensaje de soporte.

## Consulta qué puedes conceder

Solo puedes conceder lo que tú mismo tienes. Pregúntaselo a la API para no fijar una lista a mano:

**curl**

```bash
curl -sS "$ZAROZ_API/me/api-token-capabilities" -H "$AUTH"
# {"data":[{"capability":"services.read","admin_only":false}, ...]}

# para un token de organización
curl -sS "$ZAROZ_API/organizations/$ORG/api-tokens/grantable-capabilities" -H "$AUTH"
# {"data":[{"capability":"services.read"}, ...], "max_lifetime_days": null}
```

**TypeScript**

```ts
import { call } from "./zaroz";

const mine = await call("GET", "/me/api-token-capabilities");
console.log(mine.body.data.filter((c: any) => !c.admin_only).map((c: any) => c.capability));
```

**Python**

```python
from zaroz import call

_, mine = call("GET", "/me/api-token-capabilities")
print([c["capability"] for c in mine["data"] if not c["admin_only"]])
```

Los permisos que importan a la mayoría de clientes:

| Permiso | Permite |
|---|---|
| `services.read` | Toda lectura de un servicio que no sea secreta |
| `services.power` | Iniciar, detener, reiniciar |
| `services.deploy` | Fijar el origen y crear despliegues, nada más |
| `services.manage` | Cambiar el servicio (entorno, copias, archivos, cortafuegos...) y todo lo de `services.deploy` |
| `services.console` | Flujo de logs, consola de comandos, SSH |
| `services.files.read` | Explorar y descargar archivos |
| `services.external_access.manage` | Abrir y cerrar puertos públicos |
| `services.schedules.manage` | Tareas programadas |
| `services.databases.read` | Explorar bases de datos conectadas |
| `organizations.read`, `projects.read` | Leer organizaciones y proyectos |
| `organizations.members.manage` | Añadir, quitar y cambiar roles de miembros |
| `organizations.manage`, `projects.manage` | Renombrar una organización; crear, renombrar y borrar proyectos |
| `organizations.purchase` | Comprar una suscripción facturada a la organización |
| `organizations.wallet.spend`, `organizations.wallet.top_up` | Pagar facturas con el monedero; añadir fondos |
| `subscriptions.renewal.manage`, `subscriptions.cancel`, `subscriptions.resize.manage` | Activar o desactivar la renovación; terminar una suscripción; redimensionar o cambiar de plan |
| `organizations.api_tokens.manage` | Crear, listar y revocar los tokens de la organización |
| `wallet.read` | Leer saldos y movimientos del monedero |

No hay comodines: `services.read` no implica `services.power`.

## Crea un token personal

Crearlo requiere una sesión iniciada (la consola o las rutas de login), no otro token de API. El secreto se devuelve **una sola vez**, con la forma `zaroz_pat_<id>_<secreto>`; solo se guarda un resumen.

Cada concesión es un permiso más un alcance:

| `kind` del alcance | Alcanza |
|---|---|
| `global` | El permiso allí donde lo tengas |
| `resource_type` | Todos los recursos de un tipo (`resource_type`) |
| `resource` | Exactamente un recurso (`resource_type`, `resource_id`) |
| `resource_tree` | Un recurso y lo que desciende de él (`resource_type`, `root_resource_id`) |

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/me/api-tokens" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "name": "status-bot",
  "expires_at": "2027-01-01T00:00:00Z",
  "authorization": {"grants": [
    {"capability": "services.read",
     "scope": {"kind": "resource", "resource_type": "service_instance", "resource_id": "<uuid de la instancia de servicio>"}}
  ]}
}'
# {"token": {"id": "...", "name": "status-bot", "capabilities": [...]}, "secret": "zaroz_pat_..."}   se muestra una vez
```

**TypeScript**

```ts
import { call } from "./zaroz";

const created = await call("POST", "/me/api-tokens", {
  name: "status-bot",
  expires_at: "2027-01-01T00:00:00Z",
  authorization: {
    grants: [
      {
        capability: "services.read",
        scope: { kind: "resource", resource_type: "service_instance", resource_id: SERVICE },
      },
    ],
  },
});
console.log(created.body.secret); // guárdalo ahora; no se puede volver a mostrar
```

**Python**

```python
from zaroz import call

_, created = call("POST", "/me/api-tokens", {
    "name": "status-bot",
    "expires_at": "2027-01-01T00:00:00Z",
    "authorization": {"grants": [{
        "capability": "services.read",
        "scope": {"kind": "resource", "resource_type": "service_instance", "resource_id": SERVICE},
    }]},
})
print(created["secret"])  # guárdalo ahora; no se puede volver a mostrar
```

Una lista `grants` vacía crea un token que se autentica y no puede hacer nada. La ausencia deniega.

## Crea un token de organización

Quien lo crea debe ser Owner o Admin de la organización. Los permisos se aplican a toda la organización.

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/organizations/$ORG/api-tokens" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "name": "ci-deploy",
  "lifetime": "organization_owned",
  "expires_at": "2027-01-01T00:00:00Z",
  "capabilities": ["services.deploy", "services.read"]
}'
```

**TypeScript**

```ts
const org = await call("POST", `/organizations/${ORG}/api-tokens`, {
  name: "ci-deploy",
  lifetime: "organization_owned", // sobrevive a la persona que lo creó
  expires_at: "2027-01-01T00:00:00Z",
  capabilities: ["services.deploy", "services.read"],
});
```

**Python**

```python
_, org_token = call("POST", f"/organizations/{ORG}/api-tokens", {
    "name": "ci-deploy",
    "lifetime": "organization_owned",
    "expires_at": "2027-01-01T00:00:00Z",
    "capabilities": ["services.deploy", "services.read"],
})
```

`lifetime` es `membership_tied` por defecto: el token se revoca solo cuando su creador deja la organización. Usa `organization_owned` si debe seguir funcionando después. Los permisos exclusivos de personal de Zaroz no pueden ir nunca en un token de organización.

## Úsalo

Envíalo como token bearer. Esto lee un servicio con un token que solo tiene `services.read`:

**curl**

```bash
curl -sS "$ZAROZ_API/service-instances/$SERVICE" -H "Authorization: Bearer zaroz_pat_..."
curl -sS "$ZAROZ_API/service-instances/$SERVICE/resources" -H "Authorization: Bearer zaroz_pat_..."
```

**TypeScript**

```ts
// status.mjs: ZAROZ_TOKEN debe ser el token del bot; Node 20+
const API = "https://kernel.zaroz.cloud/api/v2";
const res = await fetch(`${API}/service-instances/${process.env.SERVICE}/resources`, {
  headers: { Authorization: `Bearer ${process.env.ZAROZ_TOKEN}` },
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).code}`);
const { data } = await res.json();
for (const r of data) console.log(r.kind, r.role, r.status);
```

**Python**

```python
import os, requests

API = "https://kernel.zaroz.cloud/api/v2"
r = requests.get(
    f"{API}/service-instances/{os.environ['SERVICE']}/resources",
    headers={"Authorization": "Bearer " + os.environ["ZAROZ_TOKEN"]},
    timeout=20,
)
r.raise_for_status()
for res in r.json()["data"]:
    print(res["kind"], res["role"], res["status"])
```

Prueba una ruta que el token no tiene (por ejemplo `POST /service-instances/{id}/restart` con el token anterior): responde `403`. Ese es el objetivo.

:::danger{title="No llames a la API directamente desde una web pública"}

El código del navegador lo ve cualquier visitante. Guarda el token en un servidor, un bot, el almacén de secretos del CI u otro backend de confianza.

:::

## Errores que debes manejar

- `401`: el token falta, está mal formado, caducó o fue revocado.
- `403`: el token, su propietario o ambos carecen del permiso para ese recurso.
- `404`: el id es incorrecto, o el recurso está deliberadamente oculto para esta identidad.
- `429`: espera `Retry-After` y reintenta.
- `5xx`: temporal; reintenta un número limitado de veces y registra la respuesta sin registrar el token.

## Listar, rotar y revocar

**curl**

```bash
curl -sS "$ZAROZ_API/me/api-tokens" -H "$AUTH"                              # tus tokens vivos: nunca el secreto
curl -sS "$ZAROZ_API/organizations/$ORG/api-tokens" -H "$AUTH"              # los tokens de la organización
curl -sS -X DELETE "$ZAROZ_API/me/api-tokens/$TOKEN_ID" -H "$AUTH"                       # 204
curl -sS -X DELETE "$ZAROZ_API/organizations/$ORG/api-tokens/$TOKEN_ID" -H "$AUTH"      # 204
```

**TypeScript**

```ts
const live = await call("GET", "/me/api-tokens");
for (const t of live.body.data) console.log(t.name, t.last_used_at);
await call("DELETE", `/me/api-tokens/${TOKEN_ID}`);
```

**Python**

```python
_, live = call("GET", "/me/api-tokens")
for t in live["data"]:
    print(t["name"], t["last_used_at"])
call("DELETE", f"/me/api-tokens/{TOKEN_ID}")
```

Para rotar: crea el reemplazo, actualiza la integración, comprueba que funciona y revoca el antiguo. Revocar es inmediato, y revocar un token personal requiere una sesión iniciada, no un token. Las acciones hechas con un token aparecen en la [actividad de la organización](/es/guides/organizations/members#registro-de-actividad) con el nombre del token, así que ponles nombres que reconozcas durante un incidente.

## Siguiente

- [Despliega desde CI/CD](/es/guides/ci/overview): el token estrecho `services.deploy` en la práctica.
- [Principales de servicio](/es/guides/units-api/principals): credenciales para el código que corre dentro de un Workload, en lugar de un token tuyo.
- [Monitor de estado de Minecraft](/es/discord-bot/minecraft-status-monitor)
