Four moves: post, store, serve, share. Each one is a plain HTTP call you can make by hand.
The client hashes each file and posts the list — path, size, content type, SHA-256. The server replies with a version id and a signed upload URL for every blob it does not already have.
Files are stored by hash. Posting a directory that shares 13 of 14 files with the last version uploads one file.
The commit makes the version current and returns the artifact’s name, sequence number, visibility and URL. Earlier versions stay addressable.
A share link is created against the artifact with a TTL, an optional label
and an optional password. It resolves at /s/{token}/ until it
expires or you revoke it.
# 1. declare
curl -X POST https://share.c52.com/api/v1/artifacts \
-H "Authorization: Bearer $SHARE_TOKEN" \
-d '{"title":"Q3 margin review","files":[
{"path":"index.html","size":4120,
"contentType":"text/html; charset=utf-8",
"sha256":"e3b0c442..."}]}'
# 2. put each returned upload URL
curl -X PUT "https://share.c52.com/api/v1/files/e3b0c442...?sid=...&exp=...&sig=..." \
--data-binary @index.html
# 3. commit
curl -X POST https://share.c52.com/api/v1/artifacts/q3-margin-review/versions/$VID/commit \
-H "Authorization: Bearer $SHARE_TOKEN"
Authorization has four cases and no fifth. The owner sees their own artifacts. A valid unexpired link sees the artifact it was made for. A valid link with a password sees it after the password. Everything else is a 404 — including artifacts that exist but are not yours, so probing tells you nothing.
Someone opening a link has no account and no context. Every state they can reach says plainly what happened and what to do next.
share login prints a code; you approve it in the dashboard.A FastAPI application on Python 3.13, PostgreSQL over asyncpg, Redis for cache and rate limits, files on local disk. nginx or Caddy in front for TLS; the API serves artifact bytes itself if nothing is in front.