Storage
Store your videos and serve them over a CDN with links that expire. You pay for what they occupy and what gets downloaded — no minimums, no monthly fee.
How it works #
Uploading a file is three calls: declare what you are about to upload, send the bytes straight to storage using the URLs we return, and complete the upload. The bytes never pass through our API — they go from the browser or your server to the bucket, with no middleman to pay for and no bottleneck.
To download, you ask for a signed URL. It is not a permanent address: it is a permission with an expiry and, if you want, with country or bandwidth restrictions. Mint one per viewer; it is served from a CDN, not from our API.
If you would rather see it than read it, the demo does exactly this with a file of yours and hands back both links to the same object — signed and unsigned — with the status code each one answers.
Why downloads do not go through us
Uploading a file #
Declare the file with its size. Depending on the size we answer with a single upload or a multipart plan — you do not choose, the server does.
curl -X POST https://kms.airi.live/v1/storage/files \ -H "Authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{ "name": "movie.mp4", "sizeBytes": 734003200, "contentType": "video/mp4", "customId": "movie-1234" }'
The response carries the file and the instructions. If mode is single, PUT to upload.url with the same Content-Type you declared. If it is multipart, cut the file into upload.partBytes chunks and PUT each one.
# One part per URL. Keep the ETag from each response. for part in "${parts[@]}"; do curl -X PUT --data-binary @part-$i.bin "$url" -D headers.txt etag=$(grep -i '^etag:' headers.txt | tr -d '"\r' | cut -d' ' -f2) done
curl -X POST https://kms.airi.live/v1/storage/files/$ID/complete \ -H "Authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"parts":[{"partNumber":1,"etag":"a54..."},{"partNumber":2,"etag":"7b1..."}]}'
The size you are billed for is the one we measure
sizeBytes you declare only decides whether the upload is single or multipart. On completion we check the real object, and that is the size that counts — so a wrong number changes your bill in neither direction.If something breaks mid-upload
POST /v1/storage/files/{id}/parts issues fresh ones, free and as often as needed — it covers both resuming and retrying a single part. An upload that is never completed is cleaned up after 24 hours and costs nothing.Serving the video #
Mint one signed URL per viewer, with the shortest expiry the use allows. Anyone holding the URL can download until it expires, so the expiry is your real control.
curl "https://kms.airi.live/v1/storage/files/$ID/url?expiresSeconds=600" \ -H "Authorization: Bearer $TOKEN" # { "url": "https://...", "expiresAt": "2026-08-02T12:34:56.000Z" }
Per-link restrictions
Every URL can carry its own conditions, and all of them are enforced at the CDN edge: whoever fails them never downloads anything, so you are never billed for it either.
| Parameter | Effect |
|---|---|
expiresSeconds | 60 seconds to 7 days. One hour by default. |
directory=true | The permission covers the whole folder rather than one file. Required for HLS and DASH — see below. |
countries | Comma-separated ISO codes. Only those countries can play. |
blockedCountries | Comma-separated ISO codes that are refused. |
speedLimitKbps | Delivery cap in KB/s, so one viewer cannot take all your bandwidth. |
speedLimitAfterSeconds | Seconds served uncapped before the limit applies, so playback still starts instantly. |
curl "https://kms.airi.live/v1/storage/files/$ID/url\ ?expiresSeconds=600&countries=US,CA&speedLimitKbps=4000&speedLimitAfterSeconds=5" \ -H "Authorization: Bearer $TOKEN"
HLS and DASH need `directory=true`
directory=true the signature travels in the path prefix, which relative resolution does preserve, and the segments authenticate on their own.IP locking
The delivery network #
There is nothing to buy and nothing to configure: the CDN comes with the storage. Every signed URL you issue already points at the network, and the bytes leave from whichever point of presence suits the request. That network has over 250 Tbps of capacity, so the peak on your premiere is a problem that was solved before you had it.
We serve from ten locations: Frankfurt, Paris, São Paulo, Chicago, Dallas, Los Angeles, Miami, Hong Kong, Singapore and Tokyo. Few of them, each very large, picked for throughput rather than proximity.
Why ten and not three hundred
The practical consequence is that you do not have to think about delivery at all. You can build an on-demand catalogue, a live channel, or whatever you have in mind, without sizing bandwidth, negotiating transit or reserving capacity up front.
What you pay for #
| Item | Price |
|---|---|
| Storage | $0.015 per GB per month |
| Delivery | $0.008 per GB served |
Storage is charged once a day, on whatever was active at that moment. A file uploaded and deleted the same day never pays anything; one deleted today stops counting tomorrow. Delivery is charged a day later, when the CDN publishes what it served.
No minimums, no minimum file size, no commitment. A GB stored for half a month costs half.
If your balance runs out
Compared with S3 + CloudFront #
The usual way to assemble store-and-serve yourself is S3 plus CloudFront. These are their public list prices in N. Virginia — their cheapest region — against ours, checked on 2 August 2026. The comparison for the whole platform, encoding and DRM included, is on the front page.
| Item | S3 + CloudFront | Airi |
|---|---|---|
| Storage | $0.023 per GB per month | $0.015 |
| Delivery — North America and Europe | $0.085 per GB | $0.008 |
| Delivery — Asia, India, South America, Oceania | $0.109 – $0.120 per GB | $0.008 |
| HTTPS requests | $0.0100 per 10,000 | Included |
| Regional surcharge | Up to 41% more outside NA/EU | None |
The request line is not a detail in video: an HLS stream is hundreds of requests per viewer-hour. At an average segment size of 1.5 MB, serving 500 TB a month is around 350 million requests — roughly $340 just to count them, before a byte moves. We do not count them.
Where they come out cheaper than us
What this comparison does not say
Listing, finding and deleting #
# List, cursor-paginated curl "https://kms.airi.live/v1/storage/files?limit=50" -H "Authorization: Bearer $TOKEN" # Find by your own identifier curl "https://kms.airi.live/v1/storage/files?customId=movie-1234" -H "Authorization: Bearer $TOKEN" # How much you store, and what the month will cost curl "https://kms.airi.live/v1/storage/usage" -H "Authorization: Bearer $TOKEN" # Delete curl -X DELETE "https://kms.airi.live/v1/storage/files/$ID" -H "Authorization: Bearer $TOKEN"
The customId is yours: put the identifier your own database uses and look files up by it instead of storing ours. It is unique while the file exists — deleting frees it again.
Deleting is permanent and billed up to the day you do it. If storage does not respond, the request fails and the file is not marked deleted: a visible error beats an invoice that quietly stops counting bytes which still exist.
Permissions #
- `storage:read`
- List, view details, check usage and mint signed URLs.
- `storage:write`
- Upload, complete uploads and delete.
Create separate credentials for whatever only reads. The backend handing links to your viewers has no need to delete anything, and a credential with fewer permissions is one that leaks with fewer consequences.