HTTP API
The internal API every WarrenFS instance serves on its private address (port 7480): files by key, files by path, health and metrics. Apps talk to the instance on their own node; it reaches the other nodes for them.
Overview
| Method and path | Purpose | Success |
|---|---|---|
POST /v1/files?ext=<ext> |
store a file, get its key | 201 |
GET /v1/files/<key> |
read a file (also HEAD, Range) |
200, 206, 304 |
DELETE /v1/files/<key> |
delete a file | 204 |
PUT /v1/names/<bucket>/<path> |
store a file under a path | 201 |
GET /v1/names/<bucket>/<path> |
read the file at a path (also HEAD, Range) |
200, 206, 304 |
GET /v1/names/<bucket>/<path>?stat |
the entry at a path | 200 |
GET /v1/names/<bucket>/<dir>?list[&limit=n] |
the entries in a directory | 200 |
POST /v1/names/<bucket>/<path>?op=mkdir[&parents=1] |
make a directory | 201, 200 |
POST /v1/names/<bucket>/<path>?op=rename&to=<bucket>/<path> |
rename | 204 |
POST /v1/names/<bucket>/<path>?op=touch&mtime=<ns> |
set the modification time | 200 |
DELETE /v1/names/<bucket>/<path> |
remove a file or an empty directory | 204 |
GET /healthz |
liveness, no token | 200 |
GET /metrics |
Prometheus metrics, no token | 200 |
Routes under /peer/ are for traffic between instances and take the peer token; apps never call them.
Authentication
Every /v1/ request carries the app's token:
Authorization: Bearer <token>
A missing or unknown token is 401 unauthorized. The token's app name (from WARRENFS_CLIENT_TOKENS) is logged with every write and delete, and is the only bucket the token reaches under /v1/names/.
Status codes
| Code | Meaning | What to do |
|---|---|---|
400 |
invalid path, invalid extension, or a bad op or mtime |
fix the request |
401 |
no or unknown token | |
403 |
the path is in another app's bucket | |
404 |
no such file, key, path or directory | |
409 |
the path exists, is not empty, is not a directory, or is a directory | |
503 with Retry-After: 1 |
no writable volume, or the replicas could not be reached; nothing was stored | retry the request |
507 |
a data disk is down to its reserve | add capacity |
500 |
anything else; logged on the instance |
Error bodies are short plain text, such as temporarily unavailable, retry.
Files
Store
POST /v1/files?ext=mp4
Content-Type: video/mp4
Content-Length: 300000
<the file>
{"key":"v65/19/7e/197eccba0db40feb812546205fb436a2.mp4","size":300000,"sha256":"52d348a950f6f800aca5ce2680e4b4af76ca83da8ba9ad3fd66188afaf6c8080","contentType":"video/mp4"}
extis optional: 1 to 10 letters or digits, lowercased, a leading dot allowed. It becomes the key's extension.Content-Typeis stored with the file. When it is missing orapplication/octet-stream, it is guessed fromext; when that fails too, it isapplication/octet-stream. Note thatcurl --data-binarysendsapplication/x-www-form-urlencodedunless told otherwise.- The body is streamed to both replicas as it arrives;
Content-Lengthis optional. 201means the file is on every replica, fsynced, with matching checksums. See what an acknowledgement means.
Read
GET /v1/files/v65/19/7e/197eccba0db40feb812546205fb436a2.mp4
Range: bytes=0-1023
HEAD, Range, If-None-Match, If-Modified-Since and If-Range work as in Go's http.ServeContent. The response headers:
| Header | Value |
|---|---|
ETag |
the SHA-256, quoted |
Cache-Control |
public, max-age=31536000, immutable |
Last-Modified |
when the file was stored |
Content-Type |
as stored |
Accept-Ranges |
bytes |
A key that is not in canonical form (v<volume>/<aa>/<bb>/<32 hex digits>[.<ext>], the fan-out matching the ID) is 404: no such file can exist.
Delete
DELETE /v1/files/v65/19/7e/197eccba0db40feb812546205fb436a2.mp4
204 once at least one replica recorded the delete; 404 when every replica was reached and none had the key; 503 when no replica could be reached. See Deletes.
Names
The first element of the path is the bucket, which must be the token's app. Paths are at most 4096 bytes, with elements of at most 255 bytes; empty elements, ., .. and NUL are 400.
Entries are returned as JSON:
{"path":"dev/site/hello.txt","key":"v65/b7/3e/b73e8c17f6625fc72794a8158cb30410.txt","size":14,"sha256":"e74b62bd8faa440b5e4b7d00b0970a5a62af823fa10fb20f863c641d0cd9b756","contentType":"text/plain","mtime":1792925641449054731,"version":1792925641449054822}
| Field | Meaning |
|---|---|
path |
the full path, bucket first |
dir |
true for a directory (left out for files) |
key, size, sha256, contentType |
the stored file (left out for directories) |
mtime |
modification time, Unix nanoseconds |
version |
orders changes to the path; the highest wins |
Store
PUT /v1/names/dev/site/hello.txt
Content-Type: text/plain
X-Warrenfs-Mtime: 1792925641449054731
Answers 201 with the entry. Missing parent directories are created. X-Warrenfs-Mtime (Unix nanoseconds) is optional; the default is now. An empty or application/octet-stream content type is guessed from the path's extension. Overwriting a path points it at the new file and deletes the old one.
Read, stat and list
GET /v1/names/<path>: the file, with the same headers and conditional and range handling as a read by key. TheETagandCache-Controldescribe the file the name points at now; a name can point at another file later, so do not cache name URLs as immutable on the client side. A directory is409.GET /v1/names/<path>?stat: the entry. A bucket's root always exists.GET /v1/names/<dir>?list: the entries directly in the directory, in name order, as a JSON array ([]when empty).&limit=nreturns the firstn.
Change
| Request | Result |
|---|---|
POST <path>?op=mkdir |
201 with the entry; a missing parent is 404, an existing path 409 |
POST <path>?op=mkdir&parents=1 |
like mkdir -p: 201, or 200 when the directory already exists |
POST <path>?op=rename&to=<bucket>/<path> |
204; the target must be in the same bucket and its parent must exist; an existing target file is replaced, an existing target directory only when empty. A directory is moved entry by entry; retry a failed rename to finish it. |
POST <path>?op=touch&mtime=<ns> |
200 with the entry; mtime in Unix nanoseconds |
DELETE <path> |
204; a directory must be empty (409 otherwise) |
Any other op is 400.
Health and metrics
GET /healthzanswersokwith200while the process serves HTTP. Use it for liveness and readiness probes.GET /metricsserves Prometheus text format. See Metrics.
Neither takes a token. Keep the port on the private network.