Media API
Upload an image, video, or audio file. It is stored on your CDN and served at /{org}/media/{id}. Requires a media-family key.
/api/v1/uploadRequest
Send multipart/form-data with a single file field. Max 100 MB.
curl -X POST https://manage-staging.vecto3d.dev/api/v1/upload \
-H "Authorization: Bearer mng_your_media_key" \
-F "[email protected]"Response
200 OK with the file record. The public URL is https://manage-staging.vecto3d.dev/{org}/media/{id}.
{
"id": "9f1c2e7a-…",
"url": "https://manage-staging.vecto3d.dev/your-org/media/9f1c2e7a-…",
"name": "clip.mp4",
"contentType": "video/mp4",
"category": "video",
"size": 1048576
}Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 402 | Storage limit reached for your plan; upgrade or free up space |
| 403 | Key type can't upload this category |
| 413 | File is larger than 100 MB |
| 415 | Unsupported file type (only image, video, audio) |
| 400 | No file provided, or a malformed multipart body |
Uploading from a browser or NUI
Keys never go to a game client or browser. Instead, your server mints a presigned URL with its key and hands that to the client, which uploads the file to it directly - no key, CORS enabled. The URL is valid for 10 minutes and uploads under the key that minted it (same type limits and retention). This is what phone scripts like lb-phone expect.
/api/v1/upload-urlcurl -X POST https://manage-staging.vecto3d.dev/api/v1/upload-url \
-H "Authorization: Bearer mng_your_media_key"{
"url": "https://manage-staging.vecto3d.dev/api/v1/upload/eyJrIjoi…",
"expiresAt": "2026-09-15T12:10:00.000Z"
}/api/v1/upload/{token}Called by the client with the same multipart/form-data body as /api/v1/upload and no Authorization header. Same response and errors; an expired or forged URL, or one whose key was deleted, returns 401.
const form = new FormData();
form.append("file", blob, "photo.webp");
const res = await fetch(presignedUrl, { method: "POST", body: form });
const { url } = await res.json();Errors that aren't from the API
Requests pass through a CDN edge before reaching Manage. If the edge rejects one, you get an HTML error page instead of our usual JSON. That's the quickest way to tell the two apart: if the body isn't JSON, the request never reached us, and it won't appear in your logs.
| Status | Cause | Fix |
|---|---|---|
| 501 | Your client sent a Transfer-Encoding header whose value isn't chunked. The edge refuses it as Not Implemented. | Don't set Transfer-Encoding yourself. Send a normal body with Content-Length, which is what every HTTP client does by default. |
| 405 | Wrong HTTP method — both endpoints are POST. | Use POST. |