Skip to main content

Room Panels

Two APIs managing the panels shown in a room: api/teamroom for Team Rooms and api/planningroom for Planning Rooms. They exist to let panels be provisioned programmatically — spinning up rooms at scale — and are a separate surface from the wiring the product UI itself uses.

Every route on both requires authentication, a roomContext parameter, and the EditRoom right — including the read routes.

warning
roomContext is mandatory everywhere, and the failure is unhelpful

Omit it and neither API says "missing roomContext". api/teamroom routes return 400 with {"Exceptions": ["Bad Request", "Reference: <guid>"]} — the GUID is an error-report reference, not data. api/planningroom routes return a bare 404, because the action's required parameter cannot bind. Both mean the same thing: supply roomContext=<TeamRoom:id or PlanningRoom:id>.

This applies to api/teamroom/panels as well, even though the list it returns is not room-specific — the parameter is demanded before the action runs.

They Are Not Symmetric​

The two are close enough to assume they are the same API with a different prefix. They are not.

api/teamroomapi/planningroom
Available panelspanels?roomContext=… — list is not room-specificpanels?roomContext=… — per-room flags
Installed panelsinstalledpanels?roomContext=…installedpanels?roomContext=…
Installinstallpanel — no bodyinstallpanel — optional configuration body
Remove identifiertype (string)panelId (GUID)
Update— not availableupdatepanel

The differences that break a client assuming symmetry:

  1. Removal identifiers differ. Team Room removes by panel type; Planning Room removes by the id GUID returned when the panel was installed. A Team Room holds at most one panel of a given type, while a Planning Room addresses each instance individually (and can hold several of the same type — see canInstallAnother).
  2. Only Planning Room panels carry configuration. Team Room panels have none to update — which is why its install takes no body and there is no updatepanel.

api/teamroom​

GET ~/api/teamroom/panels?roomContext=<TeamRoom:id>
GET ~/api/teamroom/installedpanels?roomContext=<TeamRoom:id>
POST ~/api/teamroom/installpanel?roomContext=<TeamRoom:id>&type=<panelType>
POST ~/api/teamroom/removepanel?roomContext=<TeamRoom:id>&type=<panelType>

Available and installed panels return the same shape; type is the value installpanel and removepanel take:

GET panels — response (abridged)
[
{
"type": "Backlog",
"name": "Backlog",
"description": "Planning and prioritization of your agile Backlog in a list view.",
"isRemovable": false
}
]

isRemovable: false marks the panels every Team Room carries; removepanel is for the removable ones. installpanel returns 201 Created with the single installed panel object (not the full list). removepanel returns 204 No Content.

api/planningroom​

GET ~/api/planningroom/panels?roomContext=<PlanningRoom:id>
GET ~/api/planningroom/installedpanels?roomContext=<PlanningRoom:id>
POST ~/api/planningroom/installpanel?roomContext=<PlanningRoom:id>&type=<panelType>
POST ~/api/planningroom/removepanel?roomContext=<PlanningRoom:id>&panelId=<guid>
POST ~/api/planningroom/updatepanel?roomContext=<PlanningRoom:id>&panelId=<guid>
RouteSuccess status
installpanel201 Created
updatepanel201 Created
removepanel204 No Content
note
updatepanel returns 201, not 200

Updating an existing panel answers 201 Created rather than 200 OK. That is unusual — 201 normally signals that a new resource was created — and a client that treats 201 as "a new panel now exists" will double-count. Read the response body rather than inferring from the status.

Listing Panels​

panels describes what could be installed, with per-room flags:

GET panels — response (abridged)
[
{
"name": "EpicBoard",
"type": "epic-board",
"description": "The panel displays a Portfolio Kanban configurable by panel-specific settings.",
"canConfigure": true,
"canInstallAnother": true,
"alreadyInstalled": true
}
]

installedpanels describes the instances actually in the room, each with its id GUID — the identifier updatepanel and removepanel take — and its configuration:

GET installedpanels — response (abridged)
[
{
"id": "c2a77134-63ab-4282-85ca-94ebe2aa013d",
"title": "Epicboard 2",
"type": "epic-board",
"description": "The panel displays a Portfolio Kanban configurable by panel-specific settings.",
"canInstallAnother": true,
"canConfigure": true,
"useRoomPerspective": false,
"perspective": {
"project": "Scope:1068",
"program": "ScopeLabel:1069",
"schedule": "Schedule:1064"
},
"isSingleLevel": false,
"epicType": null,
"url": null
}
]

The Panel Configuration Payload​

installpanel and updatepanel accept a JSON body. On install it is optional — omit it and the panel takes the room's defaults; supply it and the configuration is validated and applied in the same request.

{
"title": "Epicboard 2",
"perspective": {
"project": "Scope:1068",
"program": "ScopeLabel:1069",
"schedule": "Schedule:1064"
},
"epicType": "EpicCategory:246",
"isSingleLevel": false,
"useRoomDefaults": false,
"url": ""
}
FieldMeaning
titleDisplay title for this panel instance
perspectiveThe panel's own view slice — project (a Scope OID), and optionally program (a ScopeLabel OID) and schedule (a Schedule OID). This is how a panel looks at a different project, program or schedule than the room it lives in
useRoomDefaultsBoolean; use the room's perspective instead of a panel-specific one. Defaults to false. Note the asymmetry: the request key is useRoomDefaults; responses report it as useRoomPerspective
isSingleLevelBoolean; when a panel-specific perspective is used, exclude items belonging to child projects of the perspective's project. Defaults to false
epicTypeOptional EpicCategory OID narrowing Portfolio Items to one type; all types when omitted
urlWeb panels only — the URL the panel shows

The perspective is stored as room configuration, not as asset attributes, so this page is its schema: none of these fields are discoverable through meta.v1.

Everything Is a POST​

All mutating routes are POST, including removal. There is no DELETE and no PUT. This matches api/webhook, which also deletes via POST.