POST /v1/projects/:projectId/media
Upload a small file inline (base64) without a presigned URL.
POST
/v1/projects/:projectId/mediaPhase 1stable
- Auth
- Bearer
- Scope
- projects:write
Uploads a file under 256KB as a single base64-encoded request, skipping the presign + finalize dance. Use it for logos, icons, and small reference images where the two-step upload is more ceremony than it's worth.
Returns 201 Created with the same shape as POST /media/finalize. For anything larger than 256KB, switch to POST /media/presign — the request-size cap is hard.
Path
projectIdstring (uuid)requiredProject ID.
Body
kindstringrequiredMedia class.One of:image,video,audio,logo,reference_imagedataBase64stringrequiredFile bytes, base64-encoded. Decoded size must be < 256KB.mimeTypestringrequiredMIME type. `contentType` also accepted for backwards compat.filenamestringoptionalOriginal filename (1-256 chars).
Example request
curl -X POST https://api.layers.com/v1/projects/{projectId}/media \
-H "X-Api-Key: $LAYERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "logo",
"mimeType": "image/png",
"dataBase64": "iVBORw0KGgoAAAANSUhEUgAA...",
"filename": "acme-logo.png"
}'const res = await fetch(
`https://api.layers.com/v1/projects/${projectId}/media`,
{
method: 'POST',
headers: {
'X-Api-Key': process.env.LAYERS_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
kind: 'logo',
mimeType: 'image/png',
dataBase64: await fileToBase64(file),
filename: file.name,
}),
},
);
const asset = await res.json();import base64, os, httpx
with open("acme-logo.png", "rb") as f:
data_b64 = base64.b64encode(f.read()).decode()
r = httpx.post(
f"https://api.layers.com/v1/projects/{project_id}/media",
headers={
"X-Api-Key": os.environ["LAYERS_API_KEY"],
"Content-Type": "application/json",
},
json={
"kind": "logo",
"mimeType": "image/png",
"dataBase64": data_b64,
"filename": "acme-logo.png",
},
)
asset = r.json()Response
201Created
{
"mediaId": "med_01HXA4MNP5RSTUVWXYZABCDEFGH",
"assetId": "med_01HXA4MNP5RSTUVWXYZABCDEFGH",
"url": "https://media.layers.com/.../acme-logo.png",
"kind": "logo",
"contentType": "image/png",
"mimeType": "image/png",
"byteSize": 18472,
"sizeBytes": 18472,
"filename": "acme-logo.png",
"createdAt": "2026-04-18T19:26:02Z"
}Notes
contentTypeis accepted as an alias formimeType. Either works;mimeTypeis the canonical name in newer schemas.- Response aliases. Returns both
mediaId/assetId, bothcontentType/mimeType, and bothbyteSize/sizeBytesfor compatibility. Inline uploads do not record asha256checksum — use/media/presign+/media/finalizeif you need to capture and verify a checksum. - Hard 256KB cap. Decoded base64 must be under 256KB or the request is rejected with
413 PAYLOAD_TOO_LARGE.
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing or invalid key. |
| 403 | FORBIDDEN_SCOPE | Key lacks projects:write. |
| 404 | NOT_FOUND | Project does not exist. |
| 413 | PAYLOAD_TOO_LARGE | Decoded dataBase64 exceeds 256KB. Use POST /media/presign. |
| 422 | VALIDATION_FAILED | Unknown kind, malformed base64, missing required field. |
| 429 | RATE_LIMITED | Write budget exhausted. |
See also
POST /v1/projects/:projectId/media/presign— upload files >256KBPOST /v1/projects/:projectId/media/finalize— register a presigned upload