Files and uploads
Where an app keeps photos and documents, how big files get there, and how much space each plan includes.
Every app gets its own file storage: a private space nothing else can reach, already set up, with nothing to configure. Photos people upload, documents your app writes, a logo — they all live there.
Your AI already knows how to use it. This page is for when you want to know what it is doing, or when something is refused.
How much space you get
| Plan | Files included, for all your apps together |
|---|---|
| Free | 1 GB |
| Personal | 10 GB |
| Family | 50 GB |
| Community | 100 GB |
For a sense of scale: 10 GB is about three thousand phone photos.
The space is for all the apps you keep, together, not for each one: two apps on Personal share 10 GB between them. An app you have not kept yet has its own 1 GB until you keep it. If an app is built from parts — a website and an API, say — the parts share that allowance too, though each part keeps its own files and cannot read another's. A part shows another part's files through that part: a site showing the pictures its admin keeps links to them on the admin's address, with just those paths opened to everyone (npx uarpie open-paths /media/ in the admin's folder; see Several apps in one project).
Big files go straight to storage
A request to an app can carry about 1 MB. That is plenty for a form, a message or a small image, and nowhere near enough for a video or a scan of a passport.
So a large file does not travel through the app at all. Instead:
- The app asks the platform for an upload link (
POST /_platform/upload-url), naming the file and how big it is. - The platform answers with a link that works for fifteen minutes, for that one file, at that one size.
- The browser sends the file straight to storage.
- The app is told where it landed, and reads it back later with a link of its own (
GET /_platform/file-url, below).
Nothing about this needs a password or a key in the page. The link is the permission, it only fits the file it was made for, and it stops working shortly afterwards.
For your AI, the exact shapes. The app asks, from the browser or its server, on its own address:
POST /_platform/upload-url
{ "path": "photos/cat.jpg", "bytes": 482113, "content_type": "image/jpeg" }and gets url, fields, key, path, expires_at and max_bytes back. The file goes to url as a form (multipart/form-data, a POST, not a PUT): every entry in fields first, in the order given, then the file last, in a part named file; no other headers. To show it later:
GET /_platform/file-url?path=photos/cat.jpganswers url (the link), key, path and expires_at.
Many upload tools do the same thing with the app's own storage settings instead: the app signs the link itself and the browser sends the file straight to storage. That works too. In Payload, for example, it is the clientUploads option. Your AI only needs to turn it on.
If a file is posted to an app the old way and it is over 1 MB, the app's reply says REQUEST_TOO_LARGE and tells your AI to use the upload link instead. Showing that message to your AI is enough — it will make the change.
A single file this way can be up to 5 GB. Bigger than that has to be split.
Reading a file back
GET /_platform/file-url gives a link to one file. The link is on the platform's own files address, not the app's:
- The file comes from the part of the network nearest the person, and text files (CSV, JSON, documents) travel compressed. Photos and videos are already as small as they get.
- It is the same link for the same file all through each half hour, so a browser that has shown a picture once shows it again without downloading it. A link works for 30 to 60 minutes; the answer's
expires_atsays exactly when. - A file that is replaced or deleted shows the change within a minute.
- A web page someone uploads opens with its scripts switched off, so an uploaded file can never act as the app. PDFs open in the browser's viewer as usual.
Put the link straight into an <img src>, a <video> or a download button, and ask for it each time a page shows the file rather than saving it: an old link stops working, and asking again costs nothing.
Do the same for anything big the app makes itself, such as an export or a report: write it to storage and hand back a link. An app's own answer is quick for its first 6 MB, but after that it arrives at about 2 MB a second, the limit of what runs the app.
Who can ask for an upload link
Somebody the app knows. There are three:
- A visitor the app has signed in. Signing in sets a cookie the platform put there, and that cookie is the permission.
- You, from the dashboard, and your AI: with the app's deploy token, or through the app's own pages once it opens the app with
npx uarpie open --link. - An app with a key. If your app is a phone app or an API, the key it already sends on every request works here too.
A public app's visitors are none of these, and that is on purpose: if anybody who could open the page could also ask for upload links, a stranger could fill your space in an afternoon. So in a public app, the upload goes through your app's own code — it can write to its own storage directly, with settings it already has — or you ask people to sign in first. If your AI gets a message about this, showing it the message is enough.
When the space runs out
At four fifths of your allowance, the reply to the next upload carries a warning (STORAGE_ALMOST_FULL). The upload still works. It is a nudge, in time to do something about it.
Past the allowance:
- Uploads stop. Somebody trying to add a photo is refused (
STORAGE_FULL), and the app's reply says why. The app itself keeps running exactly as it was, everyone can still use it, and nothing is deleted. - You get an email, saying how much the app is storing and what your plan includes — either when the total passes the allowance, or the first time an upload is refused for want of room. At most one a day, so a busy app does not fill your inbox.
One thing that is not refused: files your app writes for itself. Your app has its own keys to its own storage and nothing stands in the way of that, which is exactly how an app gets past its allowance in the first place. Those files are counted, and they are what the email is usually about.
Two ways to make room: delete files the app no longer needs, or move to a plan with more space.
Paying for extra space
Nothing is charged for extra space today. Going over means the email and the refused uploads above, and no bill. Uarpie credit does not pay for extra space either.
If that changes, it would be $0.10 for each extra gigabyte a month before tax ($0.12 in the UK, with VAT), and the email always comes first: nobody is charged for extra space without being told beforehand.
What your app sees
An app that stores files (detection sees its storage library, or deploy.json says "storage": { "enabled": true }) is given UARPIE_STORAGE_BUCKET and UARPIE_STORAGE_PREFIX, and may read, write and delete anything under its own prefix and nothing else. One app can never see another's files, whoever wrote either of them. Work your app does on its own — writing a report, saving a generated image — goes through those settings directly; the upload link above is for files that come from somebody's browser.
One folder at the top of that space, _uarpie/, belongs to the platform. A Next.js app keeps saved copies of its pages there. It counts towards your space like any other file, an upload link can never put anything in it, and your app should leave it alone.
Removing an app removes its files with it.