Skip to content

HTTP API Reference for Object Operations

Stowry provides a REST API for object storage operations. All endpoints support presigned URL authentication.

http://localhost:5708

Requests require presigned URL authentication unless auth.read or auth.write is public. See Authentication for details.

Store mode only. Not available in static or SPA modes.

List objects with optional prefix filtering and pagination.

GET /

Query Parameters:

Parameter Type Default Description
prefix string - Filter objects by path prefix
limit int 100 Maximum objects per page (1-1000)
cursor string - Pagination cursor from previous response

Response: 200 OK

{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "photos/vacation.jpg",
"content_type": "image/jpeg",
"etag": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
"file_size_bytes": 1048576,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"next_cursor": "MjAyNC0wMS0xNVQxMDozMDowMFp8cGhvdG9zL3ZhY2F0aW9uLmpwZw=="
}

Example:

Terminal window
# List all objects
curl "http://localhost:5708/"
# List with prefix
curl "http://localhost:5708/?prefix=photos/"
# Paginate
curl "http://localhost:5708/?limit=10&cursor=MjAyNC0wMS0..."

Download an object by path.

GET /{path}

Path Parameters:

Parameter Description
path Object path (e.g., photos/vacation.jpg)

Response Headers:

Header Description
Content-Type MIME type of the object
ETag Object hash (SHA256)
Content-Length Object size in bytes
Last-Modified Last modification time

Response: 200 OK with file content

Errors:

Status Error Code Description
400 invalid_path Invalid path format
404 not_found Object not found

Example:

Terminal window
# Download object
curl -O http://localhost:5708/photos/vacation.jpg

Server Mode Behavior:

  • Store: Returns exact path or 404 (JSON)
  • Static: Tries exact → {path}.html → {path}/index.html → 404 (HTML)
  • SPA: Falls back to /index.html for client-side routing

Retrieve object metadata without downloading the file body.

HEAD /{path}

Path Parameters:

Parameter Description
path Object path (e.g., photos/vacation.jpg)

Response Headers:

Header Description
Content-Type MIME type of the object
ETag Object hash (SHA256), quoted
Content-Length Object size in bytes
Last-Modified Last modification time
Accept-Ranges Indicates byte-range support

Conditional Headers:

Header Description
If-None-Match Returns 304 Not Modified if ETag matches (weak comparison per RFC 9110)
If-Modified-Since Returns 304 Not Modified if object hasn’t changed since the given time

If-None-Match takes precedence over If-Modified-Since per RFC 7232.

Response: 200 OK (empty body)

Errors:

Status Error Code Description
304 - Not modified (conditional request)
400 invalid_path Invalid path format
404 not_found Object not found

Example:

Terminal window
# Get metadata
curl -I http://localhost:5708/photos/vacation.jpg
# Conditional request (ETag)
curl -I -H 'If-None-Match: "a591a6d..."' http://localhost:5708/photos/vacation.jpg
# Conditional request (time)
curl -I -H "If-Modified-Since: Wed, 15 Jan 2024 10:30:00 GMT" http://localhost:5708/photos/vacation.jpg

Server Mode Behavior:

Same fallback logic as GET — applies static and SPA mode path resolution. Returns 404 HTML in static mode.


Store mode only. Returns 405 Method Not Allowed in static and SPA modes.

Upload or overwrite an object.

PUT /{path}

Path Parameters:

Parameter Description
path Object path (e.g., photos/vacation.jpg)

Request Headers:

Header Required Description
Content-Type No MIME type (auto-detected if not provided)
If-Match No Conditional update — ETag must match; returns 412 if object doesn’t exist (per RFC 9110 §13.1.1)

Request Body: Raw file content

Response: 200 OK

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"path": "photos/vacation.jpg",
"content_type": "image/jpeg",
"etag": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
"file_size_bytes": 1048576,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}

Errors:

Status Error Code Description
400 invalid_path Invalid path format
412 precondition_failed ETag mismatch (If-Match)
500 internal_error Server error

Example:

Terminal window
# Upload file
curl -X PUT \
-H "Content-Type: image/jpeg" \
--data-binary @vacation.jpg \
http://localhost:5708/photos/vacation.jpg
# Conditional update
curl -X PUT \
-H "If-Match: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" \
--data-binary @vacation.jpg \
http://localhost:5708/photos/vacation.jpg

Store mode only. Returns 405 Method Not Allowed in static and SPA modes.

Delete an object: the metadata entry is soft-deleted and the stored file is removed.

DELETE /{path}

Path Parameters:

Parameter Description
path Object path (e.g., photos/vacation.jpg)

Response: 204 No Content

Errors:

Status Error Code Description
400 invalid_path Invalid path format
404 not_found Object not found

Example:

Terminal window
curl -X DELETE http://localhost:5708/photos/vacation.jpg

Note: A delete removes both the metadata entry and the stored file.


All errors return JSON:

{
"error": "error_code",
"message": "Human-readable error message"
}

Valid paths must:

  • Be relative (no leading /)
  • Contain no path traversal (..)
  • Contain no empty segments (//)
  • Contain no invalid characters (\ ? # ~)
  • Be valid UTF-8
  • Have no trailing slashes

Valid paths:

  • file.txt
  • photos/vacation.jpg
  • data/2024/01/report.pdf

Invalid paths:

  • /file.txt (leading slash)
  • ../secret.txt (path traversal)
  • folder//file.txt (empty segment)
  • file?.txt (invalid character)

Uploads must carry a Content-Type header. The server stores it verbatim and never guesses: a PUT without one is rejected with 400 invalid_path.

The extension is used only where there is no header to read:

  • stowry upload detects it from the local file’s extension before sending, unless --content-type overrides it
  • stowry populate detects it for files already in the storage directory

Both use Go’s mime package, so the mapping is the platform’s, and modern Go returns text/javascript; charset=utf-8 for .js. Anything unrecognised becomes application/octet-stream.

List responses use cursor-based pagination:

  1. Make initial request: GET /?limit=10
  2. If next_cursor is present in response, there are more results
  3. Request next page: GET /?limit=10&cursor={next_cursor}
  4. Repeat until next_cursor is empty or null

Benefits:

  • Consistent results during concurrent modifications
  • Efficient for large datasets
  • No offset skipping issues