BunnyCDN: List Files → Clickable Download Doc
Skip the awkward storage web UI — one Storage API call, then Markdown/HTML links your partner can open and download
← Tech · Cloudflare DNS · Docker · IndexNow
When to use this
You have a Bunny Storage zone (example: gfafiles) with a folder full of files
(example: podcasts/ — “11 pages” in the web UI). You want a
clean list of clickable public download links to share with someone.
Best path: Bunny Storage API lists the directory in one JSON response.
Then a small script (or jq) turns that into Markdown/HTML.
Process note: The runnable bits live as
scripts/list-bunny-storage.cjs and
procedures/bunny-list-storage-files.md in the repo — not only as this Astro page —
so agents can execute the procedure without “deploying” secrets into a site page.
1. Get the Storage Zone password (AccessKey)
- Open the Bunny dashboard.
- Go to Storage → your zone (e.g.
gfafiles). - Left sidebar → Access.
- Copy the Password — that value is the Storage API
AccessKey.
This is not the same as your account-level Bunny API key. Keep it out of git, screenshots, and public pages.
2. List files with curl
curl -s -X GET \
"https://storage.bunnycdn.com/gfafiles/podcasts/" \
-H "AccessKey: YOUR_STORAGE_ZONE_PASSWORD" Response is a JSON array. Typical fields:
[
{
"ObjectName": "gfa444-non-yt.mp4",
"Length": 239234567,
"LastChanged": "2024-12-31T19:07:05",
"IsDirectory": false
}
] Filenames only (needs jq):
curl -s -X GET \
"https://storage.bunnycdn.com/gfafiles/podcasts/" \
-H "AccessKey: YOUR_STORAGE_ZONE_PASSWORD" \
| jq -r '.[].ObjectName' | sort
If the call fails with a region error, check the zone’s storage hostname
(e.g. ny.storage.bunnycdn.com) in the dashboard and use that host instead of
storage.bunnycdn.com.
3. Public download URLs (Pull Zone)
Storage API access is private. Public downloads go through the Pull Zone (CDN hostname) connected to that storage zone.
- Default Bunny host:
https://YOUR-PULLZONE.b-cdn.net/podcasts/file.mp4 - Custom domain (recommended for sharing):
https://files.globalfromasia.com/podcasts/file.mp4— any hostname you attached to the pull zone in Bunny (CNAME → pull zone). Use that hostname asBUNNY_PULL_ZONE; you do not needb-cdn.net.
Path after the hostname must match the storage path (folder + filename). Confirm the pull zone is linked to the same storage zone as in the API call, and that the custom domain shows as active on the pull zone.
export BUNNY_PULL_ZONE='files.globalfromasia.com' # no https://, no trailing slash
# links become: https://files.globalfromasia.com/podcasts/… 4. Script (download + run)
Download the script from this site: /scripts/list-bunny-storage.cjs
(same file as in the repo under scripts/ and public/scripts/).
No secrets inside — only your env vars unlock a zone.
# Download: /scripts/list-bunny-storage.cjs then:
export BUNNY_ACCESS_KEY='…' # Storage zone Password (Access tab)
export BUNNY_STORAGE_ZONE='gfafiles'
export BUNNY_PULL_ZONE='files.globalfromasia.com' # custom host OK — no https://
# A) One folder only (e.g. podcasts):
export BUNNY_PATH='podcasts/'
node list-bunny-storage.cjs --out /tmp/gfa-podcasts-links.md
# B) Entire storage zone (all folders + nested files) — need --recursive:
unset BUNNY_PATH
# (BUNNY_PATH='/' also works; prefer unset — both mean zone root)
node list-bunny-storage.cjs --recursive --out /tmp/gfafiles-full.md
# C) Root level only (no subfolders):
unset BUNNY_PATH
node list-bunny-storage.cjs --out /tmp/gfafiles-root.md
open /tmp/gfafiles-full.md
node list-bunny-storage.cjs --help # recipes also printed here Useful flags:
--recursive— required for whole-zone / nested folders (API is one directory per call)--html --out /tmp/links.html— browser-friendly list--json— raw file list (pull zone optional)--help— full usage
Operator checklist (for humans + agents):
procedures/bunny-list-storage-files.md in the repo.
4b. Whole zone vs one folder
| Goal | Settings |
|---|---|
| One folder (e.g. podcasts only) | BUNNY_PATH=podcasts/ — recursive only if that folder has subfolders |
| Entire storage zone | Leave BUNNY_PATH empty (zone root) + --recursive |
| Root files only (no subfolders) | Empty path, no --recursive |
Bunny’s list endpoint is per directory. Without --recursive, you only see
that folder’s immediate children (files + directory names). With --recursive, the script
walks every subdirectory and builds one flat list of download links (paths still include folders,
e.g. podcasts/ep1.mp4, images/logo.png).
export BUNNY_ACCESS_KEY='…'
export BUNNY_STORAGE_ZONE='gfafiles'
export BUNNY_PULL_ZONE='files.globalfromasia.com'
unset BUNNY_PATH
node scripts/list-bunny-storage.cjs --recursive \
--title "gfafiles — full zone" \
--out /tmp/gfafiles-full.md
# → https://files.globalfromasia.com/… paths in the Markdown 5. Example Markdown output
# Podcast Files
- [gfa444-non-yt.mp4](https://files.globalfromasia.com/podcasts/gfa444-non-yt.mp4) (228.1 MB)
- [gfa444-yt.mp4](https://files.globalfromasia.com/podcasts/gfa444-yt.mp4) (190.4 MB)
- [gfa444.mp3](https://files.globalfromasia.com/podcasts/gfa444.mp3) (42.0 MB) Share that Markdown (or the HTML export) however you normally send docs — GFAVIP, email, Drive. Recipients just click the links if the pull zone allows public read.
6. Python alternative (one-off)
import requests
ACCESS_KEY = "YOUR_STORAGE_ZONE_PASSWORD"
ZONE = "gfafiles"
PATH = "podcasts/"
PULL_ZONE = "your-pullzone.b-cdn.net"
url = f"https://storage.bunnycdn.com/{ZONE}/{PATH}"
files = requests.get(url, headers={"AccessKey": ACCESS_KEY}).json()
print("# Podcast Download Links\n")
for f in sorted(files, key=lambda x: x["ObjectName"]):
if not f.get("IsDirectory"):
name = f["ObjectName"]
size_mb = f["Length"] / (1024 * 1024)
link = f"https://{PULL_ZONE}/{PATH}{name}"
print(f"- [{name}]({link}) ({size_mb:.1f} MB)") 7. Security & hygiene
- Never commit
BUNNY_ACCESS_KEYor paste it into Astro frontmatter. - Generated lists of private assets should stay out of the public git history.
- If links must be restricted, use Bunny token authentication / signed URLs on the pull zone — plain
b-cdn.netlinks are typically public. - Rotate the storage password if it was shared in chat by mistake.
Hand this to an agent
You only need to provide (not the password in a public ticket if you can help it):
- Storage zone name (e.g.
gfafiles) - Folder path (e.g.
podcasts/) - Pull zone hostname
- Whether subfolders should be included (
--recursive)
The agent sets BUNNY_ACCESS_KEY in a local shell (or you run the script),
writes /tmp/…-links.md, and returns the file for sharing.
No second “specialist” agent required for the happy path.
Related
- Bunny Storage API docs
- Cloudflare DNS handoff — related ops for hosting/DNS control
- Docker — packaging tools you might run next to storage workflows
- IndexNow — when public CDN URLs need search ping after publish
Comments
Approved comments appear below. Log in once with GFAVIP — it applies across the whole site. GFAVIP login
View comments archive