https://v3b.fal.media/...) carries an Access Control List (ACL) that decides who can read it. By default, CDN URLs are publicly accessible to anyone with the link. ACLs let you change that default for the files a request produces — keeping generated media private to your account, sharing it with specific teammates, or hiding it from view entirely.
ACLs are configured through the same X-Fal-Object-Lifecycle-Preference header used to control media expiration. Setting an initial_acl on an inference request applies the rules to the CDN files that request produces as output. Input files are a separate case: SDKs upload them to the CDN before the inference call, so they are not covered by the inference request’s header and keep your account’s default ACL. To restrict an input, set the ACL on the upload itself (see setting an ACL on a direct upload).
Scope: ACLs apply only to fal CDN v3 objects (URLs starting with
https://v3b.fal.media/...). They do not apply to:- The serverless
/datapersistent volume. That storage has its own account-scoped permission model and is not addressable through CDN URLs. - Files hosted outside fal (S3, GCS, R2, your own server). Those are governed by the source’s own access controls.
How an ACL Works
An ACL is adefault decision plus an optional list of per-user rules:
user matches. If no rule matches, the default decision applies — an anonymous request carries no identity, so it matches no rule and always falls through to default.
A single request can present more than one credential at once — for example, a signed URL that reads as one user together with a Bearer token for another. When several credentials are present, the CDN evaluates each and returns the most permissive outcome: allow beats forbid, and forbid beats hide. Put simply, the request succeeds if any identity it presents is allowed. For example, with the ACL { alice: forbid, bob: allow }, a request that authenticates as both alice and bob is allowed, because bob’s allow is the most permissive match. (The default only applies when none of the presented identities matches an explicit rule.)
The file’s owner — the account that created it — always retains access regardless of the ACL, so a forbid default never locks you out of your own files. ACLs only ever restrict other callers.
| Decision | HTTP response | Use it when |
|---|---|---|
allow | 200 OK, file served | The file should be readable |
forbid | 403 Forbidden | You want callers to know the file exists but is not theirs to read |
hide | 404 Not Found | You want the file to appear as if it does not exist |
user field is a fal nickname (e.g. alice). Rules referencing a user that does not exist are silently dropped.
Finding a user’s nickname. A nickname is the handle on a user’s fal profile, also used in their app URLs (
fal.run/<nickname>/...). Because unknown users are dropped silently, double-check the nickname if a rule doesn’t take effect.Setting an ACL Per Request
To apply an ACL to the files produced by a single inference call, passX-Fal-Object-Lifecycle-Preference on the request. The header accepts both expiration_duration_seconds and initial_acl, and they can be combined.
X-Fal-Object-Lifecycle-Preference header applies. The SDKs also wrap it in a convenient lifecycle argument:
Reading a Restricted File
A restricted file cannot be read with yourFAL_KEY directly — the CDN needs a token that identifies you as a user the file’s ACL allows. Exchange your key for a short-lived CDN token, then send it as a Bearer header when you fetch the file.
expires_at. Tokens currently last up to 30 days, and you can request a shorter lifetime by passing expiration_seconds in the request body. Refresh the token before it expires.
Sharing With Someone Who Has No fal Account
Mint a signed URL — a self-contained link that embeds a time-limited, read-only credential, so the recipient needs no fal credentials of their own:...?identity=... URL can download the file until it expires.
ACLs Inside a Serverless App
When a fal App returns a file viafal.toolkit.File, fal.toolkit.Image, fal.toolkit.Video, or fal.toolkit.Audio, the toolkit uploads it to the CDN. If the caller passed X-Fal-Object-Lifecycle-Preference on the inference request, that preference (including initial_acl) flows through to the gateway and is applied automatically when your handler returns — no extra code on your side:
Enforcing a Server-Side ACL
If you are deploying an app and you want every output to follow a default ACL regardless of what the caller passes (for example, your app is for internal use and outputs must always be restricted to your team), set the lifecycle preference on the current request before constructing any file:current_app.current_request.lifecycle_preference whenever it builds a file, so any Image, Video, Audio, or File you return after this assignment uses your enforced ACL. The override is scoped to the current request — the framework resets the context on each new inference call.
A few notes on the pattern:
- You override, not merge. Anything you write to
lifecycle_preference["initial_acl"]replaces the caller’sinitial_acloutright. Spreading**(ctx.lifecycle_preference or {})at the top, as in the example, preserves the caller’sexpiration_duration_secondsso callers can still control retention even though they cannot widen access. - Factor it out for multiple endpoints. If several endpoints need the same enforcement, write a small
enforce_outputs(...)helper and call it at the top of each handler, or wire it in as a FastAPI middleware onself.app. - Combine with retention. Set
expiration_duration_secondsin the same dict to enforce both retention and access in one place.
Common Patterns
Make every file from this request private to meX-Fal-Object-Lifecycle-Preference header on the inference call. Every CDN file produced by the request returns 403 to anyone other than you.
Share files with one teammate
hide and forbid
Use forbid when callers should be able to tell a file exists but is not theirs (a 403 is informative). Use hide when callers should not be able to enumerate or probe for the file (a 404 is indistinguishable from a missing file).
Account-Wide Defaults
The patterns above let an individual request or a single deployed app set an ACL. If instead you want a default that applies across all activity on a fal account — every model call, every upload, every app you deploy, with no header or handler code involved — contact your fal account team or support@fal.ai. Self-serve account-level configuration is not yet available. When an account default is set, aninitial_acl passed on an individual request takes precedence for that request — the per-request ACL replaces the account default rather than merging with it.
Managing Existing Files
An ACL is set at creation viainitial_acl, but you can change it afterward — adding or removing allowed users, or flipping the default — through the storage REST API, authenticating with your FAL_KEY:
GET https://rest.fal.ai/storage/files/acl?url=<file_url>— read the file’s current ACL.PUT https://rest.fal.ai/storage/files/acl?url=<file_url>with body{ "default": "forbid", "rules": [{ "user": "alice", "decision": "allow" }] }— replace it.
Reference
| Header | X-Fal-Object-Lifecycle-Preference |
| Header body | JSON object with expiration_duration_seconds and/or initial_acl |
| Decisions | allow (200), forbid (403), hide (404) |
| Default decision (new accounts) | allow |
| User identifier format | fal nickname (e.g. alice) |
| Scope | fal CDN v3 URLs only (https://v3b.fal.media/...) |
| Auth (set ACL) | FAL_KEY on the inference / upload request |
| Auth (read a restricted file) | CDN Bearer token from POST /storage/auth/token, or a signed URL |
| CDN token endpoint | POST https://rest.fal.ai/storage/auth/token?storage_type=fal-cdn-v3 |
| Signed URL endpoint | POST https://v3b.fal.media/files/b/<store>/<filename>/sign |
Related
fal CDN
How files are uploaded to and served from the fal CDN
Data Retention
Configure how long CDN files are retained before deletion
Working with Files
Server-side file handling inside a fal App
Common Parameters
Full reference for platform headers