Appearance
menu
6 endpoints.
GET /api/v1/menu
Get Menu
The menu tree visible to the current user on the active site.
200 — Successful Response
json
{
"items": {
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"title": {
"type": "string",
"title": "Title"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Url"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"app_slug": {
"type": "string",
"title": "App Slug"
},
"min_role": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Min Role"
},
"sort_order": {
"type": "integer",
"title": "Sort Order"
},
"colour": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Colour"
},
"children": {
"items": {
"$ref": "#/components/schemas/MenuItemOut"
},
"type": "array",
"title": "Children"
}
},
"type": "object",
"required": [
"id",
"title",
"app_slug",
"sort_order"
],
"title": "MenuItemOut"
},
"type": "array",
"title": "Response Get Menu Api V1 Menu Get"
}POST /api/v1/menu
Create Menu Item
Request body
json
{
"properties": {
"parent_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Parent Id"
},
"title": {
"type": "string",
"title": "Title"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Url"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"app_slug": {
"type": "string",
"title": "App Slug",
"default": "system"
},
"min_role": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Min Role"
},
"sort_order": {
"type": "integer",
"title": "Sort Order",
"default": 0
},
"active": {
"type": "boolean",
"title": "Active",
"default": true
}
},
"type": "object",
"required": [
"title"
],
"title": "MenuItemIn"
}201 — Successful Response
json
{
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"title": {
"type": "string",
"title": "Title"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Url"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"app_slug": {
"type": "string",
"title": "App Slug"
},
"min_role": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Min Role"
},
"sort_order": {
"type": "integer",
"title": "Sort Order"
},
"colour": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Colour"
},
"children": {
"items": {
"$ref": "#/components/schemas/MenuItemOut"
},
"type": "array",
"title": "Children"
}
},
"type": "object",
"required": [
"id",
"title",
"app_slug",
"sort_order"
],
"title": "MenuItemOut"
}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}PUT /api/v1/menu/{item_id}
Update Menu Item
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
item_id | path | yes | integer |
Request body
json
{
"properties": {
"parent_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Parent Id"
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Title"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Url"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"app_slug": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "App Slug"
},
"min_role": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Min Role"
},
"sort_order": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Sort Order"
},
"active": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Active"
}
},
"type": "object",
"title": "MenuItemPatch"
}200 — Successful Response
json
{
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"title": {
"type": "string",
"title": "Title"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Url"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"app_slug": {
"type": "string",
"title": "App Slug"
},
"min_role": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Min Role"
},
"sort_order": {
"type": "integer",
"title": "Sort Order"
},
"colour": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Colour"
},
"children": {
"items": {
"$ref": "#/components/schemas/MenuItemOut"
},
"type": "array",
"title": "Children"
}
},
"type": "object",
"required": [
"id",
"title",
"app_slug",
"sort_order"
],
"title": "MenuItemOut"
}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}DELETE /api/v1/menu/{item_id}
Delete Menu Item
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
item_id | path | yes | integer |
200 — Successful Response
json
{
"type": "object",
"additionalProperties": true,
"title": "Response Delete Menu Item Api V1 Menu Item Id Delete"
}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/menu/reorder
Reorder Menu
Persist a new top-to-bottom order for a parent's children by rewriting sort_order. Reorders siblings within that parent only — the top-level groups (apps) themselves are never touched here; their order comes from the Apps Library (POST /apps/reorder).
The client sends only the ids it can see (visibility is gated by installed apps + role). We renumber the parent's WHOLE sibling set so those submitted ids lead in the given order, and any sibling the client couldn't see keeps its relative position right after — so reordering a partial view never makes two siblings collide on the same sort_order or silently shuffles a hidden group into the middle of the visible ones.
The menu is one platform-wide structure, so the same order goes everywhere. Until 2026-08-25 this handler walked site_registry.list_sites() itself and wrote each site in turn, which reached the sites on THIS box and nothing else — portal.telecloud.co.za, portal.lubb.co.za and the rest kept whatever order they had. It now replays the edit onto the hub and hands off to app.services.hub_sync, which converges the whole estate, peer boxes included, and retries anything that was unreachable. See that package's docstring for why the hub is written first rather than the sites directly.
Request body
json
{
"properties": {
"parent_id": {
"type": "integer",
"title": "Parent Id"
},
"ordered_ids": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Ordered Ids"
},
"ordered_app_slugs": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Ordered App Slugs"
}
},
"type": "object",
"required": [
"parent_id"
],
"title": "ReorderIn"
}200 — Successful Response
json
{
"additionalProperties": true,
"type": "object",
"title": "Response Reorder Menu Api V1 Menu Reorder Post"
}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/menu/reset
Reset Menu
Wipe the active site's menu and rebuild the canonical defaults.
200 — Successful Response
json
{
"additionalProperties": true,
"type": "object",
"title": "Response Reset Menu Api V1 Menu Reset Post"
}