madebydanny.uk / cdn
Developer API
A REST API for programmatic access to MBD CDN — upload files, manage your library, and check usage from your own apps and scripts.
base url
https://cdn.madebydanny.uk
make your first request in 60 seconds
Get an API key
Reach out via Bluesky DMs to request a key, with your APP_NAME and the KEY_OWNER. Keys look like mbd_ followed by 64 hex characters.
Upload a file
Send the raw file body as a POST with the correct Content-Type. Pass your key in the Authorization header.
curl -X POST https://cdn.madebydanny.uk/v1/upload \ -H "Authorization: Bearer mbd_your_key_here" \ -H "Content-Type: image/png" \ --data-binary @photo.png
Get your public URL
A successful upload returns a JSON object. The url field is your permanent public link — ready to embed anywhere.
{
"success": true,
"url": "https://cdn.madebydanny.uk/user-content/2026-04-29/abc123.png",
"id": "abc12345-1234-1234-1234-abc123456789",
"contentType": "image/png",
"fileType": "image",
"size": 204800
}
Use the URL anywhere
Files are served over Cloudflare's global edge with Cache-Control: immutable. Embed in HTML, markdown, social posts, or wherever you like.
<img src="https://cdn.madebydanny.uk/user-content/2026-04-29/abc123.png" alt="My photo">
javascript example
// Upload a File object (e.g. from an <input type="file">) async function uploadToCDN(file, apiKey) { const res = await fetch('https://cdn.madebydanny.uk/v1/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': file.type, }, body: file, }); const data = await res.json(); if (!data.success) throw new Error(data.error); return data.url; // permanent public URL } // Usage const url = await uploadToCDN(fileInputEl.files[0], 'mbd_your_key_here'); console.log(url); // https://cdn.madebydanny.uk/user-content/...
All /v1/ routes require an API key sent as a Bearer token in the Authorization header. Anonymous uploads via POST / continue to work without any key.
sending your key
Authorization: Bearer mbd_your_key_here
key format
mbd_ followed by 64 hex characters (32 random bytes). Example: mbd_a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2
file operations
| Name | Type | Required | Description |
|---|---|---|---|
| Authorization | string | required | Bearer token — Bearer mbd_… |
| Content-Type | string | required | MIME type of the file, e.g. image/png |
| Content-Length | integer | optional | File size in bytes. Recommended — allows accurate quota pre-checks. |
{
"success": true,
"url": "https://cdn.madebydanny.uk/user-content/2026-04-29/<uuid>.png",
"id": "<uuid>",
"path": "user-content/2026-04-29/<uuid>.png",
"contentType": "image/png",
"fileType": "image",
"size": 204800
}
| Header | Description |
|---|---|
| X-RateLimit-Limit-Files | Max files per day for this key |
| X-RateLimit-Remaining-Files | Files remaining today |
| X-RateLimit-Limit-Bytes | Max bytes per day for this key |
| X-RateLimit-Remaining-Bytes | Bytes remaining today |
| X-RateLimit-Reset | Unix timestamp when quota resets (midnight UTC) |
| Name | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Number of files to return (default 50, max 100). |
| cursor | string | optional | Pagination cursor from previous response. |
tier comparison
| Limit | Anonymous (No Key) | API Key |
|---|---|---|
| Max file size | 5 MB | 25 MB |
| Files per day | 20 | 1,000 |
| Bandwidth per day | 100 MB | 5 GB |
X-RateLimit-* response headers returned after a successful upload.
http status codes
Everything worked as expected.
Missing file, unsupported content type, or the payload is too large. Check the response body for details.
Missing, malformed, or invalid API key. Ensure you are passing the key exactly as
Bearer mbd_....
You have hit your daily quota for either files or bytes. Check the
X-RateLimit headers to see when your limit resets.
Something went wrong on our end (e.g., an issue connecting to the R2 bucket or D1 database). Try again later.