HTTP API
PutFS speaks plain HTTP. Any client that can issue PUT, GET, HEAD, and DELETE works without any extra SDK.
Authentication
The native API uses a two-header model enforced by nginx:
| Header | Purpose |
|---|---|
X-Api-Key |
Key ID |
X-Api-Secret |
Key secret |
The key is then matched against a method/URI pattern to scope what it can do. See Auth for the full pattern reference.
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 in path prefix (trailing slash) |
Examples
The examples below 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" – the method carries the semantics, 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. Listing returns paths relative to the listed prefix.
The only filter is the recursion depth, sent as a plain-text integer body (depth: 1 lists immediate children only). 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 the server to reject a QUERY whose Content-Type is missing or unusable, so a depth body without Content-Type: text/plain returns 415 (curl's plain -d sends application/x-www-form-urlencoded – set the header explicitly). A body that is not an integer, or longer than 64 bytes, returns 400. A bodiless QUERY needs no Content-Type at all.
Migrating from GET /prefix/
Listing and object access are now disjoint: GET on a trailing-slash key returns 405 with Allow: QUERY (behind nginx, a plain 404 – a trailing-slash path is not a file), and QUERY on an object key returns 405 with Allow: GET, HEAD, PUT, DELETE. The exclude_prefix filter is gone from the wire protocol – the CLI and Python client still accept it and filter client-side, as they always did. Two things need updating – api-key scopes that name methods explicitly (an (GET|HEAD) read scope must gain QUERY, see Auth), and any client that lists. Note that requests and httpx silently downgrade a QUERY to a GET when following a 302, dropping the body with it; answer listing redirects with 307/308 instead.
Delete
curl -X DELETE \
-H "X-Api-Key: putfs_demo" \
-H "X-Api-Secret: demo" \
http://localhost:8000/my-dataset/hello.txt
Status codes
| Code | Meaning |
|---|---|
200 |
OK (GET, QUERY) |
201 |
Created (PUT) |
204 |
No content (DELETE, PUT to existing key under default WORM) |
400 |
Bad request – unusable key (../NUL in path, too deeply nested, a parent path segment is itself a stored object, or a checksum pattern matched with no recognized algorithm group) |
401 |
Missing or invalid key/secret |
403 |
Key not authorized for this method/path; or filesystem EPERM/EACCES/EROFS (WORM, readonly) |
404 |
Object not found |
405 |
Method not allowed for this target – an object method (GET/HEAD/PUT/DELETE) on a trailing-slash listing key, QUERY on an object key, or a method outside the API's set. The Allow header names the methods the target supports |
409 |
Conflict – PUT on an existing key under WORM strict mode |
415 |
Unsupported media type – QUERY depth body is not text/plain |
422 |
Unprocessable – PUT body digest does not match the value captured from the URL (see content-addressed verification) |
507 |
Insufficient storage (ENOSPC) – disk is full |
See WORM for the PUT/DELETE behavior under write-once-read-many semantics.