File Serving
Files stored in agent databases can be served directly via a built-in HTTP endpoint. This works for PDFs, images, documents, and any other binary file type.
Overview
The file serving system has three parts:
- Storage: Files are stored as BLOBs in the
filestable viaaktorStoreFileWithContent - URL: Files are accessible at
/databases/:databaseId/files/:fileId - Response: The endpoint sets the correct
Content-Typeand serves the file inline (browser displays it directly)
Storing Files
From Uint8Array (server-side)
import { aktorStoreFileWithContent } from '@operaide/vector';
const storedFile = await aktorStoreFileWithContent({
client: dbClient,
filename: 'photo.png',
content: fileBuffer, // Uint8Array
}).get();
// storedFile.file_id → use this to build the URL
MIME type and extension are detected automatically from the filename. Duplicate files (same SHA-256 hash) are deduplicated unless skipDeduplication: true is set.
From browser File objects
import { aktorProcessJSFile } from '@operaide/vector';
const storedFile = await aktorProcessJSFile({
client: dbClient,
file: browserFileObject, // File from <input type="file">
}).get();
Via the Embedding Pipeline
aktorVektorEmbeddingPipeline automatically stores the file as the first step. The resulting file_id is available in the pipeline result.
Generating File URLs
The URL pattern is:
/databases/{databaseId}/files/{fileId}
Build it in an aktor like this:
const fileUrl = `/databases/${databaseId}/files/${storedFile.file_id}`;
For absolute URLs (e.g. to return in API responses), prepend the platform root URL:
import { getPlatform } from '@operaide/aktor';
const absoluteUrl = `${getPlatform().config.getRootUrl()}/databases/${databaseId}/files/${fileId}`;
HTTP Endpoint
Request: GET /databases/:databaseId/files/:fileId
Response:
| Status | Meaning |
|---|---|
| 200 | File served with binary content |
| 404 | Database or file not found |
| 500 | Server error |
Headers (on 200):
Content-Type: Determined from storedmime_typeor filename extensionContent-Disposition:inline; filename="..."(browser displays the file)Content-Length: File size in bytes
Supported MIME Types
| Extension | Content-Type |
|---|---|
.png | image/png |
.jpg, .jpeg | image/jpeg |
.gif | image/gif |
.svg | image/svg+xml |
.webp | image/webp |
.pdf | application/pdf |
.doc | application/msword |
.docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
.txt | text/plain |
.csv | text/csv |
.html | text/html |
.md | text/markdown |
.json | application/json |
.xml | application/xml |
.zip | application/zip |
.mp4 | video/mp4 |
.mp3 | audio/mpeg |
.wav | audio/wav |
Unknown extensions fall back to application/octet-stream.
Complete Example
Store a PDF, generate a URL, and return it to the user:
import { aktorStoreFileWithContent } from '@operaide/vector';
import { createAktorFunctionAsync } from '@operaide/aktor';
import * as fs from 'fs';
const aktorUploadAndServe = createAktorFunctionAsync(
'aktorUploadAndServe',
async (p: { client: Client; databaseId: string; filePath: string }) => {
// Read file
const buffer = fs.readFileSync(p.filePath);
const filename = p.filePath.split('/').pop()!;
// Store in database
const stored = await aktorStoreFileWithContent({
client: p.client,
filename,
content: new Uint8Array(buffer),
}).get();
// Return serving URL
return `/databases/${p.databaseId}/files/${stored.file_id}`;
}
);
For a working example, see the op-demo-database app which implements aktorGenerateFileUrl.
Next Steps
- Data Schema - File table structure details
- Available Aktors - All file operation aktors
- Embedding Pipeline - Automatic file processing