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"}
  • ext is optional: 1 to 10 letters or digits, lowercased, a leading dot allowed. It becomes the key's extension.
  • Content-Type is stored with the file. When it is missing or application/octet-stream, it is guessed from ext; when that fails too, it is application/octet-stream. Note that curl --data-binary sends application/x-www-form-urlencoded unless told otherwise.
  • The body is streamed to both replicas as it arrives; Content-Length is optional.
  • 201 means 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. The ETag and Cache-Control describe 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 is 409.
  • 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=n returns the first n.

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 /healthz answers ok with 200 while the process serves HTTP. Use it for liveness and readiness probes.
  • GET /metrics serves Prometheus text format. See Metrics.

Neither takes a token. Keep the port on the private network.