Zaroz
Object storage

Object storage from the API

The Object storage quick start shows how to talk to a bucket with an S3 client. This page is the management side: creating the bucket and its keys and reading usage, all through the Zaroz API instead of the console. Files themselves still go through the S3 endpoint (or the signed links below). The samples use the helper from Using the Zaroz API.

Object storage may be absent on some deployments: check object_storage_buckets in what the deployment offers. Everything here is addressed by the subscription id of your object storage plan, and writing needs the authority to buy for the organization (Owner or Admin).

Create the bucket

One bucket per subscription. cap_bytes is the storage cap (1 GiB to 100 TiB, default 100 GiB); region_id defaults to the region of the plan.

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"label": "site-assets", "cap_bytes": 107374182400}'
JSONCode
{ "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

TypeScriptCode
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); // the two values your S3 client needs

Python

Code
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"]) # the two values your S3 client needs

A second bucket answers 409, and an inactive subscription 422. bucket_name is the real name to use in S3 requests (not your label). access says what the data plane allows right now: open, read_only (over the cap), suspended or deleted. GET /subscriptions/{id}/bucket returns the same object (404 when there is no bucket yet).

Change the cap, the label or public read

TerminalCode
curl -sS -X PATCH "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"public_read": true, "cap_bytes": 214748364800}'
TypeScriptCode
await call("PATCH", `/subscriptions/${SUB}/bucket`, { public_read: true, cap_bytes: 200 * 1024 ** 3 });
Code
call("PATCH", f"/subscriptions/{SUB}/bucket", {"public_read": True, "cap_bytes": 200 * 1024**3})

Absent fields are left alone. A cap change and a public-read change reach the data plane at once. With public_read on, anyone can download an object at public_url/KEY without a key (listing and writing still need one); turn it off to close it. Not allowed once the bucket is scheduled for deletion (422).

Access keys

A key is read_only or read_write and works only on this bucket. At most 20 active keys per bucket (429 past it).

curl

TerminalCode
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

TypeScriptCode
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); // the secret is shown ONCE await call("DELETE", `/subscriptions/${SUB}/bucket/keys/${k.body.data.id}`); // revoke

Python

Code
_, k = call("POST", f"/subscriptions/{SUB}/bucket/keys", {"name": "ci-upload", "scope": "read_write"}) print(k["data"]["access_key_id"], k["secret_access_key"]) # the secret is shown ONCE call("DELETE", f"/subscriptions/{SUB}/bucket/keys/{k['data']['id']}") # revoke

The secret is returned only in this response; listing never shows it, and Zaroz cannot show it again. Use access_key_id and secret_access_key as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the quick start. Revoking stops that key at once.

Usage and the bill

TerminalCode
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"
TypeScriptCode
const u = await call("GET", `/subscriptions/${SUB}/bucket/usage`); console.log(u.body.data);
Code
_, u = call("GET", f"/subscriptions/{SUB}/bucket/usage") print(u["data"])

Without a window you get this month so far (at most 93 days): hourly stored-byte and traffic samples, the average stored size in GiB-months and the bytes sent out, which are the two billed meters. Uploads are free.

Browse and sign transfers

If you do not want a key at all (for instance a web back end that hands links to browsers), ask the API to sign a link per object. Listing is metadata only and not billed.

curl

TerminalCode
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"}' # up to 64 MiB: a signed PUT of exactly that many bytes. Larger (up to 625 GiB): a multipart upload. curl -sS -X POST "$ZAROZ_API$O/download" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"key": "photos/a.jpg", "save_as": "holiday.jpg"}' # a 15-minute signed GET

TypeScript

TypeScriptCode
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" }); // For a single PUT upload, send exactly size_bytes bytes to the signed URL in start.body.data const dl = await call("POST", `${O}/download`, { key: "photos/a.jpg" }); console.log(dl.body.data); // the signed URL, valid for 15 minutes

Python

Code
O = f"/subscriptions/{SUB}/bucket/objects" _, start = call("POST", f"{O}/upload", {"key": "photos/a.jpg", "size_bytes": len(data), "content_type": "image/jpeg"}) # For a single PUT upload, send exactly size_bytes bytes to the signed URL in start["data"] _, dl = call("POST", f"{O}/download", {"key": "photos/a.jpg"}) print(dl["data"]) # the signed URL, valid for 15 minutes

Downloads go straight to the storage endpoint and are billed as egress like any S3 client, previews included. Larger uploads are multipart: POST .../objects/upload/parts signs each part, .../upload/complete finishes and .../upload/abort cancels. POST .../objects/delete deletes objects and POST .../objects/folders creates a folder marker. Uploads are refused while the bucket is over its cap or suspended. For heavy or repeated use, an access key and an S3 client are simpler than signing each object.

Delete the bucket

TerminalCode
curl -sS -X DELETE "$ZAROZ_API/subscriptions/$SUBSCRIPTION/bucket" -H "$AUTH" # 204
TypeScriptCode
await call("DELETE", `/subscriptions/${SUB}/bucket`);
Code
call("DELETE", f"/subscriptions/{SUB}/bucket")

The keys stop working immediately and the slot frees, but the data stays for a seven-day recovery window (purge_at shows the date) before it is purged for good.

Last modified on