Other endpoints
Beyond the object surface, the engine exposes these controller areas. Each base route hosts the operations for one feature; follow the linked guide for behaviour and payloads.
Identity & access
Section titled “Identity & access”| Base route | Controller | Covers |
|---|---|---|
/api/v1/auth |
AuthController |
Login, refresh, logout, MFA challenge/verify, password reset. |
/api/v1/genie/auth |
AuthorizationBoardController |
The Access Dashboard: statistics, the access workspace (/board/principals for the rail and scope options, /board/matrix for one principal’s access, /board/apply for a batch of toggles), the access-explorer trees, and RBAC import/export. /board/matrix accepts a Scope of All/Global/Company that only ever narrows the caller’s own scope. Open to System, Admin and AccessManager; import/export is System-only. |
/api/v1/genie/company |
CompanyController |
Tenant/company listing & switching. |
/api/v1/impersonation |
ImpersonationController |
Start impersonating a user (System role) and stop again (any authenticated session — it runs as the impersonated user). |
/api/v1/genie/timezone |
TimeZoneController |
Get/set the user’s IANA time zone. |
See Identity, RBAC & permissions and Multi-tenant company scoping.
Navigation, search & content
Section titled “Navigation, search & content”| Base route | Controller | Covers |
|---|---|---|
/api/v1/genie/navbar |
NavbarController |
The permission-filtered navigation tree (badge-free, returns fast). |
/api/v1/genie/navbar/badges |
NavbarController |
Computed badge values (counts / dots) keyed by item name, fetched separately so the tree renders first. |
/api/v1/genie/search |
SearchController |
Global search (SQL or Meilisearch provider). |
/report |
ReportController |
Code-defined reports: GET /report lists them, GET /report/{key} generates one (?inline=true to preview rather than download). A root route, not under /api/v1/genie. |
/files/{path} |
FileController |
Streams a stored file (attachment/export/temp), inline where the type allows. Also a root route. |
See Navigation, Search and Reports.
Notifications
Section titled “Notifications”| Base route | Controller | Covers |
|---|---|---|
/api/v1/notification |
NotificationController |
A user’s notifications. |
/api/v1/notifications |
NotificationApiController |
Notification API surface. |
/api/v1/push-subscription |
PushSubscriptionController |
Web-push subscription management. |
Real-time delivery is over SignalR — see Notifications.
Wizards & workflows
Section titled “Wizards & workflows”| Base route | Controller | Covers |
|---|---|---|
/api/v1/genie/wizard |
WizardController |
Wizard execution and form generation. |
/api/v1/genie/workflow |
WorkflowDesignerController |
Workflow definitions, instances and the designer. |
Report pages
Section titled “Report pages”Two calls, mirroring the object endpoints’ structure/data split: structure is SQL-free and fetched once, data is gated and re-fetched per filter change.
| Method & route | Returns | Gate |
|---|---|---|
GET /api/v1/genie/reports/{key} |
The report’s filters, widgets, layout and dataset names/modes. Every [SqlBody] property is stripped, so no query text leaves the server. |
Authentication only |
POST /api/v1/genie/reports/{key}/data |
Every dataset a visible widget reads, executed once each. | List on the report’s RBAC resource |
{key} is the report’s Name or Slug. Plural /reports — distinct from the singular
/report/{key} at the API root, which belongs to the unrelated code-defined
PDF feature.
// POST /api/v1/genie/reports/inventory-overview/data{ "Arguments": { "FltWarehouse": "3", "FltRangeFrom": "2026-08-27", "FltRangeTo": "2026-09-09" }}// → SuccessDataResult<ReportDataResult>{ "success": true, "data": { "Name": "InventoryOverview", "RequiredFilterMissing": false, "DataSets": { "DsKpis": { "Columns": [{ "Name": "StockValue", "ClrType": "Decimal" }], "Rows": [{ "StockValue": "1284750.00" }] } }, "AccessibleWidgets": ["TlStockValue", "ChCategoryValue", "VwLowStock"] }}Three things to note about the response:
RequiredFilterMissing: truemeans no dataset ran at all. A blank required filter gates the whole page;DataSetsis empty and the client shows a prompt.AccessibleWidgetsis authoritative. A widget withheld byRolesAllowedis absent, and so is any dataset only it read — the client closes the layout over it.- Every cell is a string or null, numbers included, matching the grid. Client code must parse and
guard
NaN.
An embedded <View> widget is not served by these endpoints: it fetches through the ordinary
object endpoints under its own resource, so embedding never launders
a permission.
See Report pages and Runtime & sources.
Report designer
Section titled “Report designer”Ten more calls, behind ReportDesignerController. Separate from the two above on purpose: those
run a report for a viewer and are gated on the report’s own resource, these edit the document
and are gated on the definitions grid’s resource — merging them would put a viewer’s read path and an
author’s write path behind one permission.
| Method & route | Returns | Gate |
|---|---|---|
GET /api/v1/genie/report-designer/definitions/{key} |
The definition’s identity plus its source XML. DefinitionXml is null for a row stored before the XML was retained. |
View on ReportDefinitions |
POST /api/v1/genie/report-designer/definitions/save |
Upserts by the document’s own Name, so one call both creates and updates. A parse error is a 4xx carrying the parser’s message. |
Update on ReportDefinitions |
GET …/definitions/{key}/versions |
Every version, newest first, with the current one flagged. | View on ReportDefinitions |
GET …/definitions/{key}/versions/{versionHash} |
The document as that version saved it. | View on ReportDefinitions |
The draft lifecycle. {key} addresses the stored report by Name or Slug — never the draft
document’s own name, which the author may have changed:
| Method & route | Returns | Gate |
|---|---|---|
GET …/definitions/{key}/draft |
The report’s draft, or null. Includes IsStale, true when the published document moved since the draft forked. |
View on ReportDefinitions |
POST …/definitions/{key}/draft |
Creates or replaces the draft. Not versioned, and not parsed — a draft is allowed to hold text the parser would reject. | Update on ReportDefinitions |
DELETE …/definitions/{key}/draft |
Discards the draft. false when there was none — idempotent, so a stale grid’s second click is not a 404. |
Update on ReportDefinitions |
POST …/definitions/{key}/publish |
Promotes the draft: appends a version, repoints the report, clears the draft. Unchanged: true when the draft matched what was live (nothing appended, draft still cleared). A 4xx when the draft will not parse, or when publishing would rename the report onto a name another one holds. |
Update on ReportDefinitions |
GET …/definitions/{key}/draft/structure |
The draft’s structure, SQL-free. | Update on ReportDefinitions |
POST …/definitions/{key}/draft/data |
The draft’s datasets. | Update on ReportDefinitions |
The last two are Update, not View, and they match each other deliberately. Running an unpublished
document is more than reading one; and giving structure the weaker gate would let a client render a
layout it can never fill. They exist as their own endpoints rather than a ?draft=1 flag on
GET /reports/{key} because that call is ungated beyond authentication — right for a published
layout, a disclosure for an author’s work in progress.
Row-level security in a draft preview resolves against the stored report’s resource name, so renaming a draft is not a way out of a row filter.
// POST /api/v1/genie/report-designer/definitions/save{ "DefinitionXml": "<Report Name=\"InventoryOverview\" Slug=\"inventory-overview\"> … </Report>", "Notes": "Added the supplier-mix chart"}// → SuccessDataResult<ReportSaveResult>{ "success": true, "data": { "Name": "InventoryOverview", "Slug": "inventory-overview", "Created": false, // True when the incoming text matched what was stored: nothing written, no version appended. "Unchanged": false, "VersionHash": "4f9c2a" }}Restoring a version is a save with the text …/versions/{versionHash} returns — it appends a new
version rather than rewriting history, which is why there is no restore endpoint. See
The report designer.
Files, jobs & scripting
Section titled “Files, jobs & scripting”| Base route | Controller | Covers |
|---|---|---|
/api/v1/genie/hosted-services |
HostedServicesController |
Start/stop/inspect background hosted services. |
/api/v1/hangfire |
HangfireController |
Hangfire job info. |
/api/v1/genie/execute-script |
ExecuteScriptController |
Execute a registered server script. |
/api/v1/genie/object/list, /api/v1/genie/scripts |
ModelCatalogController |
Read the model catalog (see below). |
/api/v1/genie/handlers |
GenieHandlersController |
Pluggable export/import handlers. |
| (files) | FileController |
File download/serving. |
The model catalog (System role)
Section titled “The model catalog (System role)”The read counterpart of execute-script — deploy writes the catalog, these read it back. All three
are gated to the System role, because script bodies contain the authored SQL that the metadata
endpoints deliberately never expose:
| Endpoint | Returns |
|---|---|
GET /api/v1/genie/object/list |
Every deployed object (ViewStore): Name, Slug, Label, ViewType, VersionHash. |
GET /api/v1/genie/scripts?type= |
Executed scripts, latest per name, optionally filtered by type (ObjectView/Entity/Navbar/StoredProcedure/Rbac). |
GET /api/v1/genie/scripts/{name} |
One script’s latest execution including its verbatim authored body (matched by Name or FileName). |
This is what lets an external client (GenieClient CLI/MCP) enumerate a deployment with no local
checkout, export the deployed XML, and diff a local model file against the source that is actually
live (VersionHash is the MD5 of the deployed body, so an identical hash ends the question).
See Background jobs & hosted services.
The AI assistant
Section titled “The AI assistant”The assistant chat is delivered over a SignalR hub rather than a REST controller, with a supporting natural-language-query endpoint. See AI Assistant.