LinconwavesLinconwavesUnified docs
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 (if password, supply password and it must not match the account password)
    • password: required when accessType === 'password'
    • allowDownload: boolean
    • mediaModifications (stubbed; ignored server-side currently)

Uploads (multipart, Cloudflare R2)

  1. Create session - POST /storage/uploads/sessions
PropTypeWhat it controls
fileNamestring (required)File name with extension (extension must remain the same on rename).
sizenumber (required)Total bytes for the upload.
contentTypestring (required)MIME type used for presigning.
folderIdstringDestination folder id (optional).
accessTypepublic | passwordAccess control; password requires password field.
passwordstringRequired when accessType === "password"; cannot match account password.
metadataobjectOptional metadata saved with the file.
partNumbersnumber[]Request specific presigned part numbers (optional; otherwise server allocates sequentially).
  • Returns { sessionId, uploadId, objectKey, partSize, totalParts, presignedUrls[] }.
  • If accessType is password, password is required and cannot equal the account password.
  1. Upload parts - PUT each presigned URL with the file bytes for that part. Capture the returned ETag per part.
  2. Request more URLs (optional) - POST /storage/uploads/sessions/:sessionId/presign with { partNumbers: number[] }.
  3. Complete session - POST /storage/uploads/sessions/:sessionId/complete with { parts: [{ partNumber, etag }] }.
    • Returns { status: 'queued' | 'completed', sessionId } while background finalization runs.
  4. 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.