Skip to main content

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.

No owner filtering

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 fieldTypeRequiredDescription
filefile (stream)YesThe 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}).

Not shown

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.

Get file metadataGET /api/files/00000000-0000-4000-8000-000000000001
GET /api/files/{id} resolves the file through the `file.lookup` server method (a plain File.find by id, no read_access scope and no owner filter) and returns its metadata as a single HAL resource. Any file in the caller’s tenant is visible by id; an unknown id returns 404. The response includes filename, contentType, size (bytes), embedded, role, createdAt/updatedAt, and links to self and inf:download. No permission check is performed.
Response · 200
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.

File properties:

FieldTypeDescription
idstring (UUID)File id
filenamestringStored filename
extensionstringFilename extension, derived from filename
contentTypestringMIME type
sizestringFile size in bytes (serialized as a string)
lobIdstringPostgres large-object id holding the bytes
embeddedbooleanWhether the file is embedded in another entity
rolestring | nullFile role for embedded/template files (e.g. template asset); null for standalone files
directorybooleanWhether this row represents a directory
indexedbooleanWhether the file's contents have been indexed
dataobjectDriver/metadata bag
ownerIdstringOwning user's username
templateIdstring | nullAssociated Template id when the file belongs to a Template
descriptionstring | nullOptional description
createdAt / updatedAtdateTimestamps

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.

Missing file

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.

Get a file that does not existGET /api/files/00000000-0000-4000-8000-000000000001
GET /api/files/{id} answers 404 when no file with that id exists in the tenant. The `file.lookup` server method throws boom.notFound() rather than returning an empty body.
Response · 404
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Not shown

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:

ParameterTypeDefaultDescription
qstring-Partial, case-insensitive filename match (ILIKE)
startinteger0Paging offset
limitinteger-Page size
sortstring-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.

Search files by filenameGET /api/files?q=report
GET /api/files is the db.query HAL collection over the File model. The search query param is `q` (a partial, case-insensitive ILIKE match against the filename), not a `filename` param. It returns the standard paged envelope (start, limit, count, total) with file rows embedded under `_embedded["inf:file"]`; each row also embeds its `inf:owner`. Standard paging params (start, limit, sort) are accepted. No permission check is performed (the File model defines no read_access scope). Rows are ordered by filename in this example for stability; the route itself returns DB order.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/files"
}
},
"start": 0,
"count": 0,
"total": 0,
"_embedded": {
"inf:file": []
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

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-Disposition header.