REST API
Every SDK sits on these endpoints. Use them directly from any language. All requests take JSON bodies and return JSON; authenticate with your project API key in the x-devstorage-key header.
Base URL & auth
https://api.devstorage.io
# every request:
# x-devstorage-key: dsk_live_...Keys carry scopes (upload, read, delete) set when you create them in the dashboard. Errors return { "error": "message" } with a matching HTTP status; upload endpoints are rate-limited and answer 429 when exceeded.
Upload a file
/v1/upload/presignAsk for a presigned PUT URL. The session expires if unused.
curl https://api.devstorage.io/v1/upload/presign \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY" \
-H "content-type: application/json" \
-d '{
"fileName": "photo.jpg",
"fileSize": 482113,
"mimeType": "image/jpeg",
"metadata": { "userId": "u42" }
}'{
"sessionId": "file_01J...",
"presignedUrl": "https://...r2.cloudflarestorage.com/...",
"key": "proj_x/file_01J.../photo.jpg",
"expiresAt": "2026-07-02T12:34:56.000Z"
}Then PUT the raw bytes to presignedUrl — this call goes to Cloudflare R2, not the DevStorage API, and needs no API key:
curl -X PUT "$PRESIGNED_URL" \
-H "content-type: image/jpeg" \
--data-binary @photo.jpg/v1/upload/completeConfirm the upload. DevStorage verifies the object exists and matches the declared size, records the file, and fires your webhook. Returns the file record:
curl https://api.devstorage.io/v1/upload/complete \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY" \
-H "content-type: application/json" \
-d '{ "sessionId": "file_01J..." }'{
"id": "file_01J...",
"name": "photo.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 482113,
"r2Key": "proj_x/file_01J.../photo.jpg",
"url": "https://...",
"metadata": { "userId": "u42" },
"uploadedAt": "2026-07-02T12:35:10.000Z"
}/v1/upload/abortCancel a session you won't complete ({ "sessionId" }). Abandoned sessions are also cleaned up automatically after 24 hours.
Multipart uploads (large files)
For files that are too large for a single PUT (the SDKs switch at 100 MiB), upload in chunks. Every part except the last must be exactly partSize bytes:
# 1. Start — returns sessionId, partSize, partCount
curl https://api.devstorage.io/v1/upload/multipart/start \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY" \
-H "content-type: application/json" \
-d '{ "fileName": "video.mp4", "fileSize": 734003200, "mimeType": "video/mp4" }'
# 2. For each part 1..partCount: get a URL, PUT the chunk, keep the ETag
curl https://api.devstorage.io/v1/upload/multipart/part \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY" \
-H "content-type: application/json" \
-d '{ "sessionId": "file_01J...", "partNumber": 1 }'
# 3. Complete with every part's ETag
curl https://api.devstorage.io/v1/upload/multipart/complete \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY" \
-H "content-type: application/json" \
-d '{
"sessionId": "file_01J...",
"parts": [
{ "partNumber": 1, "etag": "\"9a0364b9...\"" },
{ "partNumber": 2, "etag": "\"e3b0c442...\"" }
]
}'POST /v1/upload/multipart/abort cancels an in-progress multipart session.
ETagresponse header, or the client can't collect part ETags for the complete call.Files
/v1/filesList completed files, newest first. Query params: limit (1–100, default 20) and cursor (from a previous page's nextCursor). Requires the read scope.
curl "https://api.devstorage.io/v1/files?limit=50" \
-H "x-devstorage-key: $DEVSTORAGE_API_KEY"{ "files": [ { "id": "file_01J...", ... } ], "nextCursor": "file_01H...", "hasMore": true }/v1/files/:fileIdFetch one file's record with a fresh download url.
/v1/files/:fileIdDelete the record and the stored object. Requires the delete scope. Returns { "deleted": true }.
Endpoint summary
| Endpoint | Purpose |
|---|---|
POST /v1/upload/presign | Get a presigned PUT URL for a single-shot upload. |
POST /v1/upload/complete | Verify and finalize an upload; returns the file record. |
POST /v1/upload/abort | Cancel an upload session. |
POST /v1/upload/multipart/start | Begin a chunked upload; returns partSize / partCount. |
POST /v1/upload/multipart/part | Get a presigned URL for one part. |
POST /v1/upload/multipart/complete | Finalize a chunked upload from the part ETags. |
POST /v1/upload/multipart/abort | Cancel a chunked upload. |
GET /v1/files | List files (cursor-paginated). |
GET /v1/files/:id | Get one file + download URL. |
DELETE /v1/files/:id | Delete a file. |