Files API

Files are content-addressed by SHA-256 hash. The same bytes always produce the same hash, so identical files are stored only once. Upload files before pushing a version that references them.

Workflow

  1. Compute the SHA-256 hash of your file locally
  2. Check if it exists with HEAD
  3. If not, upload it with PUT
  4. Reference it in records as {"$file": "sha256:<hash>"}
  5. Push your version โ€” the server verifies all referenced files exist

HEAD /api/collections/:owner/:slug/files/:hash

No auth required

Check if a file exists. Returns headers only, no body.

Parameters

:hashSHA-256 hash, optionally prefixed with sha256:

Response

200File exists. Content-Length and Content-Type headers set.
404File not found.

Example

curl -I https://underlay.org/api/collections/kf/archive/files/sha256:a1b2c3...
# HTTP/2 200
# Content-Length: 1048576
# Content-Type: application/pdf

GET /api/collections/:owner/:slug/files/:hash

No auth required

Download a file. Returns the raw binary data with appropriate content type. Response is cacheable (immutable content).

Headers

Content-TypeMIME type of the file
Cache-Controlpublic, max-age=31536000, immutable
ETagThe file hash

Example

curl -o paper.pdf \
  https://underlay.org/api/collections/kf/archive/files/sha256:a1b2c3...

PUT /api/collections/:owner/:slug/files/:hash

Auth: write scope

Upload a file. The server verifies the SHA-256 hash of the uploaded bytes matches the hash in the URL. If the file already exists, returns 200 without re-uploading.

Request

Send the file as the raw request body with the appropriate Content-Type header.

# Compute hash
HASH=$(shasum -a 256 paper.pdf | cut -d' ' -f1)

# Upload
curl -X PUT \
  "https://underlay.org/api/collections/kf/archive/files/sha256:$HASH" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary @paper.pdf

Response 201

{
  "hash": "a1b2c3d4e5f6...",
  "size": 1048576
}

Errors

200File already exists: {"hash": "...", "status": "exists"}
400Hash mismatch โ€” the uploaded bytes don't match the hash in the URL.

File references in records

To link a file to a record, use the $file convention:

{
  "id": "pub-001",
  "type": "Publication",
  "data": {
    "title": "An Example Paper",
    "pdf": {"$file": "sha256:a1b2c3d4e5f6..."},
    "thumbnail": {"$file": "sha256:f6e5d4c3b2a1..."}
  }
}

When pushing a version, the server scans all record data for $file references and verifies each referenced file exists. If any are missing, the push returns 422 with a list of needed hashes.