Storage
Endpoints for working with standalone files in Informer storage: upload, single
file metadata lookup, raw download, and filename search. All routes live under
the /api prefix.
The Storage module registers exactly four routes, and none of them performs a
permission check or a license/feature gate. They are plain authenticated routes:
any authenticated principal (a session or an API token) may call them. File
visibility is tenant-scoped only. The File model defines no read_access scope,
so every file in the caller's Tenant is reachable by id and matches a search,
regardless of owner.
Lookup and search are not scoped to the caller. Any file in the Tenant is visible to any authenticated principal by id or via search.
POST /api/files
Upload a new file via multipart form data.
Authentication: Required (session or API token)
Request: multipart/form-data with a single file part streamed to the
server.
| Form field | Type | Required | Description |
|---|---|---|---|
file | file (stream) | Yes | The file to upload |
Example Request:
curl -X POST https://informer.example.com/api/files \
-H "Authorization: Bearer <token>" \
-F "file=@monthly-report.pdf"
The handler writes the uploaded bytes into a Postgres large object and replies
201 Created with the full File resource and a Location header pointing at
the new file's lookup route (/api/files/{id}).
This is a binary upload, so it has no captured example. The request body is a
raw multipart buffer rather than a documentable JSON payload. The response is
the same File HAL resource shown under GET /api/files/{id} below.
GET /api/files/{id}
Look up a single file's metadata.
Authentication: Required (session or API token)
Pre-blocks: file.lookup(params.id)
The file.lookup server method is a plain File.find by id with no
read_access scope and no owner filter, so any file in the Tenant is visible by
id. The response is a single HAL resource with the file's metadata and links to
self and inf:download.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/files/00000000-0000-4000-8000-000000000001"
}
},
"extension": "pdf",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000001",
"filename": "monthly-report.pdf",
"embedded": false,
"contentType": "application/pdf",
"lobId": "16400",
"role": null,
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": "524288",
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin",
"templateId": null,
"integrationId": null
}
File properties:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | File id |
filename | string | Stored filename |
extension | string | Filename extension, derived from filename |
contentType | string | MIME type |
size | string | File size in bytes (serialized as a string) |
lobId | string | Postgres large-object id holding the bytes |
embedded | boolean | Whether the file is embedded in another entity |
role | string | null | File role for embedded/template files (e.g. template asset); null for standalone files |
directory | boolean | Whether this row represents a directory |
indexed | boolean | Whether the file's contents have been indexed |
data | object | Driver/metadata bag |
ownerId | string | Owning user's username |
templateId | string | null | Associated Template id when the file belongs to a Template |
description | string | null | Optional description |
createdAt / updatedAt | date | Timestamps |
The model also serializes association ids such as parentId, libraryId,
assistantId, chatId, messageId, integrationId, oauthClientId,
letterheadId, and jobActionId/jobActionAttachmentId, all null for a
standalone file. There is no datasetId column; a file's Dataset association is
tracked through other columns.
A request for an id that does not exist in the Tenant answers 404. The
file.lookup server method throws rather than returning an empty body.
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
GET /api/files/{id}/download
Download the raw file contents.
Authentication: Required (session or API token)
Pre-blocks: file.lookup(params.id)
Streams the raw file bytes through the shared file.getContents handler. The
response carries the file's Content-Type and a
Content-Disposition: attachment header so browsers download rather than render
it.
This is a binary response, so it has no captured example. A file with no stored
content short-circuits to 204 No Content.
GET /api/files
Search files in the Tenant by filename.
Authentication: Required (session or API token)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | - | Partial, case-insensitive filename match (ILIKE) |
start | integer | 0 | Paging offset |
limit | integer | - | Page size |
sort | string | - | Sort field |
The search query param is q (a partial filename match), not filename. The
route is the standard db.query HAL collection over the File model and returns
the paged-collection envelope (start, limit, count, total) with file
rows under _embedded["inf:file"]. Each embedded file also embeds its
inf:owner (resolved through user.lookup).
The File model defines no default sort, so rows come back in database order. The captured example below was sorted by filename for a stable byte-identical capture; the route itself does not impose an ORDER BY.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/files"
}
},
"start": 0,
"count": 0,
"total": 0,
"_embedded": {
"inf:file": []
}
}
Notes
- Files can be standalone or embedded in another entity (Templates, and so on). Embedded files are managed through their parent entity's API.
- File upload supports streaming for large files; bytes are stored in a Postgres large object.
- Downloaded files include the original filename in the
Content-Dispositionheader.