---
url: /intents-doc.md
---
# Intents Reference

> Auto-generated from application and service manifests.\
> Generated on: 2026-10-29

## Table of Contents

### Applications

* [Auth (`auth`)](#auth-auth)
* [Calendar (`events`)](#calendar-events)
* [Calls (`videochat`)](#calls-videochat)
* [Chats (`textchat`)](#chats-textchat)
* [Contacts (`contacts`)](#contacts-contacts)
* [Content Studio (`content-studio`)](#content-studio-content-studio)
* [Content Studio (`staging-content-studio`)](#content-studio-staging-content-studio)
* [Groups (`groups`)](#groups-groups)
* [LLM Debug (`iframe_llm_debug`)](#llm-debug-iframe-llm-debug)
* [Media (`cms`)](#media-cms)
* [Merchant Console (`merchant`)](#merchant-console-merchant)
* [Metaverse (`metaverse`)](#metaverse-metaverse)
* [Metaverse 2D (`hubs`)](#metaverse-2d-hubs)
* [My Cart (`cart`)](#my-cart-cart)
* [Profile (`profile`)](#profile-profile)
* [Resources Viewer (`preview`)](#resources-viewer-preview)
* [Rooms (`rooms`)](#rooms-rooms)
* [Social (`community`)](#social-community)
* [User Card (`user-card`)](#user-card-user-card)
* [Valu Games (`games`)](#valu-games-games)
* [Valu Guru (`valuguru`)](#valu-guru-valuguru)
* [Valu Puzzles (`puzzles`)](#valu-puzzles-puzzles)

### Services

* [Application Service (`Application`)](#application-service-application)
* [Application Storage Service (`ApplicationStorage`)](#application-storage-service-applicationstorage)
* [CBAC Service (`Cbac`)](#cbac-service-cbac)
* [CMS Service (`CMS`)](#cms-service-cms)
* [Commerce (`Commerce`)](#commerce-commerce)
* [Community Service (`Community`)](#community-service-community)
* [Data Provider Service (`DataProvider`)](#data-provider-service-dataprovider)
* [Developer Service (`Developer`)](#developer-service-developer)
* [Events Service (`Events`)](#events-service-events)
* [Groups Service (`Groups`)](#groups-service-groups)
* [HTTP Service (`Http`)](#http-service-http)
* [Logging Service (`Logging`)](#logging-service-logging)
* [Networks Service (`Networks`)](#networks-service-networks)
* [Profile Service (`Profile`)](#profile-service-profile)
* [Resource Service (`Resources`)](#resource-service-resources)
* [Room Points Service (`RoomPoints`)](#room-points-service-roompoints)
* [Room Zones Service (`RoomZones`)](#room-zones-service-roomzones)
* [Rooms Service (`Rooms`)](#rooms-service-rooms)
* [Text Chat Service (`TextChat`)](#text-chat-service-textchat)
* [Time Service (`Time`)](#time-service-time)
* [Users Service (`Users`)](#users-service-users)
* [Valu Guru Service (`AiGuru`)](#valu-guru-service-aiguru)
* [Verus Wallet Service (`VerusWallet`)](#verus-wallet-service-veruswallet)

***

## Application Intents

### Auth (`auth`)

User login flow

#### `logout`

Logout from system

**Availability:** Developer

*No parameters.*

***

### Calendar (`events`)

#### `open-calendar`

Opens the calendar view, optionally filtered by a specific source type (all, room, group, user, or community event).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `source` | string | Yes | The source type for the calendar filter. Must be one of: all, room, group, user, community. Use "all" to show meetings from every source type. |
| `id` | string | No | The unique identifier of the room, group, or user. |
| `fullName` | string | No | The display name of the room, group, or user. |
| `communityId` | string | No | The unique identifier of the community (used when source is community). |
| `communityFullName` | string | No | The display name of the community. |
| `eventId` | string | No | The unique identifier of the community event. |
| `eventFullName` | string | No | The display name of the community event. |

***

### Calls (`videochat`)

#### `connect-to-meeting`

Connects to a specific meeting by room, prop, group, or user and manages video chat and presentation settings.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | No | The unique identifier of the room to connect to. |
| `propId` | string | No | The unique identifier of the prop to connect to. |
| `groupId` | string | No | The unique identifier of the group to connect to. |
| `userId` | string | No | The unique identifier of the user to connect to. |
| `withLocalTracks` | boolean | No | If true, view shared screens or presenter content without joining the video chat. |
| `autoConnectToVideochat` | boolean | No | If true, automatically join the video chat after connecting. |

#### `leave-meeting`

Leaves the current call only when it is the given prop's call (roomId + propId); otherwise does nothing. Returns {left: boolean}. Used by Metaverse 2D video chat zones.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room of the prop whose call to leave. |
| `propId` | string | Yes | The prop whose call to leave; any other call is left as it is. |

#### `open-source-picker`

Opens the "Open Videochat from" source picker (Rooms, Groups, Contacts, Textchats) as the inline full-view list. Selecting an item starts a video chat with that source.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `standalone` | boolean | No | If true, render the picker as a standalone window with no back button (e.g. when launched from the native app, where there is no video chat to return to). |

***

### Chats (`textchat`)

#### `open-channel`

Opens a text chat session based on channel, user, group, room, or prop.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `channelId` | string | No | The unique identifier of the text channel to open. |
| `userId` | string | No | The unique identifier of the user for a direct chat. |
| `groupId` | string | No | The unique identifier of the group to open a group chat. |
| `roomId` | string | No | The unique identifier of the room to open a room chat. |
| `propId` | string | No | The unique identifier of the prop to open a prop-specific chat. |

#### `close-channel`

Closes the currently open channel and returns to the channel list. Mirrors the toolbar back button used on mobile/narrow layouts.

**Availability:** AI, Developer

*No parameters.*

#### `share-card`

Shares one or more entities into a chat as rich, interactive cards — Rooms, Groups, Communities, Meetings, or Contacts — instead of plain links. Pass "id" for a single card or "ids" for several in one message. All cards in one call must be the SAME cardType (mixed types need separate calls). Posts into the currently open channel by default; pass channelId to target a specific channel.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `cardType` | string | Yes | The entity type to share: "room", "group", "community", "meeting", or "contact". Applies to every id in this call. |
| `id` | string | No | A single entity id to share (room/group/community id, user id for a contact, or — for meetings — the occurrence id from list-events; a one-off meeting id also works). Use this OR "ids". |
| `ids` | string\[] | No | Several entity ids to share as multiple cards in one message — all of the given cardType (for meetings, occurrence ids from list-events). Use this OR "id". |
| `channelId` | string | No | Target channel id. Defaults to the currently active channel. |
| `networkId` | string | No | The network the entities belong to. Defaults to the active network. |

#### `get-ui-state`

Read-only. Returns a snapshot of what the chat is currently showing — the channels list, an open channel, or both side by side (wide layout) — plus the in-app back action. Lets non-web clients (e.g. the Android device back button) mirror the toolbar back-button navigation. Changes nothing. Returns { view: "list" | "channel" | "list-and-channel", isWide, activeChannelId, openThreadId, threadTakeover, canNavigateBack, backAction: "close-thread" | "close-channel" | null }.

**Availability:** AI, Developer

*No parameters.*

#### `options`

Sets the text‑chat view layout (e.g. wide or narrow mode).

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `isWide` | boolean | Yes | Whether to enable wide‑mode layout in the chat view. |

#### `open-room-ai-chat`

Opens an AI‑powered chat session scoped to a specific room.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique identifier of the room in which to start the AI chat. |

#### `send-message`

Sends a rich text message to a text-chat channel on behalf of an initiator (e.g. an AI agent or automated service). Silent — does NOT open or change the visible chat. Use channelId when you know it; otherwise provide userId for a direct message.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `text` | string | Yes | The message body to send. |
| `channelId` | string | No | Target channel ID. Takes precedence over userId. |
| `userId` | string | No | Target user ID for a direct message. Ignored if channelId is provided. |
| `buttons` | object\[] | No | Optional interactive buttons to attach to the message as a rich card. |

***

### Contacts (`contacts`)

Manage and chat with your personal connections.

#### `show-user-connections`

Open the connections view for the specified user

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user to display |

***

### Content Studio (`content-studio`)

#### `open-create`

Open the content upload page with an optional upload destination

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | No | ID of the room to upload to |
| `communityId` | string | No | ID of the community to upload to |
| `channelId` | string | No | ID of the channel to upload to |
| `groupId` | string | No | ID of the group to upload to |
| `label` | string | No | Label to apply to the content |
| `department` | string | No | Department associated with the content |
| `theme` | string | No | Theme to apply to the upload page |

#### `open-edit`

Open the edit page for a specific content item

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `contentItemId` | string | Yes | ID of the content item to edit |
| `roomId` | string | No | ID of the room context |
| `communityId` | string | No | ID of the community context |
| `channelId` | string | No | ID of the channel context |
| `groupId` | string | No | ID of the group context |
| `label` | string | No | Label to apply to the content |
| `department` | string | No | Department associated with the content |
| `theme` | string | No | Theme to apply to the edit page |

#### `open-search`

Open the search page

**Availability:** none (platform-internal)

*No parameters.*

#### `navigate`

Navigate to a specific route within Content Studio

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `route` | string | Yes | The route path to navigate to within Content Studio |

***

### Content Studio (`staging-content-studio`)

#### `open-create`

Open the content upload page with an optional upload destination

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | No | ID of the room to upload to |
| `communityId` | string | No | ID of the community to upload to |
| `channelId` | string | No | ID of the channel to upload to |
| `groupId` | string | No | ID of the group to upload to |
| `label` | string | No | Label to apply to the content |
| `department` | string | No | Department associated with the content |
| `theme` | string | No | Theme to apply to the upload page |

#### `open-edit`

Open the edit page for a specific content item

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `contentItemId` | string | Yes | ID of the content item to edit |
| `roomId` | string | No | ID of the room context |
| `communityId` | string | No | ID of the community context |
| `channelId` | string | No | ID of the channel context |
| `groupId` | string | No | ID of the group context |
| `label` | string | No | Label to apply to the content |
| `department` | string | No | Department associated with the content |
| `theme` | string | No | Theme to apply to the edit page |

#### `open-search`

Open the search page

**Availability:** none (platform-internal)

*No parameters.*

#### `navigate`

Navigate to a specific route within Content Studio

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `route` | string | Yes | The route path to navigate to within Content Studio |

***

### Groups (`groups`)

#### `open`

Opens the groups view and navigates to a specific group.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `groupId` | string | Yes | The unique identifier of the group to open. |

***

### LLM Debug (`iframe_llm_debug`)

Live LLM / agent activity debug panel (your own agents)

#### `open-session`

Opens the LLM Debug panel and navigates to a specific debug session (chat, agent, or sub-agent) by its session ID.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sessionId` | string | Yes | The LLM debug session ID to open. |

#### `open-agent`

Opens the LLM Debug panel and navigates to a specific agent's page by its agent ID (its persistent session). Pass `sessionId` to target one of the agent's runs instead.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | The agent ID (its persistent agent session ID) to open. |
| `sessionId` | string | No | A specific session of the agent to open instead of its main page. |

#### `open-run`

Opens the LLM Debug panel on a specific agent/chatbot RUN by its run id. Pass the entity kind + id and the run id from the originating message.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `entityKind` | string | Yes | The entity kind: "agent" or "chatbot". |
| `entityId` | string | Yes | The agent or chatbot id the run belongs to. |
| `runId` | string | Yes | The run id (agentRunId/chatbotRunId) to open. |

***

### Media (`cms`)

Content management module

#### `show-chat-channel`

Open and display the specified chat channel

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `channelId` | string | Yes | The unique identifier of the chat channel to display |

#### `show-folder`

Navigate to and open a specific folder in the CMS by its folder ID

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `folderId` | string | Yes | The unique identifier of the folder to open |
| `resourceId` | string | No | A resource inside that folder to open in the Edit side panel after navigating. |

#### `show-resource`

Reveal a resource in the CMS file browser by its resource ID — navigates to the folder holding it (or to its section root when it is not in a folder), selects it and opens its Edit side panel. Use this when you only have a resource id and want to show the user where that file lives; use preview-resource instead to just view its contents.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource to reveal |

#### `preview-resource`

Open a resource (file) in the CMS preview viewer by its resource ID

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource to preview |

#### `show-my-files`

Navigate to the user's own files in the CMS (the "My Files" / "My Content" view). Use this when the user asks about their files, uploads, or personal content — do not use show-folder for this.

**Availability:** AI, Developer

*No parameters.*

#### `show-application-storage`

Open an application's storage folder in the CMS — the files that app has stored under the current user, shown at Applications › `<app>`. Provide the application id (e.g. "valuguru"). The app must declare `storage: true` in its manifest.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `applicationId` | string | Yes | The id of the application whose storage folder to open (e.g. "valuguru"). |
| `resourceId` | string | No | A resource inside that storage folder to open in the Edit side panel after navigating. |

#### `show-community-channel`

Open and display the specified community channel

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The unique identifier of the community |
| `communityChannelId` | string | Yes | The unique identifier of the channel within the community |

***

### Merchant Console (`merchant`)

Sell in Valu: list products and bundles, choose where they sell, and moderate a network's commerce

#### `open-section`

Open the Merchant Console on one of its tabs. The tab becomes part of the address, so the place it opens on can be linked to and comes back on a reload. Home is the setup checklist until the store can sell, then the overview. Promotions, Analytics and Store need a store; Moderation is for network admins. Bundles opens Products filtered to bundles; the old names overview and api-access still work (Home, Store).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `section` | string | Yes | Which tab to open: home, products, bundles, promotions, analytics, store or moderation. |
| `popup` | boolean | No | true draws the product list over the pane of the app that asked instead of docking the console, so the app the seller came from stays visible behind it. Products is the only tab with a popup form; any other section docks the console as usual. Where the caller is not on screen the popup falls back to a window over the whole page. |

#### `open-products`

Open the seller's product list. With `popup` it is drawn over the calling app instead of docking the console.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `popup` | boolean | No | true draws the product list over the pane of the app that asked instead of docking the console, so the app the seller came from stays visible behind it. Products is the only tab with a popup form; any other section docks the console as usual. Where the caller is not on screen the popup falls back to a window over the whole page. |

#### `open-product`

Open one of the seller's own products for editing.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product to open |

#### `open-moderation`

Open the moderation tab for the current network: who may sell here, what content is allowed, and the sellers waiting for a decision. Network admins only.

**Availability:** Developer

*No parameters.*

***

### Metaverse (`metaverse`)

#### `set-route`

initiate the opening of the Metaverse 3d application with spsific route

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `route` | string | Yes | The route to set. Defines the current view or action in the application. If not provided, the default application state will open. |

#### `open-room`

Open a specific room in the ValuVerse 3D environment. Both networkId and roomId are required. Use the networkId provided in context.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `networkId` | string | Yes | The network the room belongs to. Use the networkId from context. |
| `roomId` | string | Yes | The ID of the room to open. |

#### `preview-prop`

Navigate the camera to a specific prop in a room, opening the Metaverse view. networkId, roomId, and propId are all required. Only call this when the user explicitly asks to show, open, navigate to, or view a prop. Do not call this automatically when listing or describing props.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `networkId` | string | Yes | The network the room belongs to. Use the networkId from context. |
| `roomId` | string | Yes | The ID of the room containing the prop. |
| `propId` | string | Yes | The ID of the prop to navigate to. |

***

### Metaverse 2D (`hubs`)

See who is in a room hub, join or switch hubs, and create a private hub

#### `open`

Opens the Metaverse 2D app (the 2D room maps and their hubs), optionally preselecting a room. Joining a hub stays a user action.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | No | Room to preselect in the room picker. |

#### `enter-room`

Opens the Metaverse 2D app on a room and joins that room's public hub, as picking the room there does (Rooms → Room Info → Go in 2D, the user card → Go in 2D).

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room to enter. Metaverse 2D checks that the user may join it. |
| `hubId` | string | No | A hub of that room to switch to once its public hub is joined, when it is still there and has room (the user card's location). |

#### `agent-enter-room`

Brings your agent into a room's hub as its own character: you enter that room in Metaverse 2D (its public hub, or your group's hub in group mode) and the agent appears a metre beside you. Needs the same join rights as you. Use find-people first to choose a room.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room to enter. |

#### `agent-people`

Lists who is in the hub with the agent: userId, name, whether they are your friend or connection, whether they are in 3D, and how many metres they are from the agent. Includes you (isYou).

**Availability:** AI

*No parameters.*

#### `agent-walk-to`

Walks the agent on the room's 2D map, around walls and props: to a person (stopping 1.5 m short, by userId from agent-people) or to a point (x, z in metres). Returns at once with the distance and seconds the walk takes; check agent-status.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | No | Walk to this person (from agent-people). |
| `x` | number | No | Or walk to this point: x in metres. |
| `z` | number | No | And z in metres. |

#### `agent-say`

The agent says a short text in a speech bubble over its character, seen by everyone on the web map of that hub (not by people in 3D yet). At most 140 characters, at most once a second. It speaks in your name, labelled as your agent: keep it friendly and brief.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `text` | string | Yes | What to say, up to 140 characters. |

#### `agent-status`

The agent's state in Metaverse 2D: whether it is in a hub, its room and hub, its position, and whether it is still walking.

**Availability:** AI

*No parameters.*

#### `agent-leave`

Takes the agent out of the hub.

**Availability:** AI

*No parameters.*

#### `find-people`

Counts the people in each hub of some rooms in this network (default: the room on the map and its linked rooms, at most 9). Needs you to be in a hub. Use it to choose where the agent goes.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomIds` | string\[] | No | The rooms to look into. |

***

### My Cart (`cart`)

Everything you added in any Valu app, in one cart, paid for with one QR scan

#### `open-section`

Open My Cart on one of its tabs. The tab becomes part of the address, so the place it opens on can be linked to and comes back on a reload.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `section` | string | Yes | Which tab to open: discover, cart, purchased (what the user owns) or orders (receipts). |

#### `open-cart`

Open the cart. Pass an app id to show only that app's items and check them out on their own, and `popup` to draw it over the calling app instead of docking My Cart.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | show only the items added from this app |
| `popup` | boolean | No | true draws this over the pane of the app that asked instead of docking My Cart, so the app the buyer came from stays visible and usable behind it. Where the caller is not on screen the popup falls back to a window over the whole page. |
| `promoCode` | string | No | a discount code to type into the cart and apply, as if the buyer had typed it; they see whether it applies and can remove it. Paying is still their own press of Pay. |

#### `open-orders`

Open the order history — every purchase made across Valu apps, with its receipt. With `popup` it is drawn over the calling app instead of docking My Cart.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `popup` | boolean | No | true draws this over the pane of the app that asked instead of docking My Cart, so the app the buyer came from stays visible and usable behind it. Where the caller is not on screen the popup falls back to a window over the whole page. |

#### `open-purchased`

Open what the user OWNS — every product they bought or got free, with its files, how far through them they are, and their rating. With `productId`, that product opened and shown. Orders (open-orders) are the receipts only.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | No | a product the user owns, to open and show |
| `popup` | boolean | No | true draws this over the pane of the app that asked instead of docking My Cart, so the app the buyer came from stays visible and usable behind it. Where the caller is not on screen the popup falls back to a window over the whole page. |

#### `open-order`

Open one order and show its receipt.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `orderId` | string | Yes | the order to show |
| `popup` | boolean | No | true draws this over the pane of the app that asked instead of docking My Cart, so the app the buyer came from stays visible and usable behind it. Where the caller is not on screen the popup falls back to a window over the whole page. |

#### `open-product`

Open a product's page — what it is, what it is made of, what makes up its price, and whether this buyer can buy it.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product to show |

#### `add-to-cart`

Add a product to the cart and open it. The item is credited to the app that asks — the platform knows which one, so there is no app to name — or to My Cart itself when the asker is not an app. The server re-checks that the product can be bought in this network before accepting it.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product to add |
| `qty` | number | No | how many, at least 1 |

***

### Profile (`profile`)

Your user profile

#### `show-current-user`

Opens the profile view for the currently signed-in user.

**Availability:** AI, Developer

*No parameters.*

#### `show-user`

Shows the profile view for a specific user, or the current user if no userId is provided.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | No | The unique identifier of the user whose profile to open. Defaults to the current user. |

#### `open-profile-with-mode`

Opens the profile view for a user and optionally navigates to a specific mode such as the marketplace.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | No | The unique identifier of the user whose profile to open. Defaults to the current user. |
| `offerId` | string | No | The identifier of the marketplace offer to display. |

***

### Resources Viewer (`preview`)

#### `preview`

Opens the resource viewer to preview a specific resource.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource to preview. |

***

### Rooms (`rooms`)

View and manage rooms

#### `open`

Opens the Rooms app on a chosen list. With no parameter the app simply opens on whatever it was last showing.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `tab` | string | No | Which list to show. Defaults to the currently visible one. |

#### `show-room`

Opens the Rooms app on a specific room's detail view — info, members and settings.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique identifier of the room to show. |

#### `invite-to-room`

Opens the room invitation dialog for a specific room.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique identifier of the room to invite users to. |

***

### Social (`community`)

#### `create-post`

Open view and open post editor

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | Resource id for post attachment |
| `rootChannel` | object | Yes | Root chanel of community |
| `community` | object | Yes | Community data |
| `path` | string | Yes | Full path to channel or subchannel |
| `title` | string | No | Pre-fills the post title in the editor. Optional; the editor opens with this title instead of blank. |
| `body` | string | No | Pre-fills the post body (Markdown) in the editor. Optional; the editor opens with this body instead of blank. |

#### `create-channel`

Opens the community view and starts channel creation in a specific community context.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `community` | object | Yes | Community data object. |
| `contentDirectoryId` | string | No | The content directory identifier for the new channel. |
| `rootChannel` | object | No | Root channel of the community. |
| `subChannelId` | string | No | The sub-channel identifier to navigate to. |
| `mode` | string | No | The creation mode for the channel. |
| `contentDirectoryTitle` | string | No | Display title for the content directory. |

#### `open-community`

Opens the community view and navigates to a community by its ID. Use when you only have a communityId and no specific channel to target — the app will land on the first available channel.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The community id. |

#### `show-channel`

Opens the community view and navigates to a specific channel. Use communityId from AI context. For a root channel: pass its id as rootChannelId, omit subChannelId. For a sub-channel: the sub-channel object in AI context has a rootChannelId field — pass that as rootChannelId, and the sub-channel id as subChannelId.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The community id from AI context. |
| `rootChannelId` | string | Yes | The top-level root channel id. Never a sub-channel id. |
| `subChannelId` | string | No | The sub-channel id to navigate into. Omit when navigating to the root channel itself. |

#### `show-post`

Opens the community view and navigates to a specific post. Use communityId from AI context. For a post in a root channel: pass the channel id as rootChannelId, omit subChannelId. For a post in a sub-channel: pass the sub-channel's rootChannelId as rootChannelId, and the sub-channel id as subChannelId. Pass the post id as messageId.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The community id from AI context. |
| `rootChannelId` | string | Yes | The top-level root channel id. Never a sub-channel id. |
| `messageId` | string | Yes | The post id from AI context. |
| `subChannelId` | string | No | The sub-channel id containing the post. Omit when the post is in the root channel itself. |

***

### User Card (`user-card`)

Contextual profile card shown when tapping a user avatar

#### `show`

Shows the contextual profile card for a user.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user whose card to show. |
| `anchor` | object | No | Viewport rect {left, top, right, bottom, width, height} of the tapped avatar, used to position the card in web. Ignored by native hosts, which anchor the card their own way. Omitted, the card is centred. |
| `hostApplicationId` | string | No | The application the card is opened from, and so the view a native host should present it above (e.g. "textchat"). Absent when the caller could not name one — present over the topmost view in that case. |

***

### Valu Games (`games`)

Valu board games

#### `open`

Open the Valu Games app.

**Availability:** AI, Developer

*No parameters.*

***

### Valu Guru (`valuguru`)

#### `open`

Opens the Valu Guru app on a chat with the built-in Valu Guru assistant, without sending anything. Resumes the most recent built-in-assistant conversation, or lands on the empty new-chat screen when there is none. Custom chatbot conversations are never resumed, and nothing is created on the server — a session appears only once a message is sent.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `newChat` | boolean | No | When true, always lands on the empty new-chat screen instead of resuming the most recent Valu Guru conversation. |

#### `start-session`

Opens the Valu Guru app and starts a brand-new conversation session with an initial message. Always creates a new session regardless of existing ones.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `message` | string | Yes | The initial user message to send in the new session. |
| `chatbotId` | string | No | Chatbot ID to start the session with. Accepts a personal "my chatbots" id, a published LIBRARY chatbot id (auto-added to "my chatbots" if not already there), or 'default'. Defaults to the default Valu Guru chatbot ('default'). |

#### `show-agents`

Opens the Valu Guru app and switches to the Agents tab in the left panel.

**Availability:** Developer

*No parameters.*

#### `open-chat`

Opens the Valu Guru app and navigates to a specific chat session by ID.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `chatId` | string | Yes | The chat session ID to open. |

#### `open-agent`

Opens the Valu Guru app and navigates to a specific agent chat by ID.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | The agent ID to open. |

#### `open-chatbot`

Opens the Valu Guru app on a library chatbot's detail page. Unlike open-chat, the target is not personal — any user in the network can view it (used e.g. by the bot badge on shared chatbot replies).

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `chatbotId` | string | Yes | The library chatbot ID whose detail page to open. |

#### `create-chatbot`

Opens the Valu Guru app on the new-chatbot creation wizard, optionally pre-filled. Pass `instructions` to land on the Design step fully filled in, or just `prompt` to land on the Describe step with the task pre-typed and ready to generate.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `prompt` | string | No | Plain-language task description to seed the Describe step (e.g. "a bot that answers HR policy questions"). Ignored if `instructions` is also given. |
| `instructions` | string | No | Full system-prompt instructions for the chatbot. When given, the wizard opens straight on the Design step instead of Describe. |
| `name` | string | No | Chatbot display name. |
| `description` | string | No | Short chatbot description shown in the library/list. |
| `icon` | string | No | Font Awesome icon class (e.g. "fa-light fa-robot") or a DiceBear avatar URL. |
| `skillIds` | string\[] | No | IDs of skills (from the shared Skills Catalogue) to pre-select for the chatbot. |

***

### Valu Puzzles (`puzzles`)

Valu puzzle games

#### `start-puzzle`

Open Valu Puzzles and launch a specific puzzle, optionally with a score-to-beat challenge from another user.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `puzzleId` | string | Yes | Which puzzle to start. One of: "sliding" (Sliding Puzzle), "memory" (Memory Match), "sudoku" (Mini Sudoku), "wordscramble" (Word Scramble). |
| `scoreToBeat` | number | No | Target score (0–999) the recipient should try to beat. Shown as a challenge banner above the puzzle; if the player exceeds it the result screen reads "Beat `<challengerName>`!". |
| `challengerName` | string | No | Display name of the user who issued the challenge. Shown on the challenge banner alongside the target score. |

***

## Service Intents

### Application Service (`Application`)

Issues short-lived signed identity JWTs for iFrame (mini-app) applications. Allows an embedded application to obtain a signed token proving the current user's identity to the mini-app's own backend.

*Source: `src/Services/Application/ApplicationService.js`*

#### `get-identity-token`

Issues a short-lived signed identity JWT for the calling mini-app. The target application is inferred from the intent sender — no parameters needed. Requires the user to be authenticated. JWT claims: sub=userId, aud=callingApplicationId, iss=platform, exp=5min.

**Availability:** Developer

*No parameters.*

#### `close_all`

Closes all currently loaded applications and clears all docks. Unlike the normal close flow, does not re-open the default application — the UI stays blank with no application displayed.

**Availability:** Developer

*No parameters.*

#### `expand-application`

Expands the calling application to fill the dock by closing every other loaded application. The target application is inferred from the intent sender — no parameters needed. Mirrors the "Expand Application" default header action for iframe apps that render their own context menu (header: false).

**Availability:** Developer

*No parameters.*

#### `close-application`

Closes the calling application (unloads it from its dock), then re-opens the default network application if nothing is left docked. The target application is inferred from the intent sender — no parameters needed. Mirrors the "Close Application" default header action for iframe apps that render their own context menu (header: false).

**Availability:** Developer

*No parameters.*

***

### Application Storage Service (`ApplicationStorage`)

Per-application file storage service for uploading, searching, and deleting resources scoped to the calling application and current user.

*Source: `src/Services/UploadManager/ApplicationStorageService.js`*

#### `resource-upload`

Uploads files to the calling application's storage for the current user.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `files` | FileList | Yes | The files to upload. |

#### `resource-search`

Searches resources in the calling application's storage for the current user.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `limit` | number | No | Maximum number of results to return. Defaults to 10. |
| `query` | string | No | Search query string. |
| `cursor` | string | No | Pagination cursor for fetching the next page of results. |

#### `resource-delete`

Deletes a resource from the calling application's storage.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource to delete. |

***

### CBAC Service (`Cbac`)

Badge-based access control (CBAC). Manages policies that grant a permission on a target entity (room, community, or group) to users holding specific badges. Policy mutations require manage permission on the target.

*Source: `src/Services/Cbac/CbacService.js`*

#### `list-policies`

Lists all CBAC policies configured for a target entity.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `networkId` | string | Yes | ID of the network the target belongs to. |
| `targetType` | string | Yes | Type of the target entity: "room", "community", or "group". |
| `targetId` | string | Yes | ID of the target entity. |

#### `create-policy`

Creates a CBAC policy on a target entity. Valid grantedPermission values depend on targetType: room → room.view / room.comment / room.contribute / room.edit; community → community.join; group → group.join. Requires manage permission on the target.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `networkId` | string | Yes | ID of the network the target belongs to. |
| `targetType` | string | Yes | Type of the target entity: "room", "community", or "group". |
| `targetId` | string | Yes | ID of the target entity. |
| `badgeIds` | array | Yes | One or more badge IDs required to gain access; must not be empty. |
| `badgeMatchMode` | string | Yes | How badges are evaluated: "any" (one badge suffices) or "all" (every badge required). |
| `grantedPermission` | string | Yes | Permission granted when the badge requirement is met. Must be valid for the targetType (see intent description). |

#### `delete-policy`

Deletes a single CBAC policy by ID. Requires manage permission on the target.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `policyId` | string | Yes | ID of the policy to delete. |
| `networkId` | string | Yes | ID of the network the target belongs to. |
| `targetType` | string | Yes | Type of the target entity: "room", "community", or "group". |
| `targetId` | string | Yes | ID of the target entity. |

#### `list-badges`

Lists every badge visible to the current user — badges scoped to their networks plus global ones. Returns an array of `{networkId, badgeId, badgeName, badgeDescription, thumbnailId}`, where `networkId` is "all" for a global badge. Use this to resolve a badge name to the badgeId required by search-users-by-badge-id and the policy intents.

**Availability:** AI, Developer

*No parameters.*

#### `search-users-by-badge-id`

Lists the users holding a given badge, optionally filtered by a name query. The reverse of looking up which badges a single user holds. Call list-badges first to resolve a badge name to its badgeId. Returns `{users, total}` where `total` is the full match count across all pages — page through it with offset/size rather than assuming the first page is everything.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `badgeId` | string | Yes | ID of the badge whose holders are returned. |
| `query` | string | No | Search query to filter badge holders by name. Defaults to an empty string, which returns all holders. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 10. |

***

### CMS Service (`CMS`)

Content management service for uploading, searching, and deleting resources scoped to rooms, props, communities, channels, and directories.

*Source: `src/Services/CMS/CMSService.js`*

#### `resource-upload`

Uploads files to a resource storage scoped by belonging (room, prop, community, channel, directory, or post).

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `files` | FileList | Yes | The files to upload. |
| `communityId` | string | No | The community to associate the uploaded resources with. |
| `channelId` | string | No | The channel to associate the uploaded resources with. |
| `directoryId` | string | No | The directory to associate the uploaded resources with. If omitted and channelId is provided, it is resolved automatically from the channel's contentDirectoryId. |
| `postId` | string | No | The post to attach the uploaded resources to. |
| `roomId` | string | No | The room to associate the uploaded resources with. |
| `propId` | string | No | The prop to move the uploaded resources into. |

#### `resource-search`

Searches for resources within a given scope (room, prop, community, channel, directory, or post). Each returned resource has an id (and a title/name) — tag every resource you reference in your reply as @\[cms-resource:id|title].

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | No | The community to search resources in. |
| `channelId` | string | No | The channel to search resources in. |
| `directoryId` | string | No | The directory to search resources in. If omitted and channelId is provided, it is resolved automatically from the channel's contentDirectoryId. |
| `postId` | string | No | The post to load resources for. |
| `roomId` | string | No | The room to search resources in. |
| `propId` | string | No | The prop to search resources in. |
| `limit` | number | No | Maximum number of results to return. Defaults to 10. |
| `query` | string | No | Search query string. |
| `cursor` | string | No | Pagination cursor for fetching the next page of results. |

#### `resource-delete`

Deletes a resource or removes it from a prop or post.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource to delete. |
| `communityId` | string | No | The community the resource belongs to. |
| `channelId` | string | No | The channel the resource belongs to. |
| `postId` | string | No | The post to remove the resource from (removes association, does not delete the resource). |
| `roomId` | string | No | The room the resource belongs to. |
| `propId` | string | No | The prop to remove the resource from (removes association, does not delete the resource). |

#### `pick-resource`

Opens a file picker over the Network Resources of the current network so a network admin can choose ONE file, and waits for the choice. The popup opens over whatever is on screen and does not open Media. BLOCKS until the user picks or cancels. Returns the picked resource `{id, title, contentType, thumbnailUrl}` or `null` when the user cancels or closes the popup. Refused (an error, and no popup) for anyone who is not an admin of the network, for an unknown source, and for a folderId outside the requested sources. The returned resource is always inside the requested sources.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `sources` | string\[] | Yes | Where files may be picked from. Only "networkResources" (the public files of the network, `networkPublic:{networkId}`) is supported. Nothing outside these sources is shown or reachable. |
| `accept` | string\[] | No | File types that can be picked: a family ("image"), a family wildcard ("image/\*") or an exact type ("image/png"). Other files are shown disabled. Omit to allow any file. |
| `folderId` | string | No | Folder to open in. Must be a folder inside the requested sources, otherwise the call is refused. |
| `title` | string | No | Popup title. Defaults to "Choose a file". |

***

### Commerce (`Commerce`)

Products, the cart, orders and the seller's own store, shared across apps. An app shows the network's products and adds them to the one cart the user pays for with a single QR scan, and it can run the signed-in seller's store: products, publishing, promotions and analytics. Prices, totals and payouts are decided server-side. Paying is never an action here: `checkout` hands the buyer to My Cart, where they press Pay and scan the code themselves, and answers with what they did. A write that costs the user money, gives goods away, cannot be undone or speaks for them in public waits for their yes in a dialog the platform draws.

*Source: `src/Services/Commerce/CommerceService.js`*

#### `list-products`

Search the live products available in the user's current network — the buyer's shelf, every seller's goods, not only yours. Products the network or its admins have refused are simply absent from the answer. Each product names its seller, who is a PERSON, not a store: `sellerName` (their profile name), `sellerUserId` (draw their avatar with the users API's getIcon({userId: sellerUserId})) and `sellerIdentityName` (the Verus ID payments go to). `storeName` carries the same person's name, for older callers; show the person, never a store. For the signed-in seller's OWN products, drafts included, use list-seller-products.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | free-text search over the product title, or a whole tag |
| `storeId` | string | No | only this seller's products: a product's `storeId`, from list-products or get-product. Every user has one store, so this is everything one person sells (My Cart's "Everything from this store") |
| `category` | string | No | one of the platform's category ids — see list-categories |
| `tag` | string | No | one tag, matched exactly (case and spacing do not matter) |
| `attributes` | object | No | app-defined facets to filter by, e.g. {"subject": "Mathematics"} |
| `sort` | string | No | newest | popular | priceAsc | priceDesc | rating |
| `limit` | number | No | page size, up to 100 (default 24) |
| `offset` | number | No | where the page starts |

#### `get-product`

One product with its price, its parts when it is a bundle, its seller, its reviews and whether the current user already owns it. The seller is a PERSON, as in list-products: `sellerName`, `sellerUserId` (their avatar through the users API's getIcon) and `sellerIdentityName`; the store record's name is that person too. `contents` is what the product holds, as a tree to draw: `{type: "folder", title, items}`, `{type: "file", resourceId, title, kind, contentType, fileSize}` and `{type: "product", productId, title}`. `children` is NOT the content: it is only the products a bundle includes, empty for anything else. `items` is the same content as flat database rows, kept for older callers. A file's `resourceId` (and `resource_id` in items) is null unless the user owns the product or sells it: titles and types are the shop window, the ids open the paid files. For something the user bought, use get-purchase-contents.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product to read |

#### `list-categories`

The platform's product categories as `{categories: [{id, label}]}`. Every product is filed under exactly one; pass an `id` to list-products as `category`, and show the `label`.

**Availability:** AI, Developer

*No parameters.*

#### `add-to-cart`

Put a product in the user's cart, credited to your app. The server re-checks that it can be bought here before accepting it, so a refusal comes back with a code to show.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product to add |
| `qty` | number | No | how many, at least 1 (default 1) |

#### `check-entitlements`

Which of these products the current user owns. This is how an app unlocks a ticket, a seat or an in-app good it sold through the shared cart.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productIds` | array | Yes | the product ids to check |

#### `get-cart`

Reads the user's cart — every item in it, from every app, as the buyer will check it out. Returns `{items, count}` where `count` excludes anything saved for later. Use it to show a badge, a summary, or to tell whether something this app sells is already in there. Each item's `appId` says which app added it. An app changes only its own rows: `add-to-cart` adds one, `update-cart-item` changes one, `remove-from-cart` and `clear-cart` take them out. Every other app's items are the buyer's to change in My Cart: send them there with `open-cart`.

**Availability:** AI, Developer

*No parameters.*

#### `create-product`

Create a product for the seller, as a DRAFT. Two ways in. With no params it opens the platform's own 'list something for sale' form in a modal and BLOCKS until the seller creates a product or cancels. With a `title` it creates the draft directly from the fields given — name, description, price, category, tags, cover and content — without a form: use this when you already have the resource ids (a generated cover, files found in Media). Either way returns `{success: true, product}` (or `{success: false, product: null, code}`; `cancelled` when the seller backed out of the form), and the product is already in the seller's catalogue. It is NEVER published here: publishing decides money and networks, so the seller confirms it separately — in the publish dialog an app opens with publish-product, or in the Merchant Console. Opening a store first (a verified Verus identity) is handled inside.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | No | the product's name; giving one creates the draft directly instead of opening the form |
| `description` | string | No | what a buyer reads before paying |
| `priceAmount` | number | No | the price; 0 or omitted is a free product. When items include other products, this is the price ON TOP of theirs |
| `priceCurrency` | string | No | VRSC (default) or USD |
| `category` | string | No | one of the platform's category ids — see list-categories; required before the seller can publish |
| `tags` | array | No | up to 10 tags of up to 24 characters; normalised (lowercase, trimmed) |
| `imageResourceId` | string | No | the cover: a resource id in the seller's own Media, e.g. one you generated |
| `items` | array | No | the content a buyer receives: resource ids ("res_1"), named files ({resourceId, title}), folders ({folder: "Unit 1", items: \[…]}) or other products ({productId}) for a bundle |
| `stock` | number | No | how many may be sold; omit for unlimited |

#### `list-seller-products`

Every product the SIGNED-IN user sells, from their own store, so an app can show a seller their own products: live, draft (unlisted ones included), archived and broken, in every network, including ones a network blocked. Unlike list-products, the buyer's shelf of live products allowed here. The store is always the current user's; there is no way to name another. Read-only, newest change first. Returns `{hasStore, products}`; `hasStore` is false (and products empty) when the user has no store. Each product: id, title, description, status, category, tags, priceAmount, priceCurrency, totalPriceAmount, isBundle, coverResourceId, stock (null = unlimited), soldCount, contentCount. Show totalPriceAmount as the price: on a bundle priceAmount is only the seller's part on top of what it includes, and totalPriceAmount is null when the server could not price the bundle.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `status` | string | No | only products in this state |
| `limit` | number | No | how many to return: 100 by default, up to 500 |

#### `list-my-products`

List the SELLER's own products in their store — drafts, unlisted, live and archived — unlike list-products, which is the buyer's shelf and never shows drafts. Use it to find the productId to edit. Each product carries `editable`: true only for a draft (never published, or unlisted by the seller). Returns `{hasStore, products}`.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `status` | string | No | only products in this state |
| `limit` | number | No | how many to return |

#### `get-my-product`

One of the seller's own products with its content, in exactly the shape update-product takes: `{product, items}`. Read it before changing the content — `items` in update-product REPLACES the whole tree, so edit this list and send it back rather than sending only the new files. `product.editable` is true for a draft, whose fields and content may change; a `broken` product (needs attention: something it included was archived) takes new content only.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product, from list-seller-products (an app) or list-my-products (the AI) |

#### `update-product`

Edit one of the seller's own DRAFT products — one never published, or one the seller unlisted. A live or archived product is refused with code `not_editable` (a live one must be unlisted first: unlist-product, or the seller in the Merchant Console). A `broken` product (needs attention: something it included was archived) accepts `items` only — its other fields are `not_editable` — and goes back on sale when it is published again (publish-product). Only the fields given change. `items` REPLACES the content: call get-my-product first and send back the edited list; an empty list is refused. Never publishes. Returns `{success: true, product}` or `{success: false, code, error}`.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the draft (or broken product) to edit, from list-seller-products (an app) or list-my-products (the AI) |
| `title` | string | No | the product's name |
| `description` | string | No | what a buyer reads before paying; an empty string clears it |
| `priceAmount` | number | No | the price; 0 makes it free. On a product that includes other products this is the price ON TOP of theirs (a buyer pays the included products plus this) |
| `priceCurrency` | string | No | VRSC or USD |
| `stock` | number | No | how many may be sold (the quantity on sale) |
| `unlimitedStock` | boolean | No | true removes the quantity limit |
| `category` | string | No | one of the platform's category ids — see list-categories; an empty string clears it |
| `tags` | array | No | the full tag list (replaces the current one): up to 10 tags of up to 24 characters |
| `imageResourceId` | string | No | the cover: a resource id in the seller's own Media |
| `items` | array | No | the WHOLE new content: resource ids ("res_1"), named files ({resourceId, title}), folders ({folder: "Unit 1", items: \[…]}) or other products ({productId}) for a bundle |

#### `list-purchases`

What the signed-in user OWNS — every product they bought, wherever they bought it, as My Cart's Orders tab lists it. Returns `{purchases: [{productId, title, coverResourceId, purchasedAt, orderId, boughtInAppId, isBundlePart, delivered, deletedBySeller, progress}]}`. `delivered` is true once the buyer's own copy is in their Media; `isBundlePart` marks a product that came inside a bundle; a purchase the seller deleted is still theirs. `progress` is `{openedFiles, totalFiles, complete}` — how many of its files the user has opened (a bundle counts its parts); `complete` marks a purchase read to the end. Null when unknown.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `thisAppOnly` | boolean | No | true lists only what was bought in YOUR app |

#### `get-purchase-contents`

One purchase opened up: the version the user BOUGHT (frozen at purchase, not the product as it is now), as a tree to draw. Returns `{productId, title, deletedBySeller, contents, progress}`; contents nodes are `{type: "folder", title, items}`, `{type: "file", resourceId, title, kind, contentType, fileSize, deliveredResourceId, seen}` and `{type: "product", productId, title}` (a product inside a bundle — open it with its own get-purchase-contents). `deliveredResourceId` is the buyer's own copy in Media, null until delivered. `seen` is true once the user has opened that file (draw a green check — bought products are often lessons); a file whose `kind` is "folder" has no `seen`. `progress` is `{openedFiles, totalFiles, complete}` (a bundle counts its parts; `complete` once every file is opened), null when unknown. Refused with code `not_owned` when the user does not own the product — the server decides.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | a product the user owns, from list-purchases |

#### `preview-purchase`

Open the platform's previewer on what the user bought — the Orders tab's click on a file. It holds every file of the purchase in tree order, with previous / next arrows between them, and opens on `resourceId` when given (it must be in the purchase: its `resourceId` or `deliveredResourceId` from get-purchase-contents), on the first file otherwise. Every file the user lands on is marked as opened (`seen` in get-purchase-contents). A folder resource is shown on its own. Returns `{success: true, count}`, or `{success: false, code}` with `not_owned`, `not_in_purchase` or `empty`. The previewer is a window over the whole page.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | a product the user owns |
| `resourceId` | string | No | the file of that purchase to open on |

#### `mark-purchase-file-seen`

Mark one of the user's purchased files as OPENED, for an app that shows a purchase's files itself (preview-purchase marks the files it shows by itself). The file must be in the purchase (its `resourceId` or `deliveredResourceId` from get-purchase-contents) and not a folder. Returns `{success: true, resourceId, progress}` — `progress` as in get-purchase-contents — or `{success: false, code}` with `not_owned` or `not_in_purchase`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | a product the user owns |
| `resourceId` | string | Yes | the file of that purchase the user opened |

#### `open-purchase-in-media`

Open a purchase where it lives, in Media's Purchased section — the Orders tab's "Show in Media". The buyer's delivered folder when there is one, otherwise a read-only view of the version they bought. Without `productId`, the whole Purchased section. Media docks beside your app. Returns `{success: true}`, or `{success: false, code: "not_owned"}` without opening anything.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | No | a product the user owns; omit for everything they bought |

#### `open-cart`

Open My Cart for the user, scoped to your app's items. With `popup` it is drawn over your own app instead of docking My Cart — mid-purchase, that keeps the user where they were. It answers as soon as the cart is open; for a Pay button use `checkout`, which opens the same cart and answers with what the buyer did there.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `popup` | boolean | No | true draws the surface over YOUR app's own pane instead of docking the application, so the page the user was on stays visible and usable behind it. Where your app is not on screen it falls back to a window over the whole page. |

#### `open-purchases`

Open what the user OWNS in My Cart's Purchased tab — each product with its files, how far through them they are, and their rating; with `productId`, that product opened and shown. Receipts are `open-order`. With `popup` it is drawn over your own app instead of docking My Cart.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | No | a product the user owns, to open and show |
| `popup` | boolean | No | true draws the surface over YOUR app's own pane instead of docking the application, so the page the user was on stays visible and usable behind it. Where your app is not on screen it falls back to a window over the whole page. |

#### `open-products`

Open the seller's own products in the Merchant Console — the seller's side of `open-cart`. Use it for a "My products" link: it shows everything they sell across every app, not just yours, and whether they may sell at all is the platform's decision, not your app's. With `popup` it is drawn over your own app instead of docking the console.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `popup` | boolean | No | true draws the surface over YOUR app's own pane instead of docking the application, so the page the user was on stays visible and usable behind it. Where your app is not on screen it falls back to a window over the whole page. |

#### `update-cart-item`

Change one of YOUR app's rows in the user's cart: its quantity, whether it is selected for checkout, or whether it is saved for later. Give at least one of them; only the fields given change. A row added by another app is refused with code `not_yours` — the buyer changes those in My Cart (`open-cart`) — and a row the cart does not hold with `not_found`. Returns the whole cart as get-cart reads it: `{success: true, items, count}`, `count` excluding what is saved for later. The server re-checks stock and availability and refuses with its own code.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `itemId` | string | Yes | the cart row to change: an item `id` from get-cart, one your app added |
| `qty` | number | No | the new quantity, a whole number of at least 1 |
| `selected` | boolean | No | true to include it in the next checkout, false to leave it out |
| `savedForLater` | boolean | No | true moves it out of the cart into saved for later; false brings it back |

#### `remove-from-cart`

Take one of YOUR app's rows out of the user's cart. Another app's row is refused with code `not_yours` (the buyer removes those in My Cart), a row the cart does not hold with `not_found`. Returns the whole cart: `{success: true, items, count}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `itemId` | string | Yes | the cart row to remove: an item `id` from get-cart, one your app added |

#### `clear-cart`

Remove every row YOUR app added to the user's cart, saved-for-later ones included. Other apps' rows are never touched. No dialog: they are your own rows. Returns what is left — `{success: true, items, count}` — so an empty `items` means the cart held only your rows.

**Availability:** Developer

*No parameters.*

#### `preview-promo`

What a discount code would do to the items YOUR app has selected in the cart — exactly what `checkout` will charge for them. Returns `{valid, reason, discounts: [{kind, label, amountVrsc}], subtotalVrsc, totalVrsc}`. `reason` is null when valid, else `promo_invalid`, `promo_expired`, `promo_exhausted` or `not_applicable` (a real code that takes nothing off this selection, e.g. under its minimum). `discounts` lists everything that takes money off, the code and any volume discount or sale alike. Read-only: nothing is reserved, and My Cart prices the code again when the buyer pays. With nothing of yours selected the server refuses `validation_failed`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `code` | string | Yes | the code as the buyer typed it; case and surrounding spaces do not matter |

#### `get-display`

How this network SHOWS prices: `{currency, vrscUsd}`, where `currency` is `USD` or `VRSC` (the network admins' choice) and `vrscUsd` is the server's VRSC→USD rate, null when it is unknown. Every amount Commerce answers with stays in VRSC (`…Vrsc`); multiply by `vrscUsd` to show dollars, and show VRSC when the rate is null.

**Availability:** Developer

*No parameters.*

#### `checkout`

Your app's Pay button. Your app never takes a payment: this opens My Cart's own cart over your app, showing your items with `promoCode` typed in and applied, and the BUYER presses Pay and scans the code there — only My Cart creates the order. It answers when the buyer closes it: `{success: true, outcome, orderId?, status?}`. `outcome` is `paid` (a free order too: unlock it), `pending` (an order was made but is not paid yet — follow it with get-order or the order-updated event), `cancelled` (the order will not be paid: the buyer cancelled it, or it expired or failed — `status` says which — and its items are back in the cart) or `dismissed` (closed without making an order). Refused with code `empty`, and nothing opens, when none of your items is selected in the cart (add them first). With `popup: false` My Cart docks instead and it answers at once with `outcome: "opened"`, as it does where there is no popup to draw. A second checkout while one is open replaces it; the first answers at once — `dismissed`, unless an order was already made in it.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `promoCode` | string | No | a discount code to type in and apply for the buyer; check it first with preview-promo |
| `popup` | boolean | No | false docks My Cart instead of drawing its cart over your app (default true) |

#### `open-order`

Open one of the user's orders in My Cart, expanded: where the buyer resumes a pending payment ("Show code"), cancels or deletes an unpaid order, or reads a receipt. Those are the buyer's own actions in My Cart; your app cannot take them. With `popup` it is drawn over your own app instead of docking My Cart. Returns `{success: true, popup}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `orderId` | string | Yes | the order to show, e.g. from list-orders or checkout |
| `popup` | boolean | No | true draws the surface over YOUR app's own pane instead of docking the application, so the page the user was on stays visible and usable behind it. Where your app is not on screen it falls back to a window over the whole page. |

#### `list-orders`

The user's orders, newest first, with their whole receipt as frozen at checkout. Returns `{orders: [{id, status, createdAt, paidAt, expiresAt, isTest, subtotalVrsc, totalVrsc, rateVrscUsd, items: [{productId, appId, title, qty, unitPriceVrsc, lineTotalVrsc, imageResourceId, deletedBySeller, parts: [{productId, title}], bundleFeeVrsc}], discounts: [{kind, label, amountVrsc}], notCharged: [{productId, title, amountVrsc}], payouts: [{kind, identityName, storeId, amountVrsc, status, donationPercent, donationMemberCount}], payment: {method, transactionId, failureReason} | null}]}`. `status` is `pending_payment`, `paid`, `expired`, `cancelled` or `failed`; without `status` you get pending and paid orders (a cancelled order is deleted). `notCharged` are parts of a bundle the user already owned. An order never carries a way to pay it: send the buyer to it with open-order.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `status` | string | No | only orders in this state |
| `limit` | number | No | how many orders to read, newest first; counted before thisAppOnly narrows them |
| `thisAppOnly` | boolean | No | true keeps only orders with at least one item bought in YOUR app |

#### `get-order`

One of the user's orders, in the shape list-orders answers: `{order}`. Use it to follow an order `checkout` answered with. An order that is not the user's is refused `not_found`, exactly like one that does not exist. `imageResourceId` is null here; list-orders resolves the pictures.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `orderId` | string | Yes | the order to read |

#### `rate-product`

Rate a product the user owns, one to five stars with an optional review. A rating is public (it shows on the product's page), so the platform asks the user first in its own dialog over your app: `score` and `text` only pre-fill it, the user can change both, and what they confirm is what is saved. Returns `{success: true, review: {score, text}}` — what was saved. Refused with `cancelled` when the user closes the dialog (nothing is saved; do not ask again on your own), `not_owned` when they do not own the product (no dialog), `busy` while another platform dialog is waiting, and `unsupported` where there is no dialog to ask with.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | a product the user owns, e.g. from list-purchases |
| `score` | number | No | the stars to propose, a whole number from 1 to 5 |
| `text` | string | No | the review to propose, up to 4000 characters |

#### `get-my-store`

The signed-in user's own store and where they stand to sell — with list-seller-products, enough to draw the Merchant Console's setup checklist (verified identity → store → donation set → a product → one published). Returns `{hasStore, identityVerified, sellerName, sellerUserId, store, network}`. Buyers see a PERSON as the seller, not a store: show `sellerName` (the owner's profile name) and draw their avatar from `sellerUserId` with the users API's getIcon. `store` is `{id, name, description, status, payoutIdentityName, donationTiers: [{minMembers, percent}], donationUpdatedAt}`, or null — its `name` is kept for older callers and is not what buyers see; an empty `donationTiers` means no donation is set, and such a store sells nowhere. `identityVerified` says whether the user can open a store (a Verified Verus identity; when false, send them to their Profile). `network` is the network the user is in now: `{id, memberCount, mode, allowedRatings, displayCurrency, donationTierIndex}` — `mode` is `open | approval_required | closed`, `donationTierIndex` which of the store's four donation tiers this network takes (null without a store or a known size). Read-only.

**Availability:** Developer

*No parameters.*

#### `create-store`

Open the signed-in user's store — what lets them sell; buyers see the user themselves as the seller, so it needs no name. It needs a Verified Verus identity: without one it is refused with code `not_eligible_kyc` — send the user to their Profile. A user who already has a store gets it back unchanged. Returns `{success: true, store}` in get-my-store's shape. No dialog. Next steps: set-donation-tiers (a store with no donation sells nowhere), then create-product.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | No | rarely needed — the user's own profile name is used without it; at most 120 characters |
| `description` | string | No | what they sell, at most 2000 characters |

#### `set-donation-tiers`

Set the store's network donation — the share of every sale that goes to the network it is sold in, one percentage per network size; a store with none sells nowhere. OPENS A PLATFORM DIALOG over your app showing each tier old → new (this network's marked) and saves only if the user confirms. `{success: false, code: "cancelled"}` is a normal answer: the user said no — do not ask again yourself. Lowering any tier sends the store back for approval in every network that approved it (its products hide there until an admin approves again), and the dialog says so. Sending what is already saved asks nothing and changes nothing. Returns `{success: true, store, reopenedNetworks}` — the networks that must approve the store again. Refused `busy` while another dialog from your app is open, `unsupported` where the platform cannot show one.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `tiers` | array | Yes | exactly four {minMembers, percent}, one per network size — minMembers 0, 100, 1000 and 10000 — each percent 0 to 100 with at most two decimals |

#### `list-network-access`

Where the store stands in each network it asked to sell in: `{networks: [{networkId, status, requestedAt, decidedAt, note, message}]}`. `status` is `pending | approved | rejected | suspended`; `note` is the admins' word on their decision, `message` the seller's on the request. The network the user is in now may be missing: in an `open` one (get-my-store `network.mode`) a store sells without asking. Read-only.

**Availability:** Developer

*No parameters.*

#### `request-network-access`

Ask a network's admins to let the store sell there, with an optional message; they decide, and list-network-access shows the outcome. Returns `{success: true, request}` in list-network-access's shape (`status: "pending"`). No dialog.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `networkId` | string | Yes | the network to sell in |
| `message` | string | No | a word to the admins, at most 2000 characters |

#### `quote-bundle`

What a product made of these products would cost a buyer, priced by the server exactly as checkout prices it (sales, bundles inside bundles, a part reached twice). Returns `{currency, parts: [{productId, amount}], included, own, total, duplicates, unavailable}`: `included` is the parts' sum, `own` the seller's price on top, `total` what a buyer pays; `duplicates` are parts reached twice, `unavailable` ids that are neither on sale nor the seller's own. Read-only — show it while the seller assembles a bundle; create-product with `{productId}` items makes one.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productIds` | array | Yes | the products the bundle would include |
| `priceCurrency` | string | No | the currency to quote in (default VRSC) |
| `ownPrice` | number | No | the seller's own price on top of the parts, zero or more |

#### `publish-product`

Put one of the seller's products on sale. OPENS THE MERCHANT CONSOLE'S PUBLISH DIALOG over your app: the user picks the networks to sell in — `networkIds` start ticked where the store may sell, by default this network — and confirms. Only then is the product listed (in My Cart and in YOUR app) and published, freezing this version for buyers. `{success: false, code: "cancelled"}` is a normal answer: the user did not publish — do not ask again yourself. Only a draft or a `broken` product (its content fixed with update-product) can be published; no content, no category, or a store with no donation set is refused `validation_failed` before any dialog, saying which. Returns `{success: true, product, version: {id, version, status, vaultStatus, publishedAt}}`. Refused `busy` while another dialog from your app is open, `unsupported` where the platform cannot show one.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product, from list-seller-products |
| `networkIds` | array | No | networks to start ticked in the dialog; the user decides |

#### `unlist-product`

Take a product that is on sale off every shelf: it becomes a draft again, its published version intact — buyers keep what they bought, and publishing again puts it back. OPENS THE MERCHANT CONSOLE'S UNLIST DIALOG over your app and unlists only if the user confirms; `{success: false, code: "cancelled"}` is a normal answer. Only an `active` product can be unlisted. Returns `{success: true, product}`. Unlist a live product before editing it with update-product.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | a product on sale, from list-seller-products |

#### `archive-product`

Withdraw a product from sale for good. OPENS A PLATFORM DIALOG over your app saying it leaves every shelf and cannot be undone, and naming the products that include it — they stop selling too until each is published again without it. Archives only if the user confirms; `{success: false, code: "cancelled"}` is a normal answer. Anyone who bought it keeps it. Returns `{success: true, product, brokeBundles}` — `brokeBundles` the ids of the products that stopped selling.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product, from list-seller-products |

#### `delete-product`

Delete a product for good. OPENS THE MERCHANT CONSOLE'S DELETE DIALOG over your app, where the user types the product's name to confirm; deletes only then. `{success: false, code: "cancelled"}` is a normal answer. A product that is part of another product cannot be deleted: the dialog names those products and the answer is `{success: false, code: "conflict"}` — take it out of them first (update-product on each). Buyers keep what they bought. Returns `{success: true}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product, from list-seller-products |

#### `list-promotions`

Every sale, volume discount and discount code of the store: `{sales: [{id, productId, salePriceAmount, salePriceCurrency, startsAt, endsAt, status}], tiers: [{id, minItems, percentOff, status}], codes: [{id, code, kind, value, currency, minSubtotalVrsc, maxRedemptions, redeemedCount, reservedCount, perBuyerLimit, startsAt, endsAt, status}]}`. `status` is `active | ended` for a sale and `active | disabled` for a tier or a code. A buyer's price takes a sale first, then at most one volume discount, then at most one code — they never stack beyond that. Read-only.

**Availability:** Developer

*No parameters.*

#### `set-sale`

Put one product on sale at a lower price. OPENS A PLATFORM DIALOG over your app showing the sale price against the usual one and when it runs; starts it only if the user confirms. `{success: false, code: "cancelled"}` is a normal answer. The sale price must be below the product's own and in its currency, and a bundle (priced by its parts) cannot go on sale — each refused `validation_failed` before any dialog. A product with a sale running gets this one instead. Returns `{success: true, sale}` in list-promotions' shape.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `productId` | string | Yes | the product, from list-seller-products |
| `salePriceAmount` | number | Yes | the sale price, below the product's own |
| `salePriceCurrency` | string | No | must be the product's own price currency (the default) |
| `startsAt` | string | No | ISO date it starts; omit for now |
| `endsAt` | string | No | ISO date it ends; omit to run until end-sale |

#### `end-sale`

End a sale now. Direct — stopping a discount needs no dialog. Returns `{success: true, sale}` with `status: "ended"`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `saleId` | string | Yes | the sale's id (not the product's), from list-promotions |

#### `set-volume-tier`

A store-wide volume discount: every order with at least `minItems` of the seller's items gets `percentOff` off, in every app and network the store sells in. OPENS A PLATFORM DIALOG over your app ("N or more items → P% off, every order in your store") and saves only if the user confirms; `{success: false, code: "cancelled"}` is a normal answer. With `tierId` it edits that discount (the dialog shows what it was); without, it adds one — or replaces the one at the same `minItems`. Returns `{success: true, tier}` in list-promotions' shape.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `minItems` | number | Yes | how many items an order needs, a whole number of at least 2 |
| `percentOff` | number | Yes | the discount, more than 0 and at most 100 |
| `tierId` | string | No | the discount to edit, from list-promotions |

#### `remove-volume-tier`

Remove a volume discount. Direct — no dialog. Returns `{success: true}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `tierId` | string | Yes | the discount, from list-promotions |

#### `create-discount-code`

A discount code buyers type at checkout — on the seller's own products only. OPENS A PLATFORM DIALOG over your app with the code, its value, limits and dates, and creates it only if the user confirms; `{success: false, code: "cancelled"}` is a normal answer. The code is stored upper-case; one the store already has is refused `conflict`. Returns `{success: true, discountCode}` in list-promotions' code shape — named `discountCode` because `code` is the refusal field.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `code` | string | Yes | 3 to 32 letters, digits and hyphens |
| `kind` | string | Yes | percent off, or a fixed amount off |
| `value` | number | Yes | the percent (at most 100) or the amount, more than 0 |
| `currency` | string | No | a fixed amount's currency (default VRSC) |
| `minSubtotalVrsc` | number | No | the smallest order it applies to, in VRSC |
| `maxRedemptions` | number | No | how many times it can be used in all; omit for unlimited |
| `perBuyerLimit` | number | No | how many times one buyer can use it; omit for unlimited |
| `startsAt` | string | No | ISO date it starts; omit for now |
| `endsAt` | string | No | ISO date it ends; omit for no end |

#### `update-discount-code`

Change a discount code. DIRECT when it only stops the discount: `status: "disabled"`, or an `endsAt` sooner than the one it has (any end, for a code that has none). Anything that gives more — `status: "active"` again, a later end or none (`endsAt: null`), more uses, more per buyer — OPENS A PLATFORM DIALOG over your app with each change before → after, and saves only if the user confirms; `{success: false, code: "cancelled"}` is a normal answer. Returns `{success: true, discountCode}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `codeId` | string | Yes | the code's id, from list-promotions |
| `status` | string | No | disabled turns it off; active turns it back on |
| `endsAt` | string | No | ISO date it ends, or null for no end |
| `maxRedemptions` | number | No | how many times it can be used in all, at least 1 |
| `perBuyerLimit` | number | No | how many times one buyer can use it, at least 1 |

#### `delete-discount-code`

Delete a code nothing ever used. Direct — no dialog. A code that was used is part of the record and is refused `conflict`: turn it off instead (update-discount-code with `status: "disabled"`). Returns `{success: true}`.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `codeId` | string | Yes | the code's id, from list-promotions |

#### `get-analytics`

The store's own numbers over a range — the Merchant Console's Analytics — exactly as the server sends them: `{range, totals: {revenueVrsc, orders, units, curatorFeeVrsc, revenueApproximate}, series: [{day, revenueVrsc, orders, suppressed}], topProducts: [{productId, title, revenueVrsc, units, suppressed}], byNetwork: [{networkId, revenueVrsc, orders, suppressed}], suppressedCells}`. A row with `suppressed: true` was built from too few orders and is WITHHELD: its numbers are not figures — show it as withheld, never as 0. `orders` counts order lines touched, an upper bound, not distinct orders. Read-only.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `from` | string | No | ISO date the range starts |
| `to` | string | No | ISO date the range ends |
| `testOnly` | boolean | No | true reads the test orders' figures instead of the real ones (never mixed) |

#### `subscribe-events`

Be told when commerce changes instead of polling. Name the events you want; the answer is `{success: true, events}`, your app's whole set now: each call REPLACES the previous one, and an empty list stops them all. An unknown name is refused with `validation_failed` and nothing changes; a caller that is not an embedded app is refused with `unsupported`. Each event arrives as a host push, `api:trigger` with `{action: "commerce", data: {event, …}}` (the SDK emits `ValuApi.ON_COMMERCE` with that `data`). The events and their data: `cart-updated {count}` — the cart changed; `count` is what the cart badge shows (saved-for-later excluded), never which items: re-read with get-cart. `order-updated {orderId, status}` — an order holding at least one of YOUR app's items changed: pending_payment, paid, failed, expired, cancelled or deleted. Orders without your items are never sent to you. `delivery-updated {orderId, status}` — that order's files: delivered (they are in the buyer's Media; re-read with list-purchases), unavailable or failed. `catalogue-updated {productId, change}` — the shelf of this network changed: change is added, updated or removed; productId null means re-read the whole shelf with list-products. `seller-product-updated {productId, change}` — one of the signed-in seller's own products: created, updated, content-updated, published, unlisted, archived, deleted, broken, sale-started, sale-ended, blocked or unblocked; re-read with list-seller-products. `seller-store-updated {change, networkId?}` — their store: created, updated, donation-updated or network-access-updated (networkId names the network when there is one). `seller-promotions-updated {change}` — a promotion changed: sale, tier or code. An event says WHAT changed, never the new state: treat it as a hint to re-read with the matching intent, never as data, and expect to miss one now and then (a missed event is a stale screen until the next read). Subscriptions last until unsubscribe-events, until your app's frame closes or until it reloads — moving the app to another dock reloads its frame — so subscribe again every time your app starts, after api:ready.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `events` | array | Yes | the event names to receive, replacing any earlier subscription: cart-updated, order-updated, delivery-updated, catalogue-updated, seller-product-updated, seller-store-updated, seller-promotions-updated |

#### `unsubscribe-events`

Stop receiving commerce events. Without `events`, every one your app subscribed to; with it, only those. Answers `{success: true, events}`: what your app still receives (empty when nothing). An unknown name is refused with `validation_failed` and nothing changes. There is no need to call it before your app closes: a subscription ends with the frame that made it.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `events` | array | No | the event names to stop receiving (see subscribe-events); omit to stop them all |

***

### Community Service (`Community`)

Community service for browsing communities, listing channels, and loading posts within channels.

*Source: `src/Services/Community/CommunityService.js`*

#### `search-communities`

Search for available communities. Returns community objects with communityId, name, description, and other properties.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter communities by name. |
| `limit` | number | No | Number of results to return. Defaults to 10. |
| `afterCommunityId` | string | No | Pagination cursor — ID of the last community from previous page. |

#### `get-community-info`

Retrieves detailed information about a specific community by its ID.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The unique community identifier. |

#### `get-channels`

Lists channels within a specific community. Returns {communityId, channels\[]} where each channel has channelId, rootChannelId (same as channelId — use this for entity tags), title, and other properties.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `communityId` | string | Yes | The unique community identifier. |
| `limit` | number | No | Maximum number of channels to return. Defaults to 100. |

#### `get-posts`

Loads posts/messages in a specific channel or sub-channel. Returns {communityId?, rootChannelId, subChannelId?, messages\[]}. The rootChannelId and subChannelId in the response are the exact values to use when constructing community-post or community-sub-channel-post entity tags — no need to track them separately.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `channelId` | string | Yes | The root channel ID (use rootChannelId from get-channels result). |
| `communityId` | string | No | The community ID. Pass this so the response echoes it back for entity tag construction. |
| `subChannelId` | string | No | Sub-channel ID when loading posts from a nested channel. Use the subChannelId field from the app context sub-channel entry. |
| `limit` | number | No | Number of posts to return. Defaults to 10. |
| `afterMessageId` | string | No | Pagination cursor — ID of the last message from previous page. |

***

### Data Provider Service (`DataProvider`)

Picker service for selecting items from data providers (rooms, contacts, etc.) via modal or inline overlay.

*Source: `src/Services/DataProvider/DataProviderService.js`*

#### `pick-single`

Opens an interactive picker so the END USER can choose ONE item (a room, contact, group, etc.) and returns their selection. BLOCKS until the user picks or cancels. Returns the selected item object (its shape depends on the provider — typically `{id, name, ...}`) or `null` if the user cancelled. Use this when the user's request needs an entity reference and they have NOT named a specific one — e.g. "share this in a group" without naming the group → call with providers: \["groups"]. Do not use to search programmatically; use the provider's own search/list service intent for that.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `providers` | string\[] | Yes | Provider IDs to show as picker sources. Available IDs include: "rooms", "contacts", "groups", "communities", "props", "events". Pass one entry for a single-source picker, or multiple to let the user switch sources via tabs. Example: \["rooms"] or \["contacts", "groups"]. |
| `title` | string | No | Dialog title shown above the picker. Defaults to the provider's built-in title. |
| `width` | string | No | CSS width for the modal (desktop only). |
| `height` | string | No | CSS height for the modal (desktop only). |

#### `pick-multiple`

Same as pick-single but lets the END USER select MORE THAN ONE item. BLOCKS until they confirm or cancel. Returns an array of selected items (`[{id, name, ...}, ...]`) or `null` if cancelled. Use when the user's request implies multiple targets — e.g. "invite some people to the room" → call with providers: \["contacts"].

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `providers` | string\[] | Yes | Provider IDs to show. Same set as pick-single. |
| `title` | string | No | Dialog title. |
| `confirmLabel` | string | No | Confirm button label (e.g. "Invite", "Share with"). Defaults to "Select". |
| `confirmIcon` | string | No | Confirm button icon class (Font Awesome, e.g. "fa-light fa-paper-plane-top"). |
| `width` | string | No | CSS width for the modal (desktop only). |
| `height` | string | No | CSS height for the modal (desktop only). |

***

### Developer Service (`Developer`)

Developer Portal service for creating and listing the current user's applications.

*Source: `src/Applications/DeveloperPortal/Services/DeveloperService.js`*

#### `create-application`

Creates a new application in the Developer Portal. By default the application is served in an iframe from https://{appSlug}-{userId}.valuguru.app/ — a host of its own, the slug and the user id joined with a dash (the slug is derived from the name — lowercased, dashes; deduplicated with -2, -3, … on collision) — and that site needs code deployed to it before it shows anything. Pass the optional `url` to instead point the app's iframe DIRECTLY at an existing external page (no code/build needed) — the created app then opens straight to that URL. Returns the created app's id, devId, slug, URL, and a ready-made `tag` — a chat entity tag of the form @\[application:`<appId>`|`<Name>`]. To give the user a clickable link that opens the application inside the platform, paste that `tag` value verbatim into your reply (do NOT link the raw URL).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Human-readable name for the application. Also used to derive the URL slug. |
| `description` | string | No | Short description of what the application does. |
| `url` | string | No | Optional external URL (http/https) to embed directly in the app's iframe, e.g. https://example.com. When provided, the app frames this page as-is with no build/deploy — the created app opens straight to it. The target site MUST allow third-party framing: many major sites (news portals, Google, most social networks) send X-Frame-Options or a CSP frame-ancestors directive that forbids embedding, and the browser will render them blank. Prefer a site known to permit framing. |
| `icon` | string | No | Font Awesome icon for the application, in the "fa-light fa-`<name>`" format (e.g. "fa-light fa-newspaper"). Set this for EVERY app you create, choosing an icon that represents what the app does — if omitted it falls back to a generic lightbulb. Pick the best match from this set: fa-light fa-robot, fa-light fa-brain-circuit, fa-light fa-microchip-ai, fa-light fa-user-robot, fa-light fa-message-bot, fa-light fa-cpu, fa-light fa-binary, fa-light fa-code, fa-light fa-terminal, fa-light fa-database, fa-light fa-network-wired, fa-light fa-satellite-dish, fa-light fa-palette, fa-light fa-pen-fancy, fa-light fa-paintbrush, fa-light fa-music, fa-light fa-camera, fa-light fa-film, fa-light fa-photo-film, fa-light fa-wand-magic-sparkles, fa-light fa-sparkles, fa-light fa-stars, fa-light fa-hat-wizard, fa-light fa-crystal-ball, fa-light fa-graduation-cap, fa-light fa-book-open, fa-light fa-books, fa-light fa-lightbulb, fa-light fa-magnifying-glass-chart, fa-light fa-chart-mixed, fa-light fa-flask, fa-light fa-microscope, fa-light fa-atom, fa-light fa-dna, fa-light fa-briefcase, fa-light fa-user-tie, fa-light fa-user-astronaut, fa-light fa-user-secret, fa-light fa-user-ninja, fa-light fa-users, fa-light fa-handshake, fa-light fa-comments, fa-light fa-bullhorn, fa-light fa-headset, fa-light fa-shield-check, fa-light fa-dragon, fa-light fa-unicorn, fa-light fa-cat, fa-light fa-dog, fa-light fa-rocket, fa-light fa-planet-ringed, fa-light fa-fire-flame-curved, fa-light fa-bolt, fa-light fa-leaf, fa-light fa-gem, fa-light fa-trophy. Any other valid Font Awesome 6 icon name is accepted too — always keep the "fa-light" weight. |

#### `list-applications`

Lists the current user's applications in the Developer Portal. Returns each application's appId, devId, name, slug, url, description, createdAt timestamp, and a ready-made `tag` — a chat entity tag of the form @\[application:`<appId>`|`<Name>`]. To give the user a clickable link that opens an application inside the platform, paste its `tag` value verbatim into your reply (do NOT link the raw URL).

**Availability:** AI, Developer

*No parameters.*

#### `list-application-apis`

Lists every application published in the current network that declares a REST API in its manifest. Returns {success, networkId, apis}; each api carries applicationId, address (the gateway path and token audience), title, description, gatewayUrl (where callers send requests, through the Valu Guru gateway), documentUrl (its OpenAPI document, read through the gateway), scopes, callers, availability, userContext, docsUrl and routable. Read-only: it describes the APIs, it calls none of them.

**Availability:** AI, Developer

*No parameters.*

#### `list-my-application-apis`

Lists the REST APIs of the applications the current user develops or tests, unreleased drafts included — what list-application-apis does not show. Returns {success, apis}; each api carries the same fields as list-application-apis plus devId, version (the version that describes the API: the released one when it declares an API, otherwise the newest that does) and live (true when that version is the released one). Read-only.

**Availability:** AI, Developer

*No parameters.*

***

### Events Service (`Events`)

Calendar service for listing, creating, and editing meetings.

*Source: `src/Applications/EventsApplication/Services/EventsService.js`*

#### `list-events`

Returns a list of calendar events for the specified date range and optional meeting filter, sorted by start time ascending.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `range` | string | No | Date range to fetch. Must be one of: day, week, month, year. Defaults to month. |
| `startDate` | string | No | ISO 8601 anchor date (e.g. "2026-04-07"). Defaults to today. |
| `filter` | string | No | Meeting source filter. Must be one of: all, room, group, user, community. Defaults to all. |
| `id` | string | No | ID of the room, group, user, or community. Used when filter is not all. |

#### `create-meeting`

Creates a new meeting on the calendar with the specified title, type, time range, and optional participants, description, color, and recurrence.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | The title of the meeting. |
| `type` | string | Yes | The meeting type. Must be one of: room, group, community, direct (private). |
| `startDate` | string | No | The meeting start date/time as a UTC ISO 8601 string (e.g. "2026-04-10T14:00:00.000Z"). Always use UTC — convert from local time if needed. Defaults to today's current time rounded up to the next 15-minute mark. |
| `endDate` | string | No | The meeting end date/time as a UTC ISO 8601 string (e.g. "2026-04-10T15:00:00.000Z"). Always use UTC — convert from local time if needed. Defaults to 1 hour after startDate. |
| `description` | string | No | An optional description for the meeting. |
| `color` | string | No | Hex color code for the meeting (e.g. "#4299f5"). Defaults to the type's default color when omitted. |
| `participants` | string\[] | No | Array of user IDs to invite to the meeting. For private meetings with multiple participants, a group is created automatically. |
| `recurringWeekly` | boolean | No | If true, the meeting repeats every week at the same time. Not supported for community meetings. |
| `roomId` | string | No | The room ID. Required when type is "room". |
| `groupId` | string | No | The group ID. Required when type is "group". |
| `communityId` | string | No | The community ID. Required when type is "community". |
| `globalEventId` | string | No | The community global event ID. Required when type is "community". |

#### `edit-meeting`

Updates an existing meeting's fields (title, description, time, participants, or color) by meeting ID.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `meetingId` | string | Yes | The unique identifier of the meeting to update. |
| `title` | string | No | New title for the meeting. |
| `description` | string | No | New description for the meeting. |
| `startDate` | string | No | New start date/time as a UTC ISO 8601 string (e.g. "2026-04-10T14:00:00.000Z"). Always use UTC — convert from local time if needed. |
| `endDate` | string | No | New end date/time as a UTC ISO 8601 string (e.g. "2026-04-10T15:00:00.000Z"). Always use UTC — convert from local time if needed. |
| `color` | string | No | New hex color code for the meeting (e.g. "#4299f5"). |
| `participants` | string\[] | No | Updated list of invited participant user IDs. |

***

### Groups Service (`Groups`)

Group management service for listing groups the current user belongs to and their participants.

*Source: `src/Services/Groups/GroupsService.js`*

#### `list-groups`

Returns groups the current user belongs to. Supports search and cursor-based pagination.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter groups by name. |
| `limit` | number | No | Maximum number of groups to return. Defaults to 20. |
| `cursor` | string | No | Pagination cursor for fetching the next page of results. |

#### `list-group-participants`

Returns participants of a specific group. Supports search and cursor-based pagination.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `groupId` | string | Yes | The unique identifier of the group. |
| `query` | string | No | Search query to filter participants by name. |
| `limit` | number | No | Maximum number of participants to return. Defaults to 20. |
| `cursor` | string | No | Pagination cursor for fetching the next page of results. |

#### `discover-groups`

Returns groups the current user can join via CBAC — groups whose badge policy is satisfied by the badges the user holds. Each result includes the cbacPolicies that grant access. Supports search and cursor-based pagination.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter groups by name. |
| `limit` | number | No | Maximum number of groups to return. Defaults to 20. |
| `cursor` | string | No | Pagination cursor for fetching the next page of results. |

#### `join-group`

Joins the current user to a group via CBAC. The user must hold a badge that satisfies a badge policy on the target group (use discover-groups to find eligible groups first).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `groupId` | string | Yes | The unique identifier of the group to join. |

***

### HTTP Service (`Http`)

Generic HTTP utility: reachability ping, GET, and POST against any URL. Requests are sent without user credentials (cookies are stripped); responses include status, headers, and body. Timeouts are capped server-side.

*Source: `src/Services/Http/HttpService.js`*

#### `ping`

Pings a URL with HEAD and returns whether it is reachable, the response latency in ms, and the HTTP status code (0 for cross-origin or unreachable servers). Returns: { up: boolean, latency: number, status: number, error?: string }.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | The URL to ping. |
| `timeout` | number | No | Request timeout in milliseconds. Defaults to 10000, max 60000. |

#### `get`

Performs an HTTP GET against the URL and returns the response. Body is auto-parsed as JSON when the response Content-Type is application/json (or \*+json), otherwise as text. Returns: { ok: boolean, status: number, statusText: string, headers: object, body: any, bodyType: "json"|"text", latency: number, error?: string }.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | Absolute URL to GET. |
| `headers` | object | No | Map of request headers to send (e.g. { "Accept": "application/json" }). |
| `timeout` | number | No | Request timeout in milliseconds. Defaults to 30000, max 60000. |
| `responseType` | string | No | How to parse the response body: "auto" (default — JSON if Content-Type matches, else text), "json", or "text". |

#### `post`

Performs an HTTP POST against the URL with the given body and returns the response (same shape as get). If `body` is a plain object it is JSON-stringified and Content-Type defaults to application/json; strings are sent verbatim with the supplied or existing Content-Type.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | Absolute URL to POST to. |
| `body` | object | No | Request body. Plain object → JSON-stringified; string → sent verbatim. Omit for an empty-body POST. |
| `headers` | object | No | Map of request headers to send. |
| `contentType` | string | No | Override the Content-Type header (defaults to application/json when `body` is an object). |
| `timeout` | number | No | Request timeout in milliseconds. Defaults to 30000, max 60000. |
| `responseType` | string | No | How to parse the response body: "auto" (default), "json", or "text". |

***

### Logging Service (`Logging`)

Exposes the in-memory console log buffer captured by ConsoleLogCapture for diagnostics and bug reporting.

*Source: `src/Services/Logging/LoggingService.js`*

#### `get-logs`

Returns the captured console log buffer (log, info, warn, error) since app start. Choose the format: "text" returns { format: "text", text: `<string>` } with one line per entry; "file" returns { format: "file", filename, mimeType, size, file: File } — the File is for direct callers (upload/download) and is omitted in the AI/MCP serialized response, which still includes filename, mimeType, and size.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `format` | string | No | Output format. Must be one of: "text", "file". Defaults to "text". |

***

### Networks Service (`Networks`)

Network management service for retrieving information about the current network.

*Source: `src/Applications/NetworksApplication/Services/NetworksService.js`*

#### `get-current-network`

Returns the id and name of the network the user is currently in. Call this to get the current networkId before making any room or network-related calls.

**Availability:** AI, Developer

*No parameters.*

***

### Profile Service (`Profile`)

Read-only access to a user's public profile artifacts — Verus verifiable credentials and network badges — addressed by userId. No UI side effects.

*Source: `src/Services/Profile/ProfileService.js`*

#### `get-user-credentials`

Returns the Verus verifiable credentials published on a user's profile, by their userId. These are the attestation claims of type "credential" shown in the profile Credentials view. Revoked credentials are returned only when the userId is the current user. Visibility is enforced by the backend against the current user.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user whose credentials to list. |
| `status` | string | No | Optional claim status filter (e.g. "Verified And Persisted"). Omit to return credentials of all statuses the caller may see. |

#### `get-user-badges`

Returns the badges assigned to a user by their userId. Each badge includes badgeId, badgeName, badgeDescription, thumbnailId, and networkId. Visibility is enforced by the backend against the current user.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user whose badges to list. |
| `networkId` | string | No | Network scope for the badges. Defaults to "all", which fetches badges from every network shared with the target user. |

***

### Resource Service (`Resources`)

Low-level resource service for generating URLs (thumbnails, public links, direct downloads) for resources.

*Source: `src/Services/Resources/ResourceService.js`*

#### `get-thumbnail-url`

Returns a thumbnail URL for a resource at the specified size.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource. |
| `thumbnailSize` | number | No | The desired thumbnail size in pixels. Defaults to 256. |

#### `generate-public-url`

Generates a public preview URL for a resource.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource. |

#### `generate-best-view-url`

Generates an optimized view URL for a resource.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource. |

#### `generate-direct-public-url`

Generates a direct public API URL for downloading a resource.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `resourceId` | string | Yes | The unique identifier of the resource. |

#### `list-bot-avatars`

Returns the network-allowed bot avatar collection (id, name, tags) from the shared bot-avatars directory. Avatars carrying the current network's denied tags (same modesty/policy filter as the user avatar picker) are excluded. Use a returned `id` as a 3D avatar resource id (e.g. an agent's avatarResourceId).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `limit` | number | No | Max avatars to return. Defaults to 50. |

***

### Room Points Service (`RoomPoints`)

GoTo points of a room (server name "cameraPosition"): list them, save or delete one directly, or open the point editor and let the user do it. Editing points requires the room's edit permission. Position and rotation are metres and EULER DEGREES in the room's own space — they cannot be computed from outside the 3D scene, so prefer create-point / edit-point (which receive the live camera pose from Unity) over save-point unless you are re-using a pose you already read.

*Source: `src/Services/RoomPoints/RoomPointsService.js`*

#### `list-room-points`

Returns every saved GoTo point of a room, in server order: {status: "ok", points}. Each point carries id, name, position {x,y,z}, rotation {x,y,z,w} (EULER DEGREES, w is always 0 — NOT a quaternion), cameraMode (0 = do not change, 1 = fly, 2 = third person) and the server timestamps. A room with no saved points returns an empty list — the "Home" point Unity shows in that case exists only in its memory.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room whose points to list. |
| `networkId` | string | No | Network the room belongs to. Defaults to the current network. |

#### `create-room-point`

Opens the point editor for a NEW point and BLOCKS until the user saves or cancels. Needs a pose to save the point at: pass camera (the live 3D camera pose) or a point to copy the pose from — the web layer cannot read the camera itself. Resolves {status: "ok", point, persisted, savePreview} once the point is written, or {status: "cancelled"} / {status: "error", message}.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the point belongs to. |
| `camera` | object | No | The pose the point is saved at: {position: {x,y,z}, rotation: {x,y,z,w}} with the rotation in EULER DEGREES. Required unless point carries a pose. |
| `point` | object | No | A cameraPosition to pre-fill the form with (name, camera mode, pose). Its id is ignored for a create. |
| `name` | string | No | Pre-fills the point name. Wins over the name the point carried. |
| `cameraMode` | number | No | Pre-fills the camera mode: 0 — do not change, 1 — free fly, 2 — third person. |
| `savePreview` | boolean | No | Pre-fills the "Make preview" toggle. Defaults to true. The screenshot and parallax capture themselves are done by the 3D scene, not here. |
| `persist` | boolean | No | Whether this service writes the point to the server. Defaults to true; pass false to use the editor as pure UI and save the returned point yourself. |
| `networkId` | string | No | Network the room belongs to. Defaults to the current network. |

#### `edit-room-point`

Opens the point editor for an EXISTING point and BLOCKS until the user saves, deletes or cancels. Resolves {status: "ok", point, persisted, savePreview}, {status: "deleted", point: {id}, persisted}, {status: "cancelled"} or {status: "error", message}. A point whose id is empty is treated as a create.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the point belongs to. |
| `point` | object | Yes | The point being edited, in the cameraPosition shape list-points returns. |
| `camera` | object | No | The live 3D camera pose, {position: {x,y,z}, rotation: {x,y,z,w}} in EULER DEGREES. When present the editor offers to move the point here, ticked by default. |
| `deletable` | boolean | No | Whether the editor may offer Delete. Defaults to false — a room should keep at least one point. |
| `name` | string | No | Pre-fills the point name. Wins over the name the point carried. |
| `cameraMode` | number | No | Pre-fills the camera mode: 0 — do not change, 1 — free fly, 2 — third person. |
| `savePreview` | boolean | No | Pre-fills the "Make preview" toggle. Defaults to true. |
| `persist` | boolean | No | Whether this service writes the change to the server. Defaults to true. |
| `networkId` | string | No | Network the room belongs to. Defaults to the current network. |

#### `save-room-point`

Writes a point WITHOUT opening any UI: creates it when point.id is empty or missing, otherwise overwrites the saved point with that id. An update REPLACES the whole point, so send a complete one (read it with list-room-points first) — fields left out are not kept. Returns {status: "ok", point} with the server copy, whose id is the one to keep after a create.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the point belongs to. |
| `point` | object | Yes | The complete point: {id?, name, position {x,y,z}, rotation {x,y,z,w} in EULER DEGREES, cameraMode}. |

#### `delete-room-point`

Deletes one saved point by id. Returns {status: "ok"}. The point parallax preview resource is cleaned up by the 3D scene, not here.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the point belongs to. |
| `pointId` | string | Yes | Id of the point to delete. |

***

### Room Zones Service (`RoomZones`)

Zones of a room and the claims on them (docs/superpowers/specs/2026-10-16-room-claiming-zones-design.md). A zone is an invisible box in a room: it can join its video call when someone walks in, it can be claimed as somebody's personal space, or both. A CLAIM IS A PROP INVITATION on the master server — there is no other store — so the occupant also gets edit rights on the zone prop. The server does not enforce exclusivity: the earliest claim wins and the loser is withdrawn, so always read the occupancy back rather than assuming a claim held.

*Source: `src/Services/RoomZones/RoomZonesService.js`*

#### `list-room-zones`

Every zone of a room: {status: "ok", zones}. Each zone carries id (the prop id), title, its box in room metres (x, z, rotY in degrees, width, depth, height, floorOffset), the two capabilities (call, claim) and its claim policy (settings.audience is "members" or "listed"). A room with no zones returns an empty list.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room whose zones to list. |
| `claimableOnly` | boolean | No | Only the zones that can be claimed (the "spaces"). Default false. |

#### `zone-occupancy`

Who holds the room's claimable zones right now: {status: "ok", occupancy} keyed by zone id, each {claim, conflicts, stale}. `claim` is null when the zone is free, else {userId, claimedAt, source, doorPolicy, displayName, status}. `stale` means the occupant no longer has access to the room, so the zone is effectively free. `conflicts` are losing claims a moderator should clear. This reads the server every time — there is no push for prop invitations.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room to read. |

#### `claim-zone`

Claims a zone for the SIGNED-IN user. Returns {status: "ok", claim} when it worked, or {status: "refused", reason, message} when it did not — "taken", "limit", "not-listed", "not-claimable", "no-access" or "cooldown". A user who may not write prop invitations at all gets {status: "refused", reason: "needs-request"}: they must ask a room owner to assign it instead. Never claims on behalf of somebody else — use assign-zone for that.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the zone belongs to. |
| `zoneId` | string | Yes | The zone to claim (its prop id, as list-room-zones returns it). |
| `displayName` | string | No | What to call the space, shown instead of the claimant's name. |
| `status` | string | No | A one-line status shown next to the space, e.g. "in focus until 4". |
| `doorPolicy` | string | No | Who may come in: "open" (default), "knock" or "closed". Shown, not yet enforced. |

#### `release-zone`

Releases the signed-in user's own claim on a zone: {status: "ok"}. Starts a one-minute cooldown before the same user may claim that zone again. Releasing a zone somebody else holds is not possible here — a room owner uses assign-zone or the Spaces app.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the zone belongs to. |
| `zoneId` | string | Yes | The zone to release. |

#### `assign-zone`

Gives a zone to a particular user. Requires the caller to OWN the room (manage rights) or be a network admin; anything less is refused. Bypasses the zone's audience, the room's per-user limit and the cooldown by design — this is how offices are handed out. Returns {status: "ok"}.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the zone belongs to. |
| `zoneId` | string | Yes | The zone to assign. |
| `userId` | string | Yes | Who gets it. |

***

### Rooms Service (`Rooms`)

Room management service for searching rooms, browsing room templates, creating rooms from templates, retrieving room details, managing prop content, and checking permissions.

*Source: `src/Applications/RoomsApplication/Services/RoomsService.js`*

#### `search-rooms`

Searches all public/discoverable rooms in the current network using the explorer service. Returns rooms anyone can find and join, regardless of membership. Use this when the user wants to discover or browse rooms. Every room in the result carries a ready-made entity link in its `tag` field (`@[room:networkId/roomId|Name]`) plus the resolved `networkId` — paste that `tag` verbatim when linking to the room and NEVER assemble one from ids yourself.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter rooms by name. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 10. |

#### `search-my-rooms`

Searches rooms belonging to the current user within the current network — their joined rooms, favorites, or pending invitations. Use this when the user asks about their own rooms. Every room in the result carries a ready-made entity link in its `tag` field (`@[room:networkId/roomId|Name]`) plus the resolved `networkId` — paste that `tag` verbatim when linking to the room and NEVER assemble one from ids yourself.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `filter` | string | Yes | Room filter. Must be one of: "all" — all rooms the current user belongs to; "favourite" — user's favorite rooms only; "invites" — rooms the user has been invited to but not yet joined. |
| `query` | string | No | Search query to filter rooms by name. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 10. |

#### `get-room`

Retrieves basic room model data by room ID including name, description, settings, and metadata.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique room identifier. |
| `networkId` | string | No | Network ID the room belongs to. |

#### `get-permissions`

Retrieves the current user permissions for a room (view, comment, contribute, edit, manage).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique room identifier. |

#### `get-room-props`

Lists all props (interactive objects) in a room. Returns prop objects with id, name (what the prop SHOWS, and the label on its tag: the prop's own title, else the title of its FIRST resource — a filled frame is named after its content, because a content prop carries no title of its own — else the asset's catalog name, else the bare id when nothing names it), assetTitle (the asset's catalog name, e.g. "Gold Picture Frame" or "Presentation Board" — tells you what kind of object the prop is even when the author renamed it), type (array of content types the prop can hold — empty means it cannot hold content), thumbnailCount (number of visual display canvases — a picture frame has thumbnailCount > 0), isPresentationBoard (true = a live screen-share board, NEVER a target for pasted content; null = unknown), invokeType (Default/Container/Bookshelf/FileCabinet), tags, contentCount, assetId, and `tag` — a ready-made entity link for the prop (`@[prop:networkId/roomId/propId|Name]`) that you paste verbatim when referring to it, never assembling one from ids yourself. Match the user's words against tags, name and assetTitle to find the prop they mean (e.g. "logo", "team", "the gold frame"). Each prop also carries `groupId` — the id of its prop group (props sharing the same tag set; see get-room-prop-groups). For PLACING content prefer get-room-prop-groups: it returns these same props already grouped, and paste-resources-into-prop-group spreads content over a group for you. Props are returned in the room's NAVIGATION ORDER and each carries `navIndex` (0-based position): normally that is the props-group order the room stores, but when the room's settings select a storyline to override Next/Previous navigation, THAT storyline's frame order is the source of truth for prop order — props the storyline never visits come last with `navIndex: null`.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique room identifier. |
| `networkId` | string | No | Network ID the room belongs to. |

#### `get-prop`

Returns details for a single prop by ID (name, assetTitle, type, thumbnailCount, isPresentationBoard, contentCount, assetId, `navIndex` — the prop's 0-based position in the room's navigation order, null when a Next/Previous storyline override never visits it — and the ready-made entity link `tag` — same fields as get-room-props) with no navigation or UI side effects. Use this to read prop data. Only use preview-prop when the user explicitly asks to show, open, or navigate to a prop.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `propId` | string | Yes | The unique prop identifier. |
| `roomId` | string | Yes | The room the prop belongs to. |
| `networkId` | string | No | Network ID the room belongs to. |

#### `list-prop-team-members`

Lists all team member invitations for a prop in a room. Returns an array of invitation records, each containing the invited user's ID, their permissions (view/comment/contribute/edit/manage), and any stored custom metadata. Agent assignments are stored id-only as `customParams.agentInfo.id` (string). Older rows may still carry the full snapshot `customParams.agentInfo: { id, name, description, avatarUrl }` — only `id` is read; everything else is resolved from the live agent.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the prop belongs to. |
| `propId` | string | Yes | The prop whose team members to list. |

#### `invite-to-prop`

Adds a user to a prop's team, or updates an existing team member's permissions and metadata. When called for a user already in the team this acts as an update — the invitation is replaced with the new permissions and customParams. Use list-prop-team-members first to check current membership before inviting.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the prop belongs to. |
| `propId` | string | Yes | The prop to invite the user to. |
| `invitedUser` | string | Yes | User ID of the person to invite or update. |
| `permissions` | object | No | Permission flags for the team member: { view: boolean, comment: boolean, contribute: boolean, edit: boolean, manage: boolean }. Defaults to view-only (view: true, all others false) when omitted. |
| `customParams` | object | No | Arbitrary metadata stored on the invitation. To assign an AI agent to this team member, pass `customParams: { agentInfo: { id: "<agent-id>", avatarResourceId: "<picked-3d-avatar-resource-id>" \| null } }` (id-only — do NOT include name/description/avatarUrl, those are resolved from the live agent). `avatarResourceId` is the team member's picked 3D avatar for this prop, or `null` when nothing has been picked — readers fall back to the user's own 3D profile avatar in that case. Pass `customParams: null` to clear a previously assigned agent. The full-snapshot shape `agentInfo: { id, name, description, avatarUrl }` is still read for backwards compatibility but only `id` is used. |

#### `delete-prop-invitation`

Removes a user from a prop's team by deleting their invitation. Use list-prop-team-members first to confirm the user is currently a team member.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the prop belongs to. |
| `propId` | string | Yes | The prop to remove the user from. |
| `invitedUser` | string | Yes | User ID of the team member to remove. |

#### `list-room-templates`

Lists the room templates the AI is allowed to build from: ONLY templates tagged both "community" and "ai-friendly" are returned — every other template is skipped by design, never look for or suggest one (filteredOutCount says how many of the page were skipped). Each template has an id, name, tags (e.g. "office", "gallery", "community"), price in USD (0 or missing = free; paid templates CAN be created too — the room then has to be purchased by the user within 15 minutes, see create-room-from-template), subscriptionStatus, and groupCount — the number of prop groups (tagged sections of frames) the template's room contains, read from a tag of the form "prop-groups_N" (e.g. prop-groups_5; null when the template carries no such tag). Choose a template by groupCount: prefer the one equal to the number of content sections the user has, else the smallest larger one; a template with more groups than sections leaves the extra groups on their placeholders, one with fewer forces sections to be merged — say so when you propose it. Supports text search and server-side tag filtering with pagination. Use this before create-room-from-template to find a template id, its groupCount and its price.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter templates by name. |
| `tags` | string\[] | No | Filter templates by tags (server-side), e.g. \["community"]. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 20. |

#### `create-room-from-template`

Creates a new 3D room in the current network from a room template — FREE or PAID. The tool looks the template up in the AI-approved list itself and uses the listed price (pass `price` too when you have it; it is the fallback if the lookup fails). For a paid template the room is created immediately with a one-time-payment plan and the platform keeps it only if the user completes the purchase within 15 minutes; this tool never pays anything. Returns { roomId, name, networkId, tag, price, isFree, paymentRequired } for the NEW room plus, when paymentRequired is true, paymentWindowMinutes (15) and paymentInstructions. When paymentRequired is true you MUST tell the user right away: the price, that the room is temporary until it is bought within 15 minutes, and how to pay (Valu web app -> Rooms -> open the new room -> "Pay for Room"); never claim it is paid. Creating the room and linking to it are two separate steps: to link, paste the returned `tag` (`@[room:networkId/roomId|Name]`) VERBATIM — never rebuild it from ids, never reuse a roomId from an earlier call or a template id, and never guess the networkId. Use list-room-templates first to find the template id and its price.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `templateId` | string | Yes | The room template id (from list-room-templates). |
| `roomName` | string | Yes | Name for the new room. |
| `price` | number | No | The template's price in USD as list-room-templates returned it (0 = free). Optional: the tool resolves the price from the approved list itself and only falls back to this value when the template cannot be found there. A price above 0 creates a PAID room that the user must purchase within 15 minutes to keep. |

#### `paste-resources-into-prop`

Places one or more existing CMS resources into a prop in a room. Resources are link-copied (the originals stay in their current folder) and added to the prop's content list. IDEMPOTENT: a resource already on the prop (itself, or as a link copy) is skipped and listed in skippedAlreadyPresent — so repeating a paste after a timeout can never duplicate content; when everything was already there the result carries alreadyPresent=true and nothing is changed. Template placeholder resources still sitting on the prop (from the room template) are removed first so they never mix with the user's content (removedTemplateStubs in the result lists them; set removeTemplateStubs=false to keep them). When the paste leaves more than one resource on a non-container prop, the tool automatically enables the prop's auto content slider so the items cycle as a slideshow (result field sliderEnabled reports it; sliderError appears if the paste succeeded but the slider toggle failed). The tool also CLEARS the prop's title (content props carry no caption — a section's name lives on its sign; titleCleared reports it, titleClearError if that save failed). Use this tool ONLY when the user named exactly one prop. When the target is a GROUP — a row of frames, a tagged section, several props — call paste-resources-into-prop-group instead: it spreads the content across the group's props in navigation order itself, so never hand-split content into per-prop calls. The prop must support content — check via get-room-props that its type array is non-empty and isPresentationBoard is false: presentation (screen-share) boards are rejected, they are reserved for live screen sharing. Prefer props with thumbnailCount > 0 (picture frames / display canvases). Use service__CMS__resource_search to find resource ids first. Returns the paste result plus `name` and `tag` read back AFTER the paste, so they name what the prop now shows (its first resource) — paste that `tag` verbatim when linking to the prop.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the prop belongs to. |
| `propId` | string | Yes | The prop to place the resources into. |
| `resourceIds` | string\[] | Yes | CMS resource ids to place into the prop, in display order. |
| `appendToListEnd` | boolean | No | When true (default) new resources are appended to the end of the prop's content list; when false they are inserted at the front. |
| `removeTemplateStubs` | boolean | No | When true (default) placeholder resources the prop inherited from the room template are removed before pasting. Pass false only when the user explicitly wants to keep the template content. |
| `networkId` | string | No | Network ID the room belongs to. Defaults to the current network. |

#### `get-room-prop-groups`

Lists a room's props ALREADY GROUPED by their tag set, in the room's navigation order — use this (not get-room-props) whenever content is to be placed. Returns { roomId, networkId, propOrder, groupIds, groups } — groupIds is the ordered list of every group id, read it first. A group is the set of props whose normalized tags (lowercase, spaces -> "-") are identical; its `id` is those tags sorted and joined with "+" (e.g. "building1+floor0+team"), so multi-tag rooms yield one group per tag combination ("building1+floor0+team" and "building1+floor1+team" are different groups). A SINGLE tagged prop is a group of one — the lone "logo" frame is the group "logo". Props WITHOUT tags belong to no group and are skipped entirely (never listed, never filled). Each group carries id, tags, label, isLogoGroup (true when the tags include "logo": every logo frame shows EVERY logo, so paste-resources-into-prop-group replicates there by default), navIndex (the group's first position in the room's walk; null = outside it), propCount, eligiblePropCount (props that can take content), decorativePropCount (props with no content type — walls, plants, signs — counted but NOT listed), totalContentCount, labels — the section's SIGNS: Text props (recognised by an asset tag containing "Text", or as a titled prop with no content type) whose title is the text shown in the room, members of the group because they carry its tags (an untagged sign is invisible to these tools); each { propId, text } — and props — only the props that can hold content (presentation boards included, flagged), each as a compact entry: id, name (what the prop SHOWS: its own title, else the title of its FIRST resource, else the asset's catalog name, else the bare id when nothing names it — an id-only name means an empty, unnamed frame), assetTitle, type, thumbnailCount, logoCount, invokeType, isPresentationBoard, acceptsContent (true = type non-empty and not a presentation board), contentCount, navIndex, tag. Match the user's words against group tags/label first ("the team wall on floor 0" -> tags contain team and floor0), then against prop name/assetTitle when they mean one exact prop. Pass group ids to paste-resources-into-prop-group; when the user's content is coarser than the groups ("team" over per-floor team groups) pass every group whose tags contain the facet. The ready-made `tag` on each prop is pasted verbatim when linking to it — never assemble one. propOrder says where the order came from ({ source: "storyline" | "props-group", storylineId?, storylineTitle? }).

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The unique room identifier. |
| `networkId` | string | No | Network ID the room belongs to. |

#### `paste-resources-into-prop-group`

Spreads existing CMS resources across the props of one or more prop GROUPS (ids from get-room-prop-groups), in the room's navigation order, as contiguous, order-preserving shares — one slideshow per prop. The tool does ALL the chunking (7 resources over 3 frames -> 3/2/2; 2 over 5 -> the first two frames get one each), so call it ONCE per content group with all of that group's resources — never once per prop, and never split the resources yourself. Pass several groupIds when the content is coarser than the room's groups (e.g. "team" photos over "building1+floor0+team" and "building1+floor1+team"); their props are pooled in walk order. Frames that still show only their template placeholder come FIRST (lowest navIndex first), frames that already hold user content come after — so a later top-up fills the empty slots before joining existing slideshows; each placement reports hadUserContent (true/false, null when the prop's content could not be read). distribution: "spread" (default — contiguous shares), "stack" (everything onto the first eligible prop as one slideshow — only when the user explicitly asked for a single slideshow) or "replicate" (EVERY resource onto EVERY eligible prop — the default when every selected group is a LOGO group, isLogoGroup true, so all logos show on all logo frames; never spread logos unless the user asks). Presentation boards are skipped automatically and listed in skippedProps; decorative members (walls, signs) are never candidates. Per prop this behaves exactly like paste-resources-into-prop: resources are link-copied (originals stay), a resource already on the prop (itself or as a link copy) is skipped rather than duplicated (per placement and top-level skippedAlreadyPresent — a repeat after a timeout is safe), template placeholder stubs are removed first, the auto content slider is enabled when a prop ends up with more than one item, and titles are cleared on EVERY content prop of the selected groups — the pasted ones and the untouched ones alike (content props carry no caption; the section's name lives on its sign; clearedTitlePropIds lists them, titleClearErrors any that failed). Returns { roomId, groupIds, distribution, placements: \[{ groupId, propId, name, tag, navIndex, hadUserContent, resourceIds, addedResourceIds, failed, removedTemplateStubs, sliderEnabled, titleCleared, sliderError?, titleClearError?, stubCleanupError?, error? }], clearedTitlePropIds, titleClearErrors?, unplacedResourceIds (resources that landed on no prop at all), eligiblePropCount, skippedProps } — each placement's name and tag are read back AFTER the paste, so they name what the prop now shows (its first resource), not the empty frame it was. It throws only when nothing could be attempted: an unknown group id (the message lists the valid ids), no eligible prop in the selected groups, or empty input. Report failed/unplaced items honestly; paste each placement's `tag` verbatim when linking. Use paste-resources-into-prop only when the user named exactly one prop.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the groups belong to. |
| `groupIds` | string\[] | Yes | One or more prop group ids from get-room-prop-groups (e.g. \["building1+floor0+team"]). Several ids pool their props into one navigation-ordered row. |
| `resourceIds` | string\[] | Yes | CMS resource ids to place, in display order. The tool splits them across the props itself. |
| `distribution` | string | No | "spread": contiguous, order-preserving shares across the eligible props in navigation order (default for every group except logo groups). "stack": everything onto the first eligible prop as a single slideshow — only when the user asked for one. "replicate": every resource onto every eligible prop (default for a logo group). Omit to get the default for the selected groups. |
| `appendToListEnd` | boolean | No | When true (default) new resources are appended to the end of each prop's content list; when false they are inserted at the front. |
| `removeTemplateStubs` | boolean | No | When true (default) placeholder resources a prop inherited from the room template are removed before pasting. Pass false only when the user explicitly wants to keep the template content. |
| `networkId` | string | No | Network ID the room belongs to. Defaults to the current network. |

#### `rename-prop-group`

Gives a prop GROUP a display name by retitling its section SIGNS — the group's labels from get-room-prop-groups, i.e. the tagged Text props that show the section name in the room. Content props (frames, boards) are never touched: they carry no title at all (pasting clears any they had), the name lives on the sign. Use it in the guided build once the section's name is settled (listed by the user up front, self-evident from the tag, or confirmed from your proposal — or chosen by you when the user asked you to decide everything). The group id itself comes from tags and does not change. Returns { roomId, groupId, name, hasLabels, renamedLabelPropIds, failed } — hasLabels false means the group has no sign, so there was nothing to rename (say so); renamedLabelPropIds are the signs that now show the name; failed lists signs that could not be saved. Never rename a group the user did not name or ask you to name.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `roomId` | string | Yes | The room the group belongs to. |
| `groupId` | string | Yes | The prop group id from get-room-prop-groups (e.g. "team" or "building1+floor0+team"). |
| `name` | string | Yes | The display name for the section; becomes the text of the group's signs. |
| `networkId` | string | No | Network ID the room belongs to. Defaults to the current network. |

***

### Text Chat Service (`TextChat`)

Headless text-chat I/O for non-UI callers (agents, sub-agents, scripts). Read channel history and send messages without opening the TextChat application or changing the active channel. Encryption and decryption are handled automatically.

*Source: `src/Services/TextChat/TextChatService.js`*

#### `get-channel-history`

Fetches the most recent messages for a text-chat channel by channelId, decrypted and ready to read. Returns a plain list of messages with authorId, body, timestamp, and messageType. Does not open any UI or change the active channel.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `channelId` | string | Yes | The text-chat channel ID to load messages from. |
| `limit` | number | No | Maximum number of messages to return. Defaults to 20. |
| `beforeMessageId` | string | No | Load messages older than this messageId (for paginating backwards). |
| `afterMessageId` | string | No | Load messages newer than this messageId (for paginating forwards). |

#### `send-message`

Sends a text message to a text-chat channel. You must provide EITHER a channelId (preferred when you already have one) OR a userId (for a direct message — the service resolves the direct channel automatically). Silent — does NOT open the TextChat application, does NOT change the active channel, does NOT affect any UI. Encryption is handled automatically if the channel is encrypted.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `text` | string | Yes | The message body to send. |
| `channelId` | string | No | Target channel ID. Use this when you already know the channel (e.g. from a platform event). Takes precedence over userId. |
| `userId` | string | No | Target user ID for a direct message. The service resolves the direct channel by calling channel:getDirectChannel under the hood. Ignored if channelId is also provided. |
| `buttons` | object\[] | No | Optional interactive buttons rendered below the message body. Pass a real JSON array (NOT a JSON-stringified array). Each entry MUST be an object with this exact NESTED shape: `{text: string, intent: {applicationId: string, action: string, params?: object}}`. Do NOT flatten `applicationId`/`action` onto the button itself — `intent` is a sub-object. `text` is the visible label. Clicking dispatches `intent` via Application.run on the recipient's client (use an `applicationId` + `action` the recipient's app exposes — see other service manifests for valid pairs). Entries that are malformed are repaired-or-dropped by the service and reported back in `buttonWarnings`; check that field on the response and use the canonical shape going forward. Example: `[{"text":"Open profile","intent":{"applicationId":"valuguru","action":"open-agent","params":{"agentId":"agent_xxx"}}}]`. |
| `customParams` | object | No | Optional message customParams. Only the key `open-thread` is accepted (any other key is dropped by the service). Pass `{"open-thread": true}` ONLY when the recipient explicitly expects your outreach (e.g. you are responding to their team-member avatar click) — it force-opens the chat thread on their screen, which is invasive. |

#### `message-owner`

Delivers a message authored by the AI agent itself (NOT by the logged-in user) into a user's dedicated agent-to-user channel, resolved from source `userAIAgent:{userId}:{agentId}`. The recipient sees the agent as the author and the channel is created on first use. This is the INVASIVE owner-notification channel: use it to ping/notify the owner (pass the owner's userId) — or, occasionally, any other user. Unlike send-message (which sends as the logged-in user), this is authored by the agent. Silent toward the UI — does NOT open the TextChat application or change the active channel.

**Availability:** AI

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | Recipient user ID. The user who will receive the message in their agent channel. |
| `agentId` | string | Yes | The calling agent's own ID. Used to address the dedicated `userAIAgent:{userId}:{agentId}` channel so the message is authored by this agent. |
| `text` | string | Yes | The message body to send. |
| `buttons` | object\[] | No | Optional interactive buttons rendered below the message body. Pass a real JSON array (NOT a JSON-stringified array). Each entry MUST be an object with this exact NESTED shape: `{text: string, intent: {applicationId: string, action: string, params?: object}}`. Do NOT flatten `applicationId`/`action` onto the button itself — `intent` is a sub-object. `text` is the visible label. Clicking dispatches `intent` via Application.run on the recipient's client (use an `applicationId` + `action` the recipient's app exposes — see other service manifests for valid pairs). Entries that are malformed are repaired-or-dropped by the service and reported back in `buttonWarnings`; check that field on the response and use the canonical shape going forward. Example: `[{"text":"Open profile","intent":{"applicationId":"valuguru","action":"open-agent","params":{"agentId":"agent_xxx"}}}]`. |
| `customParams` | object | No | Optional message customParams. Only the key `open-thread` is accepted (any other key is dropped by the service). Pass `{"open-thread": true}` ONLY when the recipient explicitly expects your outreach (e.g. you are responding to their team-member avatar click) — it force-opens the chat thread on their screen, which is invasive. |

***

### Time Service (`Time`)

Returns the current user's local clock context — local ISO with offset, UTC ISO for the same instant, IANA timezone, offset in minutes, day-of-week, and resolved locale. Use this before any "today"/"tomorrow"/"now" reasoning or when converting natural-language times to ISO strings.

*Source: `src/Services/Time/TimeService.js`*

#### `get-local-time`

Returns an object describing the user's current local time: { iso: local ISO 8601 with offset (e.g. "2026-05-28T13:45:30.000+02:00"), utcIso: same instant in UTC (e.g. "2026-05-28T11:45:30.000Z"), timezone: IANA name (e.g. "Europe/Berlin"), offsetMinutes: integer minutes ahead of UTC (e.g. 120), dayOfWeek: long English day name (e.g. "Thursday"), localDate: "YYYY-MM-DD" in local time, localTime: "HH:MM" in local time, locale: resolved BCP 47 locale (e.g. "en-US") }. Takes no parameters.

**Availability:** Developer

*No parameters.*

***

### Users Service (`Users`)

User management service for getting current user info, looking up users by ID, searching contacts/followers/following, and managing connection requests.

*Source: `src/Services/Users/UsersService.js`*

#### `current`

Returns the currently authenticated user info (id, firstName, lastName, etc.).

**Availability:** AI, Developer

*No parameters.*

#### `get`

Retrieves a user by their unique ID. Returns user object with id, firstName, lastName, etc.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique user identifier. |

#### `search-users`

Search for users within existing connections (contacts, followers, or following). Use this to find a user ID when you only know their name and they are already in your network. To discover new users outside your network, use find-user instead.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `filter` | string | Yes | User filter. Must be one of: "contacts" — current user's friends/connections; "followers" — users who follow the current user; "following" — users the current user follows. |
| `query` | string | No | Search query to filter users by name. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 10. |

#### `find-user`

Discover new users to connect with by searching by name. Uses a suggestion engine — distinct from search-users which searches within existing connections.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Search query to filter users by name. |
| `offset` | number | No | Pagination offset. Defaults to 0. |
| `size` | number | No | Number of results to return. Defaults to 10. |

#### `send-connection-request`

Sends a connection request to a user by their ID.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user to send a connection request to. |

#### `accept-connection-request`

Accepts a pending connection request from a user.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user whose connection request to accept. |

#### `decline-connection-request`

Declines a pending connection request from a user.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user whose connection request to decline. |

#### `cancel-connection-request`

Cancels a pending connection request that the current user previously sent.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `userId` | string | Yes | The unique identifier of the user to whom the connection request was sent. |

***

### Valu Guru Service (`AiGuru`)

System service for managing applications via AI. Provides tools to open, close, list, and check application status.

*Source: `src/Services/AiGuru/AiGuruService.js`*

#### `open`

Opens (loads) an application by its ID into the dock.

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `applicationId` | string | Yes | The unique identifier of the application to open. |

#### `close`

Closes (unloads) an application by its ID from the dock.

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `applicationId` | string | Yes | The unique identifier of the application to close. |

#### `has-application`

Checks whether an application with the given ID exists in the registry.

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `applicationId` | string | Yes | The unique identifier of the application to check. |

#### `get-applications`

Returns a list of all registered applications with their id, slug, icon, and title.

**Availability:** none (platform-internal)

*No parameters.*

#### `is-application-loaded`

Checks whether an application with the given ID is currently loaded (open) in the dock.

**Availability:** none (platform-internal)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `applicationId` | string | Yes | The unique identifier of the application to check. |

#### `get-chat-history`

Returns the in-memory message history for a chat session. Omit chatId to get the currently active session.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `chatId` | string | No | ID of the chat session. Omit to use the currently active session. |

#### `get-agent-history`

Returns the in-memory message history for a background agent.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | ID of the background agent whose message history to retrieve. |

#### `query-knowledge-base`

Queries the RAG knowledge base directly over the Valu Guru server's socket connection, bypassing chat entirely. Returns the raw tool result text.

**Availability:** Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | Yes | The search query text. |
| `toolName` | string | No | Name of the RAG tool to call. Only needed if the RAG server exposes more than one tool — omit to auto-pick the sole registered tool. |
| `args` | object | No | Extra arguments merged into the RAG tool call alongside `query` (tool-specific, e.g. scope filters). |

***

### Verus Wallet Service (`VerusWallet`)

Executes on-chain transfers from an AI agent's attached Verus wallet identity. Every call takes an agentId — the client resolves which wallet is attached to that agent. Returns an error if the agent has no wallet attached or the wallet has not finished being provisioned on-chain.

*Source: `src/Services/AiGuru/VerusWalletService.js`*

#### `transfer`

Send currency from the agent's attached Verus wallet to a destination address or identity. Call this when the user or agent needs to move funds out of an agent wallet. Fails if the agent has no wallet attached, or if the wallet is still pending creation.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | ID of the AI agent performing the transfer. The service looks up which wallet identity is attached to this agent — NEVER pass a wallet ID or i-address here. |
| `destination` | string | Yes | Recipient address, i-address, or Verus identity (e.g. "alice@"). |
| `amount` | number | Yes | Amount to send (positive number). Interpreted in the selected currency — defaults to VRSC when currency is omitted. |
| `currency` | string | No | Currency ticker or identity (e.g. "VRSC", "VRSC-BTC"). Defaults to the native chain currency. |
| `memo` | string | No | Optional memo attached to the transaction. |

#### `get-balance`

Return the last-known balance of the wallet attached to the specified agent. Reads from the client cache — call verus:getAgentsBalance (via AiGuruStore.refreshAgentBalances) for a fresh value.

**Availability:** AI, Developer

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | ID of the AI agent whose wallet balance should be returned. |

***
