Snapshot uses a consistent JSON envelope for API responses, except /health.
The health endpoint intentionally returns the smallest possible readiness
payload for Docker, reverse proxies, and uptime checks.
Success response:
{
"success": true,
"status": "success",
"message": "File uploaded successfully",
"data": {}
}Error response:
{
"success": false,
"status": "error",
"message": "Unauthorized",
"data": {
"error_code": "unauthorized",
"request_id": "b56ec299cf9442848525f0b549b010f9",
"details": null
}
}Every response includes X-Request-ID. Clients can send their own
X-Request-ID header to correlate client logs with server logs.
Protected endpoints require:
Authorization: Bearer your-api-keyX-API-Key: your-api-key is also supported. Only these two authentication
forms are supported.
Protected endpoints:
POST /uploadGET /listDELETE /raw/{file_id}.{extension}
Public endpoints:
GET /GET /healthGET /{file_id}(HTML preview page)GET /raw/{file_id}.{extension}GET /{file_id}.{extension}(legacy compatibility redirect to/{file_id})GET /docswhen docs are enabled
Rate-limited responses include:
| Header | Meaning |
|---|---|
Retry-After |
Seconds until another request should be attempted |
X-RateLimit-Limit |
Requests allowed in the current window |
X-RateLimit-Remaining |
Requests left in the current window |
X-RateLimit-Reset |
Unix timestamp when the limit resets |
Returns project metadata.
curl http://localhost:3000/Response:
{
"success": true,
"status": "success",
"message": "Service is running",
"data": {
"name": "Snapshot",
"author": "Walkaisa",
"repository": "https://github.com/Walkaisa/snapshot",
"version": "1.0.0"
}
}Health endpoint for Docker, reverse proxies, uptime monitoring, and load balancers.
curl http://localhost:3000/healthExpected status: 200 OK.
Response:
{ "status": "ok" }Uploads one file. The multipart field name must be file.
curl -X POST http://localhost:3000/upload \
-H "Authorization: Bearer your-api-key" \
-F "file=@screenshot.png"PowerShell:
curl.exe -X POST http://localhost:3000/upload `
-H "Authorization: Bearer your-api-key" `
-F "file=@screenshot.png"Success status: 201 Created.
Response:
{
"success": true,
"status": "success",
"message": "File uploaded successfully",
"data": {
"id": "abc123XYZ9",
"filename": "abc123XYZ9.png",
"extension": "png",
"url": "http://localhost:3000/abc123XYZ9",
"page_url": "http://localhost:3000/abc123XYZ9",
"raw_url": "http://localhost:3000/raw/abc123XYZ9.png",
"legacy_url": "http://localhost:3000/abc123XYZ9.png",
"delete_url": "http://localhost:3000/raw/abc123XYZ9.png",
"size": 12345,
"size_human": "12.1 KB",
"checksum": "sha256...",
"content_type": "image/png"
}
}data.url is the value ShareX and other clients should display. It always
equals page_url, the extension-less HTML preview URL — use raw_url
directly if you only want the file itself.
data.delete_url always points at the raw delete route
(/raw/{id}.{ext}) so deletion keeps working even when data.url is the HTML
page. legacy_url points at the old extension-based URL, which redirects to
data.url.
Common errors:
| Status | Error code | Reason |
|---|---|---|
400 |
file_empty |
The uploaded file is empty |
400 |
invalid_file_type |
The extension is not allowed |
400 |
invalid_content_type |
The declared MIME type or file signature is not allowed |
401 |
unauthorized |
Missing or invalid API key |
413 |
file_too_large |
File exceeds MAX_FILE_SIZE_BYTES |
429 |
rate_limit_exceeded |
Too many requests |
Lists uploaded files. This endpoint is protected because it exposes metadata for all uploads.
curl http://localhost:3000/list \
-H "Authorization: Bearer your-api-key"Response:
{
"success": true,
"status": "success",
"message": "Files returned successfully",
"data": [
{
"id": "abc123XYZ9",
"filename": "abc123XYZ9.png",
"extension": "png",
"url": "http://localhost:3000/abc123XYZ9",
"page_url": "http://localhost:3000/abc123XYZ9",
"raw_url": "http://localhost:3000/raw/abc123XYZ9.png",
"legacy_url": "http://localhost:3000/abc123XYZ9.png",
"delete_url": "http://localhost:3000/raw/abc123XYZ9.png",
"size": 12345,
"size_human": "12.1 KB",
"created_at": "2026-04-27T00:00:00+00:00",
"modified_at": "2026-04-27T00:00:00+00:00",
"checksum": "sha256...",
"content_type": "image/png"
}
]
}Returns the HTML preview page for a file. The page includes Open Graph and Twitter Card metadata so links unfurl into rich embeds on Discord and other platforms.
curl http://localhost:3000/abc123XYZ9Expected status: 200 OK with Content-Type: text/html; charset=utf-8 and
Cache-Control: public, max-age=300.
Common error:
| Status | Error code | Reason |
|---|---|---|
404 |
file_not_found |
No file with that id exists |
Returns the raw media file.
curl http://localhost:3000/raw/abc123XYZ9.png --output abc123XYZ9.pngResponses use Cache-Control: public, max-age=31536000, immutable and an
ETag derived from the file checksum. Add ?download=1 to force a download:
curl -OJ "http://localhost:3000/raw/abc123XYZ9.png?download=1"With download=1 the response adds
Content-Disposition: attachment; filename="abc123XYZ9.png". Without it, no
Content-Disposition is set so browsers and crawlers display the file inline.
Common error:
| Status | Error code | Reason |
|---|---|---|
404 |
file_not_found |
File does not exist |
Compatibility route for old extension-based links. It looks up the file by
file_id alone — the extension you pass is ignored — and, if a file exists,
responds with 301 Moved Permanently to GET /{file_id}. Unknown ids return
404. There is no delete route on this path; use
DELETE /raw/{file_id}.{extension} instead.
Deletes an uploaded file.
curl -X DELETE http://localhost:3000/raw/abc123XYZ9.png \
-H "Authorization: Bearer your-api-key"Response:
{
"success": true,
"status": "success",
"message": "File deleted successfully",
"data": {
"id": "abc123XYZ9",
"filename": "abc123XYZ9.png"
}
}Common errors:
| Status | Error code | Reason |
|---|---|---|
401 |
unauthorized |
Missing or invalid API key |
404 |
file_not_found |
File does not exist |