Upload File / Folder

Upload files or folders to your shard node by sending the file or folder data as the request body with the destination path specified as a query parameter.

POST /fso

FSO stands for "file system object" - in this case, a file or folder

Important Notes

  • • All paths must start with / (absolute paths only)
  • • File or folder data is sent as the request body in binary or zip format in case of folders
  • • Use your shard node URL and your API key

Headers

Header Type Required Description
x-user-api-key string Yes Your authentication API key
Content-Type "application/octet-stream" Optional MIME type for binary file data
Content-Type "application/zip" Optional MIME type for folder uploads using zip files. REQUIRED if a zip folder is being uploaded

Query Parameters

Parameter Type Required Description
path string Yes Absolute path where the file or folder should be stored. Must start with '/'. For files, include the filename (e.g., "/folder-1/file-1.txt"). For folders, end with '/' (e.g., "/folder-1/")
informProgress boolean No Set to 'true' to receive progress updates (see "Query Progress" section)
overwrite boolean No Set to 'true' to overwrite existing files or folders instead of returning 409 error

Example Request (File Upload)

func UploadFile(localFilePath string, pathOnShard string) {
    f, err := os.Open(localFilePath)
    if err != nil {
        panic(fmt.Errorf("could not open file: %w", err))
    }
    defer f.Close()
    
    url := fmt.Sprintf("%s/fso?path=%s", SHARD_NODE_URL, url.QueryEscape(pathOnShard))
    req, err := http.NewRequest(http.MethodPost, url, f)
    if err != nil {
        panic(fmt.Errorf("could not create upload request: %w", err))
    }
    
    req.Header.Set("x-user-api-key", API_KEY)
    req.Header.Set("Content-Type", "application/octet-stream") 
    
    client := http.DefaultClient
    resp, err := client.Do(req)
    if err != nil {
        panic(fmt.Errorf("error uploading file: %w", err))
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusCreated {
        body, _ := io.ReadAll(resp.Body)
        panic(fmt.Sprintf("Upload failed with status %d: %s", resp.StatusCode, string(body)))
    }
    
}
function uploadFile(localPath: string, pathOnShard: string): Promise<void> {
    const stats = fs.statSync(localPath);
    const stream = fs.createReadStream(localPath);

    const url = new URL(`${SHARD_URL}/fso`);
    url.searchParams.set('path', pathOnShard);

    return new Promise((resolve, reject) => {
        const req = http.request({
            hostname: url.hostname,
            port: url.port,
            path: url.pathname + url.search,
            method: 'POST',
            headers: {
                'x-user-api-key': API_KEY,
                'Content-Type': 'application/octet-stream',
                'Content-Length': stats.size
            }
        }, (res) => {
            let body = '';
            res.on('data', chunk => body += chunk);
            res.on('end', () => {
                res.statusCode === 201 ? resolve() : reject(new Error(`${res.statusCode}: ${body}`));
            });
        });

        req.on('error', reject);
        stream.on('error', reject);
        stream.pipe(req);
    });
}

Uploading Folders

You can upload a folder by using zip files. Set the Content-Type header to application/zip in your request, include the zip file as the request body, and specify a path that ends with / to denote that this is a folder (e.g., "/folder-1/"). The zip file will be extracted to the specified path on the shard node. Whatever you had in the zip file will now be present at that path.

Important Note

  • Your path must end with a "/" to denote you are uploading a folder

Example Request (Folder Upload)

func UploadFolder(localZipPath string, pathOnShard string) {
    f, err := os.Open(localZipPath)
    if err != nil {
        panic(fmt.Errorf("could not open zip file: %w", err))
    }
    defer f.Close()
    
    url := fmt.Sprintf("%s/fso?path=%s", SHARD_NODE_URL, url.QueryEscape(pathOnShard))
    req, err := http.NewRequest(http.MethodPost, url, f)
    if err != nil {
        panic(fmt.Errorf("could not create upload request: %w", err))
    }
    
    req.Header.Set("x-user-api-key", API_KEY)
    req.Header.Set("Content-Type", "application/zip")
    
    client := http.DefaultClient
    resp, err := client.Do(req)
    if err != nil {
        panic(fmt.Errorf("error uploading folder: %w", err))
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusCreated {
        body, _ := io.ReadAll(resp.Body)
        panic(fmt.Sprintf("Upload failed with status %d: %s", resp.StatusCode, string(body)))
    }
    
}
async function uploadFolder(localZipPath: string, pathOnShard: string): Promise<void> {
    const data = await fs.promises.readFile(localZipPath);
    const url = `${SHARD_NODE_URL}/fso?path=${encodeURIComponent(pathOnShard)}`;
    const res = await fetch(url, {
        method: 'POST',
        headers: { 
            'x-user-api-key': API_KEY,
            'Content-Type': 'application/zip'
        },
        body: new Uint8Array(data)
    });

    if (res.status !== 201) {
        const body = await res.text();
        throw new Error(`Upload failed with status ${res.status}: ${body}`);
    }
}

Response Status Codes

Code Status Description
201 Success File or folder uploaded successfully
400 Bad Request Invalid request parameters
401 Unauthorized Invalid or missing API key
404 Not Found Destination path doesn't exist
409 Conflict File/folder already exists at that path (use overwrite=true to replace)
5xx Server Error Internal server error - check response body for details

Example with Override Parameter

Important Notes

  • • When using overwrite=true, ensure enough space for both old and new files until upload completes. Old files aren't deleted until success. You might get an "out of space" error if there's not enough space left
func UploadFileWithOverride(localFilePath string, pathOnShard string) {
    f, err := os.Open(localFilePath)
    if err != nil {
        panic(fmt.Errorf("could not open file: %w", err))
    }
    defer f.Close()
    
    url := fmt.Sprintf("%s/fso?path=%s&overwrite=true", 
        SHARD_NODE_URL, url.QueryEscape(pathOnShard))
    
    req, err := http.NewRequest(http.MethodPost, url, f)
    if err != nil {
        panic(fmt.Errorf("could not create upload request: %w", err))
    }
    
    req.Header.Set("x-user-api-key", API_KEY)
    req.Header.Set("Content-Type", "application/octet-stream")
    
    client := http.DefaultClient
    resp, err := client.Do(req)
    if err != nil {
        panic(fmt.Errorf("error uploading file: %w", err))
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusCreated {
        body, _ := io.ReadAll(resp.Body)
        panic(fmt.Sprintf("Upload failed with status %d: %s", resp.StatusCode, string(body)))
    }
    
}
async function uploadFileWithOverride(localFilePath: string, pathOnShard: string): Promise<void>{
    const data = await fs.promises.readFile(localFilePath);
    const url = `${SHARD_NODE_URL}/fso?path=${encodeURIComponent(pathOnShard)}&overwrite=true`;
    const res = await fetch(url, {
        method: 'POST',
        headers: { 
            'x-user-api-key': API_KEY,
            'Content-Type': 'application/octet-stream'
        },
        body: new Uint8Array(data)
    });

    if (res.status !== 201) {
        const body = await res.text();
        throw new Error(`Upload failed with status ${res.status}: ${body}`);
    }
}