V0 · Developer Preview

Integration guide

NEXIUM Storage lets you upload, serve and manage files from any app via a simple REST API. Integrate in minutes — no AWS knowledge required.

Getting started

1

Create an account

Sign up at nexium.ai — free, no credit card required.

2

Create a project & bucket

A project groups your buckets. A bucket holds your files.

3

Generate an API key

Dashboard → API Keys → New key. Copy it immediately, shown only once.

Official SDKs

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/storage

Usage

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)
  }
})
The sections below document the raw REST API — useful if you're using Go, PHP, or any other language.

Authentication

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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Your API key is scoped to a single project. Never commit it to git or include it in a frontend bundle.

Upload a file

Send 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>/files

Response :

{
  "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"
}

List & search files

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
}

Download a file

Returns a permanent public URL. Use it directly in an &lt;img&gt; 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>/download

Response :

{ "url": "https://pub-xxxx.r2.dev/bucket_id/file_id/file_id_photo.jpg" }

Rename a file

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.

Direct upload (mobile & browser)

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.

1

POST /files/presign

Get a temporary upload URL and a file_id. Valid for 15 minutes.

2

PUT → R2 directly

Upload the file directly to the returned upload_url. No auth header needed.

3

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/presign

Step 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/confirm

Confirm 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"
}

Delete a file

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>

Errors

All errors return JSON with a message field.

{ "message": "unauthorized" }
400Bad RequestInvalid input — check your request body or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenYou don't have access to this resource
404Not FoundThe resource doesn't exist
409ConflictA bucket with this name already exists in this project
429Too Many RequestsRate limit exceeded — wait before retrying (Retry-After header provided)
500Server ErrorSomething went wrong on our end