Rivspace Developer API

Secure file storage and delivery. Private by default, shareable when you want.

v1

Quick Start

All endpoints require API Key authentication via headers unless noted as public.

Authentication

// Request headers
X-API-Key: <your_key_id>
X-API-Secret: <your_key_secret>

Create or view keys in Developer Dashboard → API Keys.

Base URL

Your server origin is auto-detected.

Endpoints

POST

/api/v1/files/upload

Upload a file (multipart). On private buckets, the file is stored securely and streamed on download.

Fields: file (required), bucket, folder, tags, metadata, createPublicLink ("true"), expiresIn (hours, 0=never), downloadLimit (0=unlimited).

Also accepts createShareLink for backward compatibility.

curl -X POST "$ORIGIN/api/v1/files/upload" \
  -H "X-API-Key: $KEY" \
  -H "X-API-Secret: $SECRET" \
  -F file=@"./image.png" \
  -F bucket=media \
  -F folder=images/avatars \
  -F tags="avatar,profile" \
  -F createPublicLink=true
Response
{
  "success": true,
  "file": { "fileId": "...", "fileName": "image.png", "fileSizeMB": 1.23, ... },
  "links": {
    "downloadUrl": "$ORIGIN/api/v1/files/{fileId}/download",
    "publicLink": { "shareUrl": "$ORIGIN/api/share/{linkId}", "expiresAt": "2100-01-01T00:00:00.000Z", "downloadLimit": 0 }
  }
}

Response also includes links.shareLink as a legacy alias.

GET

/api/v1/files

List files with filters. Query: bucket, folder, page, limit, sortBy, sortOrder, search, tags, view=list|tree.

curl -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" "$ORIGIN/api/v1/files?bucket=media&folder=images"
GET

/api/v1/files/:fileId

Get file metadata.

curl -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" "$ORIGIN/api/v1/files/{fileId}"
GET

/api/v1/files/:fileId/download

Stream file content (server-to-server). Requires headers.

curl -L -o file.bin \
  -H "X-API-Key: $KEY" \
  -H "X-API-Secret: $SECRET" \
  "$ORIGIN/api/v1/files/{fileId}/download"
DELETE

/api/v1/files/:fileId

Delete (mark inactive and remove from storage).

curl -X DELETE -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" "$ORIGIN/api/v1/files/{fileId}"
GET

/api/v1/structure

Returns folder structure (for building file browsers).

curl -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" "$ORIGIN/api/v1/structure"
GET

/api/v1/usage

Plan usage and limits (storage, bandwidth, API calls).

curl -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" "$ORIGIN/api/v1/usage"
GET

/api/share/:linkId

Public link (no headers). Add ?inline=true to hint inline display.

curl -L -o file.bin "$ORIGIN/api/share/{linkId}"

Axios Examples (Node.js)

Use these from your server code. Do not embed API keys in browser code.

Upload with public link

import axios from 'axios';
  import FormData from 'form-data';
  import fs from 'fs';

  const API_BASE = '$ORIGIN/api/v1';
  const headers = { 'X-API-Key': process.env.API_KEY, 'X-API-Secret': process.env.API_SECRET };

  async function upload() {
    const form = new FormData();
    form.append('file', fs.createReadStream('./image.png'));
    form.append('bucket', 'media');
    form.append('folder', 'images/avatars');
    form.append('tags', 'avatar,profile');
  form.append('createPublicLink', 'true'); // immediate public link

    const res = await axios.post(`${API_BASE}/files/upload`, form, {
      headers: { ...headers, ...form.getHeaders() },
      maxContentLength: Infinity,
      maxBodyLength: Infinity,
    });

    console.log('fileId:', res.data.file.fileId);
  console.log('shareUrl:', res.data.links?.publicLink?.shareUrl);
    console.log('downloadUrl (auth):', res.data.links?.downloadUrl);
  }
  upload().catch(console.error);

List files

import axios from 'axios';
  const API_BASE = '$ORIGIN/api/v1';
  const headers = { 'X-API-Key': process.env.API_KEY, 'X-API-Secret': process.env.API_SECRET };

  async function listFiles() {
    const res = await axios.get(`${API_BASE}/files?bucket=media&folder=images`, { headers });
    console.log(res.data.files.map(f => ({ id: f.fileId, name: f.fileName })));
  }
  listFiles().catch(console.error);

Download (stream to disk)

import axios from 'axios';
  import fs from 'fs';
  const API_BASE = '$ORIGIN/api/v1';
  const headers = { 'X-API-Key': process.env.API_KEY, 'X-API-Secret': process.env.API_SECRET };

  async function download(fileId) {
    const resp = await axios.get(`${API_BASE}/files/${fileId}/download`, {
      headers,
      responseType: 'stream',
    });
    const out = fs.createWriteStream('download.bin');
    resp.data.pipe(out);
    await new Promise((r, j) => { out.on('finish', r); out.on('error', j); });
    console.log('Downloaded');
  }
  download(process.env.FILE_ID).catch(console.error);

Create public link

import axios from 'axios';
  const API_BASE = '$ORIGIN/api/v1';
  const headers = { 'X-API-Key': process.env.API_KEY, 'X-API-Secret': process.env.API_SECRET };

  async function share(fileId) {
    const res = await axios.post(`${API_BASE}/files/${fileId}/share`, {
      expiresIn: 0,        // unlimited
      downloadLimit: 0,    // unlimited
    }, { headers });
  console.log('shareUrl:', res.data.publicLink.shareUrl);
  }
  share(process.env.FILE_ID).catch(console.error);

Use public link in HTML

<img src="$ORIGIN/api/share/{linkId}?inline=true" alt="image" />

Public links require no headers and are safe to embed. Authenticated download URLs are for server-to-server usage only.

Implementation Example: Express + React

This is a minimal end-to-end setup showing how to upload a file from a React app via your own Express server and immediately get a public link to embed. Public links are the recommended way to display files freely (no auth headers).

Server (Express)

Create a small proxy so your API keys stay on the server. The server forwards the upload to the Developer API with createPublicLink set to "true", then returns the publicLink.shareUrl.

// server.js
import express from 'express';
import multer from 'multer';
import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';

const app = express();
const upload = multer({ dest: 'tmp/' });

const API_BASE = '$ORIGIN/api/v1'; // Your Rivspace server origin is injected below
const HEADERS = {
  'X-API-Key': process.env.API_KEY,
  'X-API-Secret': process.env.API_SECRET,
};

app.post('/upload', upload.single('file'), async (req, res) => {
  try {
    const form = new FormData();
    form.append('file', fs.createReadStream(req.file.path), req.file.originalname);
    form.append('bucket', 'media');
    form.append('folder', 'public');
    form.append('createPublicLink', 'true'); // return a browser-usable public link

    const resp = await axios.post(`${API_BASE}/files/upload`, form, {
      headers: { ...HEADERS, ...form.getHeaders() },
      maxContentLength: Infinity,
      maxBodyLength: Infinity,
    });

    // Clean temp file
    try { fs.unlinkSync(req.file.path); } catch {}

    // Prefer publicLink for embedding; downloadUrl is for server-to-server (with headers)
    const publicLink = resp.data.links?.publicLink?.shareUrl
      || resp.data.links?.shareLink?.shareUrl
      || null;

    return res.json({
      success: true,
      fileId: resp.data.file.fileId,
      publicUrl: publicLink,
      downloadUrl: resp.data.links?.downloadUrl,
    });
  } catch (err) {
    return res.status(500).json({ success: false, message: 'Upload failed', error: err.message });
  }
});

app.listen(3001, () => console.log('Server running on http://localhost:3001'));

Important: never expose API keys in the browser. Keep them on the server.

Client (React)

Upload a file to your server and render the returned publicUrl.

// App.jsx (simplified)
import React, { useState } from 'react';

export default function App() {
  const [file, setFile] = useState(null);
  const [publicUrl, setPublicUrl] = useState('');

  const onUpload = async () => {
    if (!file) return;
    const form = new FormData();
    form.append('file', file);
    const resp = await fetch('/upload', { method: 'POST', body: form });
    const data = await resp.json();
    setPublicUrl(data.publicUrl);
  };

  return (
    
setFile(e.target.files?.[0] || null)} /> {publicUrl && (
Public URL:
{publicUrl}
preview
)}
); }

Add ?inline=true to hint inline display for images/video where supported.

Why public links?

Public links are designed for browser usage and embeds. They require no headers and are safe to use in HTML tags. The authenticated downloadUrl is meant for server-to-server transfers and should not be used in the browser.

Errors

Common error codes and meanings.

  • INVALID_API_KEY: API Key/Secret mismatch or inactive key.
  • FILE_TOO_LARGE, STORAGE_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED: Plan limits reached.
  • FILE_NOT_FOUND, DELETE_ERROR, DOWNLOAD_ERROR
  • RATE_LIMIT_EXCEEDED: Too many requests (per IP or per API plan).

Best Practices

  • Use public links in HTML tags (<img>, <video>, etc.).
  • Use authenticated download URLs for server-to-server (headers required).
  • Organize with bucket and folder to simplify browsing and lifecycle management.
  • Attach tags and metadata to enable rich filtering and analytics.