This project is a media processing platform built on Cloudflare Workers that handles both image and video content.
- Image Service: Handles image uploads, storage, and serves optimized images with Cloudflare's image transformations. The endpoint for this service is
https://assets.break-code.com. - Video Service: Processes video uploads with adaptive bitrate transcoding to HLS format for streaming. The endpoint for this service is
https://videos.break-code.com.
| Service | Endpoint | API Key Required | Client-Safe | Description |
|---|---|---|---|---|
| Image Service | /generate-image-upload-url |
Yes | No | Get presigned URL for image upload (server-to-server) |
| Image Service | /upload |
Yes | No | Direct image upload (server-to-server only) |
| Image Service | /assets/* |
No | Yes | Serve original images |
| Video Service | /generate-upload-url |
Yes | No | Get presigned URL for video upload |
| Video Service | /notify-upload-complete |
Yes | No | Notify API to start transcoding job |
| Video Service | /job-status |
No | Yes | Poll transcoding job status |
| Video Service | /playback-url |
No | Yes | Get HLS playback URL |
Key Points:
- API keys are only required for sensitive write operations (see table above).
- Presigned URL endpoints for both images and videos are safe for public frontend use and do not require API keys.
- Never expose your API key in the browser for
/uploador video job notification endpoints.
API keys are managed via the IV_API_KEY environment variable (comma-separated for multiple keys). See wrangler.toml for configuration. For single-user/dev, you can hardcode a strong key in your secrets and use it in your UI (for backend/server-to-server calls only).
All endpoints validate app_id, image_id, video_id, and file types/sizes. Invalid or unsupported values will return a 400 error with a clear message.
Sensitive endpoints are rate-limited to 10 requests per minute per IP (demo in-memory). For production, use Cloudflare WAF or Durable Objects for distributed rate limiting.
All uploads, job starts, and errors are logged. Sentry is integrated for error tracking and alerting in both services.
- For the Cloudflare Workers (
image-serviceandvideo-service), set your Sentry DSN in theIV_SENTRY_DSNenvironment variable inwrangler.toml(or as a secret). - For the Node.js transcoder container, use the
TRANS_SENTRY_DSNenvironment variable.
Errors and exceptions will be automatically reported to Sentry.
All sensitive endpoints (uploads, presigned URL generation, job notification) require an API key via the x-api-key header. You must provide a valid IV_API_KEY for all write operations.
For video uploads:
- The API key is only required for the
/generate-upload-urland/notify-upload-completeAPI calls. - The actual file upload to R2 (using the presigned URL) does not require the API key and is done directly from the browser.
For image uploads:
- The API key is required for the
/uploadendpoint (legacy, server-to-server only) and the/generate-image-upload-urlendpoint. Do not use these from the browser.
Example (video, client + server):
// Step 1: Get presigned upload URL (API key required, server-side only)
const res = await fetch('https://videos.break-code.com/generate-upload-url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key-here' // Only on server
},
body: JSON.stringify({
app_id: 'yourAppId',
video_id: 'yourVideoId',
content_type: file.type
})
});
const { upload_url, raw_key } = await res.json();
// Step 2: Upload file directly to R2 (no API key needed, client-side)
await fetch(upload_url, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file
});
// Step 3: Notify API upload is complete (API key required, server-side only)
await fetch('https://videos.break-code.com/notify-upload-complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key-here' // Only on server
},
body: JSON.stringify({
app_id: 'yourAppId',
video_id: 'yourVideoId',
raw_key,
content_type: file.type
})
});API keys are managed via the IV_API_KEY environment variable (comma-separated for multiple keys). See wrangler.toml for configuration. For single-user/dev, you can hardcode a strong key in your secrets and use it in your UI.
All endpoints validate app_id, image_id, video_id, and file types/sizes. Invalid or unsupported values will return a 400 error with a clear message.
Sensitive endpoints are rate-limited to 10 requests per minute per IP (demo in-memory). For production, use Cloudflare WAF or Durable Objects for distributed rate limiting.
All uploads, job starts, and errors are logged. For production, Sentry is integrated for error tracking and alerting. Set your Sentry DSN in the SENTRY_DSN environment variable in wrangler.toml.
This section explains how to use the image processing API for direct uploads and optimized image delivery, matching the actual implementation in the codebase.
This is the recommended secure flow. It requires a server to get the presigned URL, which is then passed to the client to perform the upload.
Call the /generate-image-upload-url endpoint from your backend server, providing your API key:
// This code runs on your trusted server
const res = await fetch('https://assets.break-code.com/generate-image-upload-url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-secret-api-key'
},
body: JSON.stringify({
app_id: 'yourAppId',
image_id: 'yourImageId', // optional, random if omitted
content_type: 'image/jpeg' // The content type of the file to be uploaded
})
});
const { upload_url, key, asset_url, transform_url } = await res.json();
// Now, send the `upload_url` and other data to your frontend.Your frontend receives the upload_url from your server and uses it to upload the file directly to R2. No API key is exposed to the client.
// This code runs in the browser
await fetch(upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' }, // Must match the content_type from Step 1
body: file
});Use the asset_url or transform_url (which you also passed from your server) in your UI:
<img src={transform_url} alt="Optimized image" />Note:
- The legacy
/uploadendpoint is for server-to-server or trusted backend uploads only and requires an API key. - The
/upload-directendpoint has been removed for security. All uploads must use the secure presigned URL flow.
- Use unique
image_idvalues to avoid collisions. - Use the
transform_urlfor optimized/responsive images in your frontend. - Cache optimized image URLs on the frontend for performance.
This guide explains how to use the video processing API for direct uploads, job status tracking, error handling, and reprocessing. It includes JavaScript code examples suitable for SvelteKit or any modern frontend.
Call the /generate-upload-url endpoint with your API key to get a secure, one-time URL for uploading the video file directly to storage.
const res = await fetch('https://videos.break-code.com/generate-upload-url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key-here'
},
body: JSON.stringify({
app_id: 'yourAppId',
video_id: 'yourVideoId',
content_type: file.type
})
});
const { upload_url, raw_key } = await res.json();Use the upload_url from the previous step to upload the file directly from the browser. This avoids passing large files through your server.
await fetch(upload_url, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file
});After the upload finishes, notify the API (with your API key) so it can begin the transcoding job.
await fetch('https://videos.break-code.com/notify-upload-complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key-here'
},
body: JSON.stringify({
app_id: 'yourAppId',
video_id: 'yourVideoId',
raw_key,
content_type: file.type
})
});This project uses Sentry for error tracking and alerting in both the image and video services. To enable Sentry:
- Create a new Sentry project of type JavaScript (Browser) for each Worker (image and video).
- For the Node.js transcoder container, use a Node.js Sentry project.
- Set the DSNs as secrets in your Cloudflare account:
IV_SENTRY_DSN: For theimage-serviceandvideo-serviceWorkers.TRANS_SENTRY_DSN: For thetranscode-container.
- Errors and exceptions will be automatically reported to Sentry.
For more details, see the Sentry docs for Cloudflare Workers.
After notifying the API, you need to poll the job status endpoint to track the transcoding progress.
async function pollJobStatus(app_id, video_id) {
const res = await fetch(
`https://videos.break-code.com/job-status?app_id=${app_id}&video_id=${video_id}`
);
const status = await res.json();
return status;
}
// Example polling loop
let status;
do {
status = await pollJobStatus('yourAppId', 'yourVideoId');
// You can show status.status, status.error, etc. in your UI
if (status.status === 'done' || status.status === 'error') {
break;
}
// Wait 5 seconds before checking again
await new Promise((r) => setTimeout(r, 5000));
} while (true);- If
status.status === 'error', display thestatus.errormessage to the user and offer a "Retry" button. - If
status.status === 'processing'and thestatus.heartbeattimestamp is older than 2–5 minutes, you can consider the job "stuck" and offer a retry option. - To retry a job, simply call the
/notify-upload-completeendpoint again with the same parameters.
function showStatus(status) {
if (status.status === 'done') {
// Show the video playback link
} else if (status.status === 'error') {
// Show the error message and a retry button
} else if (status.status === 'processing' && isStale(status.heartbeat)) {
// Show a "Job is taking longer than expected" message and a retry button
} else {
// Show a progress indicator (e.g., spinner)
}
}
function isStale(heartbeat) {
if (!heartbeat) return false;
// Returns true if the heartbeat is older than 5 minutes
return Date.now() - new Date(heartbeat).getTime() > 5 * 60 * 1000;
}Once the job status is done, you can retrieve the playback URL for the HLS stream.
const res = await fetch(
`https://videos.break-code.com/playback-url?app_id=yourAppId&video_id=yourVideoId`
);
const { url } = await res.json();
// Use this URL in a video player that supports HLS (e.g., HLS.js, Shaka Player, or a native player).POST https://videos.break-code.com/generate-upload-url— Get a presigned R2 upload URL.PUT <presigned-url>— Upload the video file directly to R2 storage.POST https://videos.break-code.com/notify-upload-complete— Notify the API to queue the transcoding job.GET https://videos.break-code.com/job-status?app_id=...&video_id=...— Poll for job status.GET https://videos.break-code.com/playback-url?app_id=...&video_id=...— Get the final HLS playback URL.
- Always poll the job status after notifying the API of a completed upload.
- Handle
errorandstuckstates gracefully in your UI and allow the user to retry. - Use a unique
video_idfor each upload to prevent collisions. - For production environments, consider implementing exponential backoff for polling to reduce server load.