NEXIUM Storage lets you upload, serve and manage files from any app via a simple REST API. Integrate in minutes — no AWS knowledge required.
Create an account
Sign up at nexium.ai — free, no credit card required.
Create a project & bucket
A project groups your buckets. A bucket holds your files.
Generate an API key
Dashboard → API Keys → New key. Copy it immediately, shown only once.
The official SDKs wrap the REST API with typed methods and zero boilerplate. The JavaScript SDK has no runtime dependencies — it uses native fetch and Web Crypto. The Python SDK falls back to urllib when requests is not installed (file uploads require requests).
Install
# npm
npm install @ainexium/storage
# pnpm
pnpm add @ainexium/storage
# yarn
yarn add @ainexium/storageUsage
import { NexiumStorage } from '@ainexium/storage'
const storage = new NexiumStorage({ apiKey: process.env.NEXIUM_API_KEY })
// Upload (with optional progress tracking)
const file = await storage.upload(bucketId, fileBlob, 'photo.jpg', {
onProgress: (pct) => console.log(pct + '%'),
})
console.log(file.id, file.url)
// List & search
const { files, total } = await storage.list(bucketId, { search: 'photo', page: 1 })
// Download URL
const url = await storage.download(file.id)
// Rename
const updated = await storage.rename(file.id, 'new-name.jpg')
// Delete
await storage.delete(file.id)Webhook verification
import { NexiumStorage } from '@ainexium/storage'
// Express / Node.js example
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const payload = await NexiumStorage.verifyWebhook(
req.body, // raw Buffer
req.headers['x-nexium-signature'], // "sha256=..."
process.env.NEXIUM_WEBHOOK_SECRET,
)
const event = payload as { event: string; data: unknown }
console.log(event.event, event.data) // "file.created", { id, filename, ... }
res.sendStatus(200)
} catch {
res.sendStatus(401)
}
})Pass your API key in the Authorization header on every request. All external API endpoints are prefixed with /api/v1/ext/. Store your key in an environment variable — never expose it in client-side code.
Authorization: Bearer nx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSend a multipart/form-data POST with a file field. Replace <bucket_id> with your bucket's ID from the dashboard.
curl -X POST \
-H "Authorization: Bearer nx_live_..." \
-F "[email protected]" \
https://api.nexium.ai/api/v1/ext/buckets/<bucket_id>/filesResponse :
{
"id": "87e60d98-6cde-4e8a-bc65-7ff0b448091b",
"bucket_id": "71438929-d96a-4424-a425-552ca5b7a464",
"filename": "photo.jpg",
"mime_type": "image/jpeg",
"size_bytes": 245120,
"url": "https://pub-xxxx.r2.dev/bucket_id/file_id/file_id_photo.jpg",
"created_at": "2026-08-16T00:53:23Z"
}Returns all files in a bucket, ordered by most recent first. Pass an optional ?search= query parameter to filter by filename.
# All files
curl -H "Authorization: Bearer nx_live_..." \
https://api.nexium.ai/api/v1/ext/buckets/<bucket_id>/files
# Filter by name
curl -H "Authorization: Bearer nx_live_..." \
"https://api.nexium.ai/api/v1/ext/buckets/<bucket_id>/files?search=photo"Response :
{
"files": [
{
"id": "87e60d98-...",
"bucket_id": "71438929-...",
"filename": "photo.jpg",
"mime_type": "image/jpeg",
"size_bytes": 245120,
"url": "https://pub-xxxx.r2.dev/bucket_id/file_id/file_id_photo.jpg",
"created_at": "2026-08-16T00:53:23Z"
}
],
"total": 1,
"page": 1,
"per_page": 24
}Returns a permanent public URL. Use it directly in an <img> tag, Image.network() in Flutter, or any HTTP client — no expiration. If public access is not configured, a presigned URL valid 1 hour is returned instead.
curl -H "Authorization: Bearer nx_live_..." \
https://api.nexium.ai/api/v1/ext/files/<file_id>/downloadResponse :
{ "url": "https://pub-xxxx.r2.dev/bucket_id/file_id/file_id_photo.jpg" }Updates the display name of a file. The object key in R2 is unchanged — only the filename field is updated.
curl -X PATCH \
-H "Authorization: Bearer nx_live_..." \
-H "Content-Type: application/json" \
-d '{"filename":"new-name.jpg"}' \
https://api.nexium.ai/api/v1/ext/files/<file_id>Response 200 OK — updated file object.
For mobile apps (Flutter, React Native) or browser uploads, use the presign flow to upload directly to R2 — the file never passes through your server.
POST /files/presign
Get a temporary upload URL and a file_id. Valid for 15 minutes.
PUT → R2 directly
Upload the file directly to the returned upload_url. No auth header needed.
POST /files/confirm
Send the file_id, object_key and metadata to save the file record.
Step 1 — POST /files/presign
curl -X POST \
-H "Authorization: Bearer nx_live_..." \
-H "Content-Type: application/json" \
-d '{"filename":"photo.jpg","mime_type":"image/jpeg"}' \
https://api.nexium.ai/api/v1/ext/buckets/<bucket_id>/files/presignStep 2 — PUT directly to R2
curl -X PUT \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
"<upload_url>"Step 3 — POST /files/confirm
curl -X POST \
-H "Authorization: Bearer nx_live_..." \
-H "Content-Type: application/json" \
-d '{"file_id":"<file_id>","object_key":"<object_key>","filename":"photo.jpg","mime_type":"image/jpeg","size_bytes":204800}' \
https://api.nexium.ai/api/v1/ext/buckets/<bucket_id>/files/confirmConfirm response :
{
"id": "87e60d98-...",
"filename": "photo.jpg",
"mime_type": "image/jpeg",
"size_bytes": 204800,
"url": "https://pub-xxxx.r2.dev/bucket_id/file_id/file_id_photo.jpg",
"created_at": "2026-08-16T01:20:00Z"
}Permanently removes the file from storage and R2. Cannot be undone.
curl -X DELETE \
-H "Authorization: Bearer nx_live_..." \
https://api.nexium.ai/api/v1/ext/files/<file_id>All errors return JSON with a message field.
{ "message": "unauthorized" }400Bad RequestInvalid input — check your request body or parameters401UnauthorizedMissing or invalid API key403ForbiddenYou don't have access to this resource404Not FoundThe resource doesn't exist409ConflictA bucket with this name already exists in this project429Too Many RequestsRate limit exceeded — wait before retrying (Retry-After header provided)500Server ErrorSomething went wrong on our end