# Public files

> Share stored files with a public link, or keep them private (the default). Images get their own fast host.

Every file in a bucket is **private** by default: it can only be read with an API key and your Account ID, or in the Console. Make a file **public** and it gets a link anyone can open.

Public links live on two hosts, picked by what the file is:

| File | Public link | Cache |
| --- | --- | --- |
| **Images** (`image/*`: PNG, JPEG, GIF, WebP, AVIF, SVG, ICO…) | `https://img.cdn.clatterlabs.xyz/<id>` | 1 year, `immutable` |
| **Everything else** (code, static files, archives, video, PDFs…) | `https://files.cdn-buckets.clatterlabs.xyz/<object_id>/<name>` | 60 seconds |

```
https://img.cdn.clatterlabs.xyz/V1StGXR8Z5jdHi6BmyT2kq
https://files.cdn-buckets.clatterlabs.xyz/cbo_x4p8t2w6r1k9m3q7n5d0/app.js
```

Both are served decrypted on our servers, with the right `Content-Type`, edge caching and `ETag`. Private files are never served: their links answer `404`, the same as a link that doesn't exist. You don't pick the host: `public_url` in every response is already the right link, and `served_as` says which kind it is (`image` or `file`).

## Images: img.cdn.clatterlabs.xyz

- The link is an **opaque id** (22 letters and digits), with no file name, bucket or object id in it.
- A file counts as an image when its upload's `content_type` is `image/*`, or, when that was empty or `application/octet-stream`, when its extension is an image one (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.avif`, `.svg`, `.ico`, `.bmp`, `.tif`, `.heic`, `.jxl`).
- Served with that `Content-Type` and `Cache-Control: public, max-age=31536000, immutable`: a stored file never changes, so browsers and CDNs can keep it for a year. Put these links straight into `<img>` tags, Markdown, emails or OG tags.
- When an image stops being public, its id is **retired for good**: the old link answers `404` and keeps doing so, even if you make the image public again (it then gets a new id and a new `public_url`). Browsers that already loaded the image may still show their cached copy.
- An older `files.cdn-buckets.clatterlabs.xyz` link to an image redirects (`308`) to its image link. Add `?download=1` to download the original from the files host instead.

## Files: files.cdn-buckets.clatterlabs.xyz

`https://files.cdn-buckets.clatterlabs.xyz/<object_id>/<name>`

- The file name at the end is for people and tools; the object id identifies the file.
- `Content-Type` comes from the upload's `content_type`, or from the file extension when it was `application/octet-stream`.
- Opens inline in the browser; add `?download=1` to download it as an attachment.
- Supports `HEAD`, single `Range` requests (`206 Partial Content`, so large videos and files stream and resume), and `ETag` / `If-None-Match` (`304`).
- HTML, SVG and XML files are served with a sandboxing `Content-Security-Policy`, so they can't run scripts on our domain.
- `Cache-Control: public, max-age=60, must-revalidate`.

Both hosts allow CORS from any website (`Access-Control-Allow-Origin: *`).

## File or bucket

- A **bucket** is `private` (the default) or `public`.
- A **file** (a stored zip or asset) is `inherit` (the default: follow the bucket), `private` or `public`.

What applies is the file's own setting, or the bucket's when the file is `inherit`. So you can make one image public in a private bucket, or keep one file private in a public bucket. The response's `effective_visibility` and `public_url` always tell you the result.

The **code preview tree** (the files unpacked from your zips) is never public. To publish a file from your code, upload it as an asset.

## Make a file public

<CodeGroup>
```bash cURL
curl -X PATCH "https://api.codebuckets.clatterlabs.xyz/v1/buckets/$BUCKET_ID/objects/$OBJECT_ID/visibility" \
  -H "Authorization: Bearer $CLATTER_API_KEY" \
  -H "X-Clatter-Account: $CLATTER_ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{"visibility": "public"}'
# { "object": "object", "name": "hero.png", "visibility": "public", "effective_visibility": "public",
#   "public_url": "https://img.cdn.clatterlabs.xyz/V1StGXR8Z5jdHi6BmyT2kq", "served_as": "image", ... }
```

```python Python
import os, requests

CB = "https://api.codebuckets.clatterlabs.xyz"
H = {"Authorization": f"Bearer {os.environ['CLATTER_API_KEY']}", "X-Clatter-Account": os.environ["CLATTER_ACCOUNT_ID"]}

r = requests.patch(f"{CB}/v1/buckets/{bucket_id}/objects/{object_id}/visibility", headers=H, json={"visibility": "public"})
r.raise_for_status()
print(r.json()["public_url"])  # img.cdn.clatterlabs.xyz/<id> for images, files.cdn-buckets.clatterlabs.xyz/... otherwise
```

```javascript JavaScript
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 r = await fetch(`${CB}/v1/buckets/${bucketId}/objects/${objectId}/visibility`, { method: "PATCH", headers, body: JSON.stringify({ visibility: "public" }) });
console.log((await r.json()).public_url);
```
</CodeGroup>

You can also upload straight to public: pass `"visibility": "public"` (and the right `content_type`, like `image/png`) when you [start the upload](/api-reference/code-buckets/uploads/create), and `public_url` works as soon as the upload completes.

To make a whole bucket public, use [Set bucket visibility](/api-reference/code-buckets/visibility/bucket). [Get a public URL](/api-reference/code-buckets/visibility/public-url) returns a file's link at any time, with `public` telling you whether it works right now.

## Make it private again

Send `"visibility": "private"` (or `"inherit"` in a private bucket). The link stops working on the next request: every request to the public hosts checks the file's visibility first, and edge-cached parts of a file are only served after that check. An image's id is retired at the same moment (see above). Copies already in someone's browser are out of our reach: up to 60 seconds for files, longer for images, which are cached as immutable.

Deleting a public file also turns its link off.

## Events

Every change is in the [events feed](/code-buckets/events): `object.visibility_changed` (with `from`, `to`, `effective` and `public_url` in `data`) and `bucket.visibility_changed` (with `from`, `to` and how many files follow the bucket).

## Tokens

Changing visibility is a normal request: 1 token. Traffic to your public links (both hosts) is counted for your account at the same rate as API downloads (1 token per request + 1 per MiB sent) and shows in [Get usage](/api-reference/code-buckets/usage) under `storage.public_files`.

## In the Console

Open a bucket › **Uploads & files** and pick **Public**, **Private** or **Bucket default** next to a file. Public files show their link with a copy button, and images a thumbnail served from the images host. The bucket's own default is in **Settings › Access**.
