Clatter Labs
Console

Code Buckets

Quickstart

Create a project and a bucket, upload a zip of your code, and read a file back, in about five minutes.

Before you start

You need a Clatter Labs account. If you don't have one, create it at accounts.clatterlabs.com. Code Buckets is included free: 1,000,000 tokens a month and 1 backup.

Create an API key and copy your Account ID

Sign in to the Console. Your Account ID (like CL-7K2M9QXA) is on the Home page and in Settings.

Go to API keys › Create key, and tick buckets:read, buckets:write and backups:write. The key starts with clsk_ and is shown only once.

Terminal
export CLATTER_API_KEY="clsk_..."          # Console › API keys
export CLATTER_ACCOUNT_ID="CL-XXXXXXXX"    # Console › Settings
export CB="https://api.codebuckets.clatterlabs.xyz"

Create a project

The response carries your Project ID (cbp_…).

curl $CB/v1/projects \
  -H "Authorization: Bearer $CLATTER_API_KEY" \
  -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{"name": "storefront"}'
import os, requests

CB = "https://api.codebuckets.clatterlabs.xyz"
headers = {
    "Authorization": f"Bearer {os.environ['CLATTER_API_KEY']}",
    "X-Clatter-Account": os.environ["CLATTER_ACCOUNT_ID"],
}
project = requests.post(f"{CB}/v1/projects", headers=headers, json={"name": "storefront"}).json()
print(project["id"])  # cbp_...
const CB = "https://api.codebuckets.clatterlabs.xyz";
const headers = {
  Authorization: `Bearer ${process.env.CLATTER_API_KEY}`,
  "X-Clatter-Account": process.env.CLATTER_ACCOUNT_ID,
  "Content-Type": "application/json",
};
const project = await fetch(`${CB}/v1/projects`, { method: "POST", headers, body: JSON.stringify({ name: "storefront" }) }).then((r) => r.json());
console.log(project.id); // cbp_...

Create a bucket

Terminal
curl $CB/v1/projects/PROJECT_ID/buckets \
  -H "Authorization: Bearer $CLATTER_API_KEY" \
  -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{"name": "web"}'

You get back the bucket id (cbk_…).

Upload a zip

Uploads are chunked so big files work and can resume. Start the upload with the file's name and size, send each 16 MiB chunk, then complete it.

FILE=storefront.zip
SIZE=$(wc -c < $FILE | tr -d ' ')
UP=$(curl -s $CB/v1/buckets/BUCKET_ID/uploads \
  -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
  -H "Content-Type: application/json" -d "{\"name\": \"$FILE\", \"size\": $SIZE}" | jq -r .id)

split -b 16777216 -d -a 5 $FILE part-
i=0
for p in part-*; do
  curl -s -X PUT $CB/v1/uploads/$UP/chunks/$i \
    -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
    -H "Content-Type: application/octet-stream" --data-binary @$p > /dev/null
  i=$((i+1))
done

curl -s -X POST $CB/v1/uploads/$UP/complete \
  -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID"
import os, requests

path = "storefront.zip"
size = os.path.getsize(path)
up = requests.post(f"{CB}/v1/buckets/{bucket_id}/uploads", headers=headers, json={"name": path, "size": size}).json()

with open(path, "rb") as f:
    for i in range(up["chunk_count"]):
        chunk = f.read(up["chunk_size"])
        requests.put(f"{CB}/v1/uploads/{up['id']}/chunks/{i}", headers=headers, data=chunk).raise_for_status()

done = requests.post(f"{CB}/v1/uploads/{up['id']}/complete", headers=headers).json()
print(done["status"])  # processing, then ready
import { open, stat } from "node:fs/promises";

const path = "storefront.zip";
const { size } = await stat(path);
const up = await fetch(`${CB}/v1/buckets/${bucketId}/uploads`, { method: "POST", headers, body: JSON.stringify({ name: path, size }) }).then((r) => r.json());

const fh = await open(path);
for (let i = 0; i < up.chunk_count; i++) {
  const buf = Buffer.alloc(Math.min(up.chunk_size, size - i * up.chunk_size));
  await fh.read(buf, 0, buf.length, i * up.chunk_size);
  const r = await fetch(`${CB}/v1/uploads/${up.id}/chunks/${i}`, { method: "PUT", headers: { ...headers, "Content-Type": "application/octet-stream" }, body: buf });
  if (!r.ok) throw new Error((await r.json()).error.message);
}
await fh.close();
const done = await fetch(`${CB}/v1/uploads/${up.id}/complete`, { method: "POST", headers }).then((r) => r.json());
console.log(done.status); // processing, then ready

Browse the code

A few seconds later the zip is unpacked. List the tree and read a file:

Terminal
curl "$CB/v1/buckets/BUCKET_ID/tree" \
  -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID"

curl "$CB/v1/buckets/BUCKET_ID/files/README.md" \
  -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID"

Or open the bucket in the Console and click through the files.

Back it up

Terminal
curl $CB/v1/buckets/BUCKET_ID/backups \
  -H "Authorization: Bearer $CLATTER_API_KEY" -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
  -H "Content-Type: application/json" -d '{"name": "First backup"}'

Next steps