Automated uploads
Send multipart uploads from a server, bot, job runner, or admin tool.
The EmbedCDN API turns file uploads into Discord and social-friendly file routes, compact short links, and metadata-rich previews that are easy to automate.
Programmatically upload files, mint short links, and return preview-friendly routes that work nicely with Discord, internal dashboards, moderation tools, CI jobs, and bot workflows.
Send multipart uploads from a server, bot, job runner, or admin tool.
Get a `fileLink` route that is designed for rich unfurl and preview behavior.
Every upload can produce a cleaner `shortLink` for captions, messages, and handoffs.
The API expects your token in the request header below. Keep it on the server side only.
x-api-token: YOUR_API_TOKENUse it from your backend, bot, CLI, cron job, or internal admin tooling.
The tester on this page is useful inside your own app, but production integrations should still call the API from your server or bot.
POST /api/uploadSend a `multipart/form-data` request with your file and optional metadata. The current backend expects the uploaded file field to be named `myFile`.
/api/uploadmultipart/form-data
myFile
customSlug, title, description
x-api-token
myFile: the file to uploadcustomSlug: optional branded short code, 3-40 charstitle: optional embed titledescription: optional embed description{
"data": {
"fileLink": "https://discordcdn-omega.vercel.app/files/abc123.png",
"shortLink": "https://discordcdn-omega.vercel.app/s/demo",
"shortCode": "demo",
"fileSize": "248 KB",
"fileName": "abc123.png",
"uploadLink": "https://discordcdn-omega.vercel.app/uploads/abc123.png",
"title": "Launch poster",
"description": "Tiny link. Big preview."
}
}
Each sample uploads a file, sends the token header, and reads back the returned `fileLink` or `shortLink`.
curl -X POST "https://discordcdn-omega.vercel.app/api/upload" \
+ -H "x-api-token: YOUR_API_TOKEN" \
+ -F "myFile=@./image.png" \
+ -F "customSlug=launch-assets" \
+ -F "title=Launch poster" \
+ -F "description=Tiny link. Big preview."
const formData = new FormData();
+formData.append("myFile", fileInput.files[0]);
+formData.append("customSlug", "launch-assets");
+formData.append("title", "Launch poster");
+formData.append("description", "Tiny link. Big preview.");
+
+const response = await fetch("https://discordcdn-omega.vercel.app/api/upload", {
+ method: "POST",
+ headers: {
+ "x-api-token": "YOUR_API_TOKEN"
+ },
+ body: formData
+});
+
+const payload = await response.json();
+console.log(payload);
+console.log(payload.data?.shortLink || payload.data?.fileLink);
import fs from "node:fs";
+import path from "node:path";
+
+const form = new FormData();
+const filePath = path.resolve("./image.png");
+form.append("myFile", new Blob([fs.readFileSync(filePath)]), "image.png");
+form.append("customSlug", "launch-assets");
+form.append("title", "Launch poster");
+form.append("description", "Tiny link. Big preview.");
+
+const response = await fetch("https://discordcdn-omega.vercel.app/api/upload", {
+ method: "POST",
+ headers: {
+ "x-api-token": process.env.API_TOKEN
+ },
+ body: form
+});
+
+const payload = await response.json();
+console.log(payload.data.fileLink);
+console.log(payload.data.shortLink);
import requests
+
+url = "https://discordcdn-omega.vercel.app/api/upload"
+headers = {
+ "x-api-token": "YOUR_API_TOKEN"
+}
+files = {
+ "myFile": open("./image.png", "rb")
+}
+data = {
+ "customSlug": "launch-assets",
+ "title": "Launch poster",
+ "description": "Tiny link. Big preview."
+}
+
+response = requests.post(url, headers=headers, files=files, data=data, timeout=30)
+payload = response.json()
+
+print(payload)
+print(payload["data"]["shortLink"])
$ch = curl_init("https://discordcdn-omega.vercel.app/api/upload");
+
+$postFields = [
+ "myFile" => new CURLFile(__DIR__ . "/image.png"),
+ "customSlug" => "launch-assets",
+ "title" => "Launch poster",
+ "description" => "Tiny link. Big preview."
+];
+
+curl_setopt_array($ch, [
+ CURLOPT_POST => true,
+ CURLOPT_HTTPHEADER => ["x-api-token: YOUR_API_TOKEN"],
+ CURLOPT_POSTFIELDS => $postFields,
+ CURLOPT_RETURNTRANSFER => true,
+]);
+
+$response = curl_exec($ch);
+$payload = json_decode($response, true);
+curl_close($ch);
+
+print_r($payload);
+echo $payload["data"]["fileLink"] . PHP_EOL;
var client = java.net.http.HttpClient.newHttpClient();
+var boundary = "----UnfurlXBoundary";
+var filePath = java.nio.file.Path.of("image.png");
+var fileBytes = java.nio.file.Files.readAllBytes(filePath);
+
+var body = new java.io.ByteArrayOutputStream();
+body.write(("--" + boundary + "\r\n").getBytes());
+body.write(("Content-Disposition: form-data; name=\"customSlug\"\r\n\r\nlaunch-assets\r\n").getBytes());
+body.write(("--" + boundary + "\r\n").getBytes());
+body.write(("Content-Disposition: form-data; name=\"title\"\r\n\r\nLaunch poster\r\n").getBytes());
+body.write(("--" + boundary + "\r\n").getBytes());
+body.write(("Content-Disposition: form-data; name=\"description\"\r\n\r\nTiny link. Big preview.\r\n").getBytes());
+body.write(("--" + boundary + "\r\n").getBytes());
+body.write(("Content-Disposition: form-data; name=\"myFile\"; filename=\"image.png\"\r\n").getBytes());
+body.write(("Content-Type: image/png\r\n\r\n").getBytes());
+body.write(fileBytes);
+body.write(("\r\n--" + boundary + "--\r\n").getBytes());
+
+var request = java.net.http.HttpRequest.newBuilder()
+ .uri(java.net.URI.create("https://discordcdn-omega.vercel.app/api/upload"))
+ .header("x-api-token", "YOUR_API_TOKEN")
+ .header("Content-Type", "multipart/form-data; boundary=" + boundary)
+ .POST(java.net.http.HttpRequest.BodyPublishers.ofByteArray(body.toByteArray()))
+ .build();
+
+var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
+System.out.println(response.body());
package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "os"
+)
+
+func main() {
+ file, _ := os.Open("./image.png")
+ defer file.Close()
+
+ var body bytes.Buffer
+ writer := multipart.NewWriter(&body)
+
+ part, _ := writer.CreateFormFile("myFile", "image.png")
+ io.Copy(part, file)
+ writer.WriteField("customSlug", "launch-assets")
+ writer.WriteField("title", "Launch poster")
+ writer.WriteField("description", "Tiny link. Big preview.")
+ writer.Close()
+
+ req, _ := http.NewRequest("POST", "https://discordcdn-omega.vercel.app/api/upload", &body)
+ req.Header.Set("x-api-token", "YOUR_API_TOKEN")
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ resp, _ := http.DefaultClient.Do(req)
+ defer resp.Body.Close()
+
+ payload, _ := io.ReadAll(resp.Body)
+ fmt.Println(string(payload))
+}
import { Client, GatewayIntentBits, AttachmentBuilder } from "discord.js";
+import fs from "node:fs";
+
+const client = new Client({ intents: [GatewayIntentBits.Guilds] });
+
+client.once("ready", () => {
+ console.log("Bot ready");
+});
+
+client.on("interactionCreate", async (interaction) => {
+ if (!interaction.isChatInputCommand() || interaction.commandName !== "upload") {
+ return;
+ }
+
+ const form = new FormData();
+ form.append("myFile", new Blob([fs.readFileSync("./image.png")]), "image.png");
+ form.append("customSlug", "launch-assets");
+
+ const response = await fetch("https://discordcdn-omega.vercel.app/api/upload", {
+ method: "POST",
+ headers: {
+ "x-api-token": process.env.API_TOKEN
+ },
+ body: form
+ });
+
+ const payload = await response.json();
+ await interaction.reply({
+ content: payload.data.shortLink + "\n" + payload.data.fileLink
+ });
+});
+
+client.login(process.env.DISCORD_TOKEN);
Useful for quick setup verification. The token is used only for this request and is not stored permanently by the page.
For production, call this API from your server or bot, not public frontend code.
{
"message": "Upload a file with your token to see the live response here."
}
The button below calls `/api/health` and reports the server timestamp and request duration.
{
"ok": true,
"service": "EmbedCDN API",
"time": "ISO_DATE"
}
Meaning: the token header is missing or does not match the configured API token.
Fix: send the correct value in `x-api-token` and make sure the server has `API_TOKEN` configured.
Meaning: the request did not include a file in `myFile` or the payload shape was wrong.
Fix: send `multipart/form-data` and make sure the file field name exactly matches `myFile`.
Meaning: the requested `customSlug` is already taken.
Fix: choose a different slug or leave it blank and let the server mint one automatically.
Meaning: the upload exceeded the server or platform file size limit.
Fix: reduce the asset size or adjust your deployment upload limit if your hosting setup supports it.
Meaning: storage, filesystem, or server-side upload handling failed.
Fix: check server logs, storage credentials, and write permissions, then retry the request.
shortLink for clean sharing in chat messages, dashboards, and captions.fileLink when Discord embed preview behavior is required.customSlug for branded, memorable links when the name is available.