API reference
Base URL: https://cdn.darrellmucheri.com/api/public/v1
Quick start
- Ask the site owner for an API key (keys start with
nimbus_). - Send files to
POST /api/public/v1/fileswith the key as a Bearer token. - Use the returned
urlanywhere — it's served fromhttps://files.darrellmucheri.com.
Authentication
Create a key in Admin → API keys. Send it as a Bearer token (or an x-api-key header). Never expose keys in browser code.
Authorization: Bearer nimbus_your_key_here
/filesMultipart form-data. Fields: file (required, max 100 MB), folder (optional), key (optional custom file name).
curl -X POST https://cdn.darrellmucheri.com/api/public/v1/files \ -H "Authorization: Bearer $NIMBUS_KEY" \ -F "file=@./logo.png" \ -F "folder=images"
// Node / browser server
const fd = new FormData();
fd.append("file", file);
const res = await fetch("https://cdn.darrellmucheri.com/api/public/v1/files", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.NIMBUS_KEY}` },
body: fd,
});
const { url } = await res.json();Response 201
{
"key": "images/logo.png",
"size": 18234,
"contentType": "image/png",
"url": "https://files.darrellmucheri.com/images/logo.png"
}/files?prefix=&limit=&cursor=Returns up to limit files (default 100, max 1000). Pass the returned cursor to get the next page.
curl "https://cdn.darrellmucheri.com/api/public/v1/files?prefix=images/&limit=50" \ -H "Authorization: Bearer $NIMBUS_KEY"
{
"files": [
{ "key": "images/logo.png", "size": 18234, "lastModified": "2026-10-13T18:00:00.000Z",
"etag": "9b2c…", "url": "https://files.darrellmucheri.com/images/logo.png" }
],
"cursor": null
}/files/:keycurl -X DELETE https://cdn.darrellmucheri.com/api/public/v1/files/images/logo.png \
-H "Authorization: Bearer $NIMBUS_KEY"
→ { "deleted": "images/logo.png" }/api/public/cdn/:keyPublic, no key needed. Responses include Cache-Control: public, max-age=31536000, immutable, ETags and byte-range support for video. If you set a public R2 domain in settings, returned URLs use that domain instead.
<img src="https://files.darrellmucheri.com/images/logo.png" />
/api/public/uploadPowers the free uploader on the homepage. No key needed, but each request must carry a solved proof-of-work captcha from GET /api/public/captcha: find a nonce where sha256(salt + nonce) starts with difficulty zeros, then send the challenge plus nonce as JSON in the captcha field. Optional name sets a custom link; if taken, a short prefix is added.
const ch = await (await fetch("https://cdn.darrellmucheri.com/api/public/captcha")).json();
let nonce = 0;
while (!(await sha256(ch.salt + nonce)).startsWith("0".repeat(ch.difficulty))) nonce++;
const fd = new FormData();
fd.append("captcha", JSON.stringify({ ...ch, nonce }));
fd.append("name", "my-photo.jpg");
fd.append("file", file);
const { url } = await (await fetch("https://cdn.darrellmucheri.com/api/public/upload", { method: "POST", body: fd })).json();
// → https://files.darrellmucheri.com/public/my-photo.jpgCode examples
Python
import requests
r = requests.post("https://cdn.darrellmucheri.com/api/public/v1/files",
headers={"Authorization": "Bearer " + NIMBUS_KEY},
files={"file": open("logo.png", "rb")},
data={"folder": "images"})
print(r.json()["url"])PHP
$ch = curl_init("https://cdn.darrellmucheri.com/api/public/v1/files");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $key],
CURLOPT_POSTFIELDS => ["file" => new CURLFile("logo.png")],
CURLOPT_RETURNTRANSFER => true,
]);
echo json_decode(curl_exec($ch))->url;List every file (paging)
let cursor = null, all = [];
do {
const q = new URLSearchParams({ limit: "1000", ...(cursor && { cursor }) });
const r = await (await fetch(`https://cdn.darrellmucheri.com/api/public/v1/files?${q}`, { headers })).json();
all.push(...r.files); cursor = r.cursor;
} while (cursor);Limits
- Max file size: 100 MB per request.
- Any file type is accepted.
- File keys: up to 900 characters; uploading to an existing key via the API overwrites it.
- Public uploads are stored under
public/and require a fresh captcha each time. - CORS is open, but keep API keys on your server.
Errors
| 400 | Bad request — missing file or invalid key |
| 401 | Missing or invalid API key |
| 403 | Captcha missing, expired or invalid (public upload) |
| 404 | File not found |
| 413 | File larger than 100 MB |
| 502 / 503 | R2 unreachable or not configured |
{ "error": "Invalid or missing API key" }