Layers
Partner APIAPI referenceMedia

POST /v1/projects/:projectId/media

Upload a small file inline (base64) without a presigned URL.

View as Markdown
POST/v1/projects/:projectId/media
Phase 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
  • projectId
    string (uuid)required
    Project ID.
Body
  • kind
    stringrequired
    Media class.
    One of: image, video, audio, logo, reference_image
  • dataBase64
    stringrequired
    File bytes, base64-encoded. Decoded size must be < 256KB.
  • mimeType
    stringrequired
    MIME type. `contentType` also accepted for backwards compat.
  • filename
    stringoptional
    Original 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

  • contentType is accepted as an alias for mimeType. Either works; mimeType is the canonical name in newer schemas.
  • Response aliases. Returns both mediaId/assetId, both contentType/mimeType, and both byteSize/sizeBytes for compatibility. Inline uploads do not record a sha256 checksum — use /media/presign + /media/finalize if 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

StatusCodeWhen
401UNAUTHENTICATEDMissing or invalid key.
403FORBIDDEN_SCOPEKey lacks projects:write.
404NOT_FOUNDProject does not exist.
413PAYLOAD_TOO_LARGEDecoded dataBase64 exceeds 256KB. Use POST /media/presign.
422VALIDATION_FAILEDUnknown kind, malformed base64, missing required field.
429RATE_LIMITEDWrite budget exhausted.

See also

On this page