# Opera un servicio en marcha

Una **instancia de servicio** es lo que ejecuta una suscripción: un servidor de juegos, una web, una base de datos, un Workload. Esta guía es la API del día a día para cualquiera de ellos. Lo que admite un servicio concreto depende de su producto: lee antes sus [capacidades](/es/concepts/capabilities) (`GET /service-instances/{id}/capabilities`), y espera `404` en una función que el producto no ofrece. Los ejemplos usan el ayudante de [Usar la API de Zaroz](/es/guides/api/overview).

## Encontrar tus servicios

**curl**

```bash
curl -sS "$ZAROZ_API/service-instances?project_id=$PROJECT&page_size=50" -H "$AUTH"
curl -sS "$ZAROZ_API/service-instances/$SERVICE" -H "$AUTH"
curl -sS "$ZAROZ_API/service-instances/$SERVICE/resources" -H "$AUTH"
curl -sS -X PATCH "$ZAROZ_API/service-instances/$SERVICE" -H "$AUTH" -H "Content-Type: application/json" -d '{"name": "Survival"}'
```

**TypeScript**

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

const list = await call("GET", `/service-instances?project_id=${PROJECT}`);
for (const s of list.body.data) console.log(s.id, s.name, s.run_state, s.subscription_status);

const res = await call("GET", `/service-instances/${SERVICE}/resources`);
const main = res.body.data.find((r: any) => r.is_declared_primary);
const RESOURCE = main.id; // el id que usan las rutas de logs, métricas y archivos
```

**Python**

```python
from zaroz import call

_, listing = call("GET", f"/service-instances?project_id={PROJECT}")
for s in listing["data"]:
    print(s["id"], s["name"], s["run_state"], s["subscription_status"])

_, res = call("GET", f"/service-instances/{SERVICE}/resources")
RESOURCE = next(r["id"] for r in res["data"] if r["is_declared_primary"])  # lo usan logs, métricas y archivos
```

`run_state` es `not_provisioned`, `provisioning`, `running`, `stopping`, `suspended`, `crash_looping`, `degraded` o `failed`. Se lee de la base de datos, no de una llamada en vivo al clúster, así que mira `availability`: `cluster_unavailable` significa que no se pudo refrescar ahora. Renombrar (`PATCH`) nunca cambia el slug; cambiar el slug es un campo aparte y explícito porque mueve todas las URL que apuntan al servicio.

## Energía

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/start" -H "$AUTH"
curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/stop" -H "$AUTH"
curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/restart" -H "$AUTH"
```

**TypeScript**

```ts
const r = await call("POST", `/service-instances/${SERVICE}/restart`);
if (r.status === 409) console.log("ocupado:", r.body.code); // operation-in-progress, restore-in-progress
```

**Python**

```python
status, body = call("POST", f"/service-instances/{SERVICE}/restart")
if status == 409:
    print("ocupado:", body["code"])   # operation-in-progress, restore-in-progress
```

`start` y `stop` son explícitos y no una palanca, así que dos llamadores que lean "en marcha" a la vez no se anulan. Detener apaga el servicio y **conserva su volumen**; no cancela ni borra nada. Una suscripción suspendida, cancelada, terminada o fallida responde `404` aquí: paga antes lo adeudado. Mientras otra operación tiene el servicio recibes `409 operation-in-progress` indicando cuál y desde cuándo; durante la restauración de una copia el código es `restore-in-progress`. El permiso necesario es `services.power`.

## Logs

La API lee **la ventana retenida** de la salida de un contenedor, de más antigua a más reciente. Una respuesta vacía significa "no está en la ventana retenida", nunca "no ocurrió"; el campo `coverage` dice en una frase qué se leyó, así que muéstralo junto a las líneas.

**curl**

```bash
L=/service-instances/$SERVICE/resources/$RESOURCE/logs
curl -sS "$ZAROZ_API$L?limit=200" -H "$AUTH"
curl -sS -G "$ZAROZ_API$L" -H "$AUTH" --data-urlencode "search=error" --data-urlencode "cursor=$NEXT"
```

**TypeScript**

```ts
let cursor: string | undefined;
do {
  const q = new URLSearchParams({ limit: "500", search: "error", ...(cursor ? { cursor } : {}) });
  const { body } = await call("GET", `/service-instances/${SERVICE}/resources/${RESOURCE}/logs?${q}`);
  for (const line of body.lines) console.log(line.at, line.message);
  cursor = body.next_cursor ?? undefined;
} while (cursor);
```

**Python**

```python
from urllib.parse import urlencode

cursor = None
while True:
    q = {"limit": 500, "search": "error", **({"cursor": cursor} if cursor else {})}
    _, body = call("GET", f"/service-instances/{SERVICE}/resources/{RESOURCE}/logs?{urlencode(q)}")
    for line in body["lines"]:
        print(line["at"], line["message"])
    cursor = body["next_cursor"]
    if not cursor:
        break
```

`search` es una subcadena sin distinguir mayúsculas, no una expresión regular, y se ejecuta en el servidor sobre toda la ventana retenida. `cursor` es opaco y reanuda con exactitud; nunca construyas uno. `container` elige un contenedor cuando el pod tiene varios (`containers` los lista) y `previous=true` lee la ejecución anterior, útil tras una caída. Las líneas no traen flujo ni nivel: nada en la plataforma interpreta una severidad.

Para seguir la salida **en vivo**, genera una URL firmada de corta duración y ábrela: `POST .../logs/stream-ticket` devuelve `{ "url", "expires_at" }`. La concesión queda en el registro de actividad de la organización. (Para Workloads, mira la retención más larga en [Logs y métricas de unidades](/es/guides/units-api/logs-and-metrics).)

## Métricas

**curl**

```bash
curl -sS "$ZAROZ_API/service-instances/$SERVICE/resources/$RESOURCE/metrics?window_minutes=15" -H "$AUTH"
```

**TypeScript**

```ts
const m = await call("GET", `/service-instances/${SERVICE}/resources/${RESOURCE}/metrics?window_minutes=15`);
for (const s of m.body.series) console.log(s.id, s.limit, s.points.length); // cpu, memory, net_in, net_out
```

**Python**

```python
_, m = call("GET", f"/service-instances/{SERVICE}/resources/{RESOURCE}/metrics?window_minutes=15")
for s in m["series"]:
    print(s["id"], s["limit"], len(s["points"]))   # cpu, memory, net_in, net_out
```

`window_minutes` es `5`, `15` o `30` (por defecto); cualquier otro valor se rechaza porque la retención es de treinta minutos. Cada ventana devuelve 60 intervalos, así que uno dura 5, 15 o 30 segundos. Un intervalo sin muestras trae valor `null` para que un gráfico pueda cortar la línea. Cada punto es un par de marca de tiempo y valor; las series se llaman `cpu`, `memory`, `net_in`, `net_out` y `limit` es el techo con el que se mide (o `null` si no hay). Convierte las unidades tú.

## Variables de entorno

**curl**

```bash
E=/service-instances/$SERVICE/environment
curl -sS "$ZAROZ_API$E" -H "$AUTH"
curl -sS -X PUT "$ZAROZ_API$E/DATABASE_URL" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"value": "postgres://…", "secret": true}'
curl -sS -X POST "$ZAROZ_API$E/DATABASE_URL/reveal" -H "$AUTH"      # queda en el registro de actividad
curl -sS -X DELETE "$ZAROZ_API$E/OLD_FLAG" -H "$AUTH"
```

**TypeScript**

```ts
const E = `/service-instances/${SERVICE}/environment`;
const set = await call("PUT", `${E}/LOG_LEVEL`, { value: "debug", secret: false });
console.log(set.body.applies, set.body.pending); // "restart", etc.
```

**Python**

```python
E = f"/service-instances/{SERVICE}/environment"
_, set_ = call("PUT", f"{E}/LOG_LEVEL", {"value": "debug", "secret": False})
print(set_["applies"], set_["pending"])   # "restart", etc.
```

Una variable por llamada. El valor de un **secreto** es siempre `null` al listar: puedes escribirlo o revelarlo, nunca leerlo de la lista. Enviar un secreto sin `value` conserva el guardado. `applies` es el veredicto de la plataforma sobre cuándo llega el cambio al contenedor en marcha: `immediately`, `restart` (hace falta un pod nuevo; hoy todo cambio en una app de contenedor) o `deploy`. `pending` indica si el re-renderizado ocurrió de verdad: si no (por ejemplo la suscripción aún no está activa) el valor antiguo sigue vivo, lo que importa al rotar una credencial filtrada. Un cambio toma un bloqueo de configuración (`409` mientras otra operación tiene el servicio) y aparece en el historial de despliegues como `config_change`. `ZAROZ_AUTH_TOKEN` está reservada.

## Archivos

**curl**

```bash
F=/service-instances/$SERVICE/resources/$RESOURCE/files
curl -sS -G "$ZAROZ_API$F" -H "$AUTH" --data-urlencode "path=/data"
curl -sS -G "$ZAROZ_API$F/content" -H "$AUTH" --data-urlencode "path=/data/server.properties"
curl -sS -X PUT -G "$ZAROZ_API$F/content" -H "$AUTH" -H "Content-Type: application/json" \
  --data-urlencode "path=/data/motd.txt" -d '{"content": "Welcome\n"}'
```

**TypeScript**

```ts
const q = new URLSearchParams({ path: "/data/motd.txt" });
await call("PUT", `/service-instances/${SERVICE}/resources/${RESOURCE}/files/content?${q}`, { content: "Welcome\n" });
const back = await call("GET", `/service-instances/${SERVICE}/resources/${RESOURCE}/files/content?${q}`);
console.log(back.body.content);
```

**Python**

```python
from urllib.parse import urlencode

q = urlencode({"path": "/data/motd.txt"})
call("PUT", f"/service-instances/{SERVICE}/resources/{RESOURCE}/files/content?{q}", {"content": "Welcome\n"})
_, back = call("GET", f"/service-instances/{SERVICE}/resources/{RESOURCE}/files/content?{q}")
print(back["content"])
```

Esto es para archivos de **texto** de hasta **1 MiB** (un archivo mayor se rechaza con un mensaje que nombra el límite, nunca se trunca, y uno que no es UTF-8 se rechaza por el mismo motivo). La ruta es absoluta dentro del punto de montaje del volumen y el servidor la normaliza. Para algo mayor, binario o muchos archivos a la vez, usa SFTP (`GET /service-instances/{id}/sftp`, [móntalo como unidad](/es/sftp/mount-as-drive)) o `POST .../files/download-url` para un enlace de descarga firmado.

## Tareas programadas

Una programación ejecuta una cadena de tareas según una expresión cron. Cinco campos (minuto, hora, día del mes, mes, día de la semana), un nombre de zona horaria y una política de solapamiento: `skip` (no empezar mientras la ejecución anterior siga), `queue` o `concurrent`. Las tareas se ejecutan en orden y el primer fallo termina la ejecución: `console_command`, `run_executable`, `power_action`, `create_backup`, `wait`, como máximo 25. Es la forma más simple de tener una **copia nocturna**:

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/schedules" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "name": "Nightly backup",
  "cron_expression": "0 4 * * *",
  "timezone": "Europe/Madrid",
  "overlap_policy": "skip",
  "timeout_seconds": 3600,
  "action": {"kind": "tasks", "tasks": [{"kind": "create_backup"}]}
}'
curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/schedules/$SCHEDULE/runs" -H "$AUTH"    # ejecutarla ahora
curl -sS "$ZAROZ_API/service-instances/$SERVICE/runs" -H "$AUTH"
```

**TypeScript**

```ts
const s = await call("POST", `/service-instances/${SERVICE}/schedules`, {
  name: "Nightly backup",
  cron_expression: "0 4 * * *",
  timezone: "Europe/Madrid",
  overlap_policy: "skip",
  timeout_seconds: 3600,
  action: { kind: "tasks", tasks: [{ kind: "create_backup" }] },
});
console.log(s.body.next_fire_at, s.body.upcoming_fire_times);
```

**Python**

```python
_, s = call("POST", f"/service-instances/{SERVICE}/schedules", {
    "name": "Nightly backup",
    "cron_expression": "0 4 * * *",
    "timezone": "Europe/Madrid",
    "overlap_policy": "skip",
    "timeout_seconds": 3600,
    "action": {"kind": "tasks", "tasks": [{"kind": "create_backup"}]},
})
print(s["next_fire_at"], s["upcoming_fire_times"])
```

El planificador lee la fila nueva en unos diez segundos; la respuesta ya te da `next_fire_at` y los siguientes instantes, así que puedes comprobar tu expresión. Las programaciones de un servicio de tipo cron job usan `{"kind": "run_job"}` en lugar de tareas. Cada ejecución queda registrada (también las que no se ejecutaron): `outcome` es `succeeded`, `failed`, `interrupted`, `skipped_overlap`, `skipped_condition` o `missed`. Una programación puede llevar además `conditions` de guarda (una ventana horaria, una exclusión de calendario, la ejecución anterior, una comprobación HTTP, el éxito de otra programación) y estar `paused`. Consulta [Copias de seguridad](/es/guides/backups/overview) para saber qué hace una copia.

## Actividad

```bash
curl -sS "$ZAROZ_API/service-instances/$SERVICE/activity?page_size=25" -H "$AUTH"
```

```ts
const a = await call("GET", `/service-instances/${SERVICE}/activity?page_size=25`);
```

```python
_, a = call("GET", f"/service-instances/{SERVICE}/activity?page_size=25")
```

Lo más reciente primero: lo que el propietario hizo en este servicio (y con qué persona o token). Los eventos internos de la plataforma se excluyen.

## Compartir un servicio con alguien

Da acceso a una persona a **un servicio** sin hacerla miembro de la organización. Solo ve lo que concedes, no puede volver a compartirlo y nunca ve otros servicios ni los miembros de la organización. Los permisos son una lista de: `console`, `files`, `power`, `settings`, `databases`, `database_browse`, `database_rows`, `database_schema`, `external_access`, `schedules`, `billing` (ver el servicio va implícito en cualquier comparto). `billing` lee facturas, redimensiona y retira una cancelación programada; nunca cancela, renueva ni paga.

**curl**

```bash
S=/service-instances/$SERVICE/shares
curl -sS -X POST "$ZAROZ_API$S" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"email": "friend@example.com", "permissions": ["console", "power"]}'
curl -sS "$ZAROZ_API$S" -H "$AUTH"
curl -sS -X PATCH "$ZAROZ_API$S/$SHARE" -H "$AUTH" -H "Content-Type: application/json" -d '{"permissions": ["console"]}'
curl -sS -X DELETE "$ZAROZ_API$S/$SHARE" -H "$AUTH"
curl -sS "$ZAROZ_API/me/shared-service-instances" -H "$AUTH"       # lo compartido CONMIGO
```

**TypeScript**

```ts
const share = await call("POST", `/service-instances/${SERVICE}/shares`, {
  email: "friend@example.com",
  permissions: ["console", "power"],
});
console.log(share.body.status); // "pending" hasta que abra "Compartido conmigo"
```

**Python**

```python
_, share = call("POST", f"/service-instances/{SERVICE}/shares", {
    "email": "friend@example.com",
    "permissions": ["console", "power"],
})
print(share["status"])   # "pending" hasta que abra "Compartido conmigo"
```

Compartir requiere el rol Owner o Admin y un producto que lo ofrezca (`shareable` en sus capacidades; si no, `404`). La respuesta es el mismo comparto `pending` tenga o no cuenta esa dirección, y pasa a `accepted` cuando lo abre. Editar los permisos surte efecto en su siguiente petición. Rechazos: `409` `already_shared`, `409` `share_limit_reached`, `422` `cannot_share_with_self` y `429` pasadas 20 comparticiones nuevas por hora y servicio.
