Skip to content

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)