Minlink
Storage API
Manage folders, files, and multipart uploads in Minlink storage.
- Base URL:
https://cm.minlink.io/v1/api - Auth:
x-minlink-api-key: <your-key> - Responses:
{ status, message, data }; errors:{ status: "error", error: { code, message } }
Directory + listing
GET /storage/root- root folders/files.GET /storage/folders/:folderId/contents- list subfolders/files.GET /storage/files- list all files for the owner.GET /storage/usage- returns{ usedBytes, remainingBytes, percentUsed, ... }.
Folder/file management
POST /storage/folders- body{ name, parentId? }.POST /storage/rename-folder/:id- body{ newName }.POST /storage/move- body{ fileIds: string[], folderIds: string[], destinationFolderId?: string | null }.DELETE /storage/folders/:id- delete folder recursively.DELETE /storage/file/:id- delete one file.POST /storage/batch-delete- body{ fileIds?: string[], folderIds?: string[] }.PUT /storage/file/:id- update file metadata. Fields:newName(extension must stay the same)accessType:public|password(ifpassword, supplypasswordand it must not match the account password)password: required whenaccessType === 'password'allowDownload: booleanmediaModifications(stubbed; ignored server-side currently)
Uploads (multipart, Cloudflare R2)
- Create session -
POST /storage/uploads/sessions
| Prop | Type | What it controls |
|---|---|---|
fileName | string (required) | File name with extension (extension must remain the same on rename). |
size | number (required) | Total bytes for the upload. |
contentType | string (required) | MIME type used for presigning. |
folderId | string | Destination folder id (optional). |
accessType | public | password | Access control; password requires password field. |
password | string | Required when accessType === "password"; cannot match account password. |
metadata | object | Optional metadata saved with the file. |
partNumbers | number[] | Request specific presigned part numbers (optional; otherwise server allocates sequentially). |
- Returns
{ sessionId, uploadId, objectKey, partSize, totalParts, presignedUrls[] }. - If
accessTypeispassword,passwordis required and cannot equal the account password.
- Upload parts - PUT each presigned URL with the file bytes for that part. Capture the returned
ETagper part. - Request more URLs (optional) -
POST /storage/uploads/sessions/:sessionId/presignwith{ partNumbers: number[] }. - Complete session -
POST /storage/uploads/sessions/:sessionId/completewith{ parts: [{ partNumber, etag }] }.- Returns
{ status: 'queued' | 'completed', sessionId }while background finalization runs.
- Returns
- Abort session -
DELETE /storage/uploads/sessions/:sessionId.
Upload samples
import fs from 'node:fs/promises';
const BASE = 'https://cm.minlink.io/v1/api';
const API_KEY = process.env.MINLINK_API_KEY!;
const create = await fetch(`${BASE}/storage/uploads/sessions`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-minlink-api-key': API_KEY,
},
body: JSON.stringify({
fileName: 'demo.pdf',
size: 5 * 1024 * 1024,
contentType: 'application/pdf',
accessType: 'public',
}),
}).then((r) => r.json());
const { sessionId, presignedUrls } = create.data;
const file = await fs.readFile('./demo.pdf');
const etags: { partNumber: number; etag: string }[] = [];
for (let i = 0; i < presignedUrls.length; i++) {
const partNumber = i + 1;
const res = await fetch(presignedUrls[i], { method: 'PUT', body: file });
etags.push({ partNumber, etag: res.headers.get('etag') || '' });
}
await fetch(`${BASE}/storage/uploads/sessions/${sessionId}/complete`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-minlink-api-key': API_KEY,
},
body: JSON.stringify({ parts: etags }),
});Fetch and rename example
// List root
const root = await fetch(`${BASE}/storage/root`, { headers }).then((r) => r.json());
// Rename a folder
await fetch(`${BASE}/storage/rename-folder/${folderId}`, {
method: 'POST',
headers,
body: JSON.stringify({ newName: 'Renamed Folder' }),
});Error notes
- 401/403 for missing/invalid API key, inactive key, suspended owner, or missing permissions.
- 402 if the developer account is paused (low balance).
- 404 for missing files/folders/sessions.
- 400 for invalid input (e.g., missing fileName/size/contentType, changing file extension, moving into own descendant).
- Password-protected files require a non-empty password that differs from the account password.