REST API
Programmatic access to packages, files, magic-links, and tokens. Token-authenticated. Self-documenting OpenAPI 3 spec available on every running instance.
/api/v1/docs— Swagger UI (try requests in the browser)/api/v1/redoc— ReDoc (clean reference style)/api/v1/openapi.json— machine-readable spec
Authentication
Every request needs a Bearer token in the Authorization header:
Authorization: Bearer sf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Creating a token
- Log in to your SpeedyFiles instance
- Top-right user menu → Account → API tokens
- Click "Create token", give it a name and optional expiry, submit
- Copy the token immediately — it's shown once and never again
Token scope
Tokens inherit the role of the user that creates them:
- Admin tokens: can see/modify any package, any user
- Regular tokens: scoped to packages owned by the user
Token format
Raw tokens start with sf_ followed by ~43 base64url-safe chars
(~32 bytes of entropy). Only the SHA-256 hash is stored in the DB. The
first 11 characters (e.g. sf_4F2eTjA) are stored as a
prefix for the UI so you can identify tokens after creation —
the rest is unrecoverable.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/health | Health check (no auth) |
| GET | /api/v1/me | Current authenticated user |
| GET | /api/v1/me/tokens | List your API tokens |
| POST | /api/v1/me/tokens | Create new token (returns raw once) |
| DELETE | /api/v1/me/tokens/{id} | Revoke token |
| GET | /api/v1/packages | List packages (paginated) |
| POST | /api/v1/packages | Create new package (draft state) |
| GET | /api/v1/packages/{id} | Package details + files |
| POST | /api/v1/packages/{id}/files | Upload a file (multipart) |
| POST | /api/v1/packages/{id}/finalize | Mint magic link + send email |
| POST | /api/v1/packages/{id}/revoke | Revoke active links |
| DELETE | /api/v1/packages/{id} | Permanently delete |
Errors
Standard HTTP status codes. Body is {"detail": "..."}:
| 200 / 201 / 204 | Success |
| 401 | Missing, invalid, or expired token |
| 403 | Token valid but lacks permission (admin endpoint, or accessing another user's package) |
| 404 | Package / file / token not found |
| 409 | Conflict (e.g., email already in use) |
| 422 | Validation error — body shape or field constraint violated |
Code examples
curl — full send flow
TOKEN="sf_your_token_here"
HOST="https://files.example.com"
# 1. Create package
PKG=$(curl -s -X POST $HOST/api/v1/packages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Q4 financials",
"recipient_email": "cfo@acme.com",
"recipient_name": "Acme CFO",
"direction": "outbound",
"ttl_days": 14
}' | jq -r .id)
# 2. Upload one or more files
curl -X POST $HOST/api/v1/packages/$PKG/files \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./Q4-report.pdf"
curl -X POST $HOST/api/v1/packages/$PKG/files \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./Q4-spreadsheet.xlsx"
# 3. Finalize — recipient gets email, magic_link returned in response
curl -X POST $HOST/api/v1/packages/$PKG/finalize \
-H "Authorization: Bearer $TOKEN" | jq
Python
import requests
API = "https://files.example.com/api/v1"
auth = {"Authorization": "Bearer sf_your_token_here"}
# Create package
pkg = requests.post(f"{API}/packages", headers=auth, json={
"title": "Q4 financials",
"recipient_email": "cfo@acme.com",
"recipient_name": "Acme CFO",
"direction": "outbound",
"ttl_days": 14,
}).json()
pkg_id = pkg["id"]
# Upload files
for path in ["Q4-report.pdf", "Q4-spreadsheet.xlsx"]:
with open(path, "rb") as f:
requests.post(
f"{API}/packages/{pkg_id}/files",
headers=auth,
files={"file": f},
).raise_for_status()
# Finalize
result = requests.post(f"{API}/packages/{pkg_id}/finalize", headers=auth).json()
print("Magic link:", result["magic_link"])
Node.js (fetch)
const API = "https://files.example.com/api/v1";
const auth = { "Authorization": "Bearer sf_your_token_here" };
// 1. Create package
const pkg = await (await fetch(`${API}/packages`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({
title: "Q4 financials",
recipient_email: "cfo@acme.com",
recipient_name: "Acme CFO",
direction: "outbound",
ttl_days: 14,
}),
})).json();
// 2. Upload a file
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("Q4-report.pdf")]), "Q4-report.pdf");
await fetch(`${API}/packages/${pkg.id}/files`, {
method: "POST", headers: auth, body: form,
});
// 3. Finalize
const result = await (await fetch(`${API}/packages/${pkg.id}/finalize`, {
method: "POST", headers: auth,
})).json();
console.log("Magic link:", result.magic_link);
Webhooks (alternative)
For event-driven integrations, see Webhooks. SpeedyFiles can POST to your URL when packages are created, downloaded, uploaded, expired, or deleted — no polling required.
Schemas
The full Pydantic models — including request/response shapes for every endpoint, error codes, and example payloads — are auto-generated and live at /api/v1/docs on your running instance. Open the Swagger UI in a browser to explore.