Asset permissions
Keep assets private when access depends on your application's users, teams, subscriptions or sharing rules. Store the CalmLens asset ID alongside your own ownership and access records. CalmLens authenticates your project API key; your backend decides which of your users may access each asset. Your users do not need CalmLens accounts.
Project and folder permissions restrict what your backend's API key can access. They do not evaluate the identity of an end user signed in to your application. Keep that key on your server and grant only the permissions and folders your integration needs, including Download assets for private delivery.
Before choosing a delivery method, retain the content and configure its delivery eligibility.
Check access in your backend
Both delivery options start with the same permission check in your backend. Complete it before issuing a signed URL or fetching content to proxy:
- Authenticate the caller with your application's session or token.
- Look up the requested asset in your database, scoped to the caller's tenant and ownership or sharing rules. An asset ID supplied by the browser is not proof of access.
- Return an error without fetching or signing content if the caller cannot read it. A
404can avoid revealing that another user's asset exists. - Resolve the stored CalmLens asset ID and project from that authorised record, then use one of the delivery methods below.
Apply the same check to originals, previews, thumbnails, downloads and transformed versions. If a caller can choose a lens or transformation, validate that choice against the variants they may access; a thumbnail permission need not grant the original file.
Choose a delivery method
After the access check succeeds, deliver the permitted resource using one of these two options.
| Short-lived signed URL | Authenticated proxy | |
|---|---|---|
| Download path | Client downloads directly from CalmLens | Your backend fetches and streams the file |
| Application permission check | Before each link is issued | On every request reaching your endpoint |
| Permission removed in your database | Existing links remain valid until expiry | The next request can be denied, provided caches cannot bypass the check |
| Application-specific rendering | Use CalmLens lenses and transforms | Can also apply your own annotations or processing |
| Moderation and content availability | CalmLens checks CDN requests | CalmLens checks upstream requests; your own byte cache needs invalidation |
Prefer signed URLs for ordinary delivery when a short access window is acceptable. Use a proxy when you need to check application permissions on every request or render content in ways CalmLens does not provide.
Option 1: Issue a short-lived signed URL
After your backend grants access, create a signed URL and return it to the browser. The browser downloads directly from CalmLens, so your backend does not relay the file. Choose the resource and any lens or transforms on the server, and use a lifetime suitable for the content.
const signedUrl = await client.assets.createSignedUrl({
assetId,
resource: "content",
expiresInSeconds: 900,
});
The response contains url and expiresAt. Return them to the client and request a new link through your backend when needed, repeating the permission check. Lifetimes range from 1 second to 24 hours, with a 15-minute default. See POST / for the full contract.
A signed URL is a bearer link: anyone holding it can use it until it expires, subject to CalmLens delivery eligibility. Your application server must check its own permission records before issuing each link. If you remove a user's access in your database, your server should refuse to issue them new links; CalmLens does not read that database or enforce those application permissions. Links already issued remain valid until they expire. Use short lifetimes, or the authenticated proxy when permission must be rechecked on every request.
Option 2: Proxy content through your application
Use an authenticated endpoint in your own backend when you want to recheck permission on every request. After the access check, fetch the private resource with your server SDK and stream its response to the caller:
const response = await client.cdn.serve({
assetIdentifier: assetId,
});
Check the upstream status before returning its body, and preserve the appropriate content type. The browser receives your application's content endpoint, while the CalmLens API key stays on the server.
PolySnap uses this pattern: its asset-content route authenticates the caller, queries its project asset records by both asset ID and user ID, returns 404 if there is no owned asset, and only then calls CalmLens through a server-side content helper. That helper can also apply an application-selected lens or image edits.
Run authorisation before looking up a cached response, including cache hits. Keep any internal byte cache behind that check. For private responses sent to clients, use Cache-Control: private, no-store when each new request must reach your access check; do not forward public cache headers or allow a shared cache to serve your authenticated endpoint without authorisation. If your application caches content independently, you must also handle moderation changes, content removal and cache invalidation; CalmLens cannot check or revoke bytes served from your cache.