# Almacenamiento de objetos desde la API

La [guía rápida de almacenamiento de objetos](/es/storage/quick-start) muestra cómo hablar con un bucket con un cliente S3. Esta página es la parte de **gestión**: crear el bucket y sus claves y leer el uso, todo con la API de Zaroz en lugar de la consola. Los archivos en sí siguen yendo por el endpoint S3 (o los enlaces firmados de abajo). Los ejemplos usan el ayudante de [Usar la API de Zaroz](/es/guides/api/overview).

El almacenamiento de objetos puede faltar en algunos despliegues: comprueba `object_storage_buckets` en [qué ofrece el despliegue](/es/guides/api/overview#qué-ofrece-un-despliegue). Todo se direcciona con el id de **suscripción** de tu plan de almacenamiento de objetos, y escribir requiere la autoridad para comprar por la organización (Owner o Admin).

## Crear el bucket

Un bucket por suscripción. `cap_bytes` es el límite de almacenamiento (de 1 GiB a 100 TiB, 100 GiB por defecto); `region_id` toma por defecto la región del plan.

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"label": "site-assets", "cap_bytes": 107374182400}'
```

```json
{
  "data": {
    "id": "…", "label": "site-assets",
    "endpoint": "https://…", "bucket_name": "zb-3f2a…", "s3_region": "us-east-1",
    "addressing_style": "path",
    "cap_bytes": 107374182400, "stored_bytes": null,
    "public_read": false, "public_url": null, "access": "open", "purge_at": null
  }
}
```

**TypeScript**

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

const b = await call("POST", `/subscriptions/${SUB}/bucket`, { label: "site-assets", cap_bytes: 100 * 1024 ** 3 });
const { endpoint, bucket_name } = b.body.data;
console.log(endpoint, bucket_name); // los dos valores que necesita tu cliente S3
```

**Python**

```python
from zaroz import call

_, b = call("POST", f"/subscriptions/{SUB}/bucket", {"label": "site-assets", "cap_bytes": 100 * 1024**3})
print(b["data"]["endpoint"], b["data"]["bucket_name"])   # los dos valores que necesita tu cliente S3
```

Un segundo bucket responde `409`, y una suscripción inactiva `422`. `bucket_name` es el nombre real para las peticiones S3 (no tu etiqueta). `access` indica lo que permite ahora el plano de datos: `open`, `read_only` (por encima del límite), `suspended` o `deleted`. `GET /subscriptions/{id}/bucket` devuelve el mismo objeto (`404` si aún no hay bucket).

## Cambiar el límite, la etiqueta o la lectura pública

```bash
curl -sS -X PATCH "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"public_read": true, "cap_bytes": 214748364800}'
```

```ts
await call("PATCH", `/subscriptions/${SUB}/bucket`, { public_read: true, cap_bytes: 200 * 1024 ** 3 });
```

```python
call("PATCH", f"/subscriptions/{SUB}/bucket", {"public_read": True, "cap_bytes": 200 * 1024**3})
```

Los campos ausentes no cambian. Un cambio de límite y uno de lectura pública llegan al plano de datos al instante. Con `public_read` activado, cualquiera puede descargar un objeto en `public_url/KEY` sin clave (listar y escribir siguen necesitándola); desactívalo para cerrarlo. No se permite cuando el bucket está programado para borrarse (`422`).

## Claves de acceso

Una clave es `read_only` o `read_write` y solo funciona en este bucket. Como máximo **20** claves activas por bucket (`429` más allá).

**curl**

```bash
curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket/keys" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"name": "ci-upload", "scope": "read_write"}'
# {"data": {"id": "…", "access_key_id": "…", "scope": "read_write", "name": "ci-upload"}, "secret_access_key": "…"}

curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket/keys" -H "$AUTH"
curl -sS -X DELETE "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket/keys/$KEY" -H "$AUTH"
```

**TypeScript**

```ts
const k = await call("POST", `/subscriptions/${SUB}/bucket/keys`, { name: "ci-upload", scope: "read_write" });
console.log(k.body.data.access_key_id, k.body.secret_access_key); // el secreto se muestra UNA vez
await call("DELETE", `/subscriptions/${SUB}/bucket/keys/${k.body.data.id}`); // revocar
```

**Python**

```python
_, k = call("POST", f"/subscriptions/{SUB}/bucket/keys", {"name": "ci-upload", "scope": "read_write"})
print(k["data"]["access_key_id"], k["secret_access_key"])   # el secreto se muestra UNA vez
call("DELETE", f"/subscriptions/{SUB}/bucket/keys/{k['data']['id']}")   # revocar
```

El secreto se devuelve **solo en esta respuesta**; listar nunca lo muestra y Zaroz no puede volver a mostrarlo. Usa `access_key_id` y `secret_access_key` como `AWS_ACCESS_KEY_ID` y `AWS_SECRET_ACCESS_KEY` en la [guía rápida](/es/storage/quick-start). Revocar detiene esa clave al instante.

## Uso y factura

```bash
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket/usage" -H "$AUTH"
curl -sS -G "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket/usage" -H "$AUTH" \
  --data-urlencode "from=2026-10-01T00:00:00Z" --data-urlencode "to=2026-10-15T00:00:00Z"
```

```ts
const u = await call("GET", `/subscriptions/${SUB}/bucket/usage`);
console.log(u.body.data);
```

```python
_, u = call("GET", f"/subscriptions/{SUB}/bucket/usage")
print(u["data"])
```

Sin ventana obtienes lo que va de mes (como máximo 93 días): muestras por hora de bytes almacenados y tráfico, el tamaño medio almacenado en GiB-mes y los bytes enviados, que son los dos contadores facturados. Subir es gratis.

## Explorar y firmar transferencias

Si no quieres usar ninguna clave (por ejemplo un backend web que entrega enlaces a navegadores), pide a la API que firme un enlace por objeto. Listar es solo metadatos y no se factura.

**curl**

```bash
O=/subscriptions/$SUBSCRIPTION/bucket/objects
curl -sS -G "$ZAROZ_API$O" -H "$AUTH" --data-urlencode "prefix=photos/" --data-urlencode "limit=100"

curl -sS -X POST "$ZAROZ_API$O/upload" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"key": "photos/a.jpg", "size_bytes": 482344, "content_type": "image/jpeg"}'
# hasta 64 MiB: un PUT firmado de exactamente esos bytes. Más grande (hasta 625 GiB): subida multiparte.

curl -sS -X POST "$ZAROZ_API$O/download" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"key": "photos/a.jpg", "save_as": "holiday.jpg"}'
# un GET firmado de 15 minutos
```

**TypeScript**

```ts
const O = `/subscriptions/${SUB}/bucket/objects`;
const start = await call("POST", `${O}/upload`, { key: "photos/a.jpg", size_bytes: file.byteLength, content_type: "image/jpeg" });
// Para una subida con un solo PUT, envía exactamente size_bytes bytes a la URL firmada de start.body.data

const dl = await call("POST", `${O}/download`, { key: "photos/a.jpg" });
console.log(dl.body.data); // la URL firmada, válida 15 minutos
```

**Python**

```python
O = f"/subscriptions/{SUB}/bucket/objects"
_, start = call("POST", f"{O}/upload", {"key": "photos/a.jpg", "size_bytes": len(data), "content_type": "image/jpeg"})
# Para una subida con un solo PUT, envía exactamente size_bytes bytes a la URL firmada de start["data"]

_, dl = call("POST", f"{O}/download", {"key": "photos/a.jpg"})
print(dl["data"])   # la URL firmada, válida 15 minutos
```

Las descargas van directas al endpoint de almacenamiento y se facturan como salida igual que cualquier cliente S3, vistas previas incluidas. Las subidas grandes son multiparte: `POST .../objects/upload/parts` firma cada parte, `.../upload/complete` termina y `.../upload/abort` cancela. `POST .../objects/delete` borra objetos y `POST .../objects/folders` crea un marcador de carpeta. Las subidas se rechazan mientras el bucket supera su límite o está suspendido. Para un uso intenso o repetido, una clave de acceso y un cliente S3 son más simples que firmar cada objeto.

## Borrar el bucket

```bash
curl -sS -X DELETE "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH"      # 204
```

```ts
await call("DELETE", `/subscriptions/${SUB}/bucket`);
```

```python
call("DELETE", f"/subscriptions/{SUB}/bucket")
```

Las claves dejan de funcionar de inmediato y el hueco se libera, pero los datos permanecen un **periodo de recuperación de siete días** (`purge_at` muestra la fecha) antes de eliminarse para siempre.
