Manage

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.

POST/api/v1/upload

Request

Send multipart/form-data with a single file field. Max 100 MB.

bash
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}.

json
{
  "id": "9f1c2e7a-…",
  "url": "https://manage-staging.vecto3d.dev/your-org/media/9f1c2e7a-…",
  "name": "clip.mp4",
  "contentType": "video/mp4",
  "category": "video",
  "size": 1048576
}

Errors

StatusMeaning
401Missing or invalid API key
402Storage limit reached for your plan; upgrade or free up space
403Key type can't upload this category
413File is larger than 100 MB
415Unsupported file type (only image, video, audio)
400No 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.

POST/api/v1/upload-url
bash
curl -X POST https://manage-staging.vecto3d.dev/api/v1/upload-url \
  -H "Authorization: Bearer mng_your_media_key"
json
{
  "url": "https://manage-staging.vecto3d.dev/api/v1/upload/eyJrIjoi…",
  "expiresAt": "2026-09-15T12:10:00.000Z"
}
POST/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.

js
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.

StatusCauseFix
501Your 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.
405Wrong HTTP method — both endpoints are POST.Use POST.