# files.nineonefour.org — how to use the file API

A REST API for files. Every request (except this page and `/api/health`) needs an API key.
Only HTTPS is accepted.

## 1. Get a key

The operator of this server gives you a key. It looks like `mfk_<id>_<secret>`. A key:

- can use one or more **roots** (top folders), each with an access level:
  - `r`: list and download
  - `rw`: also upload, make folders, move and copy
  - `rwd`: also delete
- can have an expiry date. An expired or revoked key gets `401`.

Keep the key secret. Do not put it in a URL or in a git repository.

## 2. Send the key

Use one of these headers on each request:

```
Authorization: Bearer mfk_...
X-Api-Key: mfk_...
```

## 3. Paths

All paths are `{root}/{folder}/{file}`, for example `massfile/photos/2026/cat.png`.
Names that start with `.mf-` are reserved.

## 4. Requests

| Request | What it does | Access |
|---|---|---|
| `GET /v1/roots` | The roots your key can use, and your access to each | any |
| `GET /v1/files/{root}/{folder}` | List a folder (JSON) | `r` |
| `GET /v1/files/{root}/{file}` | Download a file. `Range` and `ETag` work | `r` |
| `HEAD /v1/files/{root}/{file}` | Size and date of a file, with no body | `r` |
| `GET /v1/files/{path}?stat=true` | Data about one file or folder (JSON) | `r` |
| `PUT /v1/files/{root}/{file}` | Upload the request body as the file | `rw` |
| `POST /v1/mkdir/{root}/{folder}` | Make a folder | `rw` |
| `POST /v1/move` | Move or rename. Body: `{"from":"massfile/a.txt","to":"massfile/b.txt"}` | `rw` |
| `POST /v1/copy` | Copy. Same body as move | `rw` |
| `DELETE /v1/files/{root}/{path}` | Delete a file, or a folder and all of its contents | `rwd` |
| `GET /api/health` | Server status. No key needed | none |

Query options:

- `?overwrite=true` on `PUT`: replace a file that exists. Without it you get `409`.
- `?parents=true` on `PUT` and `mkdir`: make missing folders in the path.

Uploads are atomic. The file appears only when the upload is complete. If an upload fails,
an old file with that name does not change.

If the `to` of a move or copy is a folder that exists, the item goes into that folder.

## 5. Examples

```sh
KEY=mfk_...                       # your key
API=https://files.nineonefour.org/v1

# What can I use?
curl -H "Authorization: Bearer $KEY" $API/roots

# List a folder
curl -H "Authorization: Bearer $KEY" $API/files/massfile/

# Upload (make the folders if necessary)
curl -H "Authorization: Bearer $KEY" -T cat.png "$API/files/massfile/photos/cat.png?parents=true"

# Replace a file
curl -H "Authorization: Bearer $KEY" -T cat.png "$API/files/massfile/photos/cat.png?overwrite=true"

# Download
curl -H "Authorization: Bearer $KEY" -o cat.png $API/files/massfile/photos/cat.png

# Continue a download that stopped
curl -H "Authorization: Bearer $KEY" -C - -o big.iso $API/files/massfile/isos/big.iso

# Make a folder
curl -H "Authorization: Bearer $KEY" -X POST "$API/mkdir/massfile/photos/2026?parents=true"

# Rename
curl -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
     -d '{"from":"massfile/photos/cat.png","to":"massfile/photos/2026/cat.png"}' $API/move

# Delete
curl -H "Authorization: Bearer $KEY" -X DELETE $API/files/massfile/photos/2026/cat.png
```

Python:

```python
import requests
api = "https://files.nineonefour.org/v1"
h = {"Authorization": f"Bearer {key}"}
with open("cat.png", "rb") as f:
    requests.put(f"{api}/files/massfile/photos/cat.png", data=f, headers=h,
                 params={"parents": "true"}).raise_for_status()
data = requests.get(f"{api}/files/massfile/photos/cat.png", headers=h).content
```

## 6. Responses

Data is JSON. A file or folder looks like this:

```json
{"name":"cat.png","path":"massfile/photos/cat.png","isDir":false,"size":48213,"modifiedUtc":"2026-10-15T16:09:27Z"}
```

A folder listing is `{"path": "...", "entries": [ ... ]}`.

Errors are JSON (`application/problem+json`) with a `detail` text:

| Code | Meaning |
|---|---|
| `400` | Bad request, or plain HTTP was used |
| `401` | No key, or the key is unknown, expired or revoked |
| `403` | Your key does not have the necessary access, or the path is not permitted |
| `404` | Nothing is at that path |
| `409` | The file exists (use `?overwrite=true`), or a file and a folder have the same name |
| `413` | The upload is larger than the server limit |
