Skip to content

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.