Skip to main content

Folders API Overview

Endpoints for organizing content into hierarchical Folder structures. A Folder can hold Reports, Datasets, Datasources, Jobs, Queries, Templates, Assistants, Libraries, Integrations, and Toolkits, and can nest under a parent Folder to any depth. All routes are prefixed with /api.

A Folder carries no owner column, so any Folder in the Tenant is readable by an authenticated caller (read access is enforced by the model's read_access scope). Creating Folders requires permission.folders.create, whose driver is isDesigner(): Designers and superusers pass, ordinary users do not.

GET /api/folders

List the Tenant's Folders, with optional name search.

Authentication: Required (session or API token)

Query Parameters:

ParameterTypeDescription
qstringPartial, case-insensitive match against the Folder name

Response:

A HAL collection under _embedded["inf:folder"]. There is no server-side sort, so rows come back in database order. Each Folder carries a permissions map (edit, delete, write, rename) for the caller, and a Folder that has a parent also exposes an inf:parent-folder link.

List foldersGET /api/folders
GET /api/folders is a HAL collection (db.query handler, model Folder). Folder rows embed under `inf:folder`; a folder with a parent also exposes an `inf:parent-folder` link. There is no server-side sort, and the only search query param is `q` (a partial, case-insensitive match against the folder name).
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders"
}
},
"start": 0,
"count": 5,
"total": 5,
"_embedded": {
"inf:folder": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Engineering",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": null
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"name": "Marketing Reports",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": null
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000003"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000003",
"name": "Q1 2024",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": "00000000-0000-4000-8000-000000000004"
},
"… 2 more items (5 total) — omitted from docs"
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

Pass q to filter by name. The shape is identical to the unfiltered list.

Search folders by nameGET /api/folders?q=Reports
The `q` query param does a partial (case-insensitive contains) match against the folder name. Same HAL collection shape as the unfiltered list.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders"
}
},
"start": 0,
"count": 0,
"total": 0,
"_embedded": {
"inf:folder": []
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/folders

Create a Folder.

Authentication: Required (session or API token)

Pre-blocks: permission.folders.create

Request Body:

FieldTypeRequiredDescription
namestringYesFolder name
parentIdstring (UUID) | nullNoParent Folder id. Pass null (or omit it) for a root Folder

Example Request:

{
"name": "Marketing",
"parentId": null
}

Response:

Responds 201 Created with the Folder and a Location header pointing at the new resource.

Create a folderPOST /api/folders
POST /api/folders requires permission.folders.create (the driver is isDesigner(); admins/designers pass). Payload is { name (required), parentId? (uuid|null) }; pass parentId: null (or omit it) for a root folder. Responds 201 with a Location header pointing at the new folder.
Request body
{
"name": "Marketing",
"parentId": null
}
Response · 201
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"name": "Marketing",
"parentId": null,
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"_altid": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/folders/{id}

Retrieve a single Folder.

Authentication: Required (session or API token)

Pre-blocks: folder.lookup (resolves through the read_access scope)

Path Parameters:

ParameterTypeDescription
idstring (UUID)Folder id

Response:

The Folder links to inf:permissions; a Folder that has a parent also links inf:parent-folder. The example below is the child "Q1 2024" Folder, so its parent link is shown.

Get a folderGET /api/folders/00000000-0000-4000-8000-000000000001
GET /api/folders/{id} resolves through the db.lookup handler (read_access scope). The serialized folder links to `inf:permissions`; a folder with a parent also links `inf:parent-folder`. `{id}` is the folder UUID. This example is the child "Q1 2024" folder so its parent link is shown.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001"
},
"inf:permissions": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001/permissions"
},
"inf:parent-folder": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Q1 2024",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": "00000000-0000-4000-8000-000000000002"
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/folders/{id}

Update a Folder's name or parent.

Authentication: Required (session or API token)

Pre-blocks: folder.lookup

Request Body (partial):

FieldTypeDescription
namestringNew Folder name
parentIdstring (UUID) | nullNew parent Folder id. Pass null to move the Folder to the root

Example Request:

{
"name": "Engineering & Platform",
"parentId": null
}

Response:

Responds 200 with the re-fetched Folder.

Update a folderPUT /api/folders/00000000-0000-4000-8000-000000000001
PUT /api/folders/{id} uses the db.update handler. The body fields are { name?, parentId? } (parentId may be null to move the folder to the root). Returns the re-fetched folder.
Request body
{
"name": "Engineering & Platform",
"parentId": null
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Engineering & Platform",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

DELETE /api/folders/{id}

Delete a Folder.

Authentication: Required (session or API token)

Pre-blocks: folder.lookup

Response:

Responds 200 with an empty body (Informer's db.remove convention), not 204.

Delete a folderDELETE /api/folders/00000000-0000-4000-8000-000000000001
DELETE /api/folders/{id} uses the db.remove handler and returns 200 with an EMPTY body (Informer convention), NOT 204 as the prose claimed. It cascades to child folders and nulls the folderId on any contained entities (they stay in the system, just un-foldered).
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Cascade and un-folder

Deleting a Folder cascades to its child Folders. Entities contained in the deleted Folder are not removed: their folderId is set to null, leaving them in the system but un-foldered.


GET /api/folders/{id}/folders

List the direct subfolders of a Folder.

Authentication: Required (session or API token)

Pre-blocks: folder.lookup

Path Parameters:

ParameterTypeDescription
idstring (UUID)Parent Folder id

Response:

A HAL collection under _embedded["inf:folder"], filtered to the Folders whose parentId equals {id}.

List subfoldersGET /api/folders/00000000-0000-4000-8000-000000000001/folders
GET /api/folders/{id}/folders (NOT `/subfolders`) is a HAL collection filtered to direct children (where parentId = {id}), embedded under `inf:folder`.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001/folders"
}
},
"start": 0,
"count": 1,
"total": 1,
"_embedded": {
"inf:folder": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"name": "Q1 2024",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": "00000000-0000-4000-8000-000000000001"
}
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.
Subfolders path

The subfolders route is /api/folders/\{id\}/folders, not /api/folders/\{id\}/subfolders.


POST /api/folders/{id}/folders

Create a child Folder under {id}.

Authentication: Required (session or API token)

Pre-blocks: permission.folders.create

Request Body:

FieldTypeRequiredDescription
namestringYesFolder name

The route forces parentId = {id} from the URL regardless of the body, so the new Folder is always a child of {id}.

Example Request:

{
"name": "Q2 2024"
}

Response:

Responds 201 Created with the Folder and a Location header.

Create a subfolderPOST /api/folders/00000000-0000-4000-8000-000000000001/folders
POST /api/folders/{id}/folders creates a child folder: it forces parentId = {id} regardless of the body. Requires permission.folders.create; payload is { name (required) }. Responds 201 with a Location header. (This route is undocumented on the page.)
Request body
{
"name": "Q2 2024"
}
Response · 201
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"name": "Q2 2024",
"parentId": "00000000-0000-4000-8000-000000000001",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"_altid": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/folders/{id}/permissions

Get the caller's permission map for a Folder.

Authentication: Required (session or API token)

Pre-blocks: folder.lookup

Response:

A flat object of the caller's booleans for the folders permission group (edit, delete, write, rename, create), alongside the HAL self link.

Get folder permissionsGET /api/folders/00000000-0000-4000-8000-000000000001/permissions
GET /api/folders/{id}/permissions resolves the folder via folder.lookup, then returns the caller’s permission map for the folders group (edit, delete, write, rename, create booleans).
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/folders/00000000-0000-4000-8000-000000000001/permissions"
}
},
"edit": true,
"delete": true,
"write": true,
"rename": true
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/folders-bulk

Add, remove, and update Folders in a single transaction.

Authentication: Required (session or API token)

Pre-blocks: permission.folders.create

Request Body:

FieldTypeDescription
addarrayFolders to create. Defaults to []
removearrayFolder ids (UUIDs) to delete. Defaults to []
updatearrayFolders to update. Defaults to []

add and update array items:

FieldTypeDescription
idstring (UUID)Folder id. For add, this is a client-generated UUID
namestringFolder name
parentIdstring (UUID) | nullParent Folder id

Example Request:

{
"add": [
{ "id": "7a2c5f3e-1b4d-4c6a-8e9f-0a1b2c3d4e5f", "name": "2024 Reports", "parentId": null }
],
"remove": [
"old-folder-uuid"
],
"update": [
{ "id": "sales-folder-uuid", "name": "Sales & Revenue Reports", "parentId": null }
]
}

Processing order: remove (which also cleans up child Folders), then add, then update.

Response:

Responds 200 with an empty body, not 204.

Bulk add, remove, and update foldersPOST /api/folders-bulk
POST /api/folders-bulk (NOT `/api/folders/_bulk-update`) requires permission.folders.create and runs in a transaction. Payload is { add?: [{id,name,parentId}], remove?: [uuid], update?: [{id,name,parentId}] } (each defaults to []). Processing order is remove (cleans children) -> add -> update. `add[].id` is a client-generated UUID. Returns 200 with an EMPTY body, NOT 204.
Request body
{
"add": [
{
"id": "00000000-0000-4000-8000-000000000001",
"name": "2024 Reports",
"parentId": null
}
],
"remove": [
"00000000-0000-4000-8000-000000000002"
],
"update": [
{
"id": "00000000-0000-4000-8000-000000000003",
"name": "Sales & Revenue Reports",
"parentId": null
}
]
}
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Bulk path

The bulk route is /api/folders-bulk, not /api/folders/_bulk-update.


POST /api/folders-bulk-assign

Assign a list of entities to a Folder (or remove them from any Folder), in a single transaction.

Authentication: Required (session or API token)

Request Body:

FieldTypeDescription
entitiesarrayEntity ids (UUIDs) to assign. Defaults to []
folderIdstring (UUID) | nullTarget Folder id. Pass null to remove the entities from any Folder

Example Request:

{
"entities": [
"report-uuid",
"dataset-uuid",
"job-uuid"
],
"folderId": "sales-folder-uuid"
}

Each entity is looked up under its own read_access scope and pruned unless the caller has <type>:write on it. Supported types are Reports, Datasets, Datasources, Jobs, Queries, Templates, Assistants, Libraries, Integrations, and Toolkits.

Response:

Responds 200 with an empty body. The captured example uses an empty entities list to show the contract without seeding cross-module content.

Bulk assign entities to a folderPOST /api/folders-bulk-assign
POST /api/folders-bulk-assign (NOT `/api/folders/_bulk-assign`) sets folderId on a list of entities, in a transaction. Payload is { entities?: [uuid], folderId?: uuid|null } (folderId: null removes them from any folder). Each entity is looked up under its read_access scope and pruned unless the caller has `<type>:write` on it (reports, datasets, datasources, jobs, queries, templates, assistants, libraries, integrations, toolkits). Returns 200 with an EMPTY body. The example uses an empty entities list to show the contract without seeding cross-module content.
Request body
{
"entities": [],
"folderId": "00000000-0000-4000-8000-000000000001"
}
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Bulk-assign path

The bulk-assign route is /api/folders-bulk-assign, not /api/folders/_bulk-assign.


Folder Hierarchy

Folders support nesting to any depth. A child Folder references its parent via parentId, and the serialized Folder exposes an inf:parent-folder link when a parent is set:

Root
├── Sales Reports
│ ├── Q1 2024
│ │ ├── January
│ │ └── February
│ └── Q2 2024
└── Marketing Reports

Supported Entity Types

The bulk-assign route can place these entity types into a Folder, subject to a per-entity <type>:write check:

  • Reports
  • Datasets
  • Datasources
  • Jobs
  • Queries
  • Templates
  • Assistants
  • Libraries
  • Integrations
  • Toolkits