Layers
Partner APIAPI referenceContent

GET /v1/content/:containerId/assets/:assetId

Read one asset descriptor — public R2 URL plus per-asset metadata.

View as Markdown
GET/v1/content/:containerId/assets/:assetId
Phase 1stable
Auth
Bearer
Scope
content:read

Returns the asset descriptor for a single media file on the container: the public CDN URL, kind, and any per-asset metadata the workflow stamped on it (width, height, durationMs, mimeType, sizeBytes).

URLs are durable — they're stable R2 paths, not short-lived signatures, so it's safe to embed them in your own UI for the lifetime of the container. They DO go away when the asset is replaced (via regenerate) or the container is deleted, so always serve from the current container state rather than caching the URL itself.

The assets array on GET /v1/content/:containerId carries the same shape as the body below. This endpoint exists for direct asset addressing (e.g. you have an assetId from primaryAsset and don't want to re-pull the whole container).

Path parameters

  • containerId
    stringrequired
    The container id (UUID; both raw and `cnt_<id>` accepted).
  • assetId
    stringrequired
    The asset id, taken from `container.assets[].assetId` or `primaryAsset.assetId`. Opaque — derived from the asset URL on the current implementation.

Request

terminal
curl https://api.layers.com/v1/content/{containerId}/assets/{assetId} \
  -H "X-Api-Key: $LAYERS_API_KEY"
get-asset.ts
const res = await fetch(
  `https://api.layers.com/v1/content/${containerId}/assets/${assetId}`,
  { headers: { 'X-Api-Key': process.env.LAYERS_API_KEY! } },
);
const { url, mimeType, expiresAt } = await res.json();
get_asset.py
import os, httpx

r = httpx.get(
    f"https://api.layers.com/v1/content/{container_id}/assets/{asset_id}",
    headers={"X-Api-Key": os.environ["LAYERS_API_KEY"]},
)
asset = r.json()

Responses

200Asset descriptor with public CDN URL.
{
  "containerId": "cnt_01HXZ9...",
  "assetId": "video-9c855048-b470-42e7-8f1b-032324d48555",
  "kind": "video",
  "url": "https://media.layers.com/public-media/content-container/.../media/video-9c855048-b470-42e7-8f1b-032324d48555.mp4",
  "thumbnailUrl": null
}

Optional fields populate when the workflow records them — typically present on the newest containers, sometimes absent on legacy data:

{
  "mimeType": "video/mp4",
  "sizeBytes": 1843200,
  "durationMs": 9200,
  "width": 1080,
  "height": 1920,
  "checksumSha256": "e3b0c44298fc1c...",
  "expiresAt": "2026-04-18T10:45:00Z"
}
404Asset not found on this container.
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Asset not on this container.",
    "requestId": "req_..."
  }
}

Notes

URLs are durable, not pre-signed. They're stable R2/CDN paths — fine to embed in your UI or pass to a video player without expiresAt bookkeeping. When the workflow records expiresAt (rare, typically only for partner- uploaded sources with a TTL), the field is included; otherwise treat the URL as valid until the asset is replaced or the container is deleted.

  • assetId is derived from the URL. The current implementation uses the URL filename without extension (e.g. video-<UUID> for videos, slide-3 for slideshows) so it round-trips between list, get, and this endpoint. Treat it as opaque on your side.
  • checksumSha256 is stable across re-fetches when present. Use it to dedupe downloads or verify integrity after upload to a third-party CDN. Not all rows carry it — older containers may omit the field.
  • Regenerated containers replace assets. After regenerate, old asset ids stop resolving once R2 lifecycle rules sweep the bytes (typically within minutes). Always fetch the current container state and use the asset ids it returns rather than caching ids long-term.

Errors

CodeWhen
NOT_FOUNDAsset id not on this container, or container not in this org.
FORBIDDEN_SCOPEKey lacks content:read.

See also

On this page