Every asset in Loom has two halves: the record — filename, hashes, tags, everything a pipeline worked out about it — and the bytes themselves. This page is about the bytes: where they go, how you put them there, and how you get them back.
Uploading
The quickest way to get media into Loom is a single request:
curl -X POST https://loom.example.com/api/v1/assets/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@holiday.mp4" \
-F "libraryUuid=$LIBRARY_UUID"
That stores the bytes, creates the asset, and starts any pipeline configured to run on that media type. The response is the new asset, including its UUID.
|
Tip
|
Do not set a Content-Type header yourself. Your HTTP client sets it, together with the
multipart boundary; overriding it makes the upload unreadable.
|
Uploading the same file twice
Loom identifies an asset by the SHA-512 of its content, so uploading a file it already has does not
create a duplicate — you get the existing asset back, with status 200 instead of 201. Importing
the same file into a second library adds a second location for that one asset rather than a
second asset.
Replacing the bytes of an existing asset
curl -X POST https://loom.example.com/api/v1/assets/$ASSET_UUID/binary/data \
-H "Authorization: Bearer $TOKEN" \
-F "file=@holiday-regraded.mp4"
If the asset lives in more than one library, add -F "libraryUuid=…" to say which copy you mean.
Loom will not guess, because guessing would overwrite the wrong library’s file.
Downloading
curl -O -J https://loom.example.com/api/v1/assets/$ASSET_UUID/binary/data \
-H "Authorization: Bearer $TOKEN"
Downloads support byte ranges, so a browser can seek inside a video without refetching it:
curl -r 0-1048575 https://loom.example.com/api/v1/assets/$ASSET_UUID/binary/data \
-H "Authorization: Bearer $TOKEN"
Choosing where the bytes are stored
A storage pool is either a directory on disk or an S3 bucket. A library points at a pool, and everything uploaded into that library goes there. Libraries with no pool use the server’s local storage directory, which is the default and needs no configuration at all.
Create a pool:
# A filesystem pool
curl -X POST https://loom.example.com/api/v1/pools \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "fast-ssd", "fsPath": "/mnt/fast-ssd/media"}'
# An S3 pool
curl -X POST https://loom.example.com/api/v1/pools \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "archive", "s3Bucket": "media-archive", "s3Region": "eu-central-1"}'
A pool is one or the other, never both.
Then point a library at it:
curl -X POST https://loom.example.com/api/v1/libraries/$LIBRARY_UUID \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"poolUuid": "'$POOL_UUID'"}'
Every library reports which backend it uses as storageType, either filesystem or s3.
|
Important
|
Changing a library’s pool affects future uploads only. Files already stored stay where they are and keep working — each one remembers the pool it was written to. |
S3 credentials
Bucket, region and endpoint are part of the pool. The access key and secret are not: they are set on the server, so they never end up in the database or in an API response.
LOOM_S3_ACCESS_KEY=...
LOOM_S3_SECRET_KEY=...
LOOM_S3_ENDPOINT=https://minio.example.com # only for S3-compatible storage
LOOM_S3_REGION=eu-central-1
Leave the keys unset on AWS to use the machine’s own credentials — an IAM role, IRSA on Kubernetes,
or a local AWS profile. For MinIO, Ceph and similar gateways, set LOOM_S3_ENDPOINT; path-style
addressing switches itself on automatically.
Storage settings
| Setting | Default | Purpose |
|---|---|---|
|
|
Where binaries go for libraries with no pool. |
|
unlimited |
Largest accepted upload, in bytes. Larger uploads are rejected before anything is written. |
|
1 GiB |
Loom refuses an upload that would take the disk below this. Set |
Deleting
Deleting a binary removes the file only when nothing else needs it. Because storage is content-addressed, identical files share one copy on disk — so removing one asset’s binary never takes the preview away from another asset that happens to hold the same content.
Attachments
Alongside its main binary, an asset can carry attachments: contact sheets, poster frames, waveforms, proxies, extracted audio. They upload and download the same way:
curl -X POST https://loom.example.com/api/v1/attachments \
-H "Authorization: Bearer $TOKEN" \
-F "file=@poster.jpg" -F "assetUuid=$ASSET_UUID" -F "type=POSTER_FRAME"
curl -O -J https://loom.example.com/api/v1/attachments/$ATTACHMENT_UUID/data \
-H "Authorization: Bearer $TOKEN"
An attachment is stored in the same pool as the asset it belongs to.
Processing uploaded media
Cortex workers do not receive media over the network — Loom tells them where the file is and they read it themselves. What that requires depends on the pool:
-
S3 pools — nothing extra. The worker fetches the object directly, so Loom and Cortex can run anywhere. The worker needs its own S3 settings (
CORTEX_S3_*). -
Filesystem pools — the worker must be able to see the same directory at the same path, via a shared volume or by running on the same host. If it cannot, processing fails to open the file.
If you are running Loom and Cortex on separate machines, an S3-backed library is the simpler arrangement.
How much room is left
Where the bytes go is one question; how much space they take and how much is still free is another. See Storage Usage for the report that answers it, and for the thresholds that decide when Loom starts refusing uploads.