Layers
Partner APIConcepts

App media

Per-project brand assets — logo, app screenshots, demo videos, and end-card — the content pipeline uses so generated creative shows the customer's real product instead of hallucinated visuals.

View as Markdown

The partner-facing app-media surface stores four asset slots per project:

  • logo — singleton; uploading a new one soft-deletes the previous.
  • screenshot — append-many.
  • demo-video — append-many. Required to unlock the ugc-remix content format.
  • end-card — singleton; uploading pins the partner image as the live end-card and Layers' auto-generated end-card is bypassed on new generations.

Uploads are URL-based: hand Layers a public https URL and we fetch it through the SSRF guard, validate the response's Content-Type + size, persist the bytes to Cloudflare R2, and return the canonical asset row. There is no presign / finalize dance at the partner edge.

Per-kind constraints

KindDB roleAppend vs. replaceMime allowlistByte cap
logologoReplace — uploading another logo soft-deletes the previous one.image/png, image/jpeg, image/webp, image/svg+xml5 MB
screenshotapp-screenshotAppend.image/png, image/jpeg, image/webp, image/heic10 MB
demo-videoapp-demoAppend. Unlocks the ugc-remix content format.video/mp4, video/quicktime, video/webm200 MB
end-cardend-cardReplace — uploading pins as the live end-card. Layers also auto-generates an end-card per project; the partner upload wins.image/png, image/jpeg, image/webp5 MB

media_role is a strict label — it does not float across uses. A demo-video will not be picked as a screenshot; a logo won't be substituted for a screenshot. Upload once per slot you need.

Source URL requirements

  • https only. http://, file://, ftp://, gopher://, data: are rejected with VALIDATION.
  • Publicly fetchable from Layers' egress. The SSRF guard resolves the hostname and rejects anything that maps to a private / link-local / loopback / metadata range. Embedded credentials in the URL are rejected.
  • Redirects up to 3 hops are followed; every hop is re-validated against the SSRF guard.
  • Content-Type is authoritative. Layers does not sniff file magic — if your upstream returns application/octet-stream for an mp4, the request rejects with VALIDATION. Configure the host to return the correct mime.
  • Byte cap is per-kind (see table). Upstream Content-Length header is honored when present; the stream is hard-capped at the same number, so a misleading header doesn't help.

Endpoints

Idempotency

POST /v1/projects/:projectId/app-media honors the Idempotency-Key header. Replays return the cached 201 response, so safe to retry on connection errors without creating duplicate media_library rows.

What partners do not get

  • No presign + finalize flow. Layers fetches the bytes server-side from your URL. There is no client-direct R2 PUT.
  • No per-asset metadata write. The partner only sets kind + url. Width, height, codec, etc. are probed server-side at fetch time.

See also

On this page