> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abtestly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How assets are served

> usercontent.abtestly.com. Immutable, canonical URL, one-year immutable cache header, CORS wildcard, DMCA takedown path.

Assets you upload live on `usercontent.abtestly.com`. Deliberately a
separate hostname from `app.abtestly.com` and `cdn.abtestly.com`.
customer content is isolated from the app and the snippet.

## The URL shape

```
https://usercontent.abtestly.com/a/<namespace>/<hash>/<filename>
```

* `<namespace>`, a per-site 32-character hex string. Namespaces are
  isolated from each other; site A cannot enumerate site B's files.
* `<hash>`, the SHA-256 of the file contents. Content-addressed.
  any change to bytes produces a new URL.
* `<filename>`, the canonical filename. A request with a different
  filename returns a `308 Permanent Redirect` to the canonical one.

## Cache

Every response includes:

```
Cache-Control: public, max-age=31536000, immutable
```

That is one year, marked immutable. Browsers cache aggressively;
CDNs and edges cache aggressively. Safe, because the content is
content-addressed, a change means a new URL. The old URL never
serves different bytes.

## CORS

`Access-Control-Allow-Origin: *`.

Assets are safe to reference from any domain. `<img>`, `<video>`, CSS
`background-image`, `<a href>` for PDFs, all work anywhere.

## Content type

Set from the file's sniffed type at upload, not from the URL
extension. `image/png`, `image/jpeg`, `image/gif`, `image/webp`,
`application/pdf`.

## HEAD

`HEAD /a/…` returns the same headers as `GET /a/…` with no body.
Useful for programmatic size checks.

## Rate limits

None on serving. The URL is a static-file URL cached at Cloudflare's
edge, traffic is essentially free for us. The upload endpoint on
the dashboard side is rate-limited (100 requests per minute per user),
but that only matters if you are batch-uploading.

## Takedown

Two ways an asset can stop serving:

* **You deleted it.** Soft-delete first, then permanent-delete. See
  [Recycle bin](/assets/recycle-bin).
* **We took it down.** Admin action, only in response to a legal
  request (DMCA, GDPR erasure) or an abuse report. When this
  happens, the R2 object is deleted and the URL is purged from
  Cloudflare cache within minutes.

If your file was taken down and you believe it was in error, email
support with the URL.

## Why a separate hostname

Two reasons:

* **Cookie isolation.** `usercontent.abtestly.com` shares no cookies
  with `app.abtestly.com` or `cdn.abtestly.com`. A malicious file (a
  crafted PDF with active content, for example) cannot read auth
  cookies.
* **Cache scope.** Cache-Control headers for user content are
  different from the app and the snippet. Separating hostnames keeps
  the cache behavior clean.

## The `Access-Control-Allow-Origin` is deliberately wildcard

Some CDN products default to same-origin for user content. We do the
opposite, user content should work inline anywhere the user pastes
the URL. If your compliance policy needs a specific origin, tell us.
