Code examples
Upload, list, download, copy, move, update metadata, and delete — in curl, JavaScript/TypeScript, Python, and C#.
curl
BASE="https://storage.devd.org/api/v1"
KEY="sk_live_YOUR_KEY"
# Upload
curl -X POST "$BASE/containers/contracts/files" \
-H "X-API-Key: $KEY" -F "file=@agreement.pdf"
# List
curl "$BASE/containers/contracts/items?limit=50" -H "X-API-Key: $KEY"
# Download
curl -L "$BASE/containers/contracts/files/file_XXXX/content" \
-H "X-API-Key: $KEY" -o agreement.pdf
# Copy (then poll the job)
curl -X POST "$BASE/containers/contracts/files/file_XXXX/copy" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"targetContainer":"archive"}'
curl "$BASE/jobs/job_YYYY" -H "X-API-Key: $KEY"
# Move into a folder
curl -X POST "$BASE/containers/contracts/files/file_XXXX/move" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"targetFolderId":"folder_ZZZZ"}'
# Update metadata
curl -X PATCH "$BASE/containers/contracts/files/file_XXXX/metadata" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"matterId":"MAT-10021","department":"legal"}'
# Delete
curl -X DELETE "$BASE/containers/contracts/files/file_XXXX" -H "X-API-Key: $KEY"JavaScript / TypeScript
const BASE = "https://storage.devd.org/api/v1";
const KEY = process.env.SPORAGE_API_KEY!; // never hardcode keys
async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(`${BASE}${path}`, {
...init,
headers: { "X-API-Key": KEY, ...init.headers },
});
const body = await response.json();
if (!body.success) {
throw new Error(`${body.error.code}: ${body.error.message} (${body.requestId})`);
}
return body.data as T;
}
// Upload (multipart)
async function upload(container: string, file: Blob, name: string) {
const form = new FormData();
form.append("file", file, name);
return api<{ file: { id: string; name: string } }>(
`/containers/${container}/files`,
{ method: "POST", body: form },
);
}
// List with pagination
async function listAll(container: string) {
const items = [];
let cursor: string | null = null;
do {
const page = await api<{ items: unknown[]; nextCursor: string | null }>(
`/containers/${container}/items?limit=100` +
(cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
);
items.push(...page.items);
cursor = page.nextCursor;
} while (cursor);
return items;
}
// Download to a Buffer (Node.js)
async function download(container: string, fileId: string) {
const response = await fetch(
`${BASE}/containers/${container}/files/${fileId}/content`,
{ headers: { "X-API-Key": KEY } },
);
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
return Buffer.from(await response.arrayBuffer());
}
// Copy and wait for completion
async function copyAndWait(container: string, fileId: string, target: string) {
const { job } = await api<{ job: { id: string; status: string } }>(
`/containers/${container}/files/${fileId}/copy`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetContainer: target }),
},
);
let status = job;
while (status.status === "queued" || status.status === "running") {
await new Promise((resolve) => setTimeout(resolve, 1000));
status = (await api<{ job: typeof job }>(`/jobs/${job.id}`)).job;
}
return status;
}Python
import os, time, requests
BASE = "https://storage.devd.org/api/v1"
HEADERS = {"X-API-Key": os.environ["SPORAGE_API_KEY"]}
def api(method, path, **kwargs):
response = requests.request(method, f"{BASE}{path}", headers=HEADERS, **kwargs)
body = response.json()
if not body.get("success"):
error = body["error"]
raise RuntimeError(f"{error['code']}: {error['message']} ({body['requestId']})")
return body["data"]
# Upload
with open("agreement.pdf", "rb") as f:
data = api("POST", "/containers/contracts/files", files={"file": ("agreement.pdf", f)})
file_id = data["file"]["id"]
# List
items = api("GET", "/containers/contracts/items", params={"limit": 100})["items"]
# Download
response = requests.get(
f"{BASE}/containers/contracts/files/{file_id}/content", headers=HEADERS)
response.raise_for_status()
open("downloaded.pdf", "wb").write(response.content)
# Copy and poll
job = api("POST", f"/containers/contracts/files/{file_id}/copy",
json={"targetContainer": "archive"})["job"]
while job["status"] in ("queued", "running"):
time.sleep(1)
job = api("GET", f"/jobs/{job['id']}")["job"]
# Metadata
api("PATCH", f"/containers/contracts/files/{file_id}/metadata",
json={"matterId": "MAT-10021", "documentType": "contract"})
# Delete
api("DELETE", f"/containers/contracts/files/{file_id}")C#
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var baseUrl = "https://storage.devd.org/api/v1";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key",
Environment.GetEnvironmentVariable("SPORAGE_API_KEY"));
// Upload
using var form = new MultipartFormDataContent();
var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync("agreement.pdf"));
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(fileContent, "file", "agreement.pdf");
var uploadResponse = await client.PostAsync($"{baseUrl}/containers/contracts/files", form);
var uploaded = JsonDocument.Parse(await uploadResponse.Content.ReadAsStringAsync());
var fileId = uploaded.RootElement.GetProperty("data")
.GetProperty("file").GetProperty("id").GetString();
// List
var listing = await client.GetFromJsonAsync<JsonDocument>(
$"{baseUrl}/containers/contracts/items?limit=100");
// Download
var bytes = await client.GetByteArrayAsync(
$"{baseUrl}/containers/contracts/files/{fileId}/content");
await File.WriteAllBytesAsync("downloaded.pdf", bytes);
// Metadata update
var patch = new HttpRequestMessage(HttpMethod.Patch,
$"{baseUrl}/containers/contracts/files/{fileId}/metadata")
{
Content = JsonContent.Create(new { matterId = "MAT-10021", department = "legal" })
};
await client.SendAsync(patch);
// Delete
await client.DeleteAsync($"{baseUrl}/containers/contracts/files/{fileId}");Idempotent uploads
Add an Idempotency-Key header to uploads and other writes. If the request is retried with the same key, the stored response is replayed instead of repeating the operation.
curl -X POST "$BASE/containers/contracts/files" \ -H "X-API-Key: $KEY" \ -H "Idempotency-Key: 4f9d2c81-upload-agreement" \ -F "file=@agreement.pdf"