HTTP API
PutFS speaks plain HTTP. Any client that can issue PUT, GET, HEAD, DELETE and QUERY works without an SDK.
Authentication
Two headers, enforced by nginx:
| Header | Purpose |
|---|---|
X-Api-Key |
Key ID |
X-Api-Secret |
Key secret |
The key is matched against a method/URI pattern to scope what it can do. See Auth.
Endpoints
| Method | Pattern | Action |
|---|---|---|
PUT |
/:path | Upload an object |
GET |
/:path | Download an object |
HEAD |
/:path | Check if an object exists + metadata |
DELETE |
/:path | Delete an object |
QUERY |
/:path/ | List objects under a prefix (trailing slash) |
Examples
The examples assume an auth key putfs_demo:demo and a server at http://localhost:8000.
Upload
curl -X PUT \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/hello.txt \
-d "hello world"
Upload a file from disk:
curl -X PUT \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
--data-binary @./report.pdf \
http://localhost:8000/my-dataset/report.pdf
Download
curl \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/hello.txt
Check existence
curl -I \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/hello.txt
List
Listing uses QUERY on a path with a trailing slash. GET means "get a file", QUERY means "list", so no query string or URI convention is needed:
curl -X QUERY \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/
QUERY / lists from the root. Keys come back relative to the listed prefix, one per line.
The only filter is the recursion depth, sent as a plain-text integer body (1 lists the keys directly under the prefix). Without a body the listing is fully recursive:
curl -X QUERY \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
-H "Content-Type: text/plain" \
-d 2 \
http://localhost:8000/my-dataset/
Content-Type must be text/plain when sending a depth
RFC 10008 requires rejecting a QUERY whose Content-Type is missing or unusable, so a depth body without Content-Type: text/plain returns 415 (curl's -d sends application/x-www-form-urlencoded; set the header). A body that is not an integer, optionally followed by dirs and/or stat in that order, or longer than 64 bytes, returns 400. A bodiless QUERY needs no Content-Type.
Directory names in a depth-limited listing
Every line is a key. <depth> dirs (-d "1 dirs") also lists the directories where the listing stopped descending, each with a trailing / and each a prefix to QUERY in turn. A prefix holding top.txt and sub/nested.txt lists top.txt at 1, and sub/ plus top.txt at 1 dirs. dirs needs a depth. A directory exists only while it holds a key, so a listed directory is never an empty prefix.
Size and mtime in a listing
<depth> [dirs] stat (-d "1 dirs stat") prefixes every line with <size> <mtime_ns>: the size in bytes (HEAD's Content-Length) and the modification time as integer nanoseconds since the epoch (HEAD's Last-Modified, at full precision). The entry is everything after the second space, so a key containing spaces is read back exactly. A directory marker carries its own values (its size is not a byte count). An object's mtime is its upload time, so a client can diff two stores from the listing alone, as putfs sync does. stat needs a depth too.
Delete
curl -X DELETE \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/hello.txt
Deleting the last key under a directory removes the directory too, so an emptied prefix no longer lists and its name is free for a file again.
Status codes
| Code | Meaning |
|---|---|
200 |
OK (GET, QUERY) |
201 |
Created (PUT) |
204 |
No content (DELETE, or PUT to an existing key under lenient WORM) |
400 |
Bad request: unusable key (.., NUL or newline, too deeply nested, a parent segment is a stored object), or a checksum pattern matched without a recognized algorithm group |
401 |
Missing or invalid key/secret |
403 |
Key not authorized for this method/path, or filesystem EPERM/EACCES/EROFS (WORM, read-only) |
404 |
Object not found |
405 |
Wrong method for the target: an object method on a trailing-slash key, QUERY on an object key, or a method outside the API. Allow names what the target supports |
409 |
Conflict: PUT on an existing key under strict WORM |
415 |
Unsupported media type: QUERY body without text/plain |
422 |
Unprocessable: PUT body digest does not match the URL (see content-addressed verification) |
507 |
Insufficient storage (ENOSPC) |