Bundles
A Bundle is a named, tenant-scoped manifest of entities (its entry rows) that
can later be exported to a .tgz archive and deployed or imported into another
tenant. Each entry names one entity to ship (a Tag, Report, Dataset, Query,
Datasource, Team, User, Job, Template, Letterhead, Assistant, Library, Code,
App, Toolkit, or Integration) and is backed by an entry-type driver that knows
how to export and import that kind of entity.
There is no license-feature gate on any Bundle route. Reads are open within the
tenant (no permission check). Every write route is guarded by a Superuser
permission check: POST /api/bundles requires permission.bundles.create, and
the bundle and entry write/delete routes require permission.bundle.write (the
delete and rename abilities delegate to bundle:write). All three reduce to
req.auth.credentials.superuser.
Bundles are not owner-scoped (there is no createdById column), so any
Superuser in the tenant manages any Bundle.
GET /api/bundle-drivers
List the registered bundle entry-type drivers. Use a driver id as the type
when creating an entry.
Authentication: Required (read-only, no permission check)
Response:
A HAL collection under _embedded["inf:bundle-driver"], in registration order.
Each descriptor carries an id, a name (an i18n key the client resolves to
the active locale), and an icon hint (materialIcon, icon, or iconSvg). The
editor field is a UI spec; it is a function and is dropped by JSON
serialization. The registered ids are code, global, assistant, dataset,
datasource, query, report, tagged, user, team, job, template,
letterhead, library, app, toolkit, and integration.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers"
}
},
"start": 0,
"count": 17,
"total": 17,
"_embedded": {
"inf:bundle-driver": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/app"
}
},
"id": "app",
"name": "App",
"materialIcon": "widgets",
"editor": {
"appBundleEntry": {}
}
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/assistant"
}
},
"id": "assistant",
"name": "Assistant",
"icon": "fas fa-atom",
"editor": {
"assistantBundleEntry": {}
}
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/code"
}
},
"id": "code",
"name": "Code",
"iconSvg": "/images/admin/codes.svg",
"editor": {
"codeBundleEntry": {}
}
},
"… 14 more items (17 total) — omitted from docs"
]
}
}
global is a driver, not a builderglobal is a bundle driver (it appears here and ships no icon), but it is not
an entry builder, so it does not appear in GET /api/bundle-templates.
GET /api/bundle-drivers/{id}
Retrieve a single driver descriptor.
Authentication: Required (read-only, no permission check)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | A registered bundle-driver id (e.g. dataset, tagged, report) |
Response:
The driver descriptor for that id. An unregistered id throws 400
(boom.badRequest) from the DriverManager, not a 404.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/dataset"
}
},
"id": "dataset",
"name": "Dataset",
"materialIcon": "find_in_page",
"editor": {
"datasetBundleEntry": {}
}
}
GET /api/bundle-templates
Discover the bundle-entry-builder palette: the "add entry" choices the UI
shows. This is not a CRUD list.
Authentication: Required (read-only, no permission check)
Response:
A HAL collection whose items each carry a value with group ("__top"),
name (translated), an icon hint, entry (with the type to send when
creating an entry), and a component spec. There is one builder per top-level
entry type. The global driver is intentionally absent (it is a driver, not a
builder).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-templates"
}
},
"items": [
{
"detective": "00000000-0000-4000-8000-000000000001",
"id": "00000000-0000-4000-8000-000000000001:0",
"value": {
"group": "__top",
"name": "Dataset",
"materialIcon": "find_in_page",
"entry": {
"type": "dataset"
},
"component": {
"datasetBundleEntry": {}
}
}
},
{
"detective": "00000000-0000-4000-8000-000000000002",
"id": "00000000-0000-4000-8000-000000000002:0",
"value": {
"group": "__top",
"name": "Datasource",
"icon": "fas fa-database",
"entry": {
"type": "datasource"
},
"component": {
"datasourceBundleEntry": {}
}
}
},
{
"detective": "00000000-0000-4000-8000-000000000003",
"id": "00000000-0000-4000-8000-000000000003:0",
"value": {
"group": "__top",
"name": "Job",
"icon": "flaticon-schedule",
"entry": {
"type": "job"
},
"component": {
"jobBundleEntry": {}
}
}
},
"… 13 more items (16 total) — omitted from docs"
],
"start": 0,
"count": 16,
"total": 16
}
GET /api/bundle-list
A fast, flat read of the tenant's Bundles.
Authentication: Required (read-only, no permission check)
Response:
A bare JSON array (not a HAL {start,total,count,items} collection) produced
by a raw SQL projection of the bundle table. Each item carries id,
naturalId (equal to the slug), name, description, createdAt, and
updatedAt, with no per-entry detail. RLS scopes the array to the caller's
tenant.
[
{
"id": "00000000-0000-4000-8000-000000000001",
"naturalId": "order-desk-starter",
"name": "Order Desk Starter",
"description": "Tags and reports that seed a new Order Desk tenant",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
},
{
"id": "00000000-0000-4000-8000-000000000002",
"naturalId": "scratch-bundle",
"name": "Scratch Bundle",
"description": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
]
GET /api/bundles
List the tenant's Bundles as a HAL collection.
Authentication: Required (read-only, no permission check)
Response:
A HAL collection under _embedded["inf:bundle"] with the usual start,
total, and count envelope. Each Bundle eager-loads its entries. The
default page limit applies and there is no explicit sort (database order).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles"
},
"next": {
"href": "https://informer.example.com/api/bundles?start=4&limit=10"
}
},
"start": 0,
"count": 2,
"total": 4,
"_embedded": {
"inf:bundle": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Order Desk Starter",
"slug": "order-desk-starter",
"description": "Tags and reports that seed a new Order Desk tenant",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"entries": [
{
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000004",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000005",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
]
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000006"
}
},
"id": "00000000-0000-4000-8000-000000000006",
"name": "Scratch Bundle",
"slug": "scratch-bundle",
"description": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"entries": []
}
]
}
}
POST /api/bundles
Create a Bundle.
Authentication: permission.bundles.create (Superuser)
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
slug | string | No | URL-safe identifier; persisted as naturalId |
description | string | No | Description |
This route has no Joi payload validation: the handler persists the payload
verbatim, so name, slug, and description all save and naturalId is set
from slug.
Example Request:
{
"name": "Quarterly Reporting Pack",
"slug": "quarterly-reporting-pack",
"description": "Datasets, reports, and templates for the quarterly close"
}
Response:
Responds 201 Created with the Bundle and a Location header resolved from the
slug.
{
"name": "Quarterly Reporting Pack",
"slug": "quarterly-reporting-pack",
"description": "Datasets, reports, and templates for the quarterly close"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/quarterly-reporting-pack"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"name": "Quarterly Reporting Pack",
"slug": "quarterly-reporting-pack",
"description": "Datasets, reports, and templates for the quarterly close",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z"
}
GET /api/bundles/{id}
Retrieve a single Bundle with its entries.
Authentication: Required (read-only, no permission check)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
Response:
The lookup branches on whether id is a UUID, so a Bundle resolves by either
its UUID or its slug; a missing Bundle answers 404. The body eager-loads the
Bundle's entries and adds inf:download (to ./_export) and inf:deploy
(to ./_deploy) link hrefs. Following those links is the binary or destructive
path (see below).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/order-desk-starter"
},
"inf:download": {
"href": "https://informer.example.com/api/bundles/order-desk-starter/_export"
},
"inf:deploy": {
"href": "https://informer.example.com/api/bundles/order-desk-starter/_deploy"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Order Desk Starter",
"slug": "order-desk-starter",
"description": "Tags and reports that seed a new Order Desk tenant",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"entries": [
{
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000004",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000005",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
]
}
PUT /api/bundles/{id}
Update a Bundle.
Authentication: permission.bundle.write (Superuser)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
Request Body (partial):
| Field | Type | Description |
|---|---|---|
name | string | Display name |
slug | string | URL-safe identifier |
description | string | Description |
This route is unvalidated, so any Bundle column may be sent.
Example Request:
{
"name": "Order Desk Starter (Updated)",
"description": "Refreshed starter content for new Order Desk tenants"
}
Response:
Responds 200 with the updated Bundle (entries included).
{
"name": "Order Desk Starter (Updated)",
"description": "Refreshed starter content for new Order Desk tenants"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Order Desk Starter (Updated)",
"slug": "order-desk-starter",
"description": "Refreshed starter content for new Order Desk tenants",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"entries": [
{
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000004",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"id": "00000000-0000-4000-8000-000000000005",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
]
}
DELETE /api/bundles/{id}
Delete a Bundle.
Authentication: permission.bundle.delete (delegates to bundle:write, Superuser)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
Response:
Responds 200 with an empty body (Informer's db.remove convention), not
204.
Deleting a Bundle cascade-deletes all of its entries (the entry rows have a
foreign key with onDelete: cascade). The referenced entities (the Tags,
Reports, and so on the entries pointed at) are not touched.
GET /api/bundles/{id}/entries
List a Bundle's entries.
Authentication: Required (read-only, no permission check)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
Response:
A HAL collection under _embedded["inf:bundle-entry"], scoped to the Bundle. It
eager-loads the referenced entity per type (tag, report, dataset, query,
datasource, team, user, job, template, assistant, library, letterhead, code) and
attaches the bundle driver descriptor under each item's embedded
inf:bundle-driver. The referenced entity is embedded under its own rel (here
inf:tag).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries"
}
},
"start": 0,
"count": 3,
"total": 3,
"_embedded": {
"inf:bundle-entry": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000002"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"report": null,
"dataset": null,
"query": null,
"datasource": null,
"team": null,
"user": null,
"job": null,
"template": null,
"assistant": null,
"library": null,
"letterhead": null,
"code": null,
"_embedded": {
"inf:bundle-driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/tagged"
}
},
"id": "tagged",
"name": "Tag",
"icon": "fas fa-tag",
"editor": {
"taggedBundleEntry": {}
}
},
"inf:tag": {
"_links": {
"self": {
"href": "https://informer.example.com/api/tags/00000000-0000-4000-8000-000000000003"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000003",
"name": "Order Desk"
}
}
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000004"
}
},
"id": "00000000-0000-4000-8000-000000000004",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"report": null,
"dataset": null,
"query": null,
"datasource": null,
"team": null,
"user": null,
"job": null,
"template": null,
"assistant": null,
"library": null,
"letterhead": null,
"code": null,
"_embedded": {
"inf:bundle-driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/tagged"
}
},
"id": "tagged",
"name": "Tag",
"icon": "fas fa-tag",
"editor": {
"taggedBundleEntry": {}
}
},
"inf:tag": {
"_links": {
"self": {
"href": "https://informer.example.com/api/tags/00000000-0000-4000-8000-000000000003"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000003",
"name": "Order Desk"
}
}
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000005"
}
},
"id": "00000000-0000-4000-8000-000000000005",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"report": null,
"dataset": null,
"query": null,
"datasource": null,
"team": null,
"user": null,
"job": null,
"template": null,
"assistant": null,
"library": null,
"letterhead": null,
"code": null,
"_embedded": {
"inf:bundle-driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/bundle-drivers/tagged"
}
},
"id": "tagged",
"name": "Tag",
"icon": "fas fa-tag",
"editor": {
"taggedBundleEntry": {}
}
},
"inf:tag": {
"_links": {
"self": {
"href": "https://informer.example.com/api/tags/00000000-0000-4000-8000-000000000003"
}
},
"permissions": {
"edit": true,
"delete": true,
"write": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000003",
"name": "Order Desk"
}
}
}
]
}
}
POST /api/bundles/{id}/entries
Add an entry to a Bundle.
Authentication: permission.bundle.write (Superuser)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | A registered bundle-driver id (a model validator throws otherwise) |
<type>Id | string | Yes | The id of the referenced entity, in the matching column (e.g. tagId for tagged, reportId for report) |
data | object | No | Per-driver options JSON (defaults to {}) |
bundleId is taken from the URL; the rest of the payload is unvalidated by Joi.
The <type>Id column must match the type: tagged uses tagId, report
uses reportId, dataset uses datasetId, and so on.
Example Request:
{ "type": "tagged", "tagId": "00000000-0000-4000-8000-000000000002" }
Response:
Responds 201 Created with the entry and a Location header.
{
"type": "tagged",
"tagId": "00000000-0000-4000-8000-000000000002"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000003"
}
},
"id": "00000000-0000-4000-8000-000000000003",
"data": {},
"tenant": "acme",
"type": "tagged",
"tagId": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"datasourceId": null,
"queryId": null,
"datasetId": null,
"reportId": null,
"username": null,
"teamId": null,
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null
}
A job entry whose Job is a system job is rejected with 400 ("System jobs
cannot be added to bundles").
GET /api/bundles/{id}/entries/{entry}
Retrieve a single entry.
Authentication: Required (read-only, no permission check)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
entry | string | Entry UUID |
Response:
Resolves the Bundle, then looks up the entry by its bundleId and id. A 404
is returned when either the Bundle or the entry is missing. The body adds an
inf:bundle-driver link for the entry's type.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000002"
},
"inf:bundle-driver": {
"href": "https://informer.example.com/api/bundle-drivers/tagged"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
PUT /api/bundles/{id}/entries/{entry}
Update an entry.
Authentication: permission.bundle.write (Superuser)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
entry | string | Entry UUID |
Request Body (partial):
| Field | Type | Description |
|---|---|---|
data | object | Per-driver options JSON (e.g. inclusion toggles the export driver reads) |
This route is unvalidated. The update is scoped to the entry's bundleId and
id.
Example Request:
{ "data": { "includeChildren": true } }
Response:
Responds 200 with the updated entry.
{
"data": {
"includeChildren": true
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/bundles/00000000-0000-4000-8000-000000000001/entries/00000000-0000-4000-8000-000000000002"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"bundleId": "00000000-0000-4000-8000-000000000001",
"type": "tagged",
"data": {
"includeChildren": true
},
"datasourceId": null,
"datasetId": null,
"reportId": null,
"username": null,
"queryId": null,
"teamId": null,
"tagId": "00000000-0000-4000-8000-000000000003",
"jobId": null,
"templateId": null,
"letterheadId": null,
"assistantId": null,
"libraryId": null,
"codeId": null,
"appId": null,
"toolkitId": null,
"integrationId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
DELETE /api/bundles/{id}/entries/{entry}
Delete an entry from a Bundle.
Authentication: permission.bundle.write (Superuser)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Bundle UUID or slug |
entry | string | Entry UUID |
Response:
Responds 200 with an empty body (Informer's db.remove convention), not
204. The referenced entity (for example the Tag) is left untouched; only the
manifest row is removed.
Export, deploy, import, and restore
The following routes either stream binary archives, upload files, or replace tenant data destructively, so they are documented here without captured examples. Every one of them requires Superuser.
These endpoints move .tgz archives or mutate tenant data in place. Capturing
them would either embed binary payloads or corrupt the capture tenant, so no
<ApiExample> fixture exists for them. The shapes below come from the route
definitions.
POST /api/bundles/{id}/_export
Export an existing Bundle to a .tgz archive.
Authentication: permission.bundle.write (Superuser)
Response: Streams a .tgz download Resource (binary). This is the target of
the inf:download link on a Bundle.
POST /api/bundles/{id}/_deploy
Export an existing Bundle and import it into one or more target tenants.
Authentication: permission.bundle.write (Superuser)
Response: No JSON fixture. This is the target of the inf:deploy link on a
Bundle.
Deploy exports the Bundle and then imports it into the target tenants, replacing their data. Run it only against tenants you intend to overwrite.
POST /api/_import-bundle
Import a previously exported Bundle archive.
Authentication: Superuser
Request Body: A multipart or streamed file upload of a .tgz archive.
Response: No JSON fixture (file upload).
POST /api/_bundle
Export entities to a .tgz archive in one step, without first persisting a
Bundle row.
Authentication: Superuser
Response: Streams a .tgz download (binary).
POST /api/_bundle-deploy
Export entities and deploy them in one step.
Authentication: Superuser
Response: No JSON fixture.
This combines an instant export with a deploy into target tenants, replacing their data.
POST /api/_restore
Import an archive with restore=true, replacing tenant data.
Authentication: Superuser
Response: No JSON fixture.
Restore replaces the tenant's existing data with the archive contents. It is not captured because it would corrupt the capture tenant.
POST /api/_restore-from-file
Same as _restore, but the archive comes from an uploaded file.
Authentication: Superuser
Request Body: A multipart or streamed file upload of a .tgz archive.
Response: No JSON fixture.
This restores from an uploaded archive and replaces tenant data.