Files
The core keeps everyone's files, so Files, Photos, Mail and your app all see the same ones. Your back end reaches them through the core's files API, acting for the person using the app.
Grants
files.readlets your app read and list:GETandHEADrequests.files.writelets it do everything else: write, move, copy, delete, star and share.
Call with your token and the person in X-Solel-Person, as Acting for a person describes:
curl -sS "$SOLEL_CORE_URL/api/files/me/?list" \
-H "Authorization: Bearer $SOLEL_APP_TOKEN" \
-H "X-Solel-Person: $PERSON"
Spaces and paths
- Spaces.
meis the person's own space.group:<id>is a household group's space.share:<id>is something shared with them, with the thing itself at the path"". - What they see. Your app sees only what the person does: their own space, their groups' spaces and what's shared with them. Roles don't widen it, and a child sees only what's shared with them. See Your space and shared spaces.
- Paths are names joined by
/, with no leading or trailing slash, and""is the whole space. A name can't be.or.., hold a slash or a control character, start or end with a space, or be over 255 bytes. Folders a file goes into are made as it's written.
Keeping an index
To keep up without walking the tree, as Photos does, call GET /api/files/changes for a cursor, list what's there, then ask GET /api/files/changes?since=<cursor> now and then. Each page has up to 500 changes and says if there are more. A folder that arrives in one go, by a move, a copy or a restore, comes as one created change, so list it to see what's inside.
An entry
{
"name": "IMG_0412.jpg", "path": "Photos/2026/10/IMG_0412.jpg", "kind": "file",
"size": 3481220, "modified": "2026-10-20T09:12:03Z", "type": "image/jpeg",
"etag": "<sha256 of the file>", "starred": false,
"sharedWith": [{ "groupId": "…", "name": "Grown-ups", "access": "see" }]
}
Folders have a size of 0, a type of "", no etag, and items, how many things they hold. sharedWith is shown only to the owner.
Endpoints
| Method and path | What it does |
|---|---|
GET /api/files |
The person's spaces, how much is in them, and the disk's room |
GET /api/files/{space}/{path}?list |
What's in a folder. The whole of a space is /api/files/me/?list |
GET /api/files/{space}/{path}?info |
One entry, and what the person can do with it |
GET /api/files/{space}/{path} |
The file. ?inline shows pictures, video, sound, PDFs and text in place. Ranges work |
PUT /api/files/{space}/{path} |
Writes the file from the body. If-None-Match: * won't replace one; If-Match: "<etag>" replaces only that one. 201 new, 200 replaced, 507 when it doesn't fit |
POST …?mkdir |
Makes a folder |
POST …?move, POST …?copy |
{"space": "me", "path": "New/place"}. Nothing at the new path is replaced |
POST …?star, POST …?unstar |
Stars it, for that person alone |
GET …?versions, POST …?restore |
Earlier versions, and {"version": "<id>"} to bring one back |
GET …?shares, POST …?share |
Who it's shared with, and {"shares": [{"personId" or "groupId": "…", "access": "see" or "change"}], "notify": true} to set that |
DELETE /api/files/{space}/{path} |
Moves it to the Trash |
GET /api/files/people |
Who the person can share with |
GET /api/files/shared, GET /api/files/shared-by-me |
What's shared with them, and what they share |
GET /api/files/recent, GET /api/files/starred |
What they added or changed lately, and what they starred |
GET /api/files/search?q= |
Up to 100 names with q in them |
GET /api/files/changes?since=&space= |
The log of changes |
GET /api/files/trash, DELETE /api/files/trash |
The Trash, and emptying it |
POST /api/files/trash/{id}/restore, DELETE /api/files/trash/{id} |
Restoring something, or deleting it for good |
Every POST takes JSON, even an empty {}. A changed file answers 412 when its If-Match no longer matches. Downloads come with Content-Security-Policy: sandbox, so a file never runs as part of Solel's own pages.